diff --git a/src/audio_core/hle/shared_memory.h b/src/audio_core/hle/shared_memory.h index d49bce7856..dfa9d2da1b 100644 --- a/src/audio_core/hle/shared_memory.h +++ b/src/audio_core/hle/shared_memory.h @@ -316,7 +316,7 @@ struct SourceStatus { u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count u32_dsp buffer_position; ///< Number of samples into the current buffer u16_le current_buffer_id; ///< Updated when a buffer finishes playing - INSERT_PADDING_DSPWORDS(1); + u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing }; Status status[num_sources]; diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index bdf4dc02a3..54a28bee39 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -324,6 +324,7 @@ void Source::GenerateFrame() { if (state.current_buffer.empty() && !DequeueBuffer()) { state.enabled = false; state.buffer_update = true; + state.last_buffer_id = state.current_buffer_id; state.current_buffer_id = 0; return; } @@ -411,6 +412,7 @@ bool Source::DequeueBuffer() { state.next_sample_number = state.current_sample_number; state.current_buffer_physical_address = buf.physical_address; state.current_buffer_id = buf.buffer_id; + state.last_buffer_id = 0; state.buffer_update = buf.from_queue && !buf.has_played; if (buf.is_looping) { @@ -432,9 +434,10 @@ SourceStatus::Status Source::GetCurrentStatus() { ret.is_enabled = state.enabled; ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0; state.buffer_update = false; - ret.current_buffer_id = state.current_buffer_id; - ret.buffer_position = state.current_sample_number; ret.sync_count = state.sync_count; + ret.buffer_position = state.current_sample_number; + ret.current_buffer_id = state.current_buffer_id; + ret.last_buffer_id = state.last_buffer_id; return ret; } diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index 10ebb00904..a3af06d27d 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h @@ -143,7 +143,8 @@ private: // buffer_id state bool buffer_update = false; - u32 current_buffer_id = 0; + u16 last_buffer_id = 0; + u16 current_buffer_id = 0; // Decoding state diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 590f07e783..baddc1ada3 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(tests core/memory/vm_manager.cpp precompiled_headers.h audio_core/hle/hle.cpp + audio_core/hle/source.cpp audio_core/lle/lle.cpp audio_core/audio_fixures.h audio_core/decoder_tests.cpp diff --git a/src/tests/audio_core/hle/source.cpp b/src/tests/audio_core/hle/source.cpp new file mode 100644 index 0000000000..2e0bb3ec29 --- /dev/null +++ b/src/tests/audio_core/hle/source.cpp @@ -0,0 +1,379 @@ +#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); +}