315 lines
16 KiB
C++
315 lines
16 KiB
C++
#include "Ngs2Test.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
static uintptr_t Sampler(uintptr_t system, const std::vector<std::int16_t>& pcm, std::uint32_t repeats) {
|
|
const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER));
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}});
|
|
const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), repeats, 0, static_cast<std::uint32_t>(pcm.size()), 0, 0x55};
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
|
|
return voice;
|
|
}
|
|
|
|
static std::vector<std::uint32_t> callbackFlags;
|
|
static void APS5_VABI OnBlock(const Ngs2VoiceCallbackInfo* info) {
|
|
Require(info->callback_data == 7 && info->user_data == 0x55);
|
|
callbackFlags.push_back(info->flag);
|
|
}
|
|
|
|
static std::vector<std::int16_t> RenderI16(uintptr_t system) {
|
|
std::vector<std::int16_t> out(Grain, -1);
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(std::int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1};
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
return out;
|
|
}
|
|
|
|
static void TestErrorsAndInfo() {
|
|
Ngs2SystemOption option{};
|
|
Require(sceNgs2SystemResetOption(&option) == SCE_NGS2_OK);
|
|
Require(option.size == sizeof(option) && option.max_grain_samples == 512 && option.num_grain_samples == 256 && option.sample_rate == 48000);
|
|
Require(sceNgs2SystemQueryBufferSize(&option, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_SAMPLER, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
|
|
Ngs2SystemInfo info{};
|
|
Require(sceNgs2SystemGetInfo(0x1234, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
Require(sceNgs2RackDestroy(0x1234, nullptr) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE);
|
|
|
|
const auto system = CreateSystem();
|
|
CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
Require(sceNgs2SystemGetInfo(system, nullptr, sizeof(info)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
|
|
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK);
|
|
Require(info.system_handle == system && info.uid != 0 && info.rack_count == 1 && info.sample_rate == 48000);
|
|
Require(info.num_grain_samples == Grain && info.max_grain_samples == 512 && info.render_count == 0);
|
|
|
|
Ngs2ContextBufferInfo released{};
|
|
Require(sceNgs2SystemDestroy(system, &released) == SCE_NGS2_OK && released.host_buffer != nullptr);
|
|
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
}
|
|
|
|
static void TestPcmBlockEnd() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
std::vector<std::int16_t> pcm;
|
|
for (int i = 0; i < 12; i++) pcm.push_back(static_cast<std::int16_t>(i * 1000 - 4000));
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, master);
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_CALLBACK, Ngs2VoiceCallbackParam{{}, OnBlock, 7, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
|
Require(Flags(sampler) == 0);
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
Require(Flags(sampler) == SCE_NGS2_VOICE_STATE_FLAG_INUSE);
|
|
|
|
callbackFlags.clear();
|
|
auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == pcm[i]);
|
|
Require(Flags(sampler) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING) && callbackFlags.empty());
|
|
|
|
Ngs2SamplerVoiceState state{};
|
|
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state) - 8) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
|
|
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == Grain && state.decoded_data_size == Grain * 2 && state.user_data == 0x55);
|
|
Require(state.waveform_data == pcm.data() + Grain);
|
|
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 4 ? pcm[Grain + i] : 0));
|
|
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
|
|
Require(Flags(sampler) == 0);
|
|
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == pcm.size() && state.waveform_data == pcm.data() + pcm.size());
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestPitchAndRepeat() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const std::vector<std::int16_t> pcm{0, 1000, 2000, 3000};
|
|
const auto sampler = Sampler(system, pcm, 1);
|
|
Patch(sampler, master);
|
|
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH, Ngs2SamplerVoicePitchParam{{}, 0.5f});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_CALLBACK,
|
|
Ngs2VoiceCallbackParam{{}, OnBlock, 7, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END | SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT, 0});
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
|
|
callbackFlags.clear();
|
|
const std::int16_t first[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 1500};
|
|
auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == first[i]);
|
|
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT);
|
|
|
|
const std::int16_t second[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 3000};
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == second[i]);
|
|
Require(callbackFlags.size() == 2 && callbackFlags[1] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
|
|
Require(Flags(sampler) == 0);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestSubmixerMatrix() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 2);
|
|
const auto submixer = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER));
|
|
Control(submixer, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 2, 0});
|
|
Patch(submixer, master);
|
|
const Ngs2VoiceCommand play{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_PLAY}};
|
|
Require(sceNgs2VoiceRunCommands(submixer, &play, 1) == SCE_NGS2_OK);
|
|
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, submixer);
|
|
const float levels[2] = {1.0f, 0.5f};
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 2, levels});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 0.5f});
|
|
Require(sceNgs2VoiceRunCommands(sampler, &play, 1) == SCE_NGS2_OK);
|
|
|
|
std::vector<float> out(Grain * 2, -1.0f);
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.125f);
|
|
|
|
Ngs2SubmixerVoiceState state{};
|
|
Require(sceNgs2VoiceGetState(submixer, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.voice_state.state_flags == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING));
|
|
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
for (float sample : out) Require(sample == 0.0f);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static int allocations = 0;
|
|
static std::int32_t APS5_VABI Allocate(Ngs2ContextBufferInfo* info) {
|
|
Require(info->host_buffer == nullptr && info->host_buffer_size != 0 && info->user_data == 9);
|
|
info->host_buffer = std::calloc(1, info->host_buffer_size);
|
|
allocations++;
|
|
return SCE_NGS2_OK;
|
|
}
|
|
static std::int32_t APS5_VABI Release(Ngs2ContextBufferInfo* info) {
|
|
Require(info->host_buffer != nullptr && info->user_data == 9);
|
|
std::free(info->host_buffer);
|
|
allocations--;
|
|
return SCE_NGS2_OK;
|
|
}
|
|
|
|
static void TestSampleRate() {
|
|
const auto system = CreateSystem();
|
|
Require(sceNgs2SystemSetSampleRate(0x1234, 96000) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
bool threw = false;
|
|
try {
|
|
sceNgs2SystemSetSampleRate(system, 0);
|
|
} catch (const std::invalid_argument&) {
|
|
threw = true;
|
|
}
|
|
Require(threw);
|
|
Require(sceNgs2SystemSetSampleRate(system, 96000) == SCE_NGS2_OK);
|
|
Ngs2SystemInfo info{};
|
|
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK && info.sample_rate == 96000);
|
|
|
|
const auto master = Mastering(system, 1);
|
|
const std::vector<std::int16_t> pcm{0, 1000, 2000, 3000};
|
|
const auto sampler = Sampler(system, pcm, 1);
|
|
Patch(sampler, master);
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
const std::int16_t expected[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 1500};
|
|
const auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == expected[i]);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestUserData() {
|
|
uintptr_t value = 1;
|
|
Require(sceNgs2SystemSetUserData(0x1234, 5) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
Require(sceNgs2SystemGetUserData(0x1234, &value) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE && value == 1);
|
|
const auto system = CreateSystem();
|
|
Require(sceNgs2SystemGetUserData(system, &value) == SCE_NGS2_OK && value == 0);
|
|
Require(sceNgs2SystemSetUserData(system, 0xfedcba9876543210) == SCE_NGS2_OK);
|
|
Require(sceNgs2SystemGetUserData(system, &value) == SCE_NGS2_OK && value == 0xfedcba9876543210);
|
|
bool threw = false;
|
|
try {
|
|
sceNgs2SystemGetUserData(system, nullptr);
|
|
} catch (const std::invalid_argument&) {
|
|
threw = true;
|
|
}
|
|
Require(threw);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestMasteringGain() {
|
|
const auto system = CreateSystem();
|
|
const auto stereo = Mastering(system, 2);
|
|
const auto surround = Mastering(system, 6);
|
|
const auto quad = Mastering(system, 4);
|
|
Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 1.0f});
|
|
Control(surround, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f});
|
|
Control(surround, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 1});
|
|
Control(quad, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 0.5f, 0.25f});
|
|
Control(quad, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 2});
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
const auto toStereo = Sampler(system, pcm, 0);
|
|
const float stereoLevels[2] = {1.0f, 0.5f};
|
|
Control(toStereo, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 2, stereoLevels});
|
|
Control(toStereo, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Patch(toStereo, stereo);
|
|
Event(toStereo, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
const auto toSurround = Sampler(system, pcm, 0);
|
|
const float surroundLevels[6] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
|
Control(toSurround, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 6, surroundLevels});
|
|
Control(toSurround, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Patch(toSurround, surround);
|
|
Event(toSurround, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
const auto toQuad = Sampler(system, pcm, 0);
|
|
Control(toQuad, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 4, surroundLevels});
|
|
Control(toQuad, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Patch(toQuad, quad);
|
|
Event(toQuad, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
bool rejected = false;
|
|
try { Control(stereo, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, NAN, 1.0f}); } catch (const std::exception&) { rejected = true; }
|
|
Require(rejected);
|
|
|
|
std::vector<float> outStereo(Grain * 2, -1.0f);
|
|
std::vector<float> outSurround(Grain * 6, -1.0f);
|
|
std::vector<float> outQuad(Grain * 4, -1.0f);
|
|
const Ngs2RenderBufferInfo info[3] = {
|
|
{outStereo.data(), outStereo.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2},
|
|
{outSurround.data(), outSurround.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 6},
|
|
{outQuad.data(), outQuad.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 4},
|
|
};
|
|
Require(sceNgs2SystemRender(system, info, 3) == SCE_NGS2_OK);
|
|
for (std::uint32_t i = 0; i < Grain; i++) {
|
|
Require(outStereo[i * 2] == 0.25f && outStereo[i * 2 + 1] == 0.125f);
|
|
for (std::uint32_t channel = 0; channel < 6; channel++) Require(outSurround[i * 6 + channel] == (channel == 3 ? 0.125f : 0.25f));
|
|
for (std::uint32_t channel = 0; channel < 4; channel++) Require(outQuad[i * 4 + channel] == 0.25f);
|
|
}
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
|
|
const auto loudSystem = CreateSystem();
|
|
const auto loud = Mastering(loudSystem, 1);
|
|
Control(loud, SCE_NGS2_MASTERING_VOICE_PARAM_GAIN, Ngs2MasteringVoiceGainParam{{}, 2.0f, 2.0f});
|
|
const std::vector<std::int16_t> loudPcm(Grain * 2, 24576);
|
|
const auto toLoud = Sampler(loudSystem, loudPcm, 0);
|
|
Patch(toLoud, loud);
|
|
Event(toLoud, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
auto clipped = RenderI16(loudSystem);
|
|
for (std::int16_t sample : clipped) Require(sample == 32767);
|
|
Control(loud, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 1, 0});
|
|
Event(loud, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
auto reset = RenderI16(loudSystem);
|
|
for (std::int16_t sample : reset) Require(sample == 24576);
|
|
Require(sceNgs2SystemDestroy(loudSystem, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestLock() {
|
|
const auto system = CreateSystem();
|
|
Require(sceNgs2SystemLock(0x1234) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
Require(sceNgs2SystemUnlock(0x1234) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE);
|
|
Ngs2SystemInfo info{};
|
|
std::thread free([&] { Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK); });
|
|
free.join();
|
|
|
|
Require(sceNgs2SystemLock(system) == SCE_NGS2_OK);
|
|
std::atomic<bool> done = false;
|
|
std::thread blocked([&] {
|
|
Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK);
|
|
done = true;
|
|
});
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
Require(!done);
|
|
Require(sceNgs2SystemUnlock(system) == SCE_NGS2_OK);
|
|
blocked.join();
|
|
Require(done);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestAllocator() {
|
|
const Ngs2BufferAllocator allocator{Allocate, Release, 9};
|
|
uintptr_t system = 0;
|
|
Require(sceNgs2SystemCreateWithAllocator(nullptr, &allocator, &system) == SCE_NGS2_OK && allocations == 1);
|
|
uintptr_t sampler = 0;
|
|
uintptr_t master = 0;
|
|
Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_SAMPLER, nullptr, &allocator, &sampler) == SCE_NGS2_OK);
|
|
Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_MASTERING, nullptr, &allocator, &master) == SCE_NGS2_OK && allocations == 3);
|
|
Ngs2ContextBufferInfo released{};
|
|
Require(sceNgs2RackDestroy(sampler, &released) == SCE_NGS2_OK && allocations == 2 && released.host_buffer == nullptr);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK && allocations == 0);
|
|
Require(sceNgs2RackDestroy(master, nullptr) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE);
|
|
}
|
|
|
|
int main() {
|
|
TestErrorsAndInfo();
|
|
TestPcmBlockEnd();
|
|
TestPitchAndRepeat();
|
|
TestSubmixerMatrix();
|
|
TestSampleRate();
|
|
TestUserData();
|
|
TestMasteringGain();
|
|
TestLock();
|
|
TestAllocator();
|
|
return 0;
|
|
}
|