#include #include #include "audio_core/hle/shared_memory.h" #include "common/settings.h" #include "tests/audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h" TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 1", "[audio_core][hle]") { // World's worst triangle wave generator. // Generates PCM16. auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) { for (size_t i = 0; i < size; i++) { u32 data = (i % freq) * 256; audio_buffer[i] = (data << 16) | (data & 0xFFFF); } DSP_FlushDataCache(audio_buffer, size); }; constexpr size_t NUM_SAMPLES = 160 * 1; u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer, NUM_SAMPLES, 160); u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer2, NUM_SAMPLES, 80); u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer3, NUM_SAMPLES, 40); MerryAudio::AudioState state; { std::vector dspfirm; SECTION("HLE") { // The test case assumes HLE AudioCore doesn't require a valid firmware InitDspCore(Settings::AudioEmulation::HLE); dspfirm = {0}; } SECTION("LLE Sanity") { 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; } state.waitForSync(); initSharedMem(state); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); { u16 buffer_id = 0; size_t next_queue_position = 0; state.write().source_configurations->config[0].play_position = 0; state.write().source_configurations->config[0].physical_address = osConvertVirtToPhys(audio_buffer3); state.write().source_configurations->config[0].length = NUM_SAMPLES; state.write().source_configurations->config[0].mono_or_stereo.Assign( AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo); 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] .buffers[next_queue_position] .physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer); state.write().source_configurations->config[0].buffers[next_queue_position].length = NUM_SAMPLES; state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty = false; state.write().source_configurations->config[0].buffers[next_queue_position].is_looping = false; state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id = ++buffer_id; state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position; next_queue_position = (next_queue_position + 1) % 4; state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true); state.write().source_configurations->config[0].enable = true; state.write().source_configurations->config[0].enable_dirty.Assign(true); state.notifyDsp(); for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); if (!state.read().source_statuses->status[0].is_enabled) { state.write().source_configurations->config[0].enable = true; state.write().source_configurations->config[0].enable_dirty.Assign(true); } if (state.read().source_statuses->status[0].current_buffer_id_dirty) { if (state.read().source_statuses->status[0].current_buffer_id == buffer_id || state.read().source_statuses->status[0].current_buffer_id == 0) { state.write() .source_configurations->config[0] .buffers[next_queue_position] .physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer); state.write() .source_configurations->config[0] .buffers[next_queue_position] .length = NUM_SAMPLES; state.write() .source_configurations->config[0] .buffers[next_queue_position] .adpcm_dirty = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .is_looping = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .buffer_id = ++buffer_id; state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position; next_queue_position = (next_queue_position + 1) % 4; state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true); } } state.notifyDsp(); } // current_buffer_id should be 0 if the queue is not empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0); // Let the queue finish playing for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); state.notifyDsp(); } // TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue, // that differs from the HLE implementation // REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5); // current_buffer_id should be equal to buffer_id once the queue is empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id); } end: audioExit(state); } TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 2", "[audio_core][hle]") { // World's worst triangle wave generator. // Generates PCM16. auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) { for (size_t i = 0; i < size; i++) { u32 data = (i % freq) * 256; audio_buffer[i] = (data << 16) | (data & 0xFFFF); } DSP_FlushDataCache(audio_buffer, size); }; constexpr size_t NUM_SAMPLES = 160 * 1; u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer, NUM_SAMPLES, 160); u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer2, NUM_SAMPLES, 80); u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32)); fillBuffer(audio_buffer3, NUM_SAMPLES, 40); MerryAudio::AudioState state; { std::vector dspfirm; SECTION("HLE") { // The test case assumes HLE AudioCore doesn't require a valid firmware InitDspCore(Settings::AudioEmulation::HLE); dspfirm = {0}; } SECTION("LLE Sanity") { 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; } state.waitForSync(); initSharedMem(state); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); state.waitForSync(); state.notifyDsp(); { u16 buffer_id = 0; size_t next_queue_position = 0; state.write().source_configurations->config[0].play_position = 0; state.write().source_configurations->config[0].physical_address = osConvertVirtToPhys(audio_buffer3); state.write().source_configurations->config[0].length = NUM_SAMPLES; state.write().source_configurations->config[0].mono_or_stereo.Assign( AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo); 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] .buffers[next_queue_position] .physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer); state.write().source_configurations->config[0].buffers[next_queue_position].length = NUM_SAMPLES; state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty = false; state.write().source_configurations->config[0].buffers[next_queue_position].is_looping = false; state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id = ++buffer_id; state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position; next_queue_position = (next_queue_position + 1) % 4; state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true); state.write().source_configurations->config[0].enable = true; state.write().source_configurations->config[0].enable_dirty.Assign(true); state.notifyDsp(); for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); if (!state.read().source_statuses->status[0].is_enabled) { state.write().source_configurations->config[0].enable = true; state.write().source_configurations->config[0].enable_dirty.Assign(true); } if (state.read().source_statuses->status[0].current_buffer_id_dirty) { if (state.read().source_statuses->status[0].current_buffer_id == buffer_id || state.read().source_statuses->status[0].current_buffer_id == 0) { state.write() .source_configurations->config[0] .buffers[next_queue_position] .physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer); state.write() .source_configurations->config[0] .buffers[next_queue_position] .length = NUM_SAMPLES; state.write() .source_configurations->config[0] .buffers[next_queue_position] .adpcm_dirty = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .is_looping = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .buffer_id = ++buffer_id; state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position; next_queue_position = (next_queue_position + 1) % 4; state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true); } } state.notifyDsp(); } // current_buffer_id should be 0 if the queue is not empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0); // Let the queue finish playing for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); state.notifyDsp(); } // TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue, // that differs from the HLE implementation // REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5); // current_buffer_id should be equal to buffer_id once the queue is empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id); // Restart Playing for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); if (!state.read().source_statuses->status[0].is_enabled) { state.write().source_configurations->config[0].enable = true; state.write().source_configurations->config[0].enable_dirty.Assign(true); } if (state.read().source_statuses->status[0].current_buffer_id_dirty) { if (state.read().source_statuses->status[0].current_buffer_id == buffer_id || state.read().source_statuses->status[0].current_buffer_id == 0) { state.write() .source_configurations->config[0] .buffers[next_queue_position] .physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer); state.write() .source_configurations->config[0] .buffers[next_queue_position] .length = NUM_SAMPLES; state.write() .source_configurations->config[0] .buffers[next_queue_position] .adpcm_dirty = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .is_looping = false; state.write() .source_configurations->config[0] .buffers[next_queue_position] .buffer_id = ++buffer_id; state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position; next_queue_position = (next_queue_position + 1) % 4; state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true); } } state.notifyDsp(); } // current_buffer_id should be 0 if the queue is not empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0); // Let the queue finish playing for (size_t frame_count = 0; frame_count < 10; frame_count++) { state.waitForSync(); state.notifyDsp(); } // current_buffer_id should be equal to buffer_id once the queue is empty REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id); } end: audioExit(state); }