mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 00:53:02 -05:00
A sampler voice set up with an all-zero format has no channels. Adding no waveform blocks to it, or setting a matrix with no levels, threw std::invalid_argument, which ends the title. Accept both on such a voice: the empty add only applies RESET and the CONTINUE state, and the empty matrix clears that matrix, which rendering already treats as unset. Blocks with data, APPEND, and an empty matrix on a voice with a format are still refused. Steins;Gate Re:Boot (PPSA36591) clears a voice this way when a button is pressed at its title screen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
214 lines
11 KiB
C++
214 lines
11 KiB
C++
#include "Ngs2Test.hpp"
|
|
|
|
#include <cstdint>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
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 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 StreamVoice(uintptr_t rack, std::uint32_t index, uintptr_t master) {
|
|
const auto voice = RackVoice(rack, index);
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}});
|
|
Patch(voice, master);
|
|
return voice;
|
|
}
|
|
|
|
static void AddBlock(uintptr_t voice, const void* data, std::uint64_t bytes, std::uint32_t samples, std::uint32_t flags) {
|
|
const Ngs2WaveformBlock block{0, bytes, 0, 0, samples, 0, 0};
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, data, flags, 1, &block});
|
|
}
|
|
|
|
template <typename TFunction>
|
|
static bool Throws(TFunction function) {
|
|
try {
|
|
function();
|
|
} catch (const std::exception&) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void TestStreamEndsAtDeclaredSamples() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const auto voice = StreamVoice(rack, 0, master);
|
|
const std::vector<std::int16_t> first(6, 16384);
|
|
const std::vector<std::int16_t> second(8, 8192);
|
|
const std::vector<std::int16_t> last(6, 4096);
|
|
|
|
AddBlock(voice, first.data(), first.size() * 2, 20, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 6 ? 16384 : 0));
|
|
Require(Flags(voice) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING));
|
|
Ngs2SamplerVoiceState state{};
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == 6 && state.decoded_data_size == 12 && state.waveform_data == first.data() + 6);
|
|
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == 0);
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == 6 && state.decoded_data_size == 12);
|
|
|
|
AddBlock(voice, second.data(), second.size() * 2, 20, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK && state.waveform_data == second.data());
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == 8192);
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == 14 && state.decoded_data_size == 28 && state.waveform_data == second.data() + 8);
|
|
|
|
AddBlock(voice, last.data(), last.size() * 2, 20, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 6 ? 4096 : 0));
|
|
Require(Flags(voice) == 0);
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == 20 && state.decoded_data_size == 40 && state.waveform_data == last.data() + 6);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestStreamClosedBeforeDeclaredSamples() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const auto voice = StreamVoice(rack, 0, master);
|
|
const std::vector<std::int16_t> first(6, 16384);
|
|
const std::vector<std::int16_t> closing(4, 8192);
|
|
|
|
AddBlock(voice, first.data(), first.size() * 2, 1000, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
AddBlock(voice, closing.data(), closing.size() * 2, 1000, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
|
|
auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 6 ? 16384 : 8192));
|
|
Require(Flags(voice) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING));
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 2 ? 8192 : 0));
|
|
Require(Flags(voice) == 0);
|
|
Ngs2SamplerVoiceState state{};
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == 10 && state.decoded_data_size == 20);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestStreamRejections() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const std::vector<std::int16_t> pcm(6, 1000);
|
|
|
|
const auto empty = StreamVoice(rack, 0, master);
|
|
Require(Throws([&] { AddBlock(empty, pcm.data(), pcm.size() * 2, 6, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND); }));
|
|
|
|
const auto complete = StreamVoice(rack, 1, master);
|
|
AddBlock(complete, pcm.data(), pcm.size() * 2, 6, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
Require(Throws([&] { AddBlock(complete, pcm.data(), pcm.size() * 2, 6, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND); }));
|
|
|
|
const auto repeating = StreamVoice(rack, 2, master);
|
|
const Ngs2WaveformBlock repeated{0, pcm.size() * 2, 1, 0, 20, 0, 0};
|
|
Require(Throws([&] { Control(repeating, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 1, &repeated}); }));
|
|
|
|
const auto skipping = StreamVoice(rack, 3, master);
|
|
const Ngs2WaveformBlock skipped{0, pcm.size() * 2, 0, 1, 20, 0, 0};
|
|
Require(Throws([&] { Control(skipping, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 1, &skipped}); }));
|
|
|
|
const auto partial = StreamVoice(rack, 4, master);
|
|
AddBlock(partial, pcm.data(), pcm.size() * 2, 20, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
Require(Throws([&] { AddBlock(partial, pcm.data(), 3, 20, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND); }));
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static const std::int16_t Unused = 0;
|
|
|
|
static void TestSilenceBlock() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const auto voice = StreamVoice(rack, 0, master);
|
|
|
|
AddBlock(voice, &Unused, 0, Grain, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_SILENCE);
|
|
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
auto out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == 0);
|
|
Require(Flags(voice) == 0);
|
|
Ngs2SamplerVoiceState state{};
|
|
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
|
Require(state.num_decoded_samples == Grain && state.decoded_data_size == Grain * 2);
|
|
|
|
const auto withData = StreamVoice(rack, 1, master);
|
|
const std::vector<std::int16_t> pcm(Grain, 1234);
|
|
AddBlock(withData, pcm.data(), pcm.size() * 2, Grain, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_SILENCE);
|
|
Event(withData, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
out = RenderI16(system);
|
|
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == 1234);
|
|
Require(Flags(withData) == 0);
|
|
|
|
const auto dataless = StreamVoice(rack, 2, master);
|
|
Require(Throws([&] { AddBlock(dataless, &Unused, 0, Grain, 0); }));
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestNoBlocksForClearedVoice() {
|
|
const auto system = CreateSystem();
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const auto voice = RackVoice(rack, 0);
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {}});
|
|
const Ngs2WaveformBlock unused{};
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, &Unused, 0, 0, &unused});
|
|
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, &Unused, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET, 0, &unused});
|
|
const float level = 1.0f;
|
|
Control(voice, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 0, &level});
|
|
Require(Throws([&] { Control(voice, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 1, 0, &level}); }));
|
|
Require(Flags(voice) == 0);
|
|
const std::vector<std::int16_t> pcm(4, 16384);
|
|
Require(Throws([&] { AddBlock(voice, pcm.data(), pcm.size() * 2, 4, 0); }));
|
|
Require(Throws([&] { Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, &Unused, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND, 0, &unused}); }));
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
static void TestDrainedStreamReportsEmpty() {
|
|
const auto system = CreateSystem();
|
|
const auto master = Mastering(system, 1);
|
|
const auto rack = CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER);
|
|
const auto voice = StreamVoice(rack, 0, master);
|
|
const std::vector<std::int16_t> pcm(4, 16384);
|
|
constexpr std::uint32_t playing = SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING;
|
|
|
|
AddBlock(voice, pcm.data(), pcm.size() * 2, 4, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
|
RenderI16(system);
|
|
Require(Flags(voice) == (playing | SCE_NGS2_VOICE_STATE_FLAG_EMPTY));
|
|
|
|
const std::vector<std::int16_t> longer(Grain * 2, 8192);
|
|
AddBlock(voice, longer.data(), longer.size() * 2, Grain * 2, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE);
|
|
const auto out = RenderI16(system);
|
|
Require(out[0] == 8192);
|
|
Require(Flags(voice) == playing);
|
|
|
|
AddBlock(voice, pcm.data(), pcm.size() * 2, 4, 0);
|
|
RenderI16(system);
|
|
RenderI16(system);
|
|
Require((Flags(voice) & SCE_NGS2_VOICE_STATE_FLAG_EMPTY) == 0);
|
|
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
|
}
|
|
|
|
int main() {
|
|
TestStreamEndsAtDeclaredSamples();
|
|
TestStreamClosedBeforeDeclaredSamples();
|
|
TestStreamRejections();
|
|
TestSilenceBlock();
|
|
TestDrainedStreamReportsEmpty();
|
|
TestNoBlocksForClearedVoice();
|
|
return 0;
|
|
}
|