907 lines
52 KiB
C++
907 lines
52 KiB
C++
#include "Ngs2Test.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <numbers>
|
|
#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)) == static_cast<int>(0x804A0230u));
|
|
Require(sceNgs2RackDestroy(0x1234, nullptr) == static_cast<int>(0x804A0261u));
|
|
|
|
const auto system = CreateSystem();
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
Ngs2RackInfo rackInfo{};
|
|
Require(sceNgs2RackGetInfo(0x1234, &rackInfo, sizeof(rackInfo)) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE);
|
|
Require(sceNgs2RackGetInfo(rack, nullptr, sizeof(rackInfo)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2RackGetInfo(rack, &rackInfo, sizeof(rackInfo) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
|
|
Require(sceNgs2RackGetInfo(rack, &rackInfo, sizeof(rackInfo)) == SCE_NGS2_OK);
|
|
Require(rackInfo.rack_handle == rack && rackInfo.owner_system_handle == system && rackInfo.rack_id == SCE_NGS2_RACK_ID_SAMPLER && rackInfo.type == 1);
|
|
Require(rackInfo.uid != 0 && rackInfo.max_voices == 256 && rackInfo.max_channel_works == 256 && rackInfo.max_grain_samples == 512);
|
|
Require(rackInfo.max_ports == 8 && rackInfo.max_matrices == 1 && rackInfo.active_voice_count == 0 && rackInfo.name[0] == '\0');
|
|
Ngs2SubmixerRackOption named{};
|
|
named.rack_option.size = sizeof(named);
|
|
for (auto& c : named.rack_option.name) c = 'n';
|
|
named.rack_option.max_grain_samples = 256;
|
|
named.rack_option.max_voices = 2;
|
|
named.rack_option.max_matrices = 1;
|
|
named.rack_option.max_ports = 2;
|
|
named.max_channels = 2;
|
|
named.max_inputs = 3;
|
|
Ngs2ContextBufferInfo namedQuery{};
|
|
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_SUBMIXER, &named.rack_option, &namedQuery) == SCE_NGS2_OK);
|
|
const auto namedBuffer = Buffer(namedQuery);
|
|
uintptr_t namedRack = 0;
|
|
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_SUBMIXER, &named.rack_option, &namedBuffer, &namedRack) == SCE_NGS2_OK);
|
|
Ngs2RackInfo namedInfo{};
|
|
Require(sceNgs2RackGetInfo(namedRack, &namedInfo, sizeof(namedInfo)) == SCE_NGS2_OK);
|
|
Require(namedInfo.name[0] == 'n' && namedInfo.name[62] == 'n' && namedInfo.name[63] == '\0');
|
|
Require(namedInfo.type == 2 && namedInfo.rack_id == SCE_NGS2_RACK_ID_SUBMIXER && namedInfo.uid != rackInfo.uid && namedInfo.max_grain_samples == 256);
|
|
Require(namedInfo.max_voices == 2 && namedInfo.max_ports == 2 && namedInfo.max_inputs == 3 && namedInfo.max_channel_works == 0);
|
|
Require(namedInfo.buffer_info.host_buffer == namedBuffer.host_buffer && namedInfo.render_count == 0);
|
|
uintptr_t named0 = 0;
|
|
Require(sceNgs2RackGetVoiceHandle(namedRack, 0, &named0) == SCE_NGS2_OK);
|
|
Control(named0, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 2, 0});
|
|
Event(named0, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
Require(sceNgs2RackGetInfo(namedRack, &namedInfo, sizeof(namedInfo)) == SCE_NGS2_OK && namedInfo.active_voice_count == 1);
|
|
Ngs2VoiceChannelsInfo stereoInfo{};
|
|
Require(sceNgs2VoiceQueryInfo(named0, SCE_NGS2_VOICE_INFO_CHANNELS, &stereoInfo, sizeof(stereoInfo)) == SCE_NGS2_OK && stereoInfo.num_channels == 2);
|
|
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 == 2 && 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);
|
|
Ngs2VoicePortInfo portInfo{};
|
|
Require(sceNgs2VoiceGetPortInfo(sampler, 0, nullptr, sizeof(portInfo)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2VoiceGetPortInfo(sampler, 0, &portInfo, sizeof(portInfo) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
|
|
Require(sceNgs2VoiceGetPortInfo(sampler, 0, &portInfo, sizeof(portInfo)) == SCE_NGS2_OK);
|
|
Require(portInfo.dest_handle == master && portInfo.matrix_id == -1 && portInfo.volume == 1.0f);
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 0.5f});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Require(sceNgs2VoiceGetPortInfo(sampler, 0, &portInfo, sizeof(portInfo)) == SCE_NGS2_OK);
|
|
Require(portInfo.dest_handle == master && portInfo.matrix_id == 0 && portInfo.volume == 0.5f);
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 1.0f});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, -1});
|
|
Require(sceNgs2VoiceGetPortInfo(sampler, 1, &portInfo, sizeof(portInfo)) == SCE_NGS2_OK && portInfo.dest_handle == 0);
|
|
bool portOutOfRange = false;
|
|
try { sceNgs2VoiceGetPortInfo(sampler, 8, &portInfo, sizeof(portInfo)); } catch (const std::invalid_argument&) { portOutOfRange = true; }
|
|
Require(portOutOfRange);
|
|
Ngs2VoiceChannelsInfo channelsInfo{};
|
|
Require(sceNgs2VoiceQueryInfo(sampler, SCE_NGS2_VOICE_INFO_CHANNELS, nullptr, sizeof(channelsInfo)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2VoiceQueryInfo(sampler, SCE_NGS2_VOICE_INFO_CHANNELS, &channelsInfo, sizeof(channelsInfo) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
|
|
Require(sceNgs2VoiceQueryInfo(sampler, SCE_NGS2_VOICE_INFO_CHANNELS, &channelsInfo, sizeof(channelsInfo)) == SCE_NGS2_OK);
|
|
Require(channelsInfo.num_channels == 1 && channelsInfo.reserved == 0);
|
|
bool unknownInfo = false;
|
|
try { sceNgs2VoiceQueryInfo(sampler, 0x4000, &channelsInfo, sizeof(channelsInfo)); } catch (const std::runtime_error&) { unknownInfo = true; }
|
|
Require(unknownInfo);
|
|
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 bool Near(float a, float b) {
|
|
return std::fabs(a - b) < 1e-4f;
|
|
}
|
|
|
|
static bool PanThrows(Ngs2PanWork* work, const Ngs2PanParam* params, std::uint32_t numParams, std::uint32_t format) {
|
|
float out[64] = {};
|
|
try { sceNgs2PanGetVolumeMatrix(work, params, numParams, format, out); } catch (const std::exception&) { return true; }
|
|
return false;
|
|
}
|
|
|
|
static bool PanInitThrows(Ngs2PanWork* work, const float* angles, float unitAngle, std::uint32_t numSpeakers) {
|
|
try { sceNgs2PanInit(work, angles, unitAngle, numSpeakers); } catch (const std::exception&) { return true; }
|
|
return false;
|
|
}
|
|
|
|
struct PanCase {
|
|
std::vector<float> angles;
|
|
float unitAngle;
|
|
std::uint32_t numSpeakers;
|
|
std::vector<Ngs2PanParam> params;
|
|
std::uint32_t format;
|
|
std::vector<float> expected;
|
|
};
|
|
|
|
static void TestPan() {
|
|
const float pi = std::numbers::pi_v<float>;
|
|
Ngs2PanWork work{};
|
|
Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK);
|
|
Require(work.num_speakers == 2 && work.unit_angle == 360.0f);
|
|
Require(work.speaker_angles[0] == 90.0f && work.speaker_angles[1] == 270.0f && work.speaker_angles[2] == 450.0f);
|
|
Require(sceNgs2PanInit(&work, nullptr, 2.0f * pi, 2) == SCE_NGS2_OK && work.unit_angle == 2.0f * pi);
|
|
Require(work.speaker_angles[0] == 90.0f / 180.0f * pi && work.speaker_angles[1] == 270.0f / 180.0f * pi && work.speaker_angles[2] == 450.0f);
|
|
Require(sceNgs2PanInit(&work, nullptr, 100.0f, 4) == SCE_NGS2_OK && work.unit_angle == 360.0f && work.speaker_angles[4] == 400.0f);
|
|
const float frontBack[] = {-30.0f, 30.0f, 0.0f, -110.0f, 470.0f};
|
|
Require(sceNgs2PanInit(&work, frontBack, 360.0f, 5) == SCE_NGS2_OK);
|
|
const float circle[] = {0.0f, 30.0f, 110.0f, 250.0f, 330.0f, 360.0f};
|
|
for (std::uint32_t i = 0; i < 6; i++) Require(work.speaker_angles[i] == circle[i]);
|
|
|
|
const std::vector<PanCase> cases = {
|
|
{{}, 360.0f, 2, {{0, 1, 1, 0}, {45, 1, 1, 0}, {90, 1, 1, 0}, {135, 1, 1, 0}, {180, 1, 1, 0}, {225, 1, 1, 0}, {270, 1, 1, 0}, {315, 1, 1, 0}}, 2,
|
|
{0.707106769f, 0.382683456f, 0.0f, 0.382683456f, 0.707106769f, 0.923879504f, 1.0f, 0.923879504f, 0.707106769f, 0.923879504f, 1.0f, 0.923879504f, 0.707106769f, 0.382683456f, 0.0f, 0.382683456f}},
|
|
{{}, 360.0f, 2, {{30, 0, 1, 0}, {30, -1, 1, 0}, {30, 0.5f, 0.5f, 0}, {30, 3, 1, 0}, {-30, -0.25f, 1, 0}}, 2,
|
|
{0.965925813f, 0.866025448f, 0.39667666f, 0.49999997f, 0.896872759f, 0.965925813f, 0.49999997f, 0.495722443f, 0.866025448f, 0.997858942f}},
|
|
{{}, 360.0f, 2, {{60, 1, 1, 0.5f}}, 1, {0.866025388f}},
|
|
{{}, 360.0f, 2, {{60, 1, 1, 0.5f}}, 6, {0.258819073f, 0.965925813f, 0.0f, 0.0f, 0.0f, 0.0f}},
|
|
{{}, 2.0f * pi, 2, {{0.25f * pi, 1, 1, 0}, {1.5f * pi, 1, 1, 0}}, 2, {0.999965429f, 1.0f, 0.00831161533f, 0.0f}},
|
|
{{}, 360.0f, 4, {{100, 1, 1, 0.25f}}, 6, {0.0f, 0.222520918f, 0.0f, 0.25f, 0.0f, 0.974927902f}},
|
|
{{}, 360.0f, 4, {{100, 1, 1, 0.25f}}, 2, {0.0f, 0.709984899f}},
|
|
{{}, 360.0f, 5, {{70, 1, 0.8f, 0.5f}, {200, 0.5f, 1, 0}}, 6,
|
|
{0.0f, 0.0f, 0.625465155f, 0.270598054f, 0.0f, 0.270598054f, 0.5f, 0.0f, 0.0f, 0.782271147f, 0.498791903f, 0.491533577f}},
|
|
{{}, 360.0f, 5, {{70, 1, 0.8f, 0.5f}}, 1, {0.691666603f}},
|
|
{{}, 360.0f, 7, {{180, 1, 1, 0.25f}}, 8, {0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 0.707106769f, 0.707106769f}},
|
|
{{}, 360.0f, 7, {{180, 1, 1, 0.25f}}, 6, {0.0f, 0.0f, 0.0f, 0.25f, 0.707106769f, 0.707106769f}},
|
|
{{-pi / 6, pi / 6, 0, -110 * pi / 180, 110 * pi / 180}, 2.0f * pi, 5, {{1.0f, 1, 1, 0}, {-2.5f, 0.2f, 1, 0.1f}}, 6,
|
|
{0.0f, 0.0f, 0.859783232f, 0.582614243f, 0.0f, 0.0f, 0.0f, 0.100000001f, 0.0f, 0.753403723f, 0.510659218f, 0.372568965f}},
|
|
{{-30, 30, 0, -90, 90, -150, 150}, 360.0f, 7, {{-120, 1, 1, 0}}, 1, {0.707106769f}},
|
|
{{-45, 45}, 360.0f, 2, {{10, 1, 1, 0}, {720 + 170, 1, 1, 0}}, 2, {0.57357651f, 0.664795876f, 0.819152057f, 0.747025073f}},
|
|
{{-40, 40, -120, 120}, 360.0f, 4, {{-80, 0.75f, 1, 0}}, 8, {0.69351995f, 0.0746578425f, 0.0f, 0.0f, 0.69351995f, 0.180239946f, 0.0f, 0.0f}},
|
|
};
|
|
for (const auto& panCase : cases) {
|
|
Require(sceNgs2PanInit(&work, panCase.angles.empty() ? nullptr : panCase.angles.data(), panCase.unitAngle, panCase.numSpeakers) == SCE_NGS2_OK);
|
|
std::vector<float> out(panCase.expected.size(), -1.0f);
|
|
Require(sceNgs2PanGetVolumeMatrix(&work, panCase.params.data(), static_cast<std::uint32_t>(panCase.params.size()), panCase.format, out.data()) == SCE_NGS2_OK);
|
|
for (std::size_t i = 0; i < out.size(); i++) Require(std::fabs(out[i] - panCase.expected[i]) < 1e-6f);
|
|
}
|
|
|
|
const Ngs2PanParam front{0.0f, 1.0f, 1.0f, 0.0f};
|
|
float out[2] = {};
|
|
Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK);
|
|
Require(sceNgs2PanGetVolumeMatrix(&work, &front, 1, 2, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2PanGetVolumeMatrix(&work, &front, 0, 2, out) == SCE_NGS2_OK);
|
|
Require(PanThrows(nullptr, &front, 1, 2) && PanThrows(&work, nullptr, 0, 2));
|
|
Require(PanThrows(&work, &front, 1, 4) && PanThrows(&work, &front, 1, 3) && !PanThrows(&work, &front, 1, 8));
|
|
const Ngs2PanParam many[9] = {};
|
|
Require(PanThrows(&work, many, 9, 2) && !PanThrows(&work, many, 8, 2));
|
|
const Ngs2PanParam nanAngle{NAN, 1.0f, 1.0f, 0.0f};
|
|
Require(PanThrows(&work, &nanAngle, 1, 2));
|
|
const Ngs2PanParam laterNan[2] = {front, nanAngle};
|
|
float untouched[4] = {-1.0f, -1.0f, -1.0f, -1.0f};
|
|
bool rejected = false;
|
|
try { sceNgs2PanGetVolumeMatrix(&work, laterNan, 2, 2, untouched); } catch (const std::invalid_argument&) { rejected = true; }
|
|
Require(rejected && std::all_of(std::begin(untouched), std::end(untouched), [](float level) { return level == -1.0f; }));
|
|
const Ngs2PanParam huge{1e30f, 1.0f, 1.0f, 0.0f};
|
|
Require(PanThrows(&work, &huge, 1, 2));
|
|
const float hugeSpeaker[] = {1e30f, 30.0f};
|
|
const float hugeNegative[] = {-1e30f, 30.0f};
|
|
Require(PanInitThrows(&work, hugeSpeaker, 360.0f, 2) && PanInitThrows(&work, hugeNegative, 360.0f, 2));
|
|
Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK);
|
|
work.speaker_angles[0] = -INFINITY;
|
|
Require(PanThrows(&work, &front, 1, 2));
|
|
Require(sceNgs2PanInit(&work, nullptr, 360.0f, 2) == SCE_NGS2_OK);
|
|
Require(PanInitThrows(nullptr, nullptr, 360.0f, 2));
|
|
for (std::uint32_t count : {0u, 1u, 3u, 6u, 8u}) Require(PanInitThrows(&work, nullptr, 360.0f, count));
|
|
const float pair[] = {-30.0f, 30.0f};
|
|
Require(PanInitThrows(&work, pair, 180.0f, 2) && !PanInitThrows(&work, pair, 2.0f * pi, 2));
|
|
const float invalid[] = {0.0f, NAN};
|
|
Require(PanInitThrows(&work, invalid, 360.0f, 2));
|
|
const float onUnit[] = {720.0f, 30.0f};
|
|
const float belowZero[] = {-360.0f, 30.0f};
|
|
Require(PanInitThrows(&work, onUnit, 360.0f, 2) && !PanInitThrows(&work, belowZero, 360.0f, 2));
|
|
work.unit_angle = 100.0f;
|
|
Require(PanThrows(&work, &front, 1, 2));
|
|
}
|
|
|
|
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 std::vector<float> MatrixLevelFrame(const std::vector<float>& levels, bool command) {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 2);
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, master);
|
|
if (command) {
|
|
const Ngs2VoiceCommand set{5, 0, 0x11, static_cast<std::uint16_t>(levels.size()), {.levels = levels.data()}};
|
|
Require(sceNgs2VoiceRunCommands(sampler, &set, 1) == SCE_NGS2_OK);
|
|
} else {
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, static_cast<std::uint32_t>(levels.size()), levels.data()});
|
|
}
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
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);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
return {out[0], out[1]};
|
|
}
|
|
|
|
static void TestMatrixLevelClamp() {
|
|
const float inf = INFINITY;
|
|
for (bool command : {false, true}) {
|
|
Require(MatrixLevelFrame({4.0f, -4.0f}, command) == (std::vector<float>{2.0f, -2.0f}));
|
|
Require(MatrixLevelFrame({4.0001f, -4.5f}, command) == (std::vector<float>{2.0f, -2.0f}));
|
|
Require(MatrixLevelFrame({100.0f, -100.0f}, command) == (std::vector<float>{2.0f, -2.0f}));
|
|
Require(MatrixLevelFrame({inf, -inf}, command) == (std::vector<float>{2.0f, -2.0f}));
|
|
Require(MatrixLevelFrame({3.5f, -0.25f}, command) == (std::vector<float>{1.75f, -0.125f}));
|
|
const auto nan = MatrixLevelFrame({NAN, 0.5f}, command);
|
|
Require(std::isnan(nan[0]) && nan[1] == 0.25f);
|
|
}
|
|
|
|
const auto system = CreateSystem();
|
|
const std::vector<std::int16_t> silence(Grain, 0);
|
|
const auto sampler = Sampler(system, silence, 0);
|
|
const float levels[2] = {1.0f, 1.0f};
|
|
bool empty = false;
|
|
try { Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 0, levels}); } catch (const std::invalid_argument&) { empty = true; }
|
|
Require(empty);
|
|
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;
|
|
}
|
|
|
|
struct ReverbFrame {
|
|
std::uint32_t frame;
|
|
std::vector<float> samples;
|
|
};
|
|
|
|
struct ReverbCase {
|
|
std::uint32_t channels;
|
|
float amplitude;
|
|
bool noise;
|
|
Ngs2ReverbI3DL2Param params;
|
|
std::int32_t changeGrain;
|
|
Ngs2ReverbI3DL2Param change;
|
|
std::vector<ReverbFrame> expected;
|
|
};
|
|
|
|
static std::vector<float> RenderReverb(const ReverbCase& reverbCase, std::uint32_t frames) {
|
|
const auto firstBuffer = usedBuffers;
|
|
const auto system = CreateSystem();
|
|
Require(sceNgs2SystemSetGrainSamples(system, 256) == SCE_NGS2_OK);
|
|
const auto channels = reverbCase.channels;
|
|
const auto master = Mastering(system, channels);
|
|
const auto reverb = Voice(CreateRack(system, SCE_NGS2_RACK_ID_REVERB));
|
|
Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, channels, channels, 0, 0});
|
|
Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_I3DL2, Ngs2ReverbVoiceI3DL2Param{{}, reverbCase.params});
|
|
Patch(reverb, master);
|
|
Event(reverb, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
std::vector<std::int16_t> pcm(4096 * channels, 0);
|
|
std::uint32_t seed = 1;
|
|
for (std::uint32_t i = 0; reverbCase.noise && i + 3 * channels < pcm.size(); i++) {
|
|
seed = seed * 1664525u + 1013904223u;
|
|
if (i < 3000 * channels) pcm[i + 3 * channels] = static_cast<std::int16_t>(static_cast<std::int32_t>((seed >> 16) & 0x3fff) - 0x2000);
|
|
}
|
|
for (std::uint32_t c = 0; !reverbCase.noise && c < channels; c++) pcm[3 * channels + c] = static_cast<std::int16_t>((16384 - 4000 * static_cast<std::int32_t>(c)) * reverbCase.amplitude);
|
|
const auto sampler = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER));
|
|
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels, 48000, 0, 0, 0}});
|
|
const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 0, 0, 4096, 0, 0};
|
|
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
|
|
Patch(sampler, reverb);
|
|
std::vector<float> identity(channels * channels, 0.0f);
|
|
for (std::uint32_t c = 0; c < channels; c++) identity[c * channels + c] = 1.0f;
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, channels * channels, identity.data()});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
std::vector<float> all;
|
|
std::vector<float> out(256 * channels);
|
|
for (std::uint32_t grain = 0; grain * 256 < frames; grain++) {
|
|
if (static_cast<std::int32_t>(grain) == reverbCase.changeGrain) Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_I3DL2, Ngs2ReverbVoiceI3DL2Param{{}, reverbCase.change});
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, channels};
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
all.insert(all.end(), out.begin(), out.end());
|
|
}
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
usedBuffers = firstBuffer;
|
|
return all;
|
|
}
|
|
|
|
static void TestReverb() {
|
|
const Ngs2ReverbI3DL2Param cave{1.0f, 0.0f, -1000, 0, 0, 2.91f, 1.3f, -602, 0.015f, -302, 0.022f, 100.0f, 100.0f, 5000.0f, {}};
|
|
auto changed = cave;
|
|
changed.reflection_pattern = 5;
|
|
changed.decay_time = 0.7f;
|
|
changed.decay_hf_ratio = 0.6f;
|
|
changed.reflections_delay = 0.004f;
|
|
changed.reverb_delay = 0.03f;
|
|
changed.room_hf = -900;
|
|
changed.wet = 0.6f;
|
|
changed.dry = 0.4f;
|
|
auto denser = cave;
|
|
denser.density = 99.999f;
|
|
const std::vector<ReverbCase> cases = {
|
|
{2, 1.0f, false, cave, -1, {}, {{100, {0, 0}}, {723, {0.0687032491f, 0.0687032491f}}, {1055, {0.0392590016f, 0.0392590016f}}, {1417, {0.0131517649f, 0.0131517649f}},
|
|
{1779, {0.0098147504f, 0.0098147504f}}, {2500, {0, 0}}, {4000, {1.45101382e-07f, -1.15403088e-06f}}, {6000, {0.000655628915f, 3.03641864e-05f}},
|
|
{9000, {0.000400578661f, -0.000684358703f}}, {12000, {0.000191548112f, 0.000220489033f}}, {16000, {0.000210488462f, 0.000146183695f}},
|
|
{20000, {2.41150738e-05f, -0.000864534522f}}, {26000, {0.000125532999f, -0.000234790568f}}, {32000, {-0.000462158496f, 4.56995949e-05f}}}},
|
|
{2, 1.0f, false, {1.0f, 0.0f, -500, -800, 3, 1.2f, 0.5f, -300, 0.01f, 0, 0.03f, 80.0f, 60.0f, 3000.0f, {}}, -1, {},
|
|
{{100, {0, 0}}, {723, {0.000166983227f, 0.000166983227f}}, {1055, {-3.41919585e-05f, -3.41919585e-05f}}, {1417, {0, 0}}, {1779, {0, 0}},
|
|
{2500, {0, 1.55222626e-08f}}, {4000, {-0.000106364969f, 9.90879998e-05f}}, {6000, {0.00013573296f, 7.22560799e-06f}},
|
|
{9000, {-7.91876082e-05f, 0.000225067721f}}, {12000, {0.000150469248f, 0.00036288987f}}, {16000, {-7.86288219e-05f, -0.000195921995f}},
|
|
{20000, {1.01910318e-05f, 5.79599109e-05f}}, {26000, {2.14232614e-06f, -2.05506512e-05f}}, {32000, {-4.31467561e-06f, 2.34100444e-05f}}}},
|
|
{6, 1.0f, false, {1.0f, 0.2f, -200, -300, 12, 1.0f, 0.9f, -100, 0.03f, -100, 0.04f, 70.0f, 90.0f, 4000.0f, {}}, -1, {},
|
|
{{6000, {-0.000167909253f, -0.000179917421f, -0.000647808949f, 0, 3.22720189e-05f, 0.000213803389f}},
|
|
{9000, {8.47857882e-05f, 8.62322413e-05f, -0.000100753634f, 0, 0.000109430795f, -0.000122895101f}},
|
|
{12000, {4.37544441e-05f, 3.58103216e-06f, -0.00013746327f, 0, 9.3237948e-05f, -0.00013529828f}},
|
|
{16000, {8.63046807e-08f, 1.62214419e-05f, 8.89167641e-05f, 0, 1.02178528e-05f, 0.000201705057f}},
|
|
{20000, {-9.6526619e-06f, -2.73812566e-05f, -1.92096536e-07f, 0, -0.000116049101f, 6.94858772e-06f}},
|
|
{26000, {1.27750207e-06f, -1.59169917e-06f, 5.30674515e-05f, 0, -3.59285696e-05f, 5.16466662e-06f}},
|
|
{32000, {3.5737969e-06f, 4.19655044e-06f, -2.30460955e-07f, 0, 1.16596939e-05f, 1.14889262e-05f}}}},
|
|
{2, 1.0f, false, cave, 6, changed, {{723, {0.0687032491f, 0.0687032491f}}, {1055, {0.0392590016f, 0.0392590016f}}, {1417, {0.0131517649f, 0.0131517649f}},
|
|
{1779, {4.38156894e-05f, 4.38156894e-05f}}, {2500, {0, 0}}, {4000, {-1.60370582e-05f, -4.44955149e-05f}}, {6000, {1.02755057e-05f, 8.34046841e-06f}},
|
|
{9000, {1.1641363e-05f, -4.69685165e-06f}}, {12000, {3.57917179e-06f, -2.34550089e-06f}}, {16000, {-8.00212234e-08f, 3.19533115e-06f}},
|
|
{20000, {5.66619519e-07f, 4.26969848e-07f}}, {26000, {1.46052287e-07f, -2.44572629e-08f}}, {32000, {-4.45668853e-08f, 1.51349102e-08f}}}},
|
|
{2, 0.02f, false, {40.0f, -3.0f, 5000, 500, 20, 50.0f, 5.0f, 3000, 0.9f, 5000, 0.9f, 300.0f, 300.0f, 50000.0f, {}}, -1, {},
|
|
{{14403, {0.877262115f, 0.877262115f}}, {14500, {0, 0}}, {15000, {0, 0}}, {15500, {0, 7.50572099e-07f}}, {16000, {4.95207075e-07f, 6.37215771e-06f}},
|
|
{20000, {0.00482429564f, -0.00149071764f}}, {26000, {-0.0096597122f, 0.00718426611f}}, {32000, {0.0107425917f, -0.000738018774f}}}},
|
|
{2, 1.0f, false, {1.0f, 0.3f, 0, -200, 15, 0.8f, 1.0f, 0, 0.002f, -500, 0.004f, 60.0f, 80.0f, 6000.0f, {}}, -1, {},
|
|
{{60, {0, 0}}, {99, {0.0947210863f, 0.0947210863f}}, {100, {0.0472047739f, 0.0472047739f}}, {150, {0, 0}}, {200, {0, 0}}, {300, {0, 0}},
|
|
{2000, {-9.86956729e-06f, 7.96923473e-07f}}, {3000, {-0.000152820328f, -4.59704825e-06f}}, {5000, {0.000203833857f, -0.000208990226f}},
|
|
{8000, {-3.69777154e-05f, -0.000259475899f}}}},
|
|
{2, 1.0f, true, cave, 6, changed, {{1540, {0.0222691782f, 0.0242232569f}}, {1600, {0.0201471522f, 0.0186609477f}}, {1700, {-0.0211425349f, -0.0235042125f}},
|
|
{1780, {-0.0168117173f, 0.00417749817f}}, {1800, {-0.0866222829f, -0.0945812687f}}, {2000, {0.0131965755f, -0.0707511827f}},
|
|
{2500, {0.0942443311f, -0.100531064f}}, {3500, {0.000985965831f, 0.000946713379f}}, {8000, {-0.000968122098f, 0.000556072395f}},
|
|
{15000, {0.000227724231f, 0.000151095577f}}}},
|
|
{2, 1.0f, true, cave, 20, denser, {{5200, {-0.00942458585f, -0.0064059021f}}, {6000, {0.00248076022f, 0.00317249796f}},
|
|
{8000, {0.00102439919f, 0.00105210941f}}, {12000, {1.63059376e-05f, 6.10309735e-06f}}, {20000, {-1.08853078e-06f, -2.99162899e-08f}}}},
|
|
};
|
|
for (const auto& reverbCase : cases) {
|
|
const auto out = RenderReverb(reverbCase, 32256);
|
|
for (const auto& expected : reverbCase.expected) {
|
|
for (std::uint32_t c = 0; c < reverbCase.channels; c++) {
|
|
if (c == 3) continue;
|
|
const float value = out[expected.frame * reverbCase.channels + c];
|
|
Require(std::fabs(value - expected.samples[c]) <= 2e-7f + std::fabs(expected.samples[c]) * 1e-5f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void TestReverbSetup() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 2);
|
|
const auto reverb = Voice(CreateRack(system, SCE_NGS2_RACK_ID_REVERB));
|
|
for (std::uint32_t channels : {3u, 4u, 5u, 7u}) {
|
|
bool rejected = false;
|
|
try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, channels, channels, 0, 0}); } catch (const std::invalid_argument&) { rejected = true; }
|
|
Require(rejected);
|
|
}
|
|
bool conversion = false;
|
|
try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 1, 2, 0, 0}); } catch (const std::runtime_error&) { conversion = true; }
|
|
Require(conversion);
|
|
bool flags = false;
|
|
try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 2, 2, 1, 0}); } catch (const std::runtime_error&) { flags = true; }
|
|
Require(flags);
|
|
Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_SETUP, Ngs2ReverbVoiceSetupParam{{}, 2, 2, 0, 0});
|
|
Ngs2ReverbI3DL2Param invalid{1.0f, 0.0f, 0, 0, 0, 1.0f, 1.0f, 0, 0.0f, 0, 0.0f, 100.0f, 100.0f, 5000.0f, {}};
|
|
invalid.decay_time = NAN;
|
|
bool nan = false;
|
|
try { Control(reverb, SCE_NGS2_REVERB_VOICE_PARAM_I3DL2, Ngs2ReverbVoiceI3DL2Param{{}, invalid}); } catch (const std::invalid_argument&) { nan = true; }
|
|
Require(nan);
|
|
Patch(reverb, master);
|
|
Event(reverb, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
const std::vector<std::int16_t> pcm(Grain * 64, 16384);
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, reverb);
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
std::vector<float> out(Grain * 2);
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
|
|
float loudest = 0.0f;
|
|
for (std::uint32_t grain = 0; grain < 600; grain++) {
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
for (float sample : out) loudest = std::max(loudest, std::fabs(sample));
|
|
}
|
|
Require(loudest > 0.0f && loudest < 1e-8f);
|
|
Ngs2VoiceState state{};
|
|
Require(sceNgs2VoiceGetState(reverb, &state, sizeof(state)) == SCE_NGS2_OK && (state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_INUSE) != 0);
|
|
Require(sceNgs2SystemSetSampleRate(system, 7000) == SCE_NGS2_OK);
|
|
bool lowRate = false;
|
|
try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { lowRate = true; }
|
|
Require(lowRate);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
|
|
Ngs2ReverbRackOption option{};
|
|
option.rack_option.size = sizeof(option);
|
|
option.rack_option.max_grain_samples = 512;
|
|
option.rack_option.max_voices = 1;
|
|
option.rack_option.max_input_delay_blocks = 1;
|
|
option.rack_option.max_matrices = 1;
|
|
option.rack_option.max_ports = 8;
|
|
option.max_channels = 8;
|
|
option.reverb_size = 1;
|
|
Ngs2ContextBufferInfo query{};
|
|
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_REVERB, &option.rack_option, &query) == SCE_NGS2_OK);
|
|
for (std::uint32_t size : {0u, 2u}) {
|
|
option.reverb_size = size;
|
|
bool unsupported = false;
|
|
try { sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_REVERB, &option.rack_option, &query); } catch (const std::runtime_error&) { unsupported = true; }
|
|
Require(unsupported);
|
|
}
|
|
}
|
|
|
|
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 uintptr_t RackVoice(uintptr_t rack, std::uint32_t index) {
|
|
uintptr_t voice = 0;
|
|
Require(sceNgs2RackGetVoiceHandle(rack, index, &voice) == SCE_NGS2_OK && voice != 0);
|
|
return voice;
|
|
}
|
|
|
|
static uintptr_t MasteringRack(uintptr_t system, std::uint32_t voices) {
|
|
Ngs2MasteringRackOption option{};
|
|
option.rack_option.size = sizeof(option);
|
|
option.rack_option.max_grain_samples = 512;
|
|
option.rack_option.max_voices = voices;
|
|
option.rack_option.max_input_delay_blocks = 1;
|
|
option.rack_option.max_matrices = 1;
|
|
option.rack_option.max_ports = 8;
|
|
option.max_channels = 8;
|
|
Ngs2ContextBufferInfo query{};
|
|
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_MASTERING, &option.rack_option, &query) == SCE_NGS2_OK);
|
|
const auto buffer = Buffer(query);
|
|
uintptr_t rack = 0;
|
|
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_MASTERING, &option.rack_option, &buffer, &rack) == SCE_NGS2_OK && rack != 0);
|
|
return rack;
|
|
}
|
|
|
|
static void StereoSource(uintptr_t voice, const std::vector<std::int16_t>& pcm, uintptr_t master) {
|
|
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), 0, 0, static_cast<std::uint32_t>(pcm.size()), 0, 0};
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
|
|
const float levels[2] = {1.0f, 0.5f};
|
|
Control(voice, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 2, levels});
|
|
Control(voice, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Patch(voice, master);
|
|
}
|
|
|
|
static void TestStereoIntoSurround() {
|
|
const auto system = CreateSystem();
|
|
const auto masteringRack = MasteringRack(system, 3);
|
|
const auto samplerRack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
uintptr_t masters[3];
|
|
for (std::uint32_t i = 0; i < 3; i++) {
|
|
masters[i] = RackVoice(masteringRack, i);
|
|
Control(masters[i], SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 2, 0});
|
|
Control(masters[i], SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, i, 0});
|
|
Event(masters[i], SCE_NGS2_VOICE_EVENT_PLAY);
|
|
}
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
for (std::uint32_t i = 0; i < 2; i++) {
|
|
const auto source = RackVoice(samplerRack, i);
|
|
StereoSource(source, pcm, masters[i]);
|
|
Event(source, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
}
|
|
std::vector<float> outFive(Grain * 6, -1.0f);
|
|
std::vector<float> outSeven(Grain * 8, -1.0f);
|
|
std::vector<float> outQuad(Grain * 4, -1.0f);
|
|
const Ngs2RenderBufferInfo info[3] = {
|
|
{outFive.data(), outFive.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 6},
|
|
{outSeven.data(), outSeven.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 8},
|
|
{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(outFive[i * 6] == 0.5f && outFive[i * 6 + 1] == 0.25f);
|
|
for (std::uint32_t channel = 2; channel < 6; channel++) Require(outFive[i * 6 + channel] == 0.0f);
|
|
Require(outSeven[i * 8] == 0.5f && outSeven[i * 8 + 1] == 0.25f);
|
|
for (std::uint32_t channel = 2; channel < 8; channel++) Require(outSeven[i * 8 + channel] == 0.0f);
|
|
}
|
|
|
|
const auto rejectedSource = RackVoice(samplerRack, 2);
|
|
StereoSource(rejectedSource, pcm, masters[2]);
|
|
Event(rejectedSource, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
bool threw = false;
|
|
try {
|
|
sceNgs2SystemRender(system, info, 3);
|
|
} catch (const std::runtime_error&) {
|
|
threw = true;
|
|
}
|
|
Require(threw);
|
|
Require(sceNgs2SystemDestroy(system, 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 constexpr float HalfPower = std::numbers::sqrt2_v<float> / 2.0f;
|
|
|
|
static std::vector<float> MixFrame(std::uint32_t sourceChannels, std::uint32_t sourceChannel, std::uint32_t destChannels) {
|
|
const auto firstBuffer = usedBuffers;
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, destChannels);
|
|
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, sourceChannels, 48000, 0, 0, 0}});
|
|
std::vector<std::int16_t> frames(Grain * sourceChannels, 0);
|
|
for (std::uint32_t i = 0; i < Grain; i++) frames[i * sourceChannels + sourceChannel] = 16384;
|
|
const Ngs2WaveformBlock block{0, frames.size() * sizeof(std::int16_t), 0, 0, Grain, 0, 0};
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, frames.data(), 0, 1, &block});
|
|
Patch(voice, master);
|
|
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
std::vector<float> out(Grain * destChannels, -1.0f);
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, destChannels};
|
|
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
usedBuffers = firstBuffer;
|
|
return std::vector<float>(out.begin(), out.begin() + destChannels);
|
|
}
|
|
|
|
static void TestDefaultChannelMap() {
|
|
const float half = 0.5f * HalfPower;
|
|
const float quarter = 0.5f * (std::numbers::sqrt2_v<float> / 4.0f);
|
|
Require(MixFrame(1, 0, 1) == std::vector<float>{0.5f});
|
|
Require(MixFrame(1, 0, 2) == (std::vector<float>{half, half}));
|
|
Require(MixFrame(1, 0, 6) == (std::vector<float>{0, 0, 0.5f, 0, 0, 0}));
|
|
Require(MixFrame(1, 0, 8) == (std::vector<float>{0, 0, 0.5f, 0, 0, 0, 0, 0}));
|
|
Require(MixFrame(2, 0, 1) == std::vector<float>{half} && MixFrame(2, 1, 1) == std::vector<float>{half});
|
|
Require(MixFrame(2, 1, 2) == (std::vector<float>{0, 0.5f}));
|
|
Require(MixFrame(2, 1, 6) == (std::vector<float>{0, 0.5f, 0, 0, 0, 0}));
|
|
Require(MixFrame(3, 2, 2) == (std::vector<float>{0, 0}));
|
|
Require(MixFrame(3, 2, 6) == (std::vector<float>{0, 0, 0, 0.5f, 0, 0}));
|
|
Require(MixFrame(4, 2, 1) == std::vector<float>{0.25f});
|
|
Require(MixFrame(4, 2, 2) == (std::vector<float>{half, 0}));
|
|
Require(MixFrame(4, 3, 6) == (std::vector<float>{0, 0, 0, 0, 0, 0.5f}));
|
|
Require(MixFrame(5, 2, 2) == (std::vector<float>{half, half}));
|
|
Require(MixFrame(5, 3, 1) == std::vector<float>{0.25f});
|
|
Require(MixFrame(5, 3, 2) == (std::vector<float>{0, 0}));
|
|
Require(MixFrame(6, 3, 1) == std::vector<float>{0});
|
|
Require(MixFrame(6, 4, 2) == (std::vector<float>{0.25f, 0}));
|
|
Require(MixFrame(6, 5, 8) == (std::vector<float>{0, 0, 0, 0, 0, 0.5f, 0, 0}));
|
|
Require(MixFrame(7, 6, 1) == std::vector<float>{quarter});
|
|
Require(MixFrame(7, 6, 2) == (std::vector<float>{quarter, quarter}));
|
|
Require(MixFrame(7, 6, 6) == (std::vector<float>{0, 0, 0, 0, half, half}));
|
|
Require(MixFrame(7, 6, 8) == (std::vector<float>{0, 0, 0, 0, 0, 0, half, half}));
|
|
Require(MixFrame(8, 6, 2) == (std::vector<float>{0.25f, 0}));
|
|
Require(MixFrame(8, 7, 6) == (std::vector<float>{0, 0, 0, 0, 0, 0.5f}));
|
|
Require(MixFrame(8, 7, 8) == (std::vector<float>{0, 0, 0, 0, 0, 0, 0, 0.5f}));
|
|
|
|
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{{}, 4, 0});
|
|
Patch(submixer, master);
|
|
Event(submixer, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, submixer);
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
std::vector<float> out(Grain * 2, -1.0f);
|
|
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
|
|
bool unsupported = false;
|
|
try { sceNgs2SystemRender(system, &info, 1); } catch (const std::runtime_error&) { unsupported = true; }
|
|
Require(unsupported);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestUnsetMatrix() {
|
|
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);
|
|
Event(submixer, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
|
|
const std::vector<std::int16_t> pcm(Grain, 16384);
|
|
const auto sampler = Sampler(system, pcm, 0);
|
|
Patch(sampler, submixer);
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 0.5f});
|
|
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
|
|
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);
|
|
const float spread = 0.25f * HalfPower;
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == spread && out[i * 2 + 1] == spread);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
|
|
const auto stereoSystem = CreateSystem();
|
|
const auto stereoMaster = Mastering(stereoSystem, 2);
|
|
const auto stereo = Voice(CreateRack(stereoSystem, SCE_NGS2_RACK_ID_SAMPLER));
|
|
Control(stereo, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}});
|
|
std::vector<std::int16_t> frames;
|
|
for (std::uint32_t i = 0; i < Grain; i++) {
|
|
frames.push_back(16384);
|
|
frames.push_back(8192);
|
|
}
|
|
const Ngs2WaveformBlock block{0, frames.size() * sizeof(std::int16_t), 0, 0, Grain, 0, 0};
|
|
Control(stereo, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, frames.data(), 0, 1, &block});
|
|
Patch(stereo, stereoMaster);
|
|
Control(stereo, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0});
|
|
Event(stereo, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
Require(sceNgs2SystemRender(stereoSystem, &info, 1) == SCE_NGS2_OK);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f && out[i * 2 + 1] == 0.25f);
|
|
Require(sceNgs2SystemDestroy(stereoSystem, 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);
|
|
}
|
|
|
|
static uintptr_t exitSystem = 0;
|
|
|
|
static void RenderAfterStaticTeardown() {
|
|
RenderI16(exitSystem);
|
|
Require(sceNgs2SystemDestroy(exitSystem, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
int main() {
|
|
Require(std::atexit(RenderAfterStaticTeardown) == 0);
|
|
TestErrorsAndInfo();
|
|
TestPcmBlockEnd();
|
|
TestPan();
|
|
TestPitchAndRepeat();
|
|
TestSubmixerMatrix();
|
|
TestReverb();
|
|
TestReverbSetup();
|
|
TestSampleRate();
|
|
TestUserData();
|
|
TestMasteringGain();
|
|
TestMatrixLevelClamp();
|
|
TestStereoIntoSurround();
|
|
TestLock();
|
|
TestDefaultChannelMap();
|
|
TestUnsetMatrix();
|
|
TestAllocator();
|
|
exitSystem = CreateSystem();
|
|
return 0;
|
|
}
|