AnyPS5/core/libs/prx/libSceAudioOut/tests/AudioOutMixLevelPadSpk.cpp

185 lines
6.9 KiB
C++

#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>
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<typename TFunction>
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<int>(0x80260003);
constexpr int invalidPortType = static_cast<int>(0x8026000A);
constexpr int invalidMixLevel = static_cast<int>(0x80260014);
template<typename TSample, typename TSetup>
std::vector<TSample> Play(int type, std::uint32_t format, const std::vector<TSample>& 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<int>(frames));
Require(sceAudioOutOutput(handle, nullptr) == static_cast<int>(frames));
Require(sceAudioOutClose(handle) == 0);
std::ifstream file(path, std::ios::binary);
const std::vector<char> bytes{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
file.close();
std::filesystem::remove(path);
Require(bytes.size() >= block.size() * sizeof(TSample) && bytes.size() % sizeof(TSample) == 0);
const auto* samples = reinterpret_cast<const TSample*>(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<std::int16_t> Ramp() {
std::vector<std::int16_t> block(frames);
for (std::uint32_t frame = 0; frame < frames; frame++) {
block[frame] = static_cast<std::int16_t>((frame % 2 == 0 ? 1 : -1) * 256 * (static_cast<int>(frame % 100) + 1));
}
return block;
}
std::vector<std::int16_t> Scaled(const std::vector<std::int16_t>& block, int level) {
std::vector<std::int16_t> scaled;
for (const std::int16_t sample : block) {
Require(sample * level % unity == 0);
scaled.push_back(static_cast<std::int16_t>(sample * level / unity));
}
return scaled;
}
void TestDefaultLevel() {
std::vector<std::int16_t> 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<float> 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;
}