mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +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
				
			
		|  | @ -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; | ||||
| }; | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue