fix(ngs2): reproduce sampler resampling and startup history

This commit is contained in:
Kyon committed 2026-10-26 03:31:08 +00:00
1 parent 188ec5ea15
commit 92cd244aa3
21 files changed
+499 -211

No files matched your search

@@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/Ngs2Library.cmake)
add_sce_ngs2_library(libSceNgs2.native)
if(BUILD_TESTING)
foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom PitchShift Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents)
foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom PitchShift Sampler Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents)
string(TOLOWER ${ngs2Test} testName)
add_test_executable(ngs2_${testName}_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2${ngs2Test}.cpp)
target_include_directories(ngs2_${testName}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src)
@@ -10,10 +10,11 @@ function(add_sce_ngs2_library target)
${ngs2Dir}/src/Rack.cpp
${ngs2Dir}/src/Render.cpp
${ngs2Dir}/src/Reverb.cpp
${ngs2Dir}/src/Sampler.cpp
${ngs2Dir}/src/System.cpp
${ngs2Dir}/src/Voice.cpp
)
set_property(SOURCE ${ngs2Dir}/src/PitchShift.cpp PROPERTY COMPILE_OPTIONS
set_property(SOURCE ${ngs2Dir}/src/PitchShift.cpp ${ngs2Dir}/src/Sampler.cpp PROPERTY COMPILE_OPTIONS
"$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-ffp-contract=off>;$<$<CXX_COMPILER_ID:MSVC>:/fp:strict>")
target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src)
target_link_libraries(${target} PRIVATE atrac9 libc libkernel)
@@ -490,7 +490,10 @@ static_assert(sizeof(Ngs2WaveformBlock) == 40);
struct Ngs2SamplerVoiceSetupParam {
Ngs2VoiceParamHeader header;
Ngs2WaveformFormat format;
std::uint32_t channel_mask;
std::uint32_t reserved;
};
static_assert(sizeof(Ngs2SamplerVoiceSetupParam) == 40);
struct Ngs2SamplerVoiceWaveformBlocksParam {
Ngs2VoiceParamHeader header;
@@ -503,7 +506,9 @@ struct Ngs2SamplerVoiceWaveformBlocksParam {
struct Ngs2SamplerVoicePitchParam {
Ngs2VoiceParamHeader header;
float ratio;
std::uint32_t reserved;
};
static_assert(sizeof(Ngs2SamplerVoicePitchParam) == 16);
struct Ngs2SamplerVoiceFilterParam {
Ngs2VoiceParamHeader header;
+17 -38
View File
@@ -202,7 +202,7 @@ static bool DecodeSuperframe(Ngs2Voice& voice) {
static bool EnsureFrames(Ngs2Voice& voice, std::size_t count) {
auto& atrac9 = voice.atrac9;
while (voice.state == Ngs2PlayState::Playing && BufferedFrames(voice) < count) {
while ((voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop) && BufferedFrames(voice) < count) {
if (atrac9.finishOnDrain) {
if (BufferedFrames(voice) != 0) return false;
atrac9.finishOnDrain = false;
@@ -210,47 +210,26 @@ static bool EnsureFrames(Ngs2Voice& voice, std::size_t count) {
}
if (!DecodeSuperframe(voice)) return false;
}
return voice.state == Ngs2PlayState::Playing;
return voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop;
}
static bool AdvanceAtrac9(Ngs2Voice& voice) {
while ((voice.phase >> 32) != 0 && voice.state == Ngs2PlayState::Playing) {
if (!EnsureFrames(voice, 1)) return false;
const auto count = std::min<std::uint64_t>(voice.phase >> 32, BufferedFrames(voice));
voice.atrac9.windowCursor += static_cast<std::uint32_t>(count);
voice.decodedSamples += count;
voice.phase -= count << 32;
if (BufferedFrames(voice) == 0 && voice.atrac9.finishOnDrain) {
voice.atrac9.finishOnDrain = false;
if (!Ngs2FinishBlock(voice)) return false;
}
}
return voice.state == Ngs2PlayState::Playing;
}
void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) {
constexpr double phaseOne = 4294967296.0;
const auto initialRevision = voice.waveformRevision;
const auto step = static_cast<std::uint64_t>(std::llround(voice.sampleRate * static_cast<double>(voice.pitch) / systemRate * phaseOne));
std::uint32_t Ngs2ReadSamplerAtrac9(Ngs2Voice& voice, float* dest, std::uint32_t stride, std::uint32_t count) {
const auto revision = voice.waveformRevision;
std::uint32_t written = 0;
for (; written < grain; ++written) {
if (!AdvanceAtrac9(voice) || !EnsureFrames(voice, 1)) break;
const auto revision = voice.waveformRevision;
const auto fraction = static_cast<float>(voice.phase / phaseOne);
if (fraction != 0.0f) EnsureFrames(voice, 2);
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != revision) break;
const auto* current = voice.atrac9.window.data() + static_cast<std::size_t>(voice.atrac9.windowCursor) * voice.channels;
const auto* next = BufferedFrames(voice) > 1 ? current + voice.channels : current;
for (std::uint32_t channel = 0; channel < voice.channels; ++channel)
voice.samples[static_cast<std::size_t>(channel) * grain + written] = std::lerp(current[channel], next[channel], fraction);
voice.phase += step;
while (written < count && (voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop) && voice.waveformRevision == revision) {
if (!EnsureFrames(voice, 1) || voice.waveformRevision != revision) break;
const auto available = std::min<std::size_t>(count - written, BufferedFrames(voice));
const auto* source = voice.atrac9.window.data() + static_cast<std::size_t>(voice.atrac9.windowCursor) * voice.channels;
for (std::size_t frame = 0; frame < available; ++frame) {
for (std::uint32_t channel = 0; channel < voice.channels; ++channel) {
dest[static_cast<std::size_t>(channel) * stride + written + frame] = source[frame * voice.channels + channel];
}
}
written += static_cast<std::uint32_t>(available);
voice.atrac9.windowCursor += static_cast<std::uint32_t>(available);
voice.decodedSamples += available;
}
voice.hasSamples = written != 0;
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != initialRevision) return;
AdvanceAtrac9(voice);
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != initialRevision) return;
if (!voice.acceptsBlocks && voice.blocks.empty() && BufferedFrames(voice) == 0 && voice.atrac9.remainingSamples != 0)
throw std::invalid_argument("NGS2: the ATRAC9 stream ends before its declared samples");
return written;
}
static std::uint16_t ReadLe16(const std::uint8_t* data) {
@@ -97,7 +97,6 @@ int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option
if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && option.module[m].option != nullptr) throw std::runtime_error("NGS2: an option for the custom Gain module is not implemented");
if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN && rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) continue;
if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) {
if (rackId != SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) throw std::runtime_error("NGS2: PitchShift on a custom sampler rack is not implemented");
continue;
}
if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) throw std::runtime_error("NGS2: the custom Gain module on a custom sampler rack is not implemented");
@@ -126,6 +126,10 @@ struct Ngs2Voice {
std::unique_ptr<Ngs2ReverbState, Ngs2ReverbDeleter> reverb;
float pitch = 1.0f;
std::uint64_t phase = 0;
std::array<std::array<float, 4>, NGS2_MAX_CHANNELS> samplerHistory{};
std::vector<float> samplerScratch;
std::vector<Ngs2Block> samplerReplayBlocks;
bool samplerReplayKnown = true;
std::deque<Ngs2Block> blocks;
bool acceptsBlocks = true;
std::uint64_t decodedSamples = 0;
@@ -145,8 +149,7 @@ struct Ngs2Voice {
std::vector<Ngs2VoiceCustomUserFx> customUserFx;
std::vector<Ngs2VoiceCustomGain> customGain;
std::vector<Ngs2PitchShift> customPitchShift;
Ngs2PitchShiftTransition pitchShiftTransition = Ngs2PitchShiftTransition::None;
bool pitchShiftPlayed = false;
Ngs2VoiceTransition outputTransition = Ngs2VoiceTransition::None;
Ngs2UserFxProcessHandler userFxHandler = nullptr;
std::array<std::uintptr_t, 3> userFxData{};
std::uint32_t userFxFlags = 0;
@@ -157,6 +160,7 @@ struct Ngs2Voice {
void SetEvent(std::uint32_t eventId);
void ResetSetup();
bool IsSampler() const;
const std::uint8_t* WaveformData() const;
std::uint32_t InputChannels() const { return inputChannels != 0 ? inputChannels : channels; }
};
@@ -170,6 +174,7 @@ struct Ngs2Rack {
std::uint32_t maxFilters = 0;
std::uint32_t maxGrainSamples = 0;
std::uint32_t maxChannelWorks = 0;
std::uint32_t maxWaveformBlocks = 0;
std::uint32_t maxInputs = 0;
std::uintptr_t userData = 0;
Ngs2ContextBufferInfo bufferInfo{};
@@ -242,10 +247,16 @@ void Ngs2SetupAtrac9(Ngs2Voice& voice, const Ngs2WaveformFormat& format);
void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param);
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block);
void Ngs2RestartAtrac9(Ngs2Voice& voice);
void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate);
bool Ngs2FinishBlock(Ngs2Voice& voice);
int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs);
void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option);
void Ngs2PrepareSampler(Ngs2Voice& voice);
void Ngs2RestartSampler(Ngs2Voice& voice);
void Ngs2ReplaySampler(Ngs2Voice& voice);
void Ngs2SaveSamplerBlock(Ngs2Voice& voice, const Ngs2Block& block);
void Ngs2ConsumeSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate);
std::uint32_t Ngs2ReadSamplerPcm(Ngs2Voice& voice, float* dest, std::uint32_t stride, std::uint32_t count);
std::uint32_t Ngs2ReadSamplerAtrac9(Ngs2Voice& voice, float* dest, std::uint32_t stride, std::uint32_t count);
void Ngs2CleanupUserFx(Ngs2Rack& rack);
void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param);
void Ngs2ProcessUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate);
@@ -208,8 +208,7 @@ void Ngs2ResetPitchShift(Ngs2Voice& voice) {
state.changed = false;
state.initializedChannels = 0;
}
voice.pitchShiftTransition = Ngs2PitchShiftTransition::None;
voice.pitchShiftPlayed = false;
voice.outputTransition = Ngs2VoiceTransition::None;
}
void Ngs2RestartPitchShift(Ngs2Voice& voice) {
@@ -241,20 +240,20 @@ void Ngs2ProcessPitchShift(Ngs2Voice& voice, std::size_t module, std::uint32_t g
voice.hasSamples = std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; });
}
void Ngs2ApplyPitchShiftGain(Ngs2Voice& voice, std::uint32_t grain) {
if (!Ngs2HasPitchShift(voice)) return;
const auto transition = voice.pitchShiftTransition;
if (transition == Ngs2PitchShiftTransition::None && voice.state != Ngs2PlayState::Paused) return;
if (transition != Ngs2PitchShiftTransition::None && grain < 64) throw std::runtime_error("NGS2: PitchShift voice transitions require at least 64 samples");
void Ngs2ApplyVoiceGain(Ngs2Voice& voice, std::uint32_t grain) {
if (!Ngs2HasPitchShift(voice) && !voice.IsSampler()) return;
const auto transition = voice.outputTransition;
if (transition == Ngs2VoiceTransition::None && voice.state != Ngs2PlayState::Paused) return;
if (transition != Ngs2VoiceTransition::None && grain < 64) throw std::runtime_error("NGS2: voice transitions require at least 64 samples");
for (std::uint32_t c = 0; c < voice.channels; ++c) {
auto* samples = voice.samples.data() + static_cast<std::size_t>(c) * grain;
for (std::uint32_t i = 0; i < grain; ++i) {
float gain = 1.0f;
if (transition == Ngs2PitchShiftTransition::Pause) {
if (transition == Ngs2VoiceTransition::Pause) {
if (i >= grain - 64) gain = static_cast<float>(grain - i) / 64.0f;
} else if (transition == Ngs2PitchShiftTransition::Resume) {
} else if (transition == Ngs2VoiceTransition::Resume) {
if (i < 64) gain = static_cast<float>(i) / 64.0f;
} else if (transition == Ngs2PitchShiftTransition::Stop) {
} else if (transition == Ngs2VoiceTransition::Stop) {
gain = i < 64 ? 1.0f - static_cast<float>(i) / 64.0f : 0.0f;
} else if (voice.state == Ngs2PlayState::Paused) {
gain = 0.0f;
@@ -262,6 +261,6 @@ void Ngs2ApplyPitchShiftGain(Ngs2Voice& voice, std::uint32_t grain) {
samples[i] *= gain;
}
}
voice.pitchShiftTransition = Ngs2PitchShiftTransition::None;
voice.outputTransition = Ngs2VoiceTransition::None;
voice.hasSamples = std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; });
}
@@ -34,13 +34,13 @@ struct Ngs2PitchShift {
std::uint32_t initializedChannels = 0;
};
enum class Ngs2PitchShiftTransition { None, Pause, Resume, Stop };
enum class Ngs2VoiceTransition { None, Pause, Resume, Stop };
struct Ngs2Voice;
bool Ngs2HasPitchShift(const Ngs2Voice& voice);
void Ngs2ResetPitchShift(Ngs2Voice& voice);
void Ngs2RestartPitchShift(Ngs2Voice& voice);
void Ngs2ProcessPitchShift(Ngs2Voice& voice, std::size_t module, std::uint32_t grain);
void Ngs2ApplyPitchShiftGain(Ngs2Voice& voice, std::uint32_t grain);
void Ngs2ApplyVoiceGain(Ngs2Voice& voice, std::uint32_t grain);
#endif
@@ -137,6 +137,8 @@ static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackO
std::memcpy(rack->name, options.common.name, sizeof(rack->name));
rack->name[sizeof(rack->name) - 1] = '\0';
rack->maxGrainSamples = options.common.max_grain_samples;
rack->maxWaveformBlocks = rackId == SCE_NGS2_RACK_ID_SAMPLER ? options.sampler.max_waveform_blocks
: rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER ? options.customSampler.max_waveform_blocks : 0;
if (rackId == SCE_NGS2_RACK_ID_SAMPLER) rack->maxChannelWorks = options.sampler.max_channel_works;
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) rack->maxChannelWorks = options.customSampler.max_channel_works;
if (rackId == SCE_NGS2_RACK_ID_SUBMIXER) rack->maxInputs = options.submixer.max_inputs;
+26 -50
View File
@@ -11,9 +11,6 @@
#include "prx/libc/include/General.hpp"
#include "Ngs2Internal.hpp"
static constexpr double PHASE_ONE = 4294967296.0;
static constexpr std::uint64_t PHASE_FRACTION = 0xffffffff;
static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& block, std::uint32_t frame) {
const std::size_t frameBytes = voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
if (block.data == nullptr) return nullptr;
@@ -30,16 +27,6 @@ static void ReadPcm(const Ngs2Voice& voice, const std::uint8_t* frame, float* ou
Ngs2ReadPcmFrame(voice.waveformType, voice.channels, frame, out);
}
static void ReadFrames(Ngs2Voice& voice, float* current, float* next) {
auto& block = voice.blocks.front();
ReadPcm(voice, FrameAt(voice, block, block.cursor), current);
if (block.streaming && block.cursor + 1 >= block.availableFrames) std::memcpy(next, current, voice.channels * sizeof(float));
else if (block.cursor + 1 < block.info.num_samples) ReadPcm(voice, FrameAt(voice, block, block.cursor + 1), next);
else if (block.info.num_repeats != 0) ReadPcm(voice, FrameAt(voice, block, 0), next);
else if (voice.blocks.size() > 1) ReadPcm(voice, FrameAt(voice, voice.blocks[1], 0), next);
else std::memcpy(next, current, voice.channels * sizeof(float));
}
bool Ngs2FinishBlock(Ngs2Voice& voice) {
auto& block = voice.blocks.front();
const bool repeat = block.info.num_repeats != 0;
@@ -74,45 +61,33 @@ bool Ngs2FinishBlock(Ngs2Voice& voice) {
const Ngs2Unlocked unlocked;
voice.callback(&info);
}
return voice.waveformRevision == revision && voice.state == Ngs2PlayState::Playing;
return voice.waveformRevision == revision && (voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop);
}
static void Advance(Ngs2Voice& voice, std::uint64_t frames) {
while (frames != 0 && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty()) {
std::uint32_t Ngs2ReadSamplerPcm(Ngs2Voice& voice, float* dest, std::uint32_t stride, std::uint32_t count) {
const auto revision = voice.waveformRevision;
std::uint32_t written = 0;
while (written < count && (voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop) && voice.waveformRevision == revision) {
if (voice.blocks.empty()) break;
auto& block = voice.blocks.front();
const std::uint64_t limit = block.streaming ? std::min<std::uint64_t>(block.info.num_samples, block.availableFrames) : block.info.num_samples;
if (block.cursor >= limit && block.cursor != block.info.num_samples && !(block.streaming && !voice.acceptsBlocks)) break;
const auto step = std::min<std::uint64_t>(frames, limit - block.cursor);
block.cursor += static_cast<std::uint32_t>(step);
voice.decodedSamples += step;
if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) voice.decodedBytes += step * voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
frames -= step;
const auto limit = block.streaming ? std::min<std::uint64_t>(block.info.num_samples, block.availableFrames) : block.info.num_samples;
if (block.cursor >= limit) {
if (block.cursor < block.info.num_samples && !(block.streaming && !voice.acceptsBlocks)) break;
if (!Ngs2FinishBlock(voice)) break;
continue;
}
float frame[NGS2_MAX_CHANNELS];
ReadPcm(voice, FrameAt(voice, block, block.cursor), frame);
for (std::uint32_t channel = 0; channel < voice.channels; ++channel) dest[static_cast<std::size_t>(channel) * stride + written] = frame[channel];
++written;
++block.cursor;
++voice.decodedSamples;
voice.decodedBytes += voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
if (block.streaming) {
while (block.pieces.size() > 1 && block.cursor >= block.pieces.front().firstFrame + block.pieces.front().frames) block.pieces.erase(block.pieces.begin());
}
if (block.cursor == block.info.num_samples || (block.streaming && !voice.acceptsBlocks && block.cursor == block.availableFrames)) {
if (!Ngs2FinishBlock(voice)) return;
}
}
}
static void ConsumeSamples(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) {
const auto revision = voice.waveformRevision;
const auto step = static_cast<std::uint64_t>(std::llround(voice.sampleRate * static_cast<double>(voice.pitch) / systemRate * PHASE_ONE));
std::uint32_t written = 0;
float current[NGS2_MAX_CHANNELS];
float next[NGS2_MAX_CHANNELS];
for (; written < grain && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty() && voice.waveformRevision == revision; written++) {
ReadFrames(voice, current, next);
const auto fraction = static_cast<float>(static_cast<double>(voice.phase) / PHASE_ONE);
for (std::uint32_t channel = 0; channel < voice.channels; channel++) {
voice.samples[channel * grain + written] = std::lerp(current[channel], next[channel], fraction);
}
voice.phase += step;
Advance(voice, voice.phase >> 32);
voice.phase &= PHASE_FRACTION;
}
voice.hasSamples = written != 0;
return written;
}
static bool Bypasses(const Ngs2Filter& filter, std::uint32_t channel) {
@@ -145,8 +120,7 @@ static void ApplyFilter(Ngs2Voice& voice, Ngs2Filter& filter, std::uint32_t grai
static void RenderSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) {
const auto channels = voice.channels;
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2ConsumeAtrac9(voice, grain, systemRate);
else ConsumeSamples(voice, grain, systemRate);
Ngs2ConsumeSampler(voice, grain, systemRate);
if (voice.channels != channels) {
voice.samples.assign(static_cast<std::size_t>(grain) * voice.channels, 0.0f);
voice.hasSamples = false;
@@ -246,8 +220,8 @@ static void RenderVoice(Ngs2Voice& voice, const std::vector<Ngs2Voice*>& voices,
} renderingScope{voice.rendering};
voice.samples.assign(static_cast<std::size_t>(grain) * voice.InputChannels(), 0.0f);
voice.hasSamples = false;
const bool pitchShiftActive = Ngs2HasPitchShift(voice) &&
(voice.state == Ngs2PlayState::Paused || voice.pitchShiftTransition == Ngs2PitchShiftTransition::Stop);
const bool pitchShiftActive = (Ngs2HasPitchShift(voice) || voice.IsSampler()) &&
(voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop);
if ((voice.state == Ngs2PlayState::Playing || pitchShiftActive) && voice.channels != 0) {
if (voice.rack->rackId == SCE_NGS2_RACK_ID_SAMPLER || voice.rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) {
RenderSampler(voice, grain, systemRate);
@@ -257,7 +231,7 @@ static void RenderVoice(Ngs2Voice& voice, const std::vector<Ngs2Voice*>& voices,
if (voice.rack->rackId == SCE_NGS2_RACK_ID_REVERB && voice.reverb) voice.hasSamples = Ngs2ProcessReverb(voice, grain, systemRate);
Ngs2ProcessLegacyUserFx(voice, grain, systemRate);
Ngs2ProcessUserFx(voice, grain, systemRate);
Ngs2ApplyPitchShiftGain(voice, grain);
Ngs2ApplyVoiceGain(voice, grain);
}
if (voice.samples.size() != static_cast<std::size_t>(grain) * voice.channels) voice.samples.assign(static_cast<std::size_t>(grain) * voice.channels, 0.0f);
voice.rendered = true;
@@ -348,7 +322,9 @@ void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo
}
for (auto* voice : voices) {
if (voice->state == Ngs2PlayState::Stopped) voice->state = Ngs2PlayState::Empty;
const bool wasPaused = (voice->stateFlags & SCE_NGS2_VOICE_STATE_FLAG_PAUSED) != 0;
voice->stateFlags = static_cast<std::uint32_t>(voice->state);
if (wasPaused && voice->state == Ngs2PlayState::Paused && (voice->IsSampler() || Ngs2HasPitchShift(*voice))) voice->stateFlags |= SCE_NGS2_VOICE_STATE_FLAG_PLAYING;
if (voice->state == Ngs2PlayState::Playing && voice->rack->rackId == SCE_NGS2_RACK_ID_SAMPLER && voice->blocks.empty() && voice->acceptsBlocks) voice->stateFlags |= SCE_NGS2_VOICE_STATE_FLAG_EMPTY;
}
system.renderCount++;
@@ -0,0 +1,103 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <numbers>
#include "Ngs2Internal.hpp"
static const std::array<std::array<float, 4>, 512>& SamplerKernel() {
static const auto table = [] {
std::array<std::array<float, 4>, 512> result{};
for (std::uint32_t phase = 0; phase < 512; ++phase) {
std::array<double, 4> taps{};
double sum = 0.0;
for (std::uint32_t tap = 0; tap < 4; ++tap) {
const double x = static_cast<double>(tap) - 1.0 - static_cast<double>(phase) / 512.0;
const double sinc = x == 0.0 ? 1.0 : std::sin(std::numbers::pi * x) / (std::numbers::pi * x);
taps[tap] = sinc * (0.5 + 0.5 * std::cos(std::numbers::pi * x / 2.0));
sum += taps[tap];
}
for (std::uint32_t tap = 0; tap < 4; ++tap) result[phase][tap] = static_cast<float>(std::round(taps[tap] / sum * 1e10) / 1e10);
}
return result;
}();
return table;
}
void Ngs2RestartSampler(Ngs2Voice& voice) {
voice.phase = 0;
voice.samplerHistory = {};
}
void Ngs2SaveSamplerBlock(Ngs2Voice& voice, const Ngs2Block& block) {
if (!voice.samplerReplayKnown) return;
if (voice.samplerReplayBlocks.size() >= std::max(1u, voice.rack->maxWaveformBlocks)) {
voice.samplerReplayBlocks.clear();
voice.samplerReplayKnown = false;
return;
}
voice.samplerReplayBlocks.push_back(block);
}
void Ngs2ReplaySampler(Ngs2Voice& voice) {
if (!voice.samplerReplayKnown) throw std::runtime_error("NGS2: replay after the retained waveform descriptors were exceeded is not implemented");
voice.blocks.assign(voice.samplerReplayBlocks.begin(), voice.samplerReplayBlocks.end());
++voice.waveformRevision;
voice.decodedSamples = 0;
voice.decodedBytes = 0;
voice.waveformEnd = nullptr;
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2RestartAtrac9(voice);
Ngs2RestartSampler(voice);
}
void Ngs2PrepareSampler(Ngs2Voice& voice) {
voice.samplerScratch.resize((static_cast<std::size_t>(voice.rack->maxGrainSamples) * 4 + 4) * voice.channels);
Ngs2RestartSampler(voice);
SamplerKernel();
}
void Ngs2ConsumeSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate) {
const auto revision = voice.waveformRevision;
const auto channels = voice.channels;
const auto stride = grain * 4 + 4;
if (voice.samplerScratch.size() < static_cast<std::size_t>(stride) * channels) {
throw std::runtime_error("NGS2: sampler grain exceeds its prepared scratch buffer");
}
const float ratio = (1.0f / static_cast<float>(sampleRate)) * static_cast<float>(voice.sampleRate) * voice.pitch;
const auto step = std::min(static_cast<std::uint32_t>(std::min(ratio, 4.0f) * 16384.0f), 65535u);
const auto consumed = static_cast<std::uint32_t>((voice.phase + static_cast<std::uint64_t>(step) * grain) >> 14);
for (std::uint32_t channel = 0; channel < channels; ++channel) {
auto* dest = voice.samplerScratch.data() + static_cast<std::size_t>(channel) * stride;
std::copy(voice.samplerHistory[channel].begin(), voice.samplerHistory[channel].end(), dest);
std::fill_n(dest + 4, consumed, 0.0f);
}
const auto count = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9
? Ngs2ReadSamplerAtrac9(voice, voice.samplerScratch.data() + 4, stride, consumed)
: Ngs2ReadSamplerPcm(voice, voice.samplerScratch.data() + 4, stride, consumed);
if (voice.waveformRevision != revision) return;
const auto& kernel = SamplerKernel();
for (std::uint32_t channel = 0; channel < channels; ++channel) {
const auto* source = voice.samplerScratch.data() + static_cast<std::size_t>(channel) * stride;
auto* dest = voice.samples.data() + static_cast<std::size_t>(channel) * grain;
if (step == 16384 && voice.phase == 0) {
std::copy_n(source + 1, grain, dest);
} else {
auto phase = static_cast<std::uint32_t>(voice.phase);
for (std::uint32_t i = 0; i < grain; ++i) {
const auto index = phase >> 14;
const auto& coefficients = kernel[(phase & 0x3fff) >> 5];
const float a = source[index] * coefficients[0];
const float b = source[index + 1] * coefficients[1];
const float c = source[index + 2] * coefficients[2];
const float d = source[index + 3] * coefficients[3];
dest[i] = (a + b) + (c + d);
phase += step;
}
}
std::copy_n(source + consumed, 4, voice.samplerHistory[channel].begin());
}
voice.phase = (voice.phase + static_cast<std::uint64_t>(step) * grain) & 0x3fff;
voice.hasSamples = count != 0 || std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; });
}
+56 -13
View File
@@ -30,14 +30,18 @@ static void SendEvent(Ngs2Voice& voice, std::uint32_t eventId) {
voice.SetEvent(eventId);
}
bool Ngs2Voice::IsSampler() const {
return rack != nullptr && (rack->rackId == SCE_NGS2_RACK_ID_SAMPLER || rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER);
}
void Ngs2Voice::SetEvent(std::uint32_t eventId) {
switch (eventId) {
case SCE_NGS2_VOICE_EVENT_PLAY:
if (state == Ngs2PlayState::Empty || state == Ngs2PlayState::Stopped) {
if (Ngs2HasPitchShift(*this)) {
if (IsSampler()) Ngs2ReplaySampler(*this);
if (Ngs2HasPitchShift(*this) || IsSampler()) {
Ngs2RestartPitchShift(*this);
pitchShiftTransition = pitchShiftPlayed ? Ngs2PitchShiftTransition::Resume : Ngs2PitchShiftTransition::None;
pitchShiftPlayed = true;
outputTransition = Ngs2VoiceTransition::None;
}
state = Ngs2PlayState::Playing;
stateFlags |= SCE_NGS2_VOICE_STATE_FLAG_INUSE;
@@ -47,7 +51,7 @@ void Ngs2Voice::SetEvent(std::uint32_t eventId) {
case SCE_NGS2_VOICE_EVENT_STOP:
if (state == Ngs2PlayState::Playing || state == Ngs2PlayState::Paused) {
state = Ngs2PlayState::Stopped;
if (Ngs2HasPitchShift(*this)) pitchShiftTransition = Ngs2PitchShiftTransition::Stop;
if (Ngs2HasPitchShift(*this) || IsSampler()) outputTransition = Ngs2VoiceTransition::Stop;
}
break;
case SCE_NGS2_VOICE_EVENT_STOP_IMM:
@@ -55,18 +59,18 @@ void Ngs2Voice::SetEvent(std::uint32_t eventId) {
state = Ngs2PlayState::Empty;
Ngs2ClearReverb(*this);
Ngs2RestartPitchShift(*this);
pitchShiftTransition = Ngs2PitchShiftTransition::None;
outputTransition = Ngs2VoiceTransition::None;
break;
case SCE_NGS2_VOICE_EVENT_PAUSE:
if (state == Ngs2PlayState::Playing) {
state = Ngs2PlayState::Paused;
if (Ngs2HasPitchShift(*this)) pitchShiftTransition = Ngs2PitchShiftTransition::Pause;
if (Ngs2HasPitchShift(*this) || IsSampler()) outputTransition = Ngs2VoiceTransition::Pause;
}
break;
case SCE_NGS2_VOICE_EVENT_RESUME:
if (state == Ngs2PlayState::Paused) {
state = Ngs2PlayState::Playing;
if (Ngs2HasPitchShift(*this)) pitchShiftTransition = Ngs2PitchShiftTransition::Resume;
if (Ngs2HasPitchShift(*this) || IsSampler()) outputTransition = Ngs2VoiceTransition::Resume;
}
break;
default: throw std::invalid_argument("NGS2: unknown voice event " + Ngs2Hex(eventId));
@@ -90,6 +94,8 @@ void Ngs2Voice::ResetSetup() {
pitch = 1.0f;
phase = 0;
blocks.clear();
samplerReplayBlocks.clear();
samplerReplayKnown = true;
acceptsBlocks = true;
decodedSamples = 0;
decodedBytes = 0;
@@ -110,6 +116,7 @@ const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block
const std::uint8_t* Ngs2Voice::WaveformData() const {
if (blocks.empty()) return waveformEnd;
const auto& block = blocks.front();
if (block.data == nullptr) return nullptr;
if (waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return block.data + block.dataCursor;
if (block.streaming) {
for (const auto& piece : block.pieces) {
@@ -215,6 +222,7 @@ static void SetupSampler(Ngs2Voice& voice, const Ngs2WaveformFormat& format) {
voice.sampleRate = format.sample_rate;
voice.waveformType = format.waveform_type;
if (format.waveform_type == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2SetupAtrac9(voice, format);
Ngs2PrepareSampler(voice);
}
static void AppendStreamData(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlocksParam& param, std::size_t frameBytes) {
@@ -229,6 +237,7 @@ static void AppendStreamData(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlo
block.num_skip_samples = 0;
block.num_repeats = 0;
voice.blocks.push_back({static_cast<const std::uint8_t*>(param.data) + block.data_offset, block});
Ngs2SaveSamplerBlock(voice, voice.blocks.back());
}
return;
}
@@ -242,6 +251,16 @@ static void AppendStreamData(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlo
}
stream.pieces.push_back({static_cast<const std::uint8_t*>(param.data) + block.data_offset, stream.availableFrames, block.data_size / frameBytes});
stream.availableFrames += block.data_size / frameBytes;
if (voice.samplerReplayKnown && !voice.samplerReplayBlocks.empty()) {
auto& replay = voice.samplerReplayBlocks.back();
if (replay.pieces.size() >= std::max(1u, voice.rack->maxWaveformBlocks)) {
voice.samplerReplayKnown = false;
voice.samplerReplayBlocks.clear();
} else {
replay.pieces.push_back(stream.pieces.back());
replay.availableFrames = stream.availableFrames;
}
}
}
}
@@ -250,13 +269,25 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
if ((param.flags & ~knownFlags) != 0) throw std::runtime_error("NGS2: waveform block flags " + Ngs2Hex(param.flags) + " are not implemented");
if ((param.num_blocks != 0 && (voice.channels == 0 || param.blocks == nullptr || param.data == nullptr)) || (voice.channels == 0 && (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND) != 0)) APS5_INVALID_ARG_EX;
const bool reset = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET) != 0;
const auto cursor = reset && !voice.blocks.empty() ? voice.blocks.front().cursor : 0u;
const bool preserveCodec = reset && voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && !voice.blocks.empty() && voice.blocks.front().started;
std::uint64_t codecCursor = 0;
if (preserveCodec) {
const auto& old = voice.blocks.front();
if (param.num_blocks != 1 || param.blocks[0].data_size != old.info.data_size || param.blocks[0].num_samples != old.info.num_samples ||
param.blocks[0].num_skip_samples != old.info.num_skip_samples || static_cast<const std::uint8_t*>(param.data) + param.blocks[0].data_offset != old.data) {
throw std::runtime_error("NGS2: replacing an active ATRAC9 waveform with different data is not implemented");
}
codecCursor = old.dataCursor;
}
if (!voice.acceptsBlocks && !reset) throw std::invalid_argument("NGS2: the voice waveform was already closed");
if (reset) {
++voice.waveformRevision;
voice.blocks.clear();
voice.phase = 0;
voice.samplerReplayBlocks.clear();
voice.samplerReplayKnown = true;
voice.waveformEnd = nullptr;
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2RestartAtrac9(voice);
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && !preserveCodec) Ngs2RestartAtrac9(voice);
}
const std::size_t frameBytes = voice.channels == 0 || voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? 0 : voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
if ((param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND) != 0) {
@@ -272,6 +303,7 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
if (allowsSilence && block.data_size == 0) {
if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) throw std::runtime_error("NGS2: a dataless waveform block under the silence flag is not implemented for this format");
voice.blocks.push_back({nullptr, block});
Ngs2SaveSamplerBlock(voice, voice.blocks.back());
continue;
}
const std::uint64_t bytes = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? Ngs2Atrac9BlockBytes(voice, block)
@@ -289,6 +321,12 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
stream.availableFrames = block.data_size / frameBytes;
stream.pieces.push_back({stream.data, 0, stream.availableFrames});
}
Ngs2SaveSamplerBlock(voice, voice.blocks.back());
}
if (reset && !voice.blocks.empty()) voice.blocks.front().cursor = cursor;
if (preserveCodec && !voice.blocks.empty()) {
voice.blocks.front().dataCursor = codecCursor;
voice.blocks.front().started = true;
}
}
@@ -329,9 +367,12 @@ void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) {
static void ApplySamplerParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) {
switch (param.id) {
case SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP:
case SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP:
SetupSampler(voice, ParamAs<Ngs2SamplerVoiceSetupParam>(param).format);
case SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP: {
const auto& setup = ParamAs<Ngs2SamplerVoiceSetupParam>(param);
if (setup.channel_mask != 0) throw std::runtime_error("NGS2: sampler setup channel masks are not implemented");
SetupSampler(voice, setup.format);
break;
}
case SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS:
case SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS:
AddWaveformBlocks(voice, ParamAs<Ngs2SamplerVoiceWaveformBlocksParam>(param));
@@ -345,8 +386,8 @@ static void ApplySamplerParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& para
case SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH:
case SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH: {
const float ratio = ParamAs<Ngs2SamplerVoicePitchParam>(param).ratio;
if (!std::isfinite(ratio) || ratio < 0.0f) APS5_INVALID_ARG_EX;
voice.pitch = ratio;
if (std::isnan(ratio)) APS5_INVALID_ARG_EX;
voice.pitch = std::clamp(ratio, 0.0f, 4.0f);
break;
}
case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: Ngs2SetFilter(voice, ParamAs<Ngs2SamplerVoiceFilterParam>(param)); break;
@@ -488,6 +529,7 @@ int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state
auto& sampler = *reinterpret_cast<Ngs2SamplerVoiceState*>(state);
sampler = {};
sampler.voice_state.state_flags = voice.stateFlags;
if (voice.state == Ngs2PlayState::Empty) return SCE_NGS2_OK;
sampler.envelope_height = 1.0f;
sampler.num_decoded_samples = voice.decodedSamples;
sampler.decoded_data_size = voice.decodedBytes;
@@ -513,6 +555,7 @@ int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state
auto& sampler = *reinterpret_cast<Ngs2CustomSamplerVoiceState*>(state);
sampler = {};
sampler.voice_state.state_flags = voice.stateFlags;
if (voice.state == Ngs2PlayState::Empty) return SCE_NGS2_OK;
sampler.num_decoded_samples = voice.decodedSamples;
return SCE_NGS2_OK;
}
@@ -80,9 +80,9 @@ static uintptr_t Sampler(uintptr_t system, std::uint32_t skip, std::uint32_t sam
return voice;
}
static std::vector<float> Render(uintptr_t system, std::uint32_t samples) {
static std::vector<float> Render(uintptr_t system, std::uint32_t samples, std::uint32_t grain = Grain) {
std::vector<float> rendered;
std::vector<float> out(Grain);
std::vector<float> out(grain);
const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1};
while (rendered.size() < samples) {
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
@@ -96,7 +96,7 @@ static void TestSkipAndBlockEnd(const std::vector<float>& reference) {
Patch(Sampler(system, 100, 500, 0), Mastering(system, 1));
callbackFlags.clear();
const auto rendered = Render(system, 512);
for (std::uint32_t i = 0; i < 512; i++) Require(rendered[i] == (i < 500 ? reference[100 + i] : 0.0f));
for (std::uint32_t i = 0; i < 512; i++) Require(rendered[i] == (i >= 3 && i < 503 ? reference[100 + i - 3] : 0.0f));
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -112,7 +112,7 @@ static void TestRepeatAndState(const std::vector<float>& reference) {
Require(state.num_decoded_samples == 304 && state.decoded_data_size == 2 * SuperframeBytes && state.waveform_data == Superframe + SuperframeBytes);
const auto second = Render(system, 304);
rendered.insert(rendered.end(), second.begin(), second.end());
for (std::uint32_t i = 0; i < 608; i++) Require(rendered[i] == (i < 600 ? reference[i % 300] : 0.0f));
for (std::uint32_t i = 0; i < 608; i++) Require(rendered[i] == (i >= 3 && i < 603 ? reference[(i - 3) % 300] : 0.0f));
Require(callbackFlags.size() == 2 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT && callbackFlags[1] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
Require(Flags(sampler) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
@@ -363,8 +363,9 @@ static void TestParseLoops() {
const auto loop = end - begin;
const auto rendered = Render(system, end + 2 * loop);
for (std::uint32_t i = 0; i < rendered.size(); ++i) {
const auto index = i < end ? i : begin + (i - end) % loop;
Require(rendered[i] == reference[256 + index]);
const auto frame = i >= 3 ? i - 3 : 0;
const auto index = frame < end ? frame : begin + (frame - end) % loop;
Require(rendered[i] == (i < 3 ? 0.0f : reference[256 + index]));
}
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
}
@@ -386,8 +387,9 @@ static void TestParseLoops() {
const auto tail = Render(system, samples + 600 - 1024 + Grain);
rendered.insert(rendered.end(), tail.begin(), tail.end());
for (std::uint32_t i = 0; i < rendered.size(); ++i) {
const auto index = i < 700 ? i : i - 600;
Require(rendered[i] == (i < samples + 600 ? reference[256 + index] : 0.0f));
const auto frame = i >= 3 ? i - 3 : 0;
const auto index = frame < 700 ? frame : frame - 600;
Require(rendered[i] == (i >= 3 && frame < samples + 600 ? reference[256 + index] : 0.0f));
}
Require(Flags(voice) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
@@ -530,20 +532,15 @@ static void TestStreamPartitions() {
}
const auto output = Render(system, 1664);
for (std::size_t i = 0; i < output.size(); ++i) {
const float position = i * pitch;
float expected = 0.0f;
if (position < 800) {
const auto index = static_cast<std::uint32_t>(position);
const auto next = std::min(index + 1, 799u);
const auto fraction = position - index;
expected = reference[skip + index] * (1 - fraction) + reference[skip + next] * fraction;
}
const float expected = SamplerReference(i, pitch, [&](std::uint32_t index) {
return index < 800 ? reference[skip + index] : 0.0f;
});
Require(std::abs(output[i] - expected) < 1e-6f);
}
Require(callbackFlags.size() == 5 && Flags(voice) == 0);
Ngs2SamplerVoiceState state{};
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
Require(state.num_decoded_samples == 800);
Require(state.num_decoded_samples == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
}
}
@@ -566,18 +563,28 @@ static void APS5_VABI Refill(const Ngs2VoiceCallbackInfo* info) {
static void TestRefill(const std::vector<float>& reference) {
for (int mode = 0; mode < 3; ++mode) {
const auto system = CreateSystem();
Require(sceNgs2SystemSetGrainSamples(system, 64) == SCE_NGS2_OK);
const auto voice = EmptySampler(system);
RefillState state{0, mode == 1, mode == 2};
Control(voice, SCE_NGS2_VOICE_PARAM_CALLBACK,
Ngs2VoiceCallbackParam{{}, Refill, reinterpret_cast<std::uintptr_t>(&state), SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
Queue(voice, Superframe, 13, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 500, 100);
if (mode != 0) {
for (float value : Render(system, Grain)) Require(value == 0.0f);
const auto first = Render(system, 64, 64);
for (std::uint32_t i = 0; i < first.size(); ++i) {
const float expected = state.pause && i >= 3 ? reference[100 + i - 3] * static_cast<float>(64 - i) / 64.0f : 0.0f;
Require(first[i] == expected);
}
Require(state.calls == 1);
if (state.pause) Event(voice, SCE_NGS2_VOICE_EVENT_RESUME);
}
const auto output = Render(system, 512);
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i < 500 ? reference[100 + i] : 0.0f));
const auto output = Render(system, 512, 64);
for (std::uint32_t i = 0; i < output.size(); ++i) {
const auto frame = i + (state.pause ? 64u : 0u);
float expected = frame >= 3 && frame < 503 ? reference[100 + frame - 3] : 0.0f;
if (state.pause && i < 64) expected *= static_cast<float>(i) / 64.0f;
Require(output[i] == expected);
}
Require(state.calls == 2 && Flags(voice) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
}
@@ -598,7 +605,7 @@ static void TestStarvationAndTruncation(const std::vector<float>& reference) {
} else {
Queue(voice, Superframe + 13, SuperframeBytes - 13, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
const auto output = Render(system, 512);
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i < 500 ? reference[100 + i] : 0.0f));
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i >= 3 && i < 503 ? reference[100 + i - 3] : 0.0f));
Require(Flags(voice) == 0);
}
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
@@ -615,11 +622,11 @@ static void TestAppendBoundaries(const std::vector<float>& reference) {
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS,
Ngs2SamplerVoiceWaveformBlocksParam{{}, Superframe, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND, 1, &block});
const auto output = Render(system, 512);
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i < 500 ? reference[100 + i] : 0.0f));
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i >= 3 && i < 503 ? reference[100 + i - 3] : 0.0f));
Ngs2SamplerVoiceState state{};
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
Require(state.num_decoded_samples == 500 && state.decoded_data_size == SuperframeBytes);
Require(state.waveform_data == Superframe + SuperframeBytes && Flags(voice) == 0);
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0);
Require(state.waveform_data == nullptr && Flags(voice) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
}
@@ -176,7 +176,7 @@ static void TestUserFxChain() {
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
auto out = Render(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f && out[i * 2 + 1] == 0.75f);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == (i < 3 ? 0.0f : 0.5f) && out[i * 2 + 1] == (i < 3 ? 0.25f : 0.75f));
Require(calls.processFlags == (std::vector<std::uint32_t>{1}) && calls.order == (std::vector<int>{0, 1}));
const float gain = 0.5f;
@@ -301,7 +301,7 @@ static void TestCustomSampler() {
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
const auto out = Render(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.5f && out[i * 2 + 1] == 0.75f);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == (i < 3 ? 0.0f : 0.5f) && out[i * 2 + 1] == (i < 3 ? 0.25f : 0.75f));
Ngs2CustomSamplerVoiceState state{};
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(Ngs2SamplerVoiceState)) == SCE_NGS2_ERROR_INVALID_OUT_SIZE);
@@ -363,7 +363,7 @@ static void TestCustomFilter() {
std::vector<float> out(Grain, -1.0f);
const Ngs2RenderBufferInfo renderInfo{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1};
Require(sceNgs2SystemRender(system, &renderInfo, 1) == SCE_NGS2_OK);
Require(out[0] > 0.0f && out[0] < 0.2f && out.back() > out[0]);
Require(out[0] == 0.0f && out[1] == 0.0f && out[2] == 0.0f && out[3] > 0.0f && out[3] < 0.2f && out.back() > out[3]);
Ngs2CustomVoiceFilterParam unsupported{};
unsupported.type = 2;
@@ -702,7 +702,8 @@ static void TestCustomDelay(std::uint32_t count, const DelayOption& delay) {
const auto checkGrain = [&](std::uint32_t grain) {
const auto out = Render(system);
for (std::uint32_t i = 0; i < Grain * 2; ++i) {
Require(out[i] == static_cast<float>(pcm[grain * Grain * 2 + i]) / 32768.0f);
const auto offset = grain * Grain * 2 + i;
Require(out[i] == (offset < 6 ? 0.0f : static_cast<float>(pcm[offset - 6]) / 32768.0f));
}
};
for (std::uint32_t grain = 0; grain < 3; ++grain) checkGrain(grain);
@@ -723,7 +724,7 @@ static void TestCustomDelay(std::uint32_t count, const DelayOption& delay) {
}
checkGrain(3);
Require(state.calls == 0);
Event(sampler, SCE_NGS2_VOICE_EVENT_STOP);
Event(sampler, SCE_NGS2_VOICE_EVENT_STOP_IMM);
for (const float sample : Render(system)) Require(sample == 0.0f);
option.custom_rack_option.module[0].state_size = 4;
@@ -883,7 +884,7 @@ static void TestCustomMastering() {
auto out = Render(system);
Require(out.size() == Grain * 2);
Require(out[0] != 0.0f);
Require(out[0] == 0.0f && out[6] != 0.0f);
Require(sceNgs2VoiceGetState(voice, &state, sizeof(state)) == SCE_NGS2_OK);
Require((state.state_flags & SCE_NGS2_VOICE_STATE_FLAG_PLAYING) != 0);
@@ -83,7 +83,7 @@ static void TestLowPassAndTail() {
const auto out = RenderGrain(system, 1);
for (std::uint32_t i = 0; i < Grain; i++) {
const auto frame = grain * Grain + i;
const float expected = reference.Next(frame < pcm.size() ? pcm[frame] / 32768.0 : 0.0);
const float expected = reference.Next(frame >= 3 && frame - 3 < pcm.size() ? pcm[frame - 3] / 32768.0 : 0.0);
Require(std::abs(out[i] - expected) <= 1e-7f);
}
Require(out[Grain - 1] != 0.0f);
@@ -112,12 +112,12 @@ static void TestBypassMaskAndGain() {
auto out = RenderGrain(system, 2);
for (std::uint32_t i = 0; i < Grain; i++) {
Require(out[i * 2] == pcm[i * 2] / 32768.0f);
Require(out[i * 2 + 1] == pcm[i * 2 + 1] / 32768.0f * 0.5f);
Require(out[i * 2] == (i < 3 ? 0.0f : pcm[(i - 3) * 2] / 32768.0f));
Require(out[i * 2 + 1] == (i < 3 ? 0.0f : pcm[(i - 3) * 2 + 1] / 32768.0f * 0.5f));
}
Require(Flags(sampler) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING));
out = RenderGrain(system, 2);
for (float sample : out) Require(sample == 0.0f);
for (std::uint32_t i = 0; i < Grain * 2; ++i) Require(out[i] == (i < 6 ? pcm[(Grain - 3) * 2 + i] / 32768.0f * (i % 2 ? 0.5f : 1.0f) : 0.0f));
Require(Flags(sampler) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -133,7 +133,7 @@ static void TestSystemRateAndZeroCutoff() {
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
Biquad reference(18000.0, static_cast<double>(0.70710678f), 1.0, 48000.0);
const auto out = RenderGrain(system, 1);
for (std::uint32_t i = 0; i < Grain; i++) Require(std::abs(out[i] - reference.Next(0.25)) <= 1e-7f);
for (std::uint32_t i = 0; i < Grain; i++) Require(std::abs(out[i] - reference.Next(SamplerReference(i, 0.5f, [](std::uint32_t) { return 0.25f; }))) <= 1e-7f);
SetFilter(sampler, Filter(1, 1, 1, 0, 0.0f, 0.70710678f, 1.0f));
for (float sample : RenderGrain(system, 1)) Require(sample == 0.0f);
@@ -143,7 +143,8 @@ static void TestSystemRateAndZeroCutoff() {
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block});
Patch(sampler, master);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
for (float sample : RenderGrain(system, 1)) Require(sample == 0.25f);
const auto reset = RenderGrain(system, 1);
for (std::uint32_t i = 0; i < Grain; ++i) Require(reset[i] == (i < 3 ? 0.0f : 0.25f));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -32,8 +32,8 @@ static void APS5_VABI OnBlock(const Ngs2VoiceCallbackInfo* info) {
callbackFlags.push_back(info->flag);
}
static std::vector<std::int16_t> RenderI16(uintptr_t system) {
std::vector<std::int16_t> out(Grain, -1);
static std::vector<std::int16_t> RenderI16(uintptr_t system, std::uint32_t grain = Grain) {
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;
@@ -135,7 +135,7 @@ static void TestPcmBlockEnd() {
callbackFlags.clear();
auto out = RenderI16(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == pcm[i]);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 0 : pcm[i - 3]));
Require(Flags(sampler) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING) && callbackFlags.empty());
Ngs2SamplerVoiceState state{};
@@ -145,11 +145,11 @@ static void TestPcmBlockEnd() {
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));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (Grain + i - 3 < pcm.size() ? pcm[Grain + i - 3] : 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(state.num_decoded_samples == 0 && state.waveform_data == nullptr);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -170,7 +170,7 @@ static void TestPatchToRackHandle() {
Require(port.dest_handle == master && port.dest_input_id == 0);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const auto out = RenderI16(system);
Require(out == pcm);
for (std::uint32_t i = 0; i < Grain; ++i) Require(out[i] == (i < 3 ? 0 : pcm[i - 3]));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -376,14 +376,18 @@ static void TestPitchAndRepeat() {
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
callbackFlags.clear();
const std::int16_t first[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 1500};
const std::int16_t first[Grain] = {0, 0, 0, 0, 0, -30, 0, 470};
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);
Require(callbackFlags.empty());
const std::int16_t second[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 3000};
const std::int16_t second[Grain] = {1000, 1500, 2000, 2621, 3000, 1500, 0, 379};
out = RenderI16(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == second[i]);
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT);
out = RenderI16(system);
const std::int16_t tail[Grain] = {1000, 1500, 2000, 2621, 3000, 1530, 0, -91};
for (std::uint32_t i = 0; i < Grain; ++i) Require(out[i] == tail[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);
@@ -403,7 +407,7 @@ static void TestSetupClearsCallback() {
callbackFlags.clear();
auto out = RenderI16(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < pcm.size() ? pcm[i] : 0));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i >= 3 && i - 3 < pcm.size() ? pcm[i - 3] : 0));
Require(callbackFlags.empty() && Flags(sampler) == 0);
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}});
@@ -413,7 +417,7 @@ static void TestSetupClearsCallback() {
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &stream});
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
out = RenderI16(system);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < pcm.size() ? pcm[i] : 0));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i >= 3 && i - 3 < pcm.size() ? pcm[i - 3] : 0));
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -454,9 +458,9 @@ static void TestPcmCallbackReset() {
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
if (state.num_decoded_samples != 1) std::fprintf(stderr, "PCM callback reset consumed %llu samples before the next grain, expected 1\n", static_cast<unsigned long long>(state.num_decoded_samples));
Require(state.num_decoded_samples == 1 && state.decoded_data_size == sizeof(std::int16_t) && state.waveform_data == callback.pcm.data());
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(first[frame] == (frame == 0 ? 16384 : 0));
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(first[frame] == 0);
const auto second = RenderI16(system);
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(second[frame] == callback.pcm[frame * 2]);
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(second[frame] == (frame < 2 ? 0 : callback.pcm[frame * 2 - 3]));
Require(callback.calls == 1);
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
Require(state.num_decoded_samples == 1 + Grain * 2 && state.waveform_data == callback.pcm.data() + Grain * 2);
@@ -467,6 +471,8 @@ static void TestPcmCallbackReset() {
static void TestPcmCallbackRefill() {
for (const bool pause : {false, true}) {
const auto system = CreateSystem();
constexpr std::uint32_t RenderGrain = 64;
Require(sceNgs2SystemSetGrainSamples(system, RenderGrain) == SCE_NGS2_OK);
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, 1, 48000, 0, 0, 0}});
Patch(sampler, Mastering(system, 1));
@@ -475,20 +481,27 @@ static void TestPcmCallbackRefill() {
Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS,
Ngs2SamplerVoiceWaveformBlocksParam{{}, &pcm, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 1, &block});
PcmResetCallback callback{{}, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0, 0, pause};
for (std::uint32_t frame = 0; frame < Grain * 2; ++frame) callback.pcm.push_back(static_cast<std::int16_t>(1000 + frame * 100));
for (std::uint32_t frame = 0; frame < RenderGrain * 2; ++frame) callback.pcm.push_back(static_cast<std::int16_t>(1000 + frame * 100));
Control(sampler, SCE_NGS2_VOICE_PARAM_CALLBACK,
Ngs2VoiceCallbackParam{{}, ResetPcm, reinterpret_cast<std::uintptr_t>(&callback), callback.flag, 0});
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const auto first = RenderI16(system);
Require(callback.calls == 1 && first.front() == pcm);
for (std::uint32_t frame = 1; frame < Grain; ++frame) Require(first[frame] == (pause ? 0 : callback.pcm[frame - 1]));
const auto first = RenderI16(system, RenderGrain);
Require(callback.calls == 1);
for (std::uint32_t frame = 0; frame < RenderGrain; ++frame) {
const float source = frame < 3 ? 0.0f : frame == 3 ? pcm : callback.pcm[frame - 4];
const float gain = pause ? static_cast<float>(RenderGrain - frame) / 64.0f : 1.0f;
Require(first[frame] == std::lrint(source * gain));
}
Ngs2SamplerVoiceState state{};
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
const std::uint32_t consumed = pause ? 0 : Grain - 1;
const std::uint32_t consumed = RenderGrain - 1;
Require(state.num_decoded_samples == consumed + 1 && state.waveform_data == callback.pcm.data() + consumed);
if (pause) Event(sampler, SCE_NGS2_VOICE_EVENT_RESUME);
const auto second = RenderI16(system);
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(second[frame] == callback.pcm[consumed + frame]);
const auto second = RenderI16(system, RenderGrain);
for (std::uint32_t frame = 0; frame < RenderGrain; ++frame) {
const float gain = pause ? static_cast<float>(frame) / 64.0f : 1.0f;
Require(second[frame] == std::lrint(callback.pcm[consumed + frame - 3] * gain));
}
Require(callback.calls == 1);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -537,7 +550,8 @@ static void TestPcmCallbackSetup() {
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == callback.pcm.data());
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
Require(callback.calls == 1 && std::equal(output.begin(), output.end(), callback.pcm.begin()));
Require(callback.calls == 1);
for (std::uint32_t i = 0; i < output.size(); ++i) Require(output[i] == (i < channels * 3 ? 0 : callback.pcm[i - channels * 3]));
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
Require(state.num_decoded_samples == Grain && state.decoded_data_size == Grain * channels * sizeof(std::int16_t));
Require(state.waveform_data == callback.pcm.data() + Grain * channels);
@@ -567,14 +581,14 @@ static void TestSubmixerMatrix() {
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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == (i < 3 ? 0.0f : 0.25f) && out[i * 2 + 1] == (i < 3 ? 0.0f : 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);
for (std::uint32_t i = 0; i < Grain; ++i) Require(out[i * 2] == (i < 3 ? 0.25f : 0.0f) && out[i * 2 + 1] == (i < 3 ? 0.125f : 0.0f));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -596,7 +610,7 @@ static std::vector<float> MatrixLevelFrame(const std::vector<float>& levels, boo
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]};
return {out[6], out[7]};
}
static void TestMatrixLevelClamp() {
@@ -665,9 +679,9 @@ static std::vector<float> RenderReverb(const ReverbCase& reverbCase, std::uint32
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);
if (i < 3000 * channels) pcm[i] = 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);
for (std::uint32_t c = 0; !reverbCase.noise && c < channels; c++) pcm[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};
@@ -891,7 +905,7 @@ static void TestSampleRate() {
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 std::int16_t expected[Grain] = {0, 0, 0, 0, 0, -30, 0, 470};
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);
@@ -924,7 +938,7 @@ static void TestSystemOptionFlags() {
Patch(sampler, master);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const auto out = RenderI16(system);
Require(std::equal(pcm.begin(), pcm.end(), out.begin()));
for (std::uint32_t i = 0; i < Grain; ++i) Require(out[i] == (i < 3 ? 0 : pcm[(i - 3) % pcm.size()]));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
for (const std::uint32_t flags : {0x1u, 0x3u, 0x4u, 0x80000000u, 0x80000002u}) {
@@ -1022,9 +1036,9 @@ static void TestMasteringGain() {
};
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(outStereo[i * 2] == (i < 3 ? 0.0f : 0.25f) && outStereo[i * 2 + 1] == (i < 3 ? 0.0f : 0.125f));
for (std::uint32_t channel = 0; channel < 6; channel++) Require(outSurround[i * 6 + channel] == (i < 3 ? 0.0f : channel == 3 ? 0.125f : 0.25f));
for (std::uint32_t channel = 0; channel < 4; channel++) Require(outQuad[i * 4 + channel] == (i < 3 ? 0.0f : 0.25f));
}
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
@@ -1036,7 +1050,7 @@ static void TestMasteringGain() {
Patch(toLoud, loud);
Event(toLoud, SCE_NGS2_VOICE_EVENT_PLAY);
auto clipped = RenderI16(loudSystem);
for (std::int16_t sample : clipped) Require(sample == 32767);
for (std::uint32_t i = 0; i < Grain; ++i) Require(clipped[i] == (i < 3 ? 0 : 32767));
Control(loud, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, 1, 0});
Event(loud, SCE_NGS2_VOICE_EVENT_PLAY);
auto reset = RenderI16(loudSystem);
@@ -1104,9 +1118,9 @@ static void TestStereoIntoSurround() {
};
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);
Require(outFive[i * 6] == (i < 3 ? 0.0f : 0.5f) && outFive[i * 6 + 1] == (i < 3 ? 0.0f : 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);
Require(outSeven[i * 8] == (i < 3 ? 0.0f : 0.5f) && outSeven[i * 8 + 1] == (i < 3 ? 0.0f : 0.25f));
for (std::uint32_t channel = 2; channel < 8; channel++) Require(outSeven[i * 8 + channel] == 0.0f);
}
@@ -1475,7 +1489,7 @@ static std::vector<float> MixFrame(std::uint32_t sourceChannels, std::uint32_t s
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);
return std::vector<float>(out.begin() + 3 * destChannels, out.begin() + 4 * destChannels);
}
static void TestDefaultChannelMap() {
@@ -1544,7 +1558,7 @@ static void TestUnsetMatrix() {
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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == (i < 3 ? 0.0f : spread) && out[i * 2 + 1] == (i < 3 ? 0.0f : spread));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
const auto stereoSystem = CreateSystem();
@@ -1562,7 +1576,7 @@ static void TestUnsetMatrix() {
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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == (i < 3 ? 0.0f : 0.5f) && out[i * 2 + 1] == (i < 3 ? 0.0f : 0.25f));
Require(sceNgs2SystemDestroy(stereoSystem, nullptr) == SCE_NGS2_OK);
}
@@ -1584,11 +1598,13 @@ static void TestRenderCapacity(std::uint32_t waveformType, std::uint32_t channel
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
std::fill(out.begin(), out.end(), sentinel);
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
Require(std::all_of(out.begin(), out.begin() + samples, [sentinel](TSample value) { return value > 0 && value != sentinel; }));
Require(std::all_of(out.begin(), out.begin() + 3 * channels, [](TSample value) { return value == 0; }));
Require(std::all_of(out.begin() + 3 * channels, out.begin() + samples, [sentinel](TSample value) { return value > 0 && value != sentinel; }));
Require(std::all_of(out.begin() + samples, out.end(), [sentinel](TSample value) { return value == sentinel; }));
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
Require(std::all_of(out.begin(), out.begin() + samples, [](TSample value) { return value == 0; }));
Require(std::all_of(out.begin(), out.begin() + 3 * channels, [sentinel](TSample value) { return value > 0 && value != sentinel; }));
Require(std::all_of(out.begin() + 3 * channels, out.begin() + samples, [](TSample value) { return value == 0; }));
Require(std::all_of(out.begin() + samples, out.end(), [sentinel](TSample value) { return value == sentinel; }));
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -0,0 +1,128 @@
#include "Ngs2Test.hpp"
#include <array>
#include <cmath>
#include <vector>
static constexpr std::uint32_t RenderGrain = 256;
static constexpr std::array<std::uint32_t, 8> Checkpoints{8, 12, 14, 20, 24, 30, 32, 40};
struct Case {
std::uint32_t rate;
float pitch;
std::int32_t cents;
bool events;
bool bypass;
std::array<std::array<float, 6>, 8> expected;
};
static const Case Cases[]{
{48000, 1.0f, 0, false, true, {}},
{44100, 1.3f, 700, false, false, {{
{0.137265846f, -0.0252674483f, 0.0799425617f, 0.0359304883f, -0.00174468313f, -0.0710403621f},
{-0.0386396796f, 0.113722473f, -0.0735076442f, 0.0788545236f, 0.115713701f, -0.0370367542f},
{-0.0208529234f, 0.131442904f, -0.0557390116f, 0.0966642424f, 0.133591712f, -0.0192373525f},
{0.0472084023f, -0.108676627f, 0.0123404339f, -0.158158675f, -0.103753135f, 0.0490724295f},
{0.0901324302f, -0.0657368749f, 0.055264473f, -0.100620545f, -0.0606985465f, 0.0921270251f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
}}},
{24000, 0.7f, -700, false, false, {{
{-0.127651423f, -0.075771533f, -0.0938414633f, -0.0419081151f, 0.0508089289f, 0.102808177f},
{-0.0176326279f, 0.0342472568f, 0.017153997f, 0.0691795796f, -0.144594505f, -0.092595242f},
{0.0412285328f, 0.0931084156f, 0.0762526914f, 0.128278285f, -0.0875838622f, -0.0355783924f},
{-0.0992715359f, -0.0473916531f, -0.06464196f, -0.0126163764f, 0.0828370824f, 0.134836316f},
{0.014179945f, 0.0660598278f, 0.0491285622f, 0.101154149f, -0.114638358f, -0.062632896f},
{-0.125253201f, -0.0733733177f, -0.0906971246f, -0.0386715382f, 0.0567951128f, 0.108794361f},
{-0.0694803074f, -0.0176004246f, -0.0356095247f, 0.0163238123f, 0.111254223f, -0.145683259f},
{-0.16338414f, -0.100421906f, -0.117817312f, -0.0657917261f, 0.0296836458f, 0.0816829056f},
}}},
{48000, 1.3f, 700, true, false, {{
{-0.0924464986f, -0.0188726112f, -0.0760293677f, 0.0488269478f, 0.156057909f, 0.0191847961f},
{0.0912231579f, -0.0198750645f, 0.0805369988f, -0.0305612143f, 0.000996570569f, -0.000805972959f},
{0.0f, -0.0f, 0.035238862f, -0.0581547283f, 0.0200667791f, -0.0917441919f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.130914479f, 0.0156071745f, 0.117588684f, 0.00487004546f, 0.0995834172f, -0.0121165738f},
{0.0912231579f, -0.0198750645f, 0.0805369988f, -0.0305612143f, 0.0637805164f, -0.0515822694f},
{0.0705910772f, 0.0424018614f, 0.108651653f, -0.0748682022f, -0.028412085f, 0.0848536044f},
}}},
};
static void TestSampler(const Case& test) {
const auto firstBuffer = usedBuffers;
const auto system = CreateSystem();
Require(sceNgs2SystemSetGrainSamples(system, RenderGrain) == SCE_NGS2_OK);
const auto master = Mastering(system, 2);
Ngs2CustomSamplerRackOption option{};
auto& custom = option.custom_rack_option;
auto& common = custom.rack_option;
common.size = sizeof(option);
common.max_grain_samples = 512;
common.max_voices = 1;
common.max_matrices = 1;
common.max_ports = 1;
custom.num_modules = 1;
custom.num_buffers = 1;
custom.state_size = 48;
custom.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT;
option.max_channel_works = 2;
option.max_waveform_blocks = 4;
Ngs2ContextBufferInfo query{};
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &common, &query) == SCE_NGS2_OK);
const auto storage = Buffer(query);
std::uintptr_t rack = 0;
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &common, &storage, &rack) == SCE_NGS2_OK);
const auto voice = Voice(rack);
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP,
Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, test.rate, 0, 0, 0}});
if (!test.bypass) Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_PITCH_SHIFT, Ngs2CustomVoicePitchShiftParam{{}, test.cents, 0, 0, 0});
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH, Ngs2SamplerVoicePitchParam{{}, test.pitch});
Patch(voice, master);
std::vector<std::int16_t> pcm(8192 * 2);
for (std::uint32_t i = 0; i < pcm.size(); ++i) pcm[i] = static_cast<std::int16_t>((static_cast<int>((i / 2 + i % 2 * 17) % 101) - 50) * 100);
const Ngs2WaveformBlock block{0, pcm.size() * 2, 0, 0, 8192, 0, 0x55};
const auto submit = [&](std::uint32_t flags) {
Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS,
Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), flags, 1, &block});
};
submit(0);
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
std::array<float, RenderGrain * 2> samples{};
const Ngs2RenderBufferInfo output{samples.data(), sizeof(samples), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
for (std::uint32_t grain = 0; grain <= Checkpoints.back(); ++grain) {
if (test.events) {
if (grain == 8) submit(SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET);
if (grain == 12) Event(voice, SCE_NGS2_VOICE_EVENT_PAUSE);
if (grain == 14) Event(voice, SCE_NGS2_VOICE_EVENT_RESUME);
if (grain == 20) {
Event(voice, SCE_NGS2_VOICE_EVENT_STOP_IMM);
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
}
if (grain == 30) Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_PITCH, Ngs2SamplerVoicePitchParam{{}, 0.5f});
}
Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK);
for (const float sample : samples) Require(std::isfinite(sample));
if (test.bypass) {
for (std::uint32_t i = 0; i < samples.size(); ++i) {
const auto frame = grain * RenderGrain + i / 2;
const float expected = frame >= 3 && frame < 8195 ? pcm[(frame - 3) * 2 + i % 2] / 32768.0f : 0.0f;
Require(samples[i] == expected);
}
continue;
}
for (std::size_t i = 0; i < Checkpoints.size(); ++i) {
if (grain != Checkpoints[i]) continue;
constexpr std::array<std::uint32_t, 6> indices{0, 1, 100, 101, 510, 511};
for (std::size_t j = 0; j < indices.size(); ++j) Require(std::abs(samples[indices[j]] - test.expected[i][j]) < 1e-6f);
}
}
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
usedBuffers = firstBuffer;
}
int main() {
for (const auto& test : Cases) TestSampler(test);
return 0;
}
@@ -51,30 +51,30 @@ static void TestStreamEndsAtDeclaredSamples() {
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));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i >= 3 ? 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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i == 0 ? 16384 : 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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 0 : 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));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 8192 : 4096));
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(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == nullptr);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -90,14 +90,14 @@ static void TestStreamClosedBeforeDeclaredSamples() {
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));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 0 : 16384));
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));
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i == 0 ? 16384 : i < 5 ? 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(state.num_decoded_samples == 0 && state.decoded_data_size == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
}
@@ -140,7 +140,7 @@ static void TestSilenceBlock() {
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);
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 == Grain && state.decoded_data_size == Grain * 2);
@@ -150,7 +150,10 @@ static void TestSilenceBlock() {
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);
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 0 : 1234));
Require(Flags(withData) == (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 < 3 ? 1234 : 0));
Require(Flags(withData) == 0);
const auto dataless = StreamVoice(rack, 2, master);
@@ -192,7 +195,7 @@ static void TestDrainedStreamReportsEmpty() {
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(out[0] == 0 && out[3] == 8192);
Require(Flags(voice) == playing);
AddBlock(voice, pcm.data(), pcm.size() * 2, 4, 0);
@@ -78,6 +78,17 @@ inline bool FrameInfoThrows(const Ngs2WaveformFormat& format) {
inline constexpr std::uint32_t Grain = 8;
template <typename TSignal>
inline float SamplerReference(std::uint32_t frame, float pitch, TSignal signal) {
const float position = frame * pitch;
const auto index = static_cast<std::uint32_t>(position);
const auto source = [&](std::uint32_t offset) { return offset < 4 ? 0.0f : signal(offset - 4); };
if (position == index) return source(index + 1);
Require(position - index == 0.5f);
return (source(index) * -0.0303300861f + source(index + 1) * 0.530330062f) +
(source(index + 2) * 0.530330062f + source(index + 3) * -0.0303300861f);
}
inline std::vector<std::vector<std::uint64_t>> buffers;
inline std::size_t usedBuffers = 0;
@@ -103,11 +103,11 @@ static void TestEffectAndIndependentVoices() {
Patch(sampler, first);
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
const auto mixed = Render(system, 2);
for (std::size_t i = 0; i < mixed.size(); ++i) Require(mixed[i] == (i % 2 == 0 ? 0.375f : 0.125f));
Require(firstState.calls == 1 && secondState.calls == 1 && Flags(sampler) == 0);
for (std::size_t i = 0; i < mixed.size(); ++i) Require(mixed[i] == (i < 6 ? (i % 2 == 0 ? 0.125f : 0.25f) : (i % 2 == 0 ? 0.375f : 0.125f)));
Require(firstState.calls == 1 && secondState.calls == 1 && Flags(sampler) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING));
const auto tail = Render(system, 2);
for (std::size_t i = 0; i < tail.size(); ++i) Require(tail[i] == (i % 2 == 0 ? 0.125f : 0.25f));
Require(firstState.calls == 2 && secondState.calls == 2);
for (std::size_t i = 0; i < tail.size(); ++i) Require(tail[i] == (i < 6 ? (i % 2 == 0 ? 0.375f : 0.125f) : (i % 2 == 0 ? 0.125f : 0.25f)));
Require(firstState.calls == 2 && secondState.calls == 2 && Flags(sampler) == 0);
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
}
+4 -1
View File
@@ -31,7 +31,10 @@
- [NGS2 custom Delay](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, 0x1d) - processing and defaults remain unverified on PS5. The [agreed compatibility path](https://github.com/boykopovar/AnyPS5/pull/9337#issuecomment-6351205140) preserves main's PCM pass-through for Void Terrarium (PPSA03061) until any Delay parameter is set; every `0x40001dxx` parameter throws. Only one-buffer custom submixers with no module state and a 20-byte option are accepted. Option type 0/1, taps 1..8, length 1..20000 and reserved 0 follow [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943). Other shapes and values throw because their console errors are unknown. This preserves host behavior, not console-equivalent Delay processing
- [NGS2 custom module ids](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native) - Reverb (0x1b) and Convolution (0x23) are not processed: a rack that passes their creation checks throws. Gain (0x15) on custom sampler racks also throws. Per [fma-rc's 12.70 module reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6294323150), creation returns `0x804A8302` for a wrong non-zero state size, and `0x804A8013`, `0x804A8099` and `0x804A8098` for a bad Reverb option size or word and PitchShift word. Unknown: check order, errors for a Gain option, missing Reverb options, Convolution option sizes, and Convolution's state (64 bytes from SDK 3.0). PPSA04540 uses 0x15, 0x19, 0x1b and 0x23 (#8039), PPSA35169 0x20 (#4246) and PPSA33655 0x15, 0x19 and 0x1b
- [NGS2 custom PitchShift](../../../core/libs/prx/libSceNgs2.native/src/PitchShift.cpp) (module 0x20) accepts absent options or an 8-byte option with quality 0, and processes custom submixer racks in 64-sample blocks, with cents clamped to -1200 through 1200, channel bypass, input filtering, waveform-aligned overlap and resampling. Default and setup-reset parameters bypass the module. Buffered processing continues while paused; pause, resume and stop use 64-sample output fades, and playback restart clears the history. PCM and buffer-state comparisons cover impulses, tones, noise, silence, parameter changes, reset, module indices and independent voices. The reconstructed resampling kernel produces float-rounding differences; sampled comparisons stayed below 1e-6 for input magnitudes up to 0.5. Active processing requires a grain divisible by 64. Pitch-down inputs with no selectable overlap throw instead of stalling. A channel bypassed at initialization cannot be enabled until a playback restart: this throws instead of reproducing the reference's non-terminating filter loop. Custom sampler racks with PitchShift still throw; their upstream sampler resampling path has an unmodeled three-sample startup delay, which changes the effect's waveform alignment. The title stop in [PPSA35169](https://github.com/boykopovar/AnyPS5/issues/4246) uses a custom submixer; no title run has verified progress past it.
- [NGS2 custom PitchShift](../../../core/libs/prx/libSceNgs2.native/src/PitchShift.cpp) (module 0x20) - active processing requires grains divisible by 64. Pitch-down inputs with no selectable overlap throw instead of stalling. A channel bypassed at initialization cannot be enabled until playback restart: this throws instead of reproducing a non-terminating filter loop. Custom sampler and submixer paths support cents -1200..1200, bypass, filtering, waveform alignment and resampling; setup defaults to bypass. Pause/resume/stop fade over 64 samples and restart clears history. Sampled PCM comparisons stayed below 1e-6 and ATRAC9 below 5e-6. The shared sampler uses Q14 phase, four history samples and a 512-phase kernel, producing three initial zero samples at unity rate. No title run has verified progress past the [PPSA35169 stop](https://github.com/boykopovar/AnyPS5/issues/4246).
- [NGS2 sampler replay and replacement](../../../core/libs/prx/libSceNgs2.native/src/Sampler.cpp) - playback restart retains waveform descriptors up to the rack's configured block capacity (at least one); exceeding that retention budget makes the next PLAY throw; already-running playback continues. Streaming pieces have the same retention bound. Active ATRAC9 waveform replacement preserves the decoder for the same data descriptor; replacing it with different data or lengths throws because the decoder's cached superframe replacement behavior is unmodeled. Nonzero sampler setup channel masks and transitions on grains below 64 throw. Callback-driven setup/reset still stops the current reader at its revision boundary; exact reference timing for callback replacement and partial compressed input remains unverified. The custom sampler state layout and size validation still have the limitations listed below.
- [NGS2 custom Gain module](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) (libSceNgs2.native, rack 0x4002) - follows [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943): parameter `0x40001500 | module index` holds 37 linear levels, one per channel, default 0, and the module writes source × level into dest, ramping linearly over the grain from the previous level. Unverified: where the ramp starts and ends (here the grain's first sample is one step past the previous level and its last sample is at the new level), whether stopping or setting up the voice again resets the levels, and what the library does with a level outside -32 to 32 (here it throws). On a custom sampler rack, whose parameter index is one higher per fma-rc, Gain throws. A Gain module with an option throws: fma-rc reads Gain as taking none, and the error the library returns for one is unknown. The extra buffer is ignored, as fma-rc reads it
- [NGS2 custom mastering rack](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native, rack 0x4003) - follows [fma-rc's reading of the 12.70 module](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6319332943): option size is 0x508, matching the custom submixer rack layout. Creation returns `0x804A8052` above 37 channels; channels 9 to 37 throw because voices hold at most 8 channels. Mastering racks with modules throw: attached modules and non-default option fields are unverified on PS5. Voice control parameters 0x40030000 (setup), 0x40030004 (gain) and 0x40030005 (output) configure channels, levels and destination buffer. `sceNgs2VoiceGetState` fills common voice state flags. PPSA10278 and PPSA04540 create rack 0x4003 without modules (#8039)