mirror of
https://github.com/PabloMK7/citra
synced 2024-11-15 05:08:23 +00:00
tests: Port merry's audio tests (#7354)
This commit is contained in:
parent
789654d7da
commit
228f26d1e4
8 changed files with 705 additions and 1 deletions
|
@ -200,7 +200,7 @@ void DSP_DSP::UnloadComponent(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
|
||||||
LOG_INFO(Service_DSP, "(STUBBED)");
|
LOG_INFO(Service_DSP, "called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSP_DSP::FlushDataCache(Kernel::HLERequestContext& ctx) {
|
void DSP_DSP::FlushDataCache(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
|
@ -13,6 +13,11 @@ add_executable(tests
|
||||||
audio_core/audio_fixures.h
|
audio_core/audio_fixures.h
|
||||||
audio_core/decoder_tests.cpp
|
audio_core/decoder_tests.cpp
|
||||||
video_core/shader/shader_jit_compiler.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)
|
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;
|
||||||
|
};
|
Loading…
Reference in a new issue