tests: Port merry's audio tests (#7354)
This commit is contained in:
		@@ -200,7 +200,7 @@ void DSP_DSP::UnloadComponent(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 | 
			
		||||
    rb.Push(ResultSuccess);
 | 
			
		||||
 | 
			
		||||
    LOG_INFO(Service_DSP, "(STUBBED)");
 | 
			
		||||
    LOG_INFO(Service_DSP, "called");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void DSP_DSP::FlushDataCache(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,11 @@ add_executable(tests
 | 
			
		||||
    audio_core/audio_fixures.h
 | 
			
		||||
    audio_core/decoder_tests.cpp
 | 
			
		||||
    video_core/shader/shader_jit_compiler.cpp
 | 
			
		||||
    audio_core/merryhime_3ds_audio/merry_audio/merry_audio.cpp
 | 
			
		||||
    audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h
 | 
			
		||||
    audio_core/merryhime_3ds_audio/merry_audio/service_fixture.cpp
 | 
			
		||||
    audio_core/merryhime_3ds_audio/merry_audio/service_fixture.h
 | 
			
		||||
    audio_core/merryhime_3ds_audio/audio_test_biquad_filter.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
create_target_directory_groups(tests)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								src/tests/audio_core/merryhime_3ds_audio/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/tests/audio_core/merryhime_3ds_audio/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
Port of HW tests from https://github.com/merryhime/3ds-audio
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <catch2/catch_template_test_macros.hpp>
 | 
			
		||||
#include "audio_core/hle/shared_memory.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "merry_audio/merry_audio.h"
 | 
			
		||||
 | 
			
		||||
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "AudioTest-BiquadFilter",
 | 
			
		||||
                 "[audio_core][merryhime_3ds_audio]") {
 | 
			
		||||
    // High frequency square wave, PCM16
 | 
			
		||||
    auto fillBuffer = [this](u32* audio_buffer, size_t size) {
 | 
			
		||||
        for (size_t i = 0; i < size; i++) {
 | 
			
		||||
            u32 data = (i % 2 == 0 ? 0x1000 : 0x2000);
 | 
			
		||||
            audio_buffer[i] = (data << 16) | (data & 0xFFFF);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        DSP_FlushDataCache(audio_buffer, size);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constexpr size_t NUM_SAMPLES = 160 * 200;
 | 
			
		||||
    u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
 | 
			
		||||
    fillBuffer(audio_buffer, NUM_SAMPLES);
 | 
			
		||||
 | 
			
		||||
    MerryAudio::AudioState state;
 | 
			
		||||
    {
 | 
			
		||||
        std::vector<u8> dspfirm;
 | 
			
		||||
        SECTION("HLE") {
 | 
			
		||||
            // The test case assumes HLE AudioCore doesn't require a valid firmware
 | 
			
		||||
            InitDspCore(Settings::AudioEmulation::HLE);
 | 
			
		||||
            dspfirm = {0};
 | 
			
		||||
        }
 | 
			
		||||
        SECTION("LLE") {
 | 
			
		||||
            InitDspCore(Settings::AudioEmulation::LLE);
 | 
			
		||||
            dspfirm = loadDspFirmFromFile();
 | 
			
		||||
        }
 | 
			
		||||
        if (!dspfirm.size()) {
 | 
			
		||||
            SKIP("Couldn't load firmware\n");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        auto ret = audioInit(dspfirm);
 | 
			
		||||
        if (!ret) {
 | 
			
		||||
            INFO("Couldn't init audio\n");
 | 
			
		||||
            goto end;
 | 
			
		||||
        }
 | 
			
		||||
        state = *ret;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        /*
 | 
			
		||||
        const s16 b0 = 0.057200221035302035 * (1 << 14);
 | 
			
		||||
        const s16 b1 = 0.11440044207060407 * (1 << 14);
 | 
			
		||||
        const s16 b2 = 0.0238274928983472 * (1 << 14);
 | 
			
		||||
        const s16 a1 = -1.2188761083637 * (1 << 14);
 | 
			
		||||
        const s16 a2 = 0.44767699250490806 * (1 << 14);
 | 
			
		||||
        */
 | 
			
		||||
        srand((u32)time(nullptr));
 | 
			
		||||
        const s16 b0 = rand();
 | 
			
		||||
        const s16 b1 = rand();
 | 
			
		||||
        const s16 b2 = rand();
 | 
			
		||||
        const s16 a1 = rand();
 | 
			
		||||
        const s16 a2 = rand();
 | 
			
		||||
 | 
			
		||||
        std::array<s32, 160> expected_output;
 | 
			
		||||
        {
 | 
			
		||||
            s32 x1 = 0;
 | 
			
		||||
            s32 x2 = 0;
 | 
			
		||||
            s32 y1 = 0;
 | 
			
		||||
            s32 y2 = 0;
 | 
			
		||||
            for (int i = 0; i < 160; i++) {
 | 
			
		||||
                const s32 x0 = (i % 4 == 0 || i % 4 == 1 ? 0x1000 : 0x2000);
 | 
			
		||||
                s32 y0 = ((s32)x0 * (s32)b0 + (s32)x1 * b1 + (s32)x2 * b2 + (s32)a1 * y1 +
 | 
			
		||||
                          (s32)a2 * y2) >>
 | 
			
		||||
                         14;
 | 
			
		||||
 | 
			
		||||
                y0 = std::clamp(y0, -32768, 32767);
 | 
			
		||||
                expected_output[i] = y2;
 | 
			
		||||
 | 
			
		||||
                x2 = x1;
 | 
			
		||||
                x1 = x0;
 | 
			
		||||
                y2 = y1;
 | 
			
		||||
                y1 = y0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        state.waitForSync();
 | 
			
		||||
        initSharedMem(state);
 | 
			
		||||
        state.write().dsp_configuration->aux_bus_enable_0_dirty.Assign(true);
 | 
			
		||||
        state.write().dsp_configuration->aux_bus_enable[0] = true;
 | 
			
		||||
        state.write().source_configurations->config[0].gain[1][0] = 1.0;
 | 
			
		||||
        state.write().source_configurations->config[0].gain_1_dirty.Assign(true);
 | 
			
		||||
        state.notifyDsp();
 | 
			
		||||
        state.waitForSync();
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            u16 buffer_id = 0;
 | 
			
		||||
 | 
			
		||||
            state.write().source_configurations->config[0].play_position = 0;
 | 
			
		||||
            state.write().source_configurations->config[0].physical_address =
 | 
			
		||||
                osConvertVirtToPhys(audio_buffer);
 | 
			
		||||
            state.write().source_configurations->config[0].length = NUM_SAMPLES;
 | 
			
		||||
            state.write().source_configurations->config[0].mono_or_stereo.Assign(
 | 
			
		||||
                AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Mono);
 | 
			
		||||
            state.write().source_configurations->config[0].format.Assign(
 | 
			
		||||
                AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
 | 
			
		||||
            state.write().source_configurations->config[0].fade_in.Assign(false);
 | 
			
		||||
            state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
 | 
			
		||||
            state.write().source_configurations->config[0].is_looping.Assign(false);
 | 
			
		||||
            state.write().source_configurations->config[0].buffer_id = ++buffer_id;
 | 
			
		||||
            state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
 | 
			
		||||
            state.write().source_configurations->config[0].play_position_dirty.Assign(true);
 | 
			
		||||
            state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
 | 
			
		||||
 | 
			
		||||
            state.write().source_configurations->config[0].enable = true;
 | 
			
		||||
            state.write().source_configurations->config[0].enable_dirty.Assign(true);
 | 
			
		||||
 | 
			
		||||
            state.write().source_configurations->config[0].simple_filter.b0 = 0;
 | 
			
		||||
            state.write().source_configurations->config[0].simple_filter.a1 = 0;
 | 
			
		||||
            state.write().source_configurations->config[0].simple_filter_enabled.Assign(false);
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter_enabled.Assign(true);
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter.b0 = b0;
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter.b1 = b1;
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter.b2 = b2;
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter.a1 = a1;
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter.a2 = a2;
 | 
			
		||||
            state.write().source_configurations->config[0].filters_enabled_dirty.Assign(true);
 | 
			
		||||
            state.write().source_configurations->config[0].biquad_filter_dirty.Assign(true);
 | 
			
		||||
            state.write().source_configurations->config[0].simple_filter_dirty.Assign(true);
 | 
			
		||||
            state.notifyDsp();
 | 
			
		||||
 | 
			
		||||
            bool continue_reading = true;
 | 
			
		||||
            for (size_t frame_count = 0; continue_reading && frame_count < 10; frame_count++) {
 | 
			
		||||
                state.waitForSync();
 | 
			
		||||
 | 
			
		||||
                for (size_t i = 0; i < 160; i++) {
 | 
			
		||||
                    if (state.read().intermediate_mix_samples->mix1.pcm32[0][i]) {
 | 
			
		||||
                        for (size_t j = 0; j < 60; j++) {
 | 
			
		||||
                            REQUIRE(state.read().intermediate_mix_samples->mix1.pcm32[0][j] ==
 | 
			
		||||
                                    expected_output[j]);
 | 
			
		||||
                        }
 | 
			
		||||
                        continue_reading = false;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                state.notifyDsp();
 | 
			
		||||
            }
 | 
			
		||||
            REQUIRE(continue_reading == false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
end:
 | 
			
		||||
    audioExit(state);
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,264 @@
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <climits>
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <catch2/catch_test_macros.hpp>
 | 
			
		||||
#include "audio_core/hle/shared_memory.h"
 | 
			
		||||
#include "common/common_paths.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "merry_audio.h"
 | 
			
		||||
 | 
			
		||||
#define VERIFY(call) call
 | 
			
		||||
 | 
			
		||||
namespace MerryAudio {
 | 
			
		||||
std::vector<u8> MerryAudioFixture::loadDspFirmFromFile() {
 | 
			
		||||
    std::string firm_filepath =
 | 
			
		||||
        FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "3ds" DIR_SEP "dspfirm.cdc";
 | 
			
		||||
 | 
			
		||||
    FILE* f = fopen(firm_filepath.c_str(), "rb");
 | 
			
		||||
 | 
			
		||||
    if (!f) {
 | 
			
		||||
        printf("Couldn't find dspfirm\n");
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fseek(f, 0, SEEK_END);
 | 
			
		||||
    long size = ftell(f);
 | 
			
		||||
    fseek(f, 0, SEEK_SET);
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> dspfirm_binary(size);
 | 
			
		||||
    [[maybe_unused]] std::size_t count = fread(dspfirm_binary.data(), dspfirm_binary.size(), 1, f);
 | 
			
		||||
    fclose(f);
 | 
			
		||||
 | 
			
		||||
    return dspfirm_binary;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<AudioState> MerryAudioFixture::audioInit(const std::vector<u8>& dspfirm) {
 | 
			
		||||
    AudioState ret;
 | 
			
		||||
    ret.service_fixture = this;
 | 
			
		||||
 | 
			
		||||
    if (!dspfirm.size())
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
 | 
			
		||||
    if (R_FAILED(dspInit())) {
 | 
			
		||||
        printf("dspInit() failed\n");
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VERIFY(DSP_UnloadComponent());
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        bool dspfirm_loaded = false;
 | 
			
		||||
        VERIFY(DSP_LoadComponent(dspfirm.data(), dspfirm.size(), /*progmask=*/0xFF,
 | 
			
		||||
                                 /*datamask=*/0xFF, &dspfirm_loaded));
 | 
			
		||||
        if (!dspfirm_loaded) {
 | 
			
		||||
            printf("Failed to load firmware\n");
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VERIFY(svcCreateEvent(&ret.pipe2_irq, 1));
 | 
			
		||||
 | 
			
		||||
    // interrupt type == 2 (pipe related)
 | 
			
		||||
    // pipe channel == 2 (audio pipe)
 | 
			
		||||
    VERIFY(DSP_RegisterInterruptEvents(ret.pipe2_irq, 2, 2));
 | 
			
		||||
 | 
			
		||||
    VERIFY(DSP_GetSemaphoreHandle(&ret.dsp_semaphore));
 | 
			
		||||
 | 
			
		||||
    VERIFY(DSP_SetSemaphoreMask(0x2000));
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        // dsp_mode == 0 (request initialisation of DSP)
 | 
			
		||||
        const u32 dsp_mode = 0;
 | 
			
		||||
        VERIFY(DSP_WriteProcessPipe(2, &dsp_mode, 4));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Inform the DSP that we have data for her.
 | 
			
		||||
    VERIFY(DSP_SetSemaphore(0x4000));
 | 
			
		||||
 | 
			
		||||
    // Wait for the DSP to tell us data is available.
 | 
			
		||||
    VERIFY(svcWaitSynchronization(ret.pipe2_irq, UINT64_MAX));
 | 
			
		||||
    VERIFY(svcClearEvent(ret.pipe2_irq));
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        u16 len_read = 0;
 | 
			
		||||
 | 
			
		||||
        u16 num_structs = 0;
 | 
			
		||||
        VERIFY(DSP_ReadPipeIfPossible(2, 0, &num_structs, 2, &len_read));
 | 
			
		||||
        if (len_read != 2) {
 | 
			
		||||
            printf("Reading struct addrs header: Could only read %i bytes!\n", len_read);
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        }
 | 
			
		||||
        if (num_structs != 15) {
 | 
			
		||||
            printf("num_structs == %i (!= 15): Are you sure you have the right firmware version?\n",
 | 
			
		||||
                   num_structs);
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::array<u16, 15> dsp_addrs;
 | 
			
		||||
        VERIFY(DSP_ReadPipeIfPossible(2, 0, dsp_addrs.data(), 30, &len_read));
 | 
			
		||||
        if (len_read != 30) {
 | 
			
		||||
            printf("Reading struct addrs body: Could only read %i bytes!\n", len_read);
 | 
			
		||||
            return std::nullopt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 15; i++) {
 | 
			
		||||
            const u32 addr0 = static_cast<u32>(dsp_addrs[i]);
 | 
			
		||||
            const u32 addr1 = static_cast<u32>(dsp_addrs[i]) | 0x10000;
 | 
			
		||||
            u16* vaddr0;
 | 
			
		||||
            u16* vaddr1;
 | 
			
		||||
            VERIFY(DSP_ConvertProcessAddressFromDspDram(addr0, &vaddr0));
 | 
			
		||||
            VERIFY(DSP_ConvertProcessAddressFromDspDram(addr1, &vaddr1));
 | 
			
		||||
            ret.dsp_structs[i][0] = reinterpret_cast<u16*>(vaddr0);
 | 
			
		||||
            ret.dsp_structs[i][1] = reinterpret_cast<u16*>(vaddr1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 2; i++) {
 | 
			
		||||
            ret.shared_mem[i].frame_counter = reinterpret_cast<u16*>(ret.dsp_structs[0][i]);
 | 
			
		||||
 | 
			
		||||
            ret.shared_mem[i].source_configurations =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::SourceConfiguration*>(ret.dsp_structs[1][i]);
 | 
			
		||||
            ret.shared_mem[i].source_statuses =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::SourceStatus*>(ret.dsp_structs[2][i]);
 | 
			
		||||
            ret.shared_mem[i].adpcm_coefficients =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::AdpcmCoefficients*>(ret.dsp_structs[3][i]);
 | 
			
		||||
 | 
			
		||||
            ret.shared_mem[i].dsp_configuration =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::DspConfiguration*>(ret.dsp_structs[4][i]);
 | 
			
		||||
            ret.shared_mem[i].dsp_status =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::DspStatus*>(ret.dsp_structs[5][i]);
 | 
			
		||||
 | 
			
		||||
            ret.shared_mem[i].final_samples =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::FinalMixSamples*>(ret.dsp_structs[6][i]);
 | 
			
		||||
            ret.shared_mem[i].intermediate_mix_samples =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::IntermediateMixSamples*>(ret.dsp_structs[7][i]);
 | 
			
		||||
 | 
			
		||||
            ret.shared_mem[i].compressor =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::Compressor*>(ret.dsp_structs[8][i]);
 | 
			
		||||
 | 
			
		||||
            ret.shared_mem[i].dsp_debug =
 | 
			
		||||
                reinterpret_cast<AudioCore::HLE::DspDebug*>(ret.dsp_structs[9][i]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Poke the DSP again.
 | 
			
		||||
    VERIFY(DSP_SetSemaphore(0x4000));
 | 
			
		||||
 | 
			
		||||
    ret.dsp_structs[0][0][0] = ret.frame_id;
 | 
			
		||||
    ret.frame_id++;
 | 
			
		||||
    VERIFY(svcSignalEvent(ret.dsp_semaphore));
 | 
			
		||||
 | 
			
		||||
    return {ret};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MerryAudioFixture::audioExit(const AudioState& state) {
 | 
			
		||||
    {
 | 
			
		||||
        // dsp_mode == 1 (request shutdown of DSP)
 | 
			
		||||
        const u32 dsp_mode = 1;
 | 
			
		||||
        DSP_WriteProcessPipe(2, &dsp_mode, 4);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DSP_RegisterInterruptEvents(0, 2, 2);
 | 
			
		||||
    svcCloseHandle(state.pipe2_irq);
 | 
			
		||||
    svcCloseHandle(state.dsp_semaphore);
 | 
			
		||||
    DSP_UnloadComponent();
 | 
			
		||||
 | 
			
		||||
    dspExit();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MerryAudioFixture::initSharedMem(AudioState& state) {
 | 
			
		||||
    for (auto& config : state.write().source_configurations->config) {
 | 
			
		||||
        {
 | 
			
		||||
            config.enable = 0;
 | 
			
		||||
            config.enable_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            config.interpolation_mode =
 | 
			
		||||
                AudioCore::HLE::SourceConfiguration::Configuration::InterpolationMode::None;
 | 
			
		||||
            // config.interpolation_related = 0;
 | 
			
		||||
            config.interpolation_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            config.rate_multiplier = 1.0;
 | 
			
		||||
            config.rate_multiplier_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            config.simple_filter_enabled.Assign(false);
 | 
			
		||||
            config.biquad_filter_enabled.Assign(false);
 | 
			
		||||
            config.filters_enabled_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            for (auto& gain : config.gain) {
 | 
			
		||||
                for (auto& g : gain) {
 | 
			
		||||
                    g = 0.0;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            config.gain[0][0] = 1.0;
 | 
			
		||||
            config.gain[0][1] = 1.0;
 | 
			
		||||
            config.gain_0_dirty.Assign(true);
 | 
			
		||||
            config.gain_1_dirty.Assign(true);
 | 
			
		||||
            config.gain_2_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        {
 | 
			
		||||
            config.sync_count = 1;
 | 
			
		||||
            config.sync_count_dirty.Assign(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        { config.reset_flag.Assign(true); }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        state.write().dsp_configuration->master_volume = 1.0;
 | 
			
		||||
        state.write().dsp_configuration->aux_return_volume[0] = 0.0;
 | 
			
		||||
        state.write().dsp_configuration->aux_return_volume[1] = 0.0;
 | 
			
		||||
        state.write().dsp_configuration->master_volume_dirty.Assign(true);
 | 
			
		||||
        state.write().dsp_configuration->aux_return_volume_0_dirty.Assign(true);
 | 
			
		||||
        state.write().dsp_configuration->aux_return_volume_1_dirty.Assign(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        state.write().dsp_configuration->output_format =
 | 
			
		||||
            AudioCore::HLE::DspConfiguration::OutputFormat::Stereo;
 | 
			
		||||
        state.write().dsp_configuration->output_format_dirty.Assign(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        state.write().dsp_configuration->clipping_mode = 0;
 | 
			
		||||
        state.write().dsp_configuration->clipping_mode_dirty.Assign(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        // https://www.3dbrew.org/wiki/Configuration_Memory
 | 
			
		||||
        state.write().dsp_configuration->headphones_connected = 0;
 | 
			
		||||
        state.write().dsp_configuration->headphones_connected_dirty.Assign(true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const SharedMem& AudioState::write() const {
 | 
			
		||||
    return shared_mem[frame_id % 2 == 1 ? 1 : 0];
 | 
			
		||||
}
 | 
			
		||||
const SharedMem& AudioState::read() const {
 | 
			
		||||
    return shared_mem[frame_id % 2 == 1 ? 1 : 0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioState::waitForSync() {
 | 
			
		||||
    if (!service_fixture) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    service_fixture->svcWaitSynchronization(pipe2_irq, UINT64_MAX);
 | 
			
		||||
    service_fixture->svcClearEvent(pipe2_irq);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioState::notifyDsp() {
 | 
			
		||||
    write().frame_counter[0] = frame_id;
 | 
			
		||||
    frame_id++;
 | 
			
		||||
    if (service_fixture) {
 | 
			
		||||
        service_fixture->svcSignalEvent(dsp_semaphore);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
} // namespace MerryAudio
 | 
			
		||||
@@ -0,0 +1,65 @@
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "service_fixture.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore::HLE {
 | 
			
		||||
struct SourceConfiguration;
 | 
			
		||||
struct SourceStatus;
 | 
			
		||||
struct AdpcmCoefficients;
 | 
			
		||||
struct DspConfiguration;
 | 
			
		||||
struct DspStatus;
 | 
			
		||||
struct FinalMixSamples;
 | 
			
		||||
struct IntermediateMixSamples;
 | 
			
		||||
struct Compressor;
 | 
			
		||||
struct DspDebug;
 | 
			
		||||
} // namespace AudioCore::HLE
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
class MemorySystem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace MerryAudio {
 | 
			
		||||
 | 
			
		||||
struct SharedMem {
 | 
			
		||||
    u16* frame_counter;
 | 
			
		||||
 | 
			
		||||
    AudioCore::HLE::SourceConfiguration* source_configurations; // access through write()
 | 
			
		||||
    AudioCore::HLE::SourceStatus* source_statuses;              // access through read()
 | 
			
		||||
    AudioCore::HLE::AdpcmCoefficients* adpcm_coefficients;      // access through write()
 | 
			
		||||
 | 
			
		||||
    AudioCore::HLE::DspConfiguration* dsp_configuration; // access through write()
 | 
			
		||||
    AudioCore::HLE::DspStatus* dsp_status;               // access through read()
 | 
			
		||||
 | 
			
		||||
    AudioCore::HLE::FinalMixSamples* final_samples;                   // access through read()
 | 
			
		||||
    AudioCore::HLE::IntermediateMixSamples* intermediate_mix_samples; // access through write()
 | 
			
		||||
 | 
			
		||||
    AudioCore::HLE::Compressor* compressor; // access through write()
 | 
			
		||||
 | 
			
		||||
    AudioCore::HLE::DspDebug* dsp_debug; // access through read()
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AudioState {
 | 
			
		||||
    ServiceFixture::Handle pipe2_irq = ServiceFixture::PIPE2_IRQ_HANDLE;
 | 
			
		||||
    ServiceFixture::Handle dsp_semaphore = ServiceFixture::DSP_SEMAPHORE_HANDLE;
 | 
			
		||||
 | 
			
		||||
    std::array<std::array<u16*, 2>, 16> dsp_structs{};
 | 
			
		||||
    std::array<SharedMem, 2> shared_mem{};
 | 
			
		||||
    u16 frame_id = 4;
 | 
			
		||||
 | 
			
		||||
    const SharedMem& read() const;
 | 
			
		||||
    const SharedMem& write() const;
 | 
			
		||||
    void waitForSync();
 | 
			
		||||
    void notifyDsp();
 | 
			
		||||
 | 
			
		||||
    ServiceFixture* service_fixture{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MerryAudioFixture : public ServiceFixture {
 | 
			
		||||
public:
 | 
			
		||||
    std::vector<u8> loadDspFirmFromFile();
 | 
			
		||||
    std::optional<AudioState> audioInit(const std::vector<u8>& dspfirm);
 | 
			
		||||
    void audioExit(const AudioState& state);
 | 
			
		||||
    void initSharedMem(AudioState& state);
 | 
			
		||||
};
 | 
			
		||||
} // namespace MerryAudio
 | 
			
		||||
@@ -0,0 +1,133 @@
 | 
			
		||||
#include "audio_core/hle/hle.h"
 | 
			
		||||
#include "audio_core/lle/lle.h"
 | 
			
		||||
#include "common/settings.h"
 | 
			
		||||
#include "service_fixture.h"
 | 
			
		||||
 | 
			
		||||
// SVC
 | 
			
		||||
Result ServiceFixture::svcWaitSynchronization(Handle handle, s64 nanoseconds) {
 | 
			
		||||
    ASSERT(handle == PIPE2_IRQ_HANDLE);
 | 
			
		||||
 | 
			
		||||
    using AudioCore::DspPipe::Audio;
 | 
			
		||||
    using Service::DSP::InterruptType::Pipe;
 | 
			
		||||
    const bool& audio_pipe_interrupted =
 | 
			
		||||
        interrupts_fired[static_cast<u32>(Pipe)][static_cast<u32>(Audio)];
 | 
			
		||||
 | 
			
		||||
    while (!audio_pipe_interrupted) {
 | 
			
		||||
        core_timing.GetTimer(0)->AddTicks(core_timing.GetTimer(0)->GetDowncount());
 | 
			
		||||
        core_timing.GetTimer(0)->Advance();
 | 
			
		||||
        core_timing.GetTimer(0)->SetNextSlice();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::svcClearEvent(Handle handle) {
 | 
			
		||||
    ASSERT(handle == PIPE2_IRQ_HANDLE);
 | 
			
		||||
    using AudioCore::DspPipe::Audio;
 | 
			
		||||
    using Service::DSP::InterruptType::Pipe;
 | 
			
		||||
    interrupts_fired[static_cast<u32>(Pipe)][static_cast<u32>(Audio)] = 0;
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::svcSignalEvent(Handle handle) {
 | 
			
		||||
    ASSERT(handle == DSP_SEMAPHORE_HANDLE);
 | 
			
		||||
    dsp->SetSemaphore(0x2000);
 | 
			
		||||
    // TODO: Add relevent amount of ticks
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// dsp::DSP
 | 
			
		||||
Result ServiceFixture::DSP_LoadComponent(const u8* dspfirm_data, size_t size, u8 progmask,
 | 
			
		||||
                                         u8 datamask, bool* dspfirm_loaded) {
 | 
			
		||||
    dsp->LoadComponent({dspfirm_data, size});
 | 
			
		||||
    *dspfirm_loaded = true;
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::DSP_UnloadComponent() {
 | 
			
		||||
    dsp->UnloadComponent();
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::DSP_GetSemaphoreHandle(Handle* handle) {
 | 
			
		||||
    *handle = DSP_SEMAPHORE_HANDLE;
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::DSP_SetSemaphore(u32 semaphore) {
 | 
			
		||||
    dsp->SetSemaphore(semaphore);
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::DSP_ReadPipeIfPossible(u32 channel, u32 /*peer*/, void* out_buffer, u32 size,
 | 
			
		||||
                                              u16* out_size) {
 | 
			
		||||
    const AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(channel);
 | 
			
		||||
    const u16 pipe_readable_size = static_cast<u16>(dsp->GetPipeReadableSize(pipe));
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> pipe_buffer;
 | 
			
		||||
    if (pipe_readable_size >= size) {
 | 
			
		||||
        pipe_buffer = dsp->PipeRead(pipe, size);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    *out_size = static_cast<u16>(pipe_buffer.size());
 | 
			
		||||
 | 
			
		||||
    std::memcpy(out_buffer, pipe_buffer.data(), *out_size);
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::ServiceFixture::DSP_ConvertProcessAddressFromDspDram(u32 dsp_address,
 | 
			
		||||
                                                                            u16** host_address) {
 | 
			
		||||
    *host_address = reinterpret_cast<u16*>(
 | 
			
		||||
        (dsp_address << 1) + (reinterpret_cast<uintptr_t>(dsp->GetDspMemory().data()) + 0x40000u));
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::DSP_WriteProcessPipe(u32 channel, const void* buffer, u32 length) {
 | 
			
		||||
    const AudioCore::DspPipe pipe = static_cast<AudioCore::DspPipe>(channel);
 | 
			
		||||
    dsp->PipeWrite(pipe, {reinterpret_cast<const u8*>(buffer), length});
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// libctru
 | 
			
		||||
u32 ServiceFixture::osConvertVirtToPhys(void* vaddr) {
 | 
			
		||||
    return static_cast<u32>(Memory::FCRAM_PADDR +
 | 
			
		||||
                            (reinterpret_cast<u8*>(vaddr) - memory.GetFCRAMPointer(0)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void* ServiceFixture::linearAlloc(size_t size) {
 | 
			
		||||
    void* ret = memory.GetFCRAMPointer(0) + linear_alloc_offset;
 | 
			
		||||
    linear_alloc_offset += size;
 | 
			
		||||
    return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Result ServiceFixture::dspInit() {
 | 
			
		||||
    if (!dsp) {
 | 
			
		||||
        dsp = std::make_unique<AudioCore::DspHle>(system, memory, core_timing);
 | 
			
		||||
        dsp->SetInterruptHandler([this](Service::DSP::InterruptType type, AudioCore::DspPipe pipe) {
 | 
			
		||||
            interrupts_fired[static_cast<u32>(type)][static_cast<u32>(pipe)] = 1;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return ResultSuccess;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Fixture
 | 
			
		||||
void ServiceFixture::InitDspCore(Settings::AudioEmulation dsp_core) {
 | 
			
		||||
    if (dsp_core == Settings::AudioEmulation::HLE) {
 | 
			
		||||
        dsp = std::make_unique<AudioCore::DspHle>(system, memory, core_timing);
 | 
			
		||||
    } else {
 | 
			
		||||
        dsp = std::make_unique<AudioCore::DspLle>(
 | 
			
		||||
            system, memory, core_timing, dsp_core == Settings::AudioEmulation::LLEMultithreaded);
 | 
			
		||||
    }
 | 
			
		||||
    dsp->SetInterruptHandler([this](Service::DSP::InterruptType type, AudioCore::DspPipe pipe) {
 | 
			
		||||
        interrupts_fired[static_cast<u32>(type)][static_cast<u32>(pipe)] = 1;
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,84 @@
 | 
			
		||||
#include "audio_core/dsp_interface.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/hle/result.h"
 | 
			
		||||
#include "core/memory.h"
 | 
			
		||||
 | 
			
		||||
namespace Settings {
 | 
			
		||||
enum class AudioEmulation : u32;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ServiceFixture {
 | 
			
		||||
public:
 | 
			
		||||
    typedef int Handle;
 | 
			
		||||
 | 
			
		||||
    static constexpr int PIPE2_IRQ_HANDLE = 0x919E;
 | 
			
		||||
    static constexpr int DSP_SEMAPHORE_HANDLE = 0xD59;
 | 
			
		||||
 | 
			
		||||
    // SVC
 | 
			
		||||
    Result svcCreateEvent(Handle* event, u32 reset_type) {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Result svcWaitSynchronization(Handle handle, s64 nanoseconds);
 | 
			
		||||
 | 
			
		||||
    Result svcClearEvent(Handle handle);
 | 
			
		||||
 | 
			
		||||
    Result svcSignalEvent(Handle handle);
 | 
			
		||||
 | 
			
		||||
    Result svcCloseHandle(Handle handle) {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // dsp::DSP
 | 
			
		||||
    Result DSP_LoadComponent(const u8* dspfirm_data, size_t size, u8 progmask, u8 datamask,
 | 
			
		||||
                             bool* dspfirm_loaded);
 | 
			
		||||
 | 
			
		||||
    Result DSP_UnloadComponent();
 | 
			
		||||
 | 
			
		||||
    Result DSP_RegisterInterruptEvents(Handle /*handle*/, u32 /*interrupt*/, u32 /*channel*/) {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Result DSP_GetSemaphoreHandle(Handle* handle);
 | 
			
		||||
 | 
			
		||||
    Result DSP_SetSemaphoreMask(u32 /*mask*/) {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Result DSP_SetSemaphore(u32 semaphore);
 | 
			
		||||
 | 
			
		||||
    Result DSP_ReadPipeIfPossible(u32 channel, u32 peer, void* out_buffer, u32 size, u16* out_size);
 | 
			
		||||
 | 
			
		||||
    Result DSP_ConvertProcessAddressFromDspDram(u32 dsp_address, u16** host_address);
 | 
			
		||||
 | 
			
		||||
    Result DSP_WriteProcessPipe(u32 channel, const void* buffer, u32 length);
 | 
			
		||||
 | 
			
		||||
    Result DSP_FlushDataCache(void*, size_t) {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // libctru
 | 
			
		||||
    u32 osConvertVirtToPhys(void* vaddr);
 | 
			
		||||
 | 
			
		||||
    void* linearAlloc(size_t size);
 | 
			
		||||
 | 
			
		||||
    Result dspInit();
 | 
			
		||||
 | 
			
		||||
    Result dspExit() {
 | 
			
		||||
        return ResultSuccess;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Selects the DPS Emulation to use with the fixture
 | 
			
		||||
    void InitDspCore(Settings::AudioEmulation dsp_core);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    Core::System system{};
 | 
			
		||||
    Memory::MemorySystem memory{system};
 | 
			
		||||
    Core::Timing core_timing{1, 100};
 | 
			
		||||
    std::unique_ptr<AudioCore::DspInterface> dsp{};
 | 
			
		||||
 | 
			
		||||
    std::array<std::array<bool, 3>, 4> interrupts_fired = {};
 | 
			
		||||
 | 
			
		||||
    std::size_t linear_alloc_offset = 0;
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user