|
|
|
@ -0,0 +1,379 @@
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <catch2/catch_template_test_macros.hpp>
|
|
|
|
|
#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<u8> 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<u8> 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);
|
|
|
|
|
}
|