#include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include #include extern "C" { int APS5_VABI sceAudioOutOpen(int, int, int, std::uint32_t, std::uint32_t, std::uint32_t); int APS5_VABI sceAudioOutClose(int); int APS5_VABI sceAudioOutOutput(int, const void*); int APS5_VABI sceAudioOutSetVolume(int, std::uint32_t, int*); int APS5_VABI sceAudioOutSetMixLevelPadSpk(int, int); } static void Require(bool value) { if (!value) std::abort(); } static void SetEnvironment(const char* name, const std::string& value) { #ifdef _WIN32 _putenv_s(name, value.c_str()); #else setenv(name, value.c_str(), 1); #endif } template static bool ThrowsRuntimeError(TFunction function) { try { function(); } catch (const std::runtime_error&) { return true; } return false; } namespace { constexpr int user = 0x10000000; constexpr int portTypeMain = 0; constexpr int portTypePadSpeaker = 4; constexpr std::uint32_t formatS16Mono = 0; constexpr std::uint32_t formatFloatStereo = 4; constexpr std::uint32_t frames = 256; constexpr std::uint32_t frequency = 48000; constexpr int unity = 32768; constexpr int defaultMixLevel = 11626; constexpr int invalidPort = static_cast(0x80260003); constexpr int invalidPortType = static_cast(0x8026000A); constexpr int invalidMixLevel = static_cast(0x80260014); template std::vector Play(int type, std::uint32_t format, const std::vector& block, TSetup setup) { const auto path = std::filesystem::temp_directory_path() / ("anyps5_audio_out_mix_level_pad_spk-" + std::to_string(std::random_device{}()) + ".raw"); std::filesystem::remove(path); SetEnvironment("SDL_DISKAUDIOFILE", path.string()); const int handle = sceAudioOutOpen(user, type, 0, frames, frequency, format); Require(handle > 0); setup(handle); Require(sceAudioOutOutput(handle, block.data()) == static_cast(frames)); Require(sceAudioOutOutput(handle, nullptr) == static_cast(frames)); Require(sceAudioOutClose(handle) == 0); std::ifstream file(path, std::ios::binary); const std::vector bytes{std::istreambuf_iterator(file), std::istreambuf_iterator()}; file.close(); std::filesystem::remove(path); Require(bytes.size() >= block.size() * sizeof(TSample) && bytes.size() % sizeof(TSample) == 0); const auto* samples = reinterpret_cast(bytes.data()); std::size_t first = 0; std::size_t last = bytes.size() / sizeof(TSample); while (first < last && samples[first] == TSample{}) first++; while (last > first && samples[last - 1] == TSample{}) last--; return {samples + first, samples + last}; } std::vector Ramp() { std::vector block(frames); for (std::uint32_t frame = 0; frame < frames; frame++) { block[frame] = static_cast((frame % 2 == 0 ? 1 : -1) * 256 * (static_cast(frame % 100) + 1)); } return block; } std::vector Scaled(const std::vector& block, int level) { std::vector scaled; for (const std::int16_t sample : block) { Require(sample * level % unity == 0); scaled.push_back(static_cast(sample * level / unity)); } return scaled; } void TestDefaultLevel() { std::vector block(frames); for (std::uint32_t frame = 0; frame < frames; frame++) block[frame] = frame % 2 == 0 ? 16384 : -16384; const auto played = Play(portTypePadSpeaker, formatS16Mono, block, [](int) {}); Require(played == Scaled(block, defaultMixLevel)); Require(played[0] == 5813 && played[1] == -5813); } void TestLevels() { const auto block = Ramp(); Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, unity) == 0); }) == block); Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0); }) == Scaled(block, unity / 2)); Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, unity) == 0); Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 4) == 0); }) == Scaled(block, unity / 4)); Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, 0) == 0); }).empty()); } void TestLevelWithVolume() { const auto block = Ramp(); Require(Play(portTypePadSpeaker, formatS16Mono, block, [](int handle) { int volume = unity / 2; Require(sceAudioOutSetVolume(handle, 1, &volume) == 0); Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0); }) == Scaled(block, unity / 4)); std::vector stereo(frames * 2); for (std::uint32_t frame = 0; frame < frames; frame++) { stereo[frame * 2] = (frame % 2 == 0 ? 0.5f : -0.25f); stereo[frame * 2 + 1] = (frame % 2 == 0 ? -1.0f : 0.125f); } const auto played = Play(portTypePadSpeaker, formatFloatStereo, stereo, [](int handle) { int volume[2] = {unity, unity / 2}; Require(sceAudioOutSetVolume(handle, 2, volume) == 0); Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 4) == 0); }); Require(played.size() == stereo.size()); for (std::uint32_t frame = 0; frame < frames; frame++) { Require(played[frame * 2] == stereo[frame * 2] * 0.25f); Require(played[frame * 2 + 1] == stereo[frame * 2 + 1] * 0.125f); } } void TestOtherPortType() { const auto block = Ramp(); Require(Play(portTypeMain, formatS16Mono, block, [](int) {}) == block); Require(Play(portTypeMain, formatS16Mono, block, [](int handle) { Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == invalidPortType); Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidPortType); }) == block); } void TestRejectedLevels() { const auto block = Ramp(); int closed = 0; Require(Play(portTypePadSpeaker, formatS16Mono, block, [&closed](int handle) { closed = handle; Require(sceAudioOutSetMixLevelPadSpk(handle, unity / 2) == 0); Require(sceAudioOutSetMixLevelPadSpk(handle, unity + 1) == invalidMixLevel); Require(ThrowsRuntimeError([handle] { sceAudioOutSetMixLevelPadSpk(handle, -1); })); Require(sceAudioOutSetMixLevelPadSpk(0, unity) == invalidPort); Require(sceAudioOutSetMixLevelPadSpk(handle + 1, unity) == invalidPort); }) == Scaled(block, unity / 2)); Require(sceAudioOutSetMixLevelPadSpk(closed, unity) == invalidPort); } } int main() { SetEnvironment("SDL_AUDIODRIVER", "disk"); TestDefaultLevel(); TestLevels(); TestLevelWithVolume(); TestOtherPortType(); TestRejectedLevels(); return 0; }