mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-31 12:35:51 +00:00
fix(ngs2): honor sampler streaming and replacement state
This commit is contained in:
1 parent
92cd244aa3
commit
51670ed598
13 files changed
+521
-282
No files matched your search
@@ -110,6 +110,8 @@ void Ngs2SetupAtrac9(Ngs2Voice& voice, const Ngs2WaveformFormat& format) {
|
||||
voice.atrac9.framesInSuperframe = static_cast<std::uint32_t>(info.framesInSuperframe);
|
||||
voice.atrac9.superframeBytes = static_cast<std::uint32_t>(info.superframeSize);
|
||||
Ngs2RestartAtrac9(voice);
|
||||
voice.atrac9.input.reserve(info.superframeSize);
|
||||
voice.atrac9.window.reserve(static_cast<std::size_t>(info.frameSamples) * info.channels);
|
||||
}
|
||||
|
||||
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block) {
|
||||
@@ -127,7 +129,9 @@ void Ngs2RestartAtrac9(Ngs2Voice& voice) {
|
||||
atrac9.input.clear();
|
||||
atrac9.remainingSamples = 0;
|
||||
atrac9.skipSamples = 0;
|
||||
atrac9.finishOnDrain = false;
|
||||
atrac9.inputCursor = 0;
|
||||
atrac9.frameIndex = 0;
|
||||
atrac9.restartOnDrain = false;
|
||||
}
|
||||
|
||||
static std::size_t BufferedFrames(const Ngs2Voice& voice) {
|
||||
@@ -135,80 +139,97 @@ static std::size_t BufferedFrames(const Ngs2Voice& voice) {
|
||||
}
|
||||
|
||||
static bool GatherSuperframe(Ngs2Voice& voice) {
|
||||
auto& atrac9 = voice.atrac9;
|
||||
while (atrac9.input.size() < atrac9.superframeBytes) {
|
||||
if (voice.blocks.empty()) {
|
||||
if (!voice.acceptsBlocks && atrac9.remainingSamples != 0)
|
||||
throw std::invalid_argument("NGS2: the ATRAC9 stream ends before its declared samples");
|
||||
return false;
|
||||
}
|
||||
auto& block = voice.blocks.front();
|
||||
if (!block.started) {
|
||||
if (block.info.num_samples != 0) {
|
||||
if (atrac9.remainingSamples != 0 || !atrac9.input.empty())
|
||||
throw std::invalid_argument("NGS2: a new ATRAC9 waveform interrupts an incomplete stream");
|
||||
atrac9.remainingSamples = block.info.num_samples;
|
||||
atrac9.skipSamples = block.info.num_skip_samples;
|
||||
} else if (atrac9.remainingSamples == 0) {
|
||||
throw std::invalid_argument("NGS2: an ATRAC9 data-only block has no waveform to continue");
|
||||
}
|
||||
block.started = true;
|
||||
}
|
||||
const auto count = std::min<std::size_t>(atrac9.superframeBytes - atrac9.input.size(), block.info.data_size - block.dataCursor);
|
||||
atrac9.input.insert(atrac9.input.end(), block.data + block.dataCursor, block.data + block.dataCursor + count);
|
||||
block.dataCursor += count;
|
||||
voice.decodedBytes += count;
|
||||
if (atrac9.input.size() == atrac9.superframeBytes) return true;
|
||||
if (!Ngs2FinishBlock(voice)) return false;
|
||||
auto& codec = voice.atrac9;
|
||||
while (!voice.blocks.empty() && !voice.blocks.front().started && voice.blocks.front().info.num_samples == 0 && codec.remainingSamples == 0) {
|
||||
Ngs2FinishBlock(voice);
|
||||
}
|
||||
return true;
|
||||
if (voice.blocks.empty()) {
|
||||
if (!voice.acceptsBlocks && codec.remainingSamples != 0) throw std::invalid_argument("NGS2: the ATRAC9 stream ends before its declared samples");
|
||||
return false;
|
||||
}
|
||||
auto& first = voice.blocks.front();
|
||||
if (!first.started) {
|
||||
Ngs2BindSamplerBlock(voice, false);
|
||||
first.started = true;
|
||||
}
|
||||
if (codec.restartOnDrain) {
|
||||
const auto remaining = codec.remainingSamples;
|
||||
const auto skipped = codec.skipSamples;
|
||||
Ngs2RestartAtrac9(voice);
|
||||
codec.remainingSamples = remaining;
|
||||
codec.skipSamples = skipped;
|
||||
}
|
||||
codec.input.clear();
|
||||
const auto needed = codec.superframeBytes - codec.inputCursor;
|
||||
for (const auto& block : voice.blocks) {
|
||||
if (&block != &first && block.info.num_samples != 0) throw std::invalid_argument("NGS2: a new ATRAC9 waveform interrupts a codec frame");
|
||||
if (block.dataCursor >= block.info.data_size) continue;
|
||||
const auto count = std::min<std::size_t>(needed - codec.input.size(), block.info.data_size - block.dataCursor);
|
||||
codec.input.insert(codec.input.end(), block.data + block.dataCursor, block.data + block.dataCursor + count);
|
||||
if (codec.input.size() == needed) return true;
|
||||
}
|
||||
if (!voice.acceptsBlocks) throw std::invalid_argument("NGS2: the ATRAC9 stream ends before a complete codec frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool DecodeSuperframe(Ngs2Voice& voice) {
|
||||
static void ConsumeEncoded(Ngs2Voice& voice, std::uint32_t bytes) {
|
||||
while (bytes != 0 && !voice.blocks.empty()) {
|
||||
auto& block = voice.blocks.front();
|
||||
if (block.dataCursor >= block.info.data_size) {
|
||||
Ngs2FinishBlock(voice);
|
||||
continue;
|
||||
}
|
||||
const auto count = std::min<std::uint64_t>(bytes, block.info.data_size - block.dataCursor);
|
||||
block.dataCursor += count;
|
||||
if (voice.atrac9.remainingSamples != 0) voice.waveformData = block.data + block.dataCursor;
|
||||
voice.decodedBytes += count;
|
||||
bytes -= static_cast<std::uint32_t>(count);
|
||||
if (block.dataCursor == block.info.data_size && (bytes != 0 || !voice.acceptsBlocks || voice.blocks.size() > 1) && voice.atrac9.remainingSamples != 0) Ngs2FinishBlock(voice);
|
||||
}
|
||||
}
|
||||
|
||||
static bool DecodeFrame(Ngs2Voice& voice) {
|
||||
if (!GatherSuperframe(voice)) return false;
|
||||
auto& atrac9 = voice.atrac9;
|
||||
if (atrac9.windowCursor != 0) {
|
||||
atrac9.window.erase(atrac9.window.begin(), atrac9.window.begin() + static_cast<std::ptrdiff_t>(atrac9.windowCursor) * voice.channels);
|
||||
atrac9.windowCursor = 0;
|
||||
auto& codec = voice.atrac9;
|
||||
const auto values = static_cast<std::size_t>(codec.frameSamples) * voice.channels;
|
||||
codec.window.resize(values);
|
||||
codec.windowCursor = 0;
|
||||
int used = 0;
|
||||
const int status = Atrac9DecodeF32(codec.decoder.get(), codec.input.data(), static_cast<int>(codec.input.size()), codec.window.data(), &used, 0);
|
||||
if (status != 0 || used <= 0 || static_cast<std::size_t>(used) > codec.input.size()) {
|
||||
throw std::runtime_error("NGS2: ATRAC9 decode failed with " + Ngs2Hex(static_cast<std::uint32_t>(status)));
|
||||
}
|
||||
const std::size_t frameValues = static_cast<std::size_t>(atrac9.frameSamples) * voice.channels;
|
||||
const auto superframeSamples = atrac9.frameSamples * atrac9.framesInSuperframe;
|
||||
const auto end = atrac9.window.size();
|
||||
atrac9.window.resize(end + static_cast<std::size_t>(superframeSamples) * voice.channels);
|
||||
std::size_t consumed = 0;
|
||||
for (std::uint32_t frame = 0; frame < atrac9.framesInSuperframe; ++frame) {
|
||||
int used = 0;
|
||||
const int status = Atrac9DecodeF32(atrac9.decoder.get(), atrac9.input.data() + consumed,
|
||||
static_cast<int>(atrac9.superframeBytes - consumed), atrac9.window.data() + end + frame * frameValues, &used, 0);
|
||||
if (status != 0 || used <= 0 || static_cast<std::size_t>(used) > atrac9.superframeBytes - consumed)
|
||||
throw std::runtime_error("NGS2: ATRAC9 decode failed with " + Ngs2Hex(static_cast<std::uint32_t>(status)));
|
||||
consumed += static_cast<std::size_t>(used);
|
||||
for (auto& sample : codec.window) sample = std::clamp(sample * (32767.0f / 32768.0f), -1.0f, 1.0f);
|
||||
codec.inputCursor += static_cast<std::uint32_t>(used);
|
||||
if (++codec.frameIndex == codec.framesInSuperframe) {
|
||||
if (!std::all_of(codec.input.begin() + used, codec.input.end(), [](std::uint8_t byte) { return byte == 1; })) {
|
||||
throw std::runtime_error("NGS2: ATRAC9 decode failed with invalid superframe padding");
|
||||
}
|
||||
used += static_cast<int>(codec.superframeBytes - codec.inputCursor);
|
||||
codec.inputCursor = 0;
|
||||
codec.frameIndex = 0;
|
||||
}
|
||||
atrac9.input.clear();
|
||||
const auto skipped = std::min(atrac9.skipSamples, superframeSamples);
|
||||
const auto samples = std::min(atrac9.remainingSamples, superframeSamples - skipped);
|
||||
atrac9.skipSamples -= skipped;
|
||||
atrac9.remainingSamples -= samples;
|
||||
std::memmove(atrac9.window.data() + end, atrac9.window.data() + end + static_cast<std::size_t>(skipped) * voice.channels,
|
||||
const auto skipped = std::min(codec.skipSamples, codec.frameSamples);
|
||||
const auto samples = std::min(codec.remainingSamples, codec.frameSamples - skipped);
|
||||
codec.skipSamples -= skipped;
|
||||
codec.remainingSamples -= samples;
|
||||
voice.decodedSamples += samples;
|
||||
std::memmove(codec.window.data(), codec.window.data() + static_cast<std::size_t>(skipped) * voice.channels,
|
||||
static_cast<std::size_t>(samples) * voice.channels * sizeof(float));
|
||||
atrac9.window.resize(end + static_cast<std::size_t>(samples) * voice.channels);
|
||||
if (atrac9.remainingSamples == 0) {
|
||||
atrac9.finishOnDrain = true;
|
||||
} else if (voice.blocks.front().dataCursor == voice.blocks.front().info.data_size) {
|
||||
if (!Ngs2FinishBlock(voice)) return false;
|
||||
codec.window.resize(static_cast<std::size_t>(samples) * voice.channels);
|
||||
const bool waveformEnded = codec.remainingSamples == 0;
|
||||
ConsumeEncoded(voice, static_cast<std::uint32_t>(used));
|
||||
codec.input.clear();
|
||||
if (waveformEnded) {
|
||||
codec.restartOnDrain = true;
|
||||
if (!voice.blocks.empty()) Ngs2FinishBlock(voice);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool EnsureFrames(Ngs2Voice& voice, std::size_t count) {
|
||||
auto& atrac9 = voice.atrac9;
|
||||
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;
|
||||
if (!Ngs2FinishBlock(voice)) return false;
|
||||
}
|
||||
if (!DecodeSuperframe(voice)) return false;
|
||||
if (!DecodeFrame(voice)) return false;
|
||||
}
|
||||
return voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop;
|
||||
}
|
||||
@@ -227,7 +248,6 @@ std::uint32_t Ngs2ReadSamplerAtrac9(Ngs2Voice& voice, float* dest, std::uint32_t
|
||||
}
|
||||
written += static_cast<std::uint32_t>(available);
|
||||
voice.atrac9.windowCursor += static_cast<std::uint32_t>(available);
|
||||
voice.decodedSamples += available;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
@@ -28,22 +28,13 @@ enum class Ngs2PlayState : std::uint32_t {
|
||||
Stopped = SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING | SCE_NGS2_VOICE_STATE_FLAG_STOPPED,
|
||||
};
|
||||
|
||||
struct Ngs2Piece {
|
||||
const std::uint8_t* data;
|
||||
std::uint64_t firstFrame;
|
||||
std::uint64_t frames;
|
||||
};
|
||||
|
||||
struct Ngs2Block {
|
||||
const std::uint8_t* data;
|
||||
Ngs2WaveformBlock info;
|
||||
std::uint32_t cursor = 0;
|
||||
std::uint32_t numRepeated = 0;
|
||||
std::size_t dataCursor = 0;
|
||||
bool streaming = false;
|
||||
std::uint64_t availableFrames = 0;
|
||||
std::vector<Ngs2Piece> pieces;
|
||||
bool started = false;
|
||||
std::uint32_t submittedRepeats = 0;
|
||||
};
|
||||
|
||||
struct Ngs2Atrac9DecoderDeleter {
|
||||
@@ -61,7 +52,9 @@ struct Ngs2Atrac9 {
|
||||
std::vector<std::uint8_t> input;
|
||||
std::uint32_t remainingSamples = 0;
|
||||
std::uint32_t skipSamples = 0;
|
||||
bool finishOnDrain = false;
|
||||
std::uint32_t inputCursor = 0;
|
||||
std::uint32_t frameIndex = 0;
|
||||
bool restartOnDrain = false;
|
||||
};
|
||||
|
||||
struct Ngs2ReverbState;
|
||||
@@ -127,15 +120,23 @@ struct Ngs2Voice {
|
||||
float pitch = 1.0f;
|
||||
std::uint64_t phase = 0;
|
||||
std::array<std::array<float, 4>, NGS2_MAX_CHANNELS> samplerHistory{};
|
||||
std::uint32_t samplerCachedFrames = 4;
|
||||
bool samplerStarved = false;
|
||||
bool samplerEnded = false;
|
||||
std::uint32_t pcmRemaining = 0;
|
||||
std::uint32_t pcmSkipRemaining = 0;
|
||||
std::vector<Ngs2VoiceCallbackInfo> pendingBlockCallbacks;
|
||||
std::vector<Ngs2VoiceCallbackInfo> dispatchBlockCallbacks;
|
||||
std::vector<float> samplerScratch;
|
||||
std::vector<Ngs2Block> samplerReplayBlocks;
|
||||
bool samplerReplayKnown = true;
|
||||
std::uint32_t samplerWritePosition = 0;
|
||||
std::deque<Ngs2Block> blocks;
|
||||
bool acceptsBlocks = true;
|
||||
std::uint64_t decodedSamples = 0;
|
||||
std::uint64_t decodedBytes = 0;
|
||||
std::uint64_t waveformRevision = 0;
|
||||
const std::uint8_t* waveformEnd = nullptr;
|
||||
const std::uint8_t* waveformData = nullptr;
|
||||
std::uintptr_t waveformUserData = 0;
|
||||
Ngs2VoiceCallbackHandler callback = nullptr;
|
||||
std::uintptr_t callbackData = 0;
|
||||
std::uint32_t callbackFlags = 0;
|
||||
@@ -202,7 +203,6 @@ struct Ngs2System {
|
||||
std::string Ngs2Hex(std::uint32_t value);
|
||||
std::size_t Ngs2PcmSampleBytes(std::uint32_t waveformType);
|
||||
void Ngs2ReadPcmFrame(std::uint32_t waveformType, std::uint32_t channels, const std::uint8_t* frame, float* output);
|
||||
const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block);
|
||||
std::recursive_mutex& Ngs2Mutex();
|
||||
|
||||
class Ngs2Lock {
|
||||
@@ -248,12 +248,13 @@ void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param);
|
||||
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block);
|
||||
void Ngs2RestartAtrac9(Ngs2Voice& voice);
|
||||
bool Ngs2FinishBlock(Ngs2Voice& voice);
|
||||
void Ngs2BindSamplerBlock(Ngs2Voice& voice, bool restartDecoder);
|
||||
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 Ngs2SaveSamplerBlock(Ngs2Voice& voice, 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);
|
||||
|
||||
@@ -243,7 +243,8 @@ void Ngs2ProcessPitchShift(Ngs2Voice& voice, std::size_t module, std::uint32_t g
|
||||
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;
|
||||
const bool paused = voice.state == Ngs2PlayState::Paused || (voice.stateFlags & SCE_NGS2_VOICE_STATE_FLAG_PAUSED) != 0;
|
||||
if (transition == Ngs2VoiceTransition::None && !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;
|
||||
@@ -255,7 +256,7 @@ void Ngs2ApplyVoiceGain(Ngs2Voice& voice, std::uint32_t grain) {
|
||||
if (i < 64) gain = static_cast<float>(i) / 64.0f;
|
||||
} else if (transition == Ngs2VoiceTransition::Stop) {
|
||||
gain = i < 64 ? 1.0f - static_cast<float>(i) / 64.0f : 0.0f;
|
||||
} else if (voice.state == Ngs2PlayState::Paused) {
|
||||
} else if (paused) {
|
||||
gain = 0.0f;
|
||||
}
|
||||
samples[i] *= gain;
|
||||
|
||||
@@ -109,6 +109,7 @@ static int CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option,
|
||||
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) result = Ngs2CheckCustomRack(rackId, options.customSampler.custom_rack_option, 0);
|
||||
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) result = Ngs2CheckCustomRack(rackId, options.customSubmixer.custom_rack_option, options.customSubmixer.max_inputs);
|
||||
if (result != SCE_NGS2_OK) return result;
|
||||
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER && options.customSampler.max_waveform_blocks == 0) return static_cast<int>(0x804A8053u);
|
||||
if (maxChannels > NGS2_MAX_CHANNELS) {
|
||||
if (rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER || rackId == SCE_NGS2_RACK_ID_CUSTOM_MASTERING) {
|
||||
throw std::runtime_error("NGS2: custom submixer racks with " + std::to_string(maxChannels) + " channels are not implemented");
|
||||
@@ -185,6 +186,7 @@ int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption*
|
||||
if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
|
||||
RackOptions options{};
|
||||
const int result = CheckedRackOption(rack_id, option, options);
|
||||
if (result == static_cast<int>(0x804A8053u)) return result;
|
||||
if (result != SCE_NGS2_OK) throw std::invalid_argument("NGS2: invalid rack option, rejected at creation with " + Ngs2Hex(static_cast<std::uint32_t>(result)));
|
||||
*buffer_info = {};
|
||||
buffer_info->host_buffer_size = sizeof(Ngs2Rack);
|
||||
|
||||
@@ -11,33 +11,40 @@
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "Ngs2Internal.hpp"
|
||||
|
||||
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;
|
||||
if (block.streaming) {
|
||||
for (const auto& piece : block.pieces) {
|
||||
if (frame >= piece.firstFrame && frame - piece.firstFrame < piece.frames) return piece.data + (frame - piece.firstFrame) * frameBytes;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return block.data + (static_cast<std::size_t>(block.info.num_skip_samples) + frame) * frameBytes;
|
||||
}
|
||||
|
||||
static void ReadPcm(const Ngs2Voice& voice, const std::uint8_t* frame, float* out) {
|
||||
Ngs2ReadPcmFrame(voice.waveformType, voice.channels, frame, out);
|
||||
}
|
||||
|
||||
void Ngs2BindSamplerBlock(Ngs2Voice& voice, bool restartDecoder) {
|
||||
if (voice.blocks.empty()) return;
|
||||
auto& block = voice.blocks.front();
|
||||
if (block.started || block.info.num_samples == 0) return;
|
||||
voice.waveformUserData = block.info.user_data;
|
||||
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) {
|
||||
voice.atrac9.remainingSamples = block.info.num_samples;
|
||||
voice.atrac9.skipSamples = block.info.num_skip_samples;
|
||||
if (restartDecoder) voice.atrac9.restartOnDrain = true;
|
||||
} else {
|
||||
voice.pcmRemaining = block.info.num_samples;
|
||||
voice.pcmSkipRemaining = block.info.num_skip_samples;
|
||||
}
|
||||
block.started = true;
|
||||
}
|
||||
|
||||
bool Ngs2FinishBlock(Ngs2Voice& voice) {
|
||||
auto& block = voice.blocks.front();
|
||||
if (block.data != nullptr) {
|
||||
voice.decodedBytes += block.info.data_size > block.dataCursor ? block.info.data_size - block.dataCursor : 0;
|
||||
}
|
||||
const bool repeat = block.info.num_repeats != 0;
|
||||
if (repeat) {
|
||||
if (block.info.num_repeats != UINT32_MAX) block.info.num_repeats--;
|
||||
block.numRepeated++;
|
||||
block.cursor = 0;
|
||||
block.started = false;
|
||||
block.dataCursor = 0;
|
||||
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) {
|
||||
block.dataCursor = 0;
|
||||
Ngs2RestartAtrac9(voice);
|
||||
voice.atrac9.restartOnDrain = true;
|
||||
}
|
||||
}
|
||||
const Ngs2VoiceCallbackInfo info{
|
||||
@@ -53,39 +60,55 @@ bool Ngs2FinishBlock(Ngs2Voice& voice) {
|
||||
0,
|
||||
};
|
||||
if (!repeat) {
|
||||
voice.waveformEnd = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? block.data + block.dataCursor : block.streaming ? Ngs2StreamEnd(voice, block) : FrameAt(voice, block, block.info.num_samples);
|
||||
voice.blocks.pop_front();
|
||||
Ngs2BindSamplerBlock(voice, true);
|
||||
}
|
||||
const auto revision = voice.waveformRevision;
|
||||
if (voice.callback != nullptr && (voice.callbackFlags & info.flag) != 0) {
|
||||
const Ngs2Unlocked unlocked;
|
||||
voice.callback(&info);
|
||||
voice.pendingBlockCallbacks.push_back(info);
|
||||
}
|
||||
return voice.waveformRevision == revision && (voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused || voice.outputTransition == Ngs2VoiceTransition::Stop);
|
||||
}
|
||||
|
||||
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;
|
||||
const auto frameBytes = voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
|
||||
while (written < count && !voice.blocks.empty()) {
|
||||
auto& block = voice.blocks.front();
|
||||
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;
|
||||
if (!block.started) {
|
||||
Ngs2BindSamplerBlock(voice, false);
|
||||
if (block.info.num_samples == 0) block.started = true;
|
||||
}
|
||||
if (voice.pcmRemaining == 0) {
|
||||
Ngs2FinishBlock(voice);
|
||||
continue;
|
||||
}
|
||||
if (block.data != nullptr && block.dataCursor >= block.info.data_size) {
|
||||
if (voice.blocks.size() == 1 && voice.acceptsBlocks) break;
|
||||
Ngs2FinishBlock(voice);
|
||||
continue;
|
||||
}
|
||||
if (block.data != nullptr && block.info.data_size - block.dataCursor < frameBytes) throw std::runtime_error("NGS2: a partial PCM frame is not implemented");
|
||||
if (voice.pcmSkipRemaining != 0) {
|
||||
const auto available = block.data == nullptr ? voice.pcmSkipRemaining : (block.info.data_size - block.dataCursor) / frameBytes;
|
||||
const auto skipped = std::min<std::uint64_t>(voice.pcmSkipRemaining, available);
|
||||
block.dataCursor += skipped * frameBytes;
|
||||
voice.decodedBytes += skipped * frameBytes;
|
||||
voice.pcmSkipRemaining -= static_cast<std::uint32_t>(skipped);
|
||||
continue;
|
||||
}
|
||||
float frame[NGS2_MAX_CHANNELS];
|
||||
ReadPcm(voice, FrameAt(voice, block, block.cursor), frame);
|
||||
ReadPcm(voice, block.data == nullptr ? nullptr : block.data + block.dataCursor, 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.pcmRemaining;
|
||||
++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());
|
||||
voice.decodedBytes += frameBytes;
|
||||
block.dataCursor += frameBytes;
|
||||
if (voice.pcmRemaining != 0 && (written == count || block.dataCursor == block.info.data_size)) {
|
||||
voice.waveformData = block.data == nullptr ? nullptr : block.data + block.dataCursor;
|
||||
}
|
||||
if (voice.pcmRemaining == 0) Ngs2FinishBlock(voice);
|
||||
}
|
||||
return written;
|
||||
}
|
||||
@@ -132,7 +155,7 @@ static void RenderSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t s
|
||||
voice.hasSamples = true;
|
||||
}
|
||||
const bool buffered = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && voice.atrac9.windowCursor * voice.channels < voice.atrac9.window.size();
|
||||
if (voice.state == Ngs2PlayState::Playing && voice.blocks.empty() && !buffered && !voice.acceptsBlocks && std::none_of(voice.filters.begin(), voice.filters.end(), HasHistory)) {
|
||||
if ((voice.state == Ngs2PlayState::Playing || voice.state == Ngs2PlayState::Paused) && voice.samplerEnded && !buffered && std::none_of(voice.filters.begin(), voice.filters.end(), HasHistory)) {
|
||||
voice.state = Ngs2PlayState::Empty;
|
||||
}
|
||||
}
|
||||
@@ -325,7 +348,21 @@ void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo
|
||||
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;
|
||||
if (voice->state == Ngs2PlayState::Playing && voice->IsSampler() && voice->samplerStarved && voice->acceptsBlocks) voice->stateFlags |= SCE_NGS2_VOICE_STATE_FLAG_EMPTY;
|
||||
}
|
||||
for (auto* voice : voices) {
|
||||
voice->dispatchBlockCallbacks.swap(voice->pendingBlockCallbacks);
|
||||
struct CallbackScope {
|
||||
std::vector<Ngs2VoiceCallbackInfo>& events;
|
||||
~CallbackScope() { events.clear(); }
|
||||
} callbackScope{voice->dispatchBlockCallbacks};
|
||||
for (auto info : voice->dispatchBlockCallbacks) {
|
||||
if (voice->callback == nullptr) break;
|
||||
if ((voice->callbackFlags & info.flag) == 0) continue;
|
||||
info.callback_data = voice->callbackData;
|
||||
const Ngs2Unlocked unlocked;
|
||||
voice->callback(&info);
|
||||
}
|
||||
}
|
||||
system.renderCount++;
|
||||
}
|
||||
@@ -29,25 +29,34 @@ static const std::array<std::array<float, 4>, 512>& SamplerKernel() {
|
||||
void Ngs2RestartSampler(Ngs2Voice& voice) {
|
||||
voice.phase = 0;
|
||||
voice.samplerHistory = {};
|
||||
voice.samplerCachedFrames = 4;
|
||||
voice.samplerStarved = false;
|
||||
voice.samplerEnded = false;
|
||||
voice.pcmRemaining = 0;
|
||||
voice.pcmSkipRemaining = 0;
|
||||
}
|
||||
|
||||
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 Ngs2SaveSamplerBlock(Ngs2Voice& voice, Ngs2Block& block) {
|
||||
block.submittedRepeats = block.info.num_repeats;
|
||||
if (voice.samplerWritePosition == voice.samplerReplayBlocks.size()) voice.samplerReplayBlocks.push_back(block);
|
||||
else voice.samplerReplayBlocks[voice.samplerWritePosition] = block;
|
||||
voice.samplerWritePosition = (voice.samplerWritePosition + 1) % std::max(1u, voice.rack->maxWaveformBlocks);
|
||||
}
|
||||
|
||||
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());
|
||||
const auto count = voice.samplerWritePosition != 0 ? voice.samplerWritePosition : voice.samplerReplayBlocks.size();
|
||||
voice.blocks.assign(voice.samplerReplayBlocks.begin(), voice.samplerReplayBlocks.begin() + count);
|
||||
for (auto& block : voice.blocks) {
|
||||
block.numRepeated = 0;
|
||||
block.dataCursor = 0;
|
||||
block.started = false;
|
||||
block.info.num_repeats = block.submittedRepeats;
|
||||
}
|
||||
++voice.waveformRevision;
|
||||
voice.decodedSamples = 0;
|
||||
voice.decodedBytes = 0;
|
||||
voice.waveformEnd = nullptr;
|
||||
voice.waveformData = nullptr;
|
||||
voice.waveformUserData = 0;
|
||||
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2RestartAtrac9(voice);
|
||||
Ngs2RestartSampler(voice);
|
||||
}
|
||||
@@ -55,6 +64,8 @@ void Ngs2ReplaySampler(Ngs2Voice& voice) {
|
||||
void Ngs2PrepareSampler(Ngs2Voice& voice) {
|
||||
voice.samplerScratch.resize((static_cast<std::size_t>(voice.rack->maxGrainSamples) * 4 + 4) * voice.channels);
|
||||
Ngs2RestartSampler(voice);
|
||||
voice.pendingBlockCallbacks.reserve(voice.rack->maxWaveformBlocks);
|
||||
voice.dispatchBlockCallbacks.reserve(voice.rack->maxWaveformBlocks);
|
||||
SamplerKernel();
|
||||
}
|
||||
|
||||
@@ -68,14 +79,16 @@ void Ngs2ConsumeSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sam
|
||||
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);
|
||||
const auto cached = voice.samplerCachedFrames;
|
||||
const auto requested = consumed + 4 - cached;
|
||||
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);
|
||||
std::copy_n(voice.samplerHistory[channel].begin(), cached, dest);
|
||||
std::fill_n(dest + cached, requested, 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);
|
||||
? Ngs2ReadSamplerAtrac9(voice, voice.samplerScratch.data() + cached, stride, requested)
|
||||
: Ngs2ReadSamplerPcm(voice, voice.samplerScratch.data() + cached, stride, requested);
|
||||
if (voice.waveformRevision != revision) return;
|
||||
const auto& kernel = SamplerKernel();
|
||||
for (std::uint32_t channel = 0; channel < channels; ++channel) {
|
||||
@@ -99,5 +112,8 @@ void Ngs2ConsumeSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sam
|
||||
std::copy_n(source + consumed, 4, voice.samplerHistory[channel].begin());
|
||||
}
|
||||
voice.phase = (voice.phase + static_cast<std::uint64_t>(step) * grain) & 0x3fff;
|
||||
voice.samplerCachedFrames = cached + count > consumed ? std::min(4u, cached + count - consumed) : 0;
|
||||
voice.samplerStarved = count < requested;
|
||||
voice.samplerEnded = voice.samplerStarved && !voice.acceptsBlocks;
|
||||
voice.hasSamples = count != 0 || std::any_of(voice.samples.begin(), voice.samples.end(), [](float sample) { return sample != 0.0f; });
|
||||
}
|
||||
@@ -95,11 +95,12 @@ void Ngs2Voice::ResetSetup() {
|
||||
phase = 0;
|
||||
blocks.clear();
|
||||
samplerReplayBlocks.clear();
|
||||
samplerReplayKnown = true;
|
||||
samplerWritePosition = 0;
|
||||
acceptsBlocks = true;
|
||||
decodedSamples = 0;
|
||||
decodedBytes = 0;
|
||||
waveformEnd = nullptr;
|
||||
waveformData = nullptr;
|
||||
waveformUserData = 0;
|
||||
userFxHandler = nullptr;
|
||||
userFxData = {};
|
||||
userFxFlags = 0;
|
||||
@@ -108,24 +109,8 @@ void Ngs2Voice::ResetSetup() {
|
||||
callbackFlags = 0;
|
||||
}
|
||||
|
||||
const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block) {
|
||||
const auto& last = block.pieces.back();
|
||||
return last.data + last.frames * voice.channels * Ngs2PcmSampleBytes(voice.waveformType);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (block.cursor >= piece.firstFrame && block.cursor - piece.firstFrame < piece.frames) return piece.data + (block.cursor - piece.firstFrame) * channels * Ngs2PcmSampleBytes(waveformType);
|
||||
}
|
||||
return Ngs2StreamEnd(*this, block);
|
||||
}
|
||||
if (block.data == nullptr) return nullptr;
|
||||
return block.data + (static_cast<std::size_t>(block.info.num_skip_samples) + block.cursor) * channels * Ngs2PcmSampleBytes(waveformType);
|
||||
return waveformData;
|
||||
}
|
||||
|
||||
static Ngs2Port& PortAt(Ngs2Voice& voice, std::uint32_t port) {
|
||||
@@ -226,41 +211,27 @@ static void SetupSampler(Ngs2Voice& voice, const Ngs2WaveformFormat& format) {
|
||||
}
|
||||
|
||||
static void AppendStreamData(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlocksParam& param, std::size_t frameBytes) {
|
||||
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) {
|
||||
const bool pendingWaveform = voice.atrac9.remainingSamples != 0 ||
|
||||
std::any_of(voice.blocks.begin(), voice.blocks.end(), [](const Ngs2Block& block) { return block.info.num_samples != 0; });
|
||||
if (!pendingWaveform) throw std::invalid_argument("NGS2: appending compressed data requires a waveform");
|
||||
for (std::uint32_t i = 0; i < param.num_blocks; ++i) {
|
||||
auto block = param.blocks[i];
|
||||
if (block.data_size == 0) continue;
|
||||
block.num_samples = 0;
|
||||
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;
|
||||
}
|
||||
if (voice.blocks.empty() || !voice.blocks.back().streaming) throw std::runtime_error("NGS2: appending waveform data to a voice without a streaming waveform is not implemented");
|
||||
auto& stream = voice.blocks.back();
|
||||
for (std::uint32_t i = 0; i < param.num_blocks; i++) {
|
||||
const auto& block = param.blocks[i];
|
||||
if (block.data_size == 0) continue;
|
||||
if (block.data_size % frameBytes != 0 || block.num_skip_samples != 0 || block.num_repeats != 0) {
|
||||
throw std::runtime_error("NGS2: appending a waveform block with skipped samples, repeats or a partial frame is not implemented");
|
||||
}
|
||||
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;
|
||||
const bool compressed = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9;
|
||||
const bool pending = !voice.blocks.empty() || (compressed ? voice.atrac9.remainingSamples != 0 : voice.pcmRemaining != 0);
|
||||
if (!pending) throw std::invalid_argument("NGS2: appending data requires a waveform");
|
||||
for (std::uint32_t i = 0; i < param.num_blocks; ++i) {
|
||||
auto info = param.blocks[i];
|
||||
if (info.data_size == 0) continue;
|
||||
if (!compressed && info.data_size % frameBytes != 0) throw std::runtime_error("NGS2: appending a partial PCM frame is not implemented");
|
||||
const auto* data = static_cast<const std::uint8_t*>(param.data) + info.data_offset;
|
||||
if (!voice.blocks.empty() && voice.blocks.back().data + voice.blocks.back().info.data_size == data && voice.blocks.back().info.user_data == info.user_data) {
|
||||
voice.blocks.back().info.data_size += info.data_size;
|
||||
if (!voice.samplerReplayBlocks.empty()) {
|
||||
const auto last = voice.samplerWritePosition == 0 ? voice.samplerReplayBlocks.size() - 1 : voice.samplerWritePosition - 1;
|
||||
voice.samplerReplayBlocks[last].info.data_size += info.data_size;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
info.num_repeats = 0;
|
||||
info.num_skip_samples = 0;
|
||||
info.num_samples = 0;
|
||||
voice.blocks.push_back({data, info});
|
||||
Ngs2SaveSamplerBlock(voice, voice.blocks.back());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,25 +240,17 @@ 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;
|
||||
const auto pcmCursor = reset && !voice.blocks.empty() ? voice.blocks.front().dataCursor : 0u;
|
||||
const bool preservePcm = reset && voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && !voice.blocks.empty() && voice.blocks.front().started;
|
||||
const bool preserveCodec = reset && voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && ((!voice.blocks.empty() && voice.blocks.front().started) || voice.atrac9.remainingSamples != 0);
|
||||
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 (preserveCodec && !voice.blocks.empty()) codecCursor = voice.blocks.front().dataCursor;
|
||||
if (!voice.acceptsBlocks && !reset) throw std::invalid_argument("NGS2: the voice waveform was already closed");
|
||||
if (reset) {
|
||||
++voice.waveformRevision;
|
||||
voice.blocks.clear();
|
||||
voice.samplerReplayBlocks.clear();
|
||||
voice.samplerReplayKnown = true;
|
||||
voice.waveformEnd = nullptr;
|
||||
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && !preserveCodec) Ngs2RestartAtrac9(voice);
|
||||
voice.samplerWritePosition = 0;
|
||||
}
|
||||
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) {
|
||||
@@ -315,15 +278,13 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
|
||||
throw std::invalid_argument("NGS2: waveform block " + std::to_string(i) + " does not fit its data");
|
||||
}
|
||||
voice.blocks.push_back({static_cast<const std::uint8_t*>(param.data) + block.data_offset, block});
|
||||
if (streaming) {
|
||||
auto& stream = voice.blocks.back();
|
||||
stream.streaming = true;
|
||||
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 (preservePcm && !voice.blocks.empty()) {
|
||||
auto& first = voice.blocks.front();
|
||||
first.started = true;
|
||||
first.dataCursor = pcmCursor;
|
||||
}
|
||||
if (preserveCodec && !voice.blocks.empty()) {
|
||||
voice.blocks.front().dataCursor = codecCursor;
|
||||
voice.blocks.front().started = true;
|
||||
@@ -479,6 +440,25 @@ int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHe
|
||||
auto& voice = *found;
|
||||
try {
|
||||
for (const auto* param = param_list;; param = reinterpret_cast<const Ngs2VoiceParamHeader*>(reinterpret_cast<const std::uint8_t*>(param) + param->next)) {
|
||||
if (voice.channels != 0 && (param->id >> 16) == voice.rack->rackId && (param->id == SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS || param->id == SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS)) {
|
||||
const auto& blocks = ParamAs<Ngs2SamplerVoiceWaveformBlocksParam>(*param);
|
||||
const bool reset = (blocks.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET) != 0;
|
||||
if (!voice.acceptsBlocks && !reset) return static_cast<int>(0x804A8054u);
|
||||
std::size_t size = reset ? 0 : voice.blocks.size();
|
||||
const auto* end = size != 0 && voice.blocks.back().data != nullptr ? voice.blocks.back().data + voice.blocks.back().info.data_size : nullptr;
|
||||
auto lastUserData = size != 0 ? voice.blocks.back().info.user_data : 0;
|
||||
for (std::uint32_t i = 0; blocks.blocks != nullptr && i < blocks.num_blocks; ++i) {
|
||||
const auto& block = blocks.blocks[i];
|
||||
const auto* data = blocks.data == nullptr ? nullptr : static_cast<const std::uint8_t*>(blocks.data) + block.data_offset;
|
||||
if (block.data_size == 0 && block.num_samples == 0) continue;
|
||||
if ((blocks.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND) == 0 || data != end || block.user_data != lastUserData) {
|
||||
++size;
|
||||
}
|
||||
end = data == nullptr ? nullptr : data + block.data_size;
|
||||
lastUserData = block.user_data;
|
||||
}
|
||||
if (size > std::max(1u, voice.rack->maxWaveformBlocks)) return static_cast<int>(0x804A8054u);
|
||||
}
|
||||
ApplyParam(voice, *param);
|
||||
if (param->next == 0) break;
|
||||
}
|
||||
@@ -533,7 +513,7 @@ int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state
|
||||
sampler.envelope_height = 1.0f;
|
||||
sampler.num_decoded_samples = voice.decodedSamples;
|
||||
sampler.decoded_data_size = voice.decodedBytes;
|
||||
sampler.user_data = voice.blocks.empty() ? 0 : voice.blocks.front().info.user_data;
|
||||
sampler.user_data = voice.waveformUserData;
|
||||
sampler.waveform_data = voice.WaveformData();
|
||||
return SCE_NGS2_OK;
|
||||
}
|
||||
|
||||
@@ -33,21 +33,21 @@ static const std::uint8_t Superframe[SuperframeBytes] = {
|
||||
0x19, 0x6b, 0x98, 0x7d, 0x97, 0x04, 0xe0, 0x36, 0xd6, 0x2a, 0x0c, 0x98, 0x48, 0x95, 0xca, 0xa9,
|
||||
0x85, 0xd2, 0xeb, 0x88, 0xee, 0x84, 0xe7, 0x1e, 0x78, 0x17, 0x13, 0x72, 0xe1, 0x55, 0xdb, 0xfb,
|
||||
0xc0, 0x73, 0x78, 0x57, 0x78, 0x58, 0x8d, 0x4e, 0x82, 0x99, 0xe6, 0xca, 0x2e, 0xb1, 0x73, 0xb3,
|
||||
0x83, 0x5e, 0x3c, 0x71, 0xe3, 0x23, 0x8f, 0x5b, 0x3b, 0xa3, 0xcd, 0x1c, 0xf8, 0xa9, 0x17, 0xb8,
|
||||
0x1c, 0x8f, 0x0f, 0x94, 0xe8, 0x9c, 0xe2, 0x6a, 0x35, 0xc9, 0x35, 0x63, 0x7a, 0xa8, 0x16, 0xfd,
|
||||
0x07, 0x52, 0x6f, 0xea, 0x76, 0xfe, 0x45, 0xb1, 0xa1, 0x13, 0xcd, 0x99, 0xbc, 0xe5, 0x51, 0xd8,
|
||||
0x74, 0x60, 0x6d, 0x5c, 0xfc, 0x4f, 0xc7, 0x31, 0x18, 0xfc, 0x94, 0x92, 0xa4, 0xab, 0x90, 0xab,
|
||||
0xbd, 0xff, 0x95, 0x73, 0xfd, 0xdb, 0x24, 0x9f, 0xee, 0xf2, 0x38, 0xaa, 0xd7, 0x8a, 0x82, 0x4b,
|
||||
0xea, 0xef, 0xa8, 0xe7, 0x3f, 0x6f, 0x18, 0x57, 0x6b, 0xad, 0xea, 0x0f, 0x58, 0x7a, 0xbb, 0x55,
|
||||
0x79, 0x50, 0xc9, 0x76, 0x2b, 0xed, 0x15, 0x19, 0xdf, 0x4e, 0xc3, 0xb6, 0xd8, 0x46, 0x02, 0xc2,
|
||||
0x35, 0xaa, 0xa9, 0x74, 0x19, 0xc2, 0xcc, 0x84, 0x6f, 0xb6, 0x93, 0xc7, 0x30, 0x4e, 0x1c, 0xa9,
|
||||
0x9f, 0xe5, 0x1f, 0xca, 0xd3, 0x35, 0xe4, 0xb2, 0x83, 0xa7, 0x69, 0x5b, 0xed, 0x6b, 0x1d, 0x23,
|
||||
0x15, 0xc7, 0x97, 0x2e, 0x89, 0x63, 0xb2, 0xf8, 0x19, 0x45, 0xbf, 0x49, 0x94, 0xdb, 0xf2, 0x33,
|
||||
0xc1, 0x12, 0xfd, 0x94, 0x47, 0xe1, 0x46, 0xca, 0x89, 0xaf, 0x25, 0x76, 0x1a, 0x42, 0x99, 0x2f,
|
||||
0x09, 0x31, 0x5d, 0x92, 0x94, 0x0f, 0x8a, 0xae, 0x55, 0x49, 0xf7, 0xe9, 0x25, 0xea, 0x1c, 0xe6
|
||||
0x83, 0x5e, 0x3c, 0x71, 0xe3, 0x23, 0x8f, 0x5b, 0x3b, 0xa3, 0xcd, 0x1c, 0xf8, 0x8d, 0x4e, 0x82,
|
||||
0x99, 0xe6, 0xca, 0x2e, 0xb1, 0x73, 0xb3, 0x83, 0x5e, 0x3c, 0x71, 0xe3, 0x23, 0x8f, 0x5b, 0x3b,
|
||||
0xa3, 0xcd, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
|
||||
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
|
||||
};
|
||||
|
||||
static std::vector<float> Reference(std::uint32_t superframes = 1) {
|
||||
static std::vector<float> Reference(std::uint32_t superframes = 1, std::vector<std::uint32_t>* offsets = nullptr, const std::uint8_t* packet = Superframe) {
|
||||
std::uint8_t config[4] = {0xFE, 0x70, 0x07, 0xF0};
|
||||
void* decoder = Atrac9GetHandle();
|
||||
Require(Atrac9InitDecoder(decoder, config) == 0);
|
||||
@@ -56,11 +56,13 @@ static std::vector<float> Reference(std::uint32_t superframes = 1) {
|
||||
int offset = 0;
|
||||
for (std::uint32_t frame = 0; frame < 4; frame++) {
|
||||
int used = 0;
|
||||
Require(Atrac9DecodeF32(decoder, Superframe + offset, static_cast<int>(SuperframeBytes) - offset, pcm.data() + s * SuperframeSamples + frame * 256, &used, 0) == 0);
|
||||
Require(Atrac9DecodeF32(decoder, packet + offset, static_cast<int>(SuperframeBytes) - offset, pcm.data() + s * SuperframeSamples + frame * 256, &used, 0) == 0);
|
||||
offset += used;
|
||||
if (offsets != nullptr) offsets->push_back(offset);
|
||||
}
|
||||
}
|
||||
Atrac9ReleaseHandle(decoder);
|
||||
for (auto& sample : pcm) sample = std::clamp(sample * (32767.0f / 32768.0f), -1.0f, 1.0f);
|
||||
return pcm;
|
||||
}
|
||||
|
||||
@@ -109,7 +111,9 @@ static void TestRepeatAndState(const std::vector<float>& reference) {
|
||||
auto rendered = Render(system, 304);
|
||||
Ngs2SamplerVoiceState state{};
|
||||
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
Require(state.num_decoded_samples == 304 && state.decoded_data_size == 2 * SuperframeBytes && state.waveform_data == Superframe + SuperframeBytes);
|
||||
std::vector<std::uint32_t> offsets;
|
||||
Reference(1, &offsets);
|
||||
Require(state.num_decoded_samples == 556 && state.decoded_data_size == SuperframeBytes + offsets[0] && state.waveform_data == Superframe + offsets[0]);
|
||||
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 >= 3 && i < 603 ? reference[(i - 3) % 300] : 0.0f));
|
||||
@@ -489,7 +493,7 @@ static void TestCorruptSuperframeAtPageEnd() {
|
||||
Patch(voice, Mastering(system, 1));
|
||||
bool rejected = false;
|
||||
try {
|
||||
Render(system, Grain);
|
||||
Render(system, SuperframeSamples);
|
||||
} catch (const std::runtime_error& error) {
|
||||
rejected = std::strstr(error.what(), "ATRAC9 decode failed") != nullptr;
|
||||
}
|
||||
@@ -506,7 +510,7 @@ static uintptr_t EmptySampler(uintptr_t system) {
|
||||
}
|
||||
|
||||
static void Queue(uintptr_t voice, const void* data, std::uint32_t bytes, std::uint32_t flags, std::uint32_t samples = 0, std::uint32_t skip = 0) {
|
||||
const Ngs2WaveformBlock block{0, bytes, 0, skip, samples, 0, bytes};
|
||||
const Ngs2WaveformBlock block{0, bytes, 0, skip, samples, 0, 0};
|
||||
Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, data, flags, 1, &block});
|
||||
}
|
||||
|
||||
@@ -537,7 +541,7 @@ static void TestStreamPartitions() {
|
||||
});
|
||||
Require(std::abs(output[i] - expected) < 1e-6f);
|
||||
}
|
||||
Require(callbackFlags.size() == 5 && Flags(voice) == 0);
|
||||
Require(callbackFlags.size() == 1 && Flags(voice) == 0);
|
||||
Ngs2SamplerVoiceState state{};
|
||||
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
Require(state.num_decoded_samples == 0);
|
||||
@@ -546,46 +550,24 @@ static void TestStreamPartitions() {
|
||||
}
|
||||
}
|
||||
|
||||
struct RefillState { int calls = 0; bool pause = false; bool reset = false; };
|
||||
|
||||
static void APS5_VABI Refill(const Ngs2VoiceCallbackInfo* info) {
|
||||
auto& state = *reinterpret_cast<RefillState*>(info->callback_data);
|
||||
++state.calls;
|
||||
Require(info->flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
|
||||
Require(info->user_data == info->block_size);
|
||||
if (state.calls != 1) return;
|
||||
Require(info->block_size == 13 && info->block_data == Superframe);
|
||||
if (state.reset) Queue(info->voice_handle, Superframe, SuperframeBytes, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET, 500, 100);
|
||||
else Queue(info->voice_handle, Superframe + 13, SuperframeBytes - 13, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND, 0xffffffffu, 0xffffffffu);
|
||||
if (state.pause) Event(info->voice_handle, SCE_NGS2_VOICE_EVENT_PAUSE);
|
||||
}
|
||||
|
||||
static void TestRefill(const std::vector<float>& reference) {
|
||||
for (int mode = 0; mode < 3; ++mode) {
|
||||
for (const bool contiguous : {false, true}) {
|
||||
const auto system = CreateSystem();
|
||||
Require(sceNgs2SystemSetGrainSamples(system, 64) == SCE_NGS2_OK);
|
||||
const auto voice = EmptySampler(system);
|
||||
RefillState state{0, mode == 1, mode == 2};
|
||||
std::vector<std::uint8_t> tail(Superframe + 13, Superframe + SuperframeBytes);
|
||||
callbackFlags.clear();
|
||||
Control(voice, SCE_NGS2_VOICE_PARAM_CALLBACK,
|
||||
Ngs2VoiceCallbackParam{{}, Refill, reinterpret_cast<std::uintptr_t>(&state), SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
||||
Ngs2VoiceCallbackParam{{}, OnBlock, 0, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
||||
Queue(voice, Superframe, 13, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 500, 100);
|
||||
if (mode != 0) {
|
||||
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, 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);
|
||||
for (const float value : Render(system, Grain)) Require(value == 0.0f);
|
||||
Require(callbackFlags.empty());
|
||||
Ngs2SamplerVoiceState state{};
|
||||
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == nullptr);
|
||||
Queue(voice, contiguous ? Superframe + 13 : tail.data(), SuperframeBytes - 13, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
|
||||
const auto output = Render(system, 512);
|
||||
for (std::uint32_t i = 0; i < output.size(); ++i) Require(std::abs(output[i] - (i < 499 ? reference[101 + i] : 0.0f)) < 1e-6f);
|
||||
Require(callbackFlags.size() == (contiguous ? 1u : 2u) && Flags(voice) == 0);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
|
||||
}
|
||||
}
|
||||
@@ -605,7 +587,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 >= 3 && i < 503 ? reference[100 + i - 3] : 0.0f));
|
||||
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i < 499 ? reference[101 + i] : 0.0f));
|
||||
Require(Flags(voice) == 0);
|
||||
}
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == 0);
|
||||
@@ -622,7 +604,7 @@ 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 >= 3 && i < 503 ? reference[100 + i - 3] : 0.0f));
|
||||
for (std::size_t i = 0; i < output.size(); ++i) Require(output[i] == (i < 499 ? reference[101 + i] : 0.0f));
|
||||
Ngs2SamplerVoiceState state{};
|
||||
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0);
|
||||
@@ -662,6 +644,87 @@ static void TestSilenceFlagRejected() {
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestReplacement() {
|
||||
const auto reference = Reference(3);
|
||||
Require(std::any_of(reference.begin(), reference.end(), [](float sample) { return std::abs(sample) > 0.01f; }));
|
||||
const auto system = CreateSystem();
|
||||
Require(sceNgs2SystemSetGrainSamples(system, 64) == SCE_NGS2_OK);
|
||||
const auto voice = EmptySampler(system);
|
||||
std::vector<std::uint8_t> source;
|
||||
for (std::uint32_t i = 0; i < 8; ++i) source.insert(source.end(), Superframe, Superframe + SuperframeBytes);
|
||||
const std::vector<std::uint8_t> replacement(source.begin(), source.begin() + 3 * SuperframeBytes);
|
||||
Queue(voice, source.data(), source.size(), 0, 2048, 256);
|
||||
callbackFlags.clear();
|
||||
Control(voice, SCE_NGS2_VOICE_PARAM_CALLBACK, Ngs2VoiceCallbackParam{{}, OnBlock, 0, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
||||
for (std::uint32_t grain = 0; grain < 43; ++grain) {
|
||||
if (grain == 10) {
|
||||
Queue(voice, replacement.data(), replacement.size(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET, 256, 512);
|
||||
std::fill(source.begin(), source.end(), 0xff);
|
||||
}
|
||||
if (grain == 36) {
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_STOP_IMM);
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
}
|
||||
const auto output = Render(system, 64, 64);
|
||||
for (std::uint32_t i = 0; i < output.size(); ++i) {
|
||||
const auto frame = (grain < 36 ? grain : grain - 36) * 64 + i;
|
||||
const auto length = grain < 36 ? 2048u : 256u;
|
||||
const auto skip = grain < 36 ? 256u : 512u;
|
||||
const float expected = frame >= 3 && frame < length + 3 ? reference[skip + frame - 3] : 0.0f;
|
||||
Require(std::abs(output[i] - expected) < 1e-6f);
|
||||
}
|
||||
Require(callbackFlags.size() == (grain < 28 ? 0u : grain < 36 ? 1u : 2u));
|
||||
}
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestStreamingReplay() {
|
||||
const auto reference = Reference(3);
|
||||
for (const bool split : {false, true}) {
|
||||
const auto system = CreateSystem();
|
||||
Require(sceNgs2SystemSetGrainSamples(system, 64) == SCE_NGS2_OK);
|
||||
const auto voice = EmptySampler(system);
|
||||
std::vector<std::uint8_t> source;
|
||||
for (std::uint32_t i = 0; i < 3; ++i) source.insert(source.end(), Superframe, Superframe + SuperframeBytes);
|
||||
const std::vector<std::uint8_t> middle(source.begin() + SuperframeBytes, source.end());
|
||||
const std::vector<std::uint8_t> tail(source.begin() + 2 * SuperframeBytes, source.end());
|
||||
callbackFlags.clear();
|
||||
Control(voice, SCE_NGS2_VOICE_PARAM_CALLBACK, Ngs2VoiceCallbackParam{{}, OnBlock, 0, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
||||
Queue(voice, source.data(), SuperframeBytes, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, 2048, 256);
|
||||
for (std::uint32_t grain = 0; grain < 71; ++grain) {
|
||||
if (grain == 13) Queue(voice, middle.data(), split ? SuperframeBytes : middle.size(),
|
||||
SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND | (split ? SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE : 0));
|
||||
if (grain == 29 && split) Queue(voice, tail.data(), tail.size(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND);
|
||||
if (grain == 36) {
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_STOP_IMM);
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
}
|
||||
const auto output = Render(system, 64, 64);
|
||||
for (std::uint32_t i = 0; i < output.size(); ++i) {
|
||||
float expected = 0.0f;
|
||||
if (grain < 13) {
|
||||
const auto frame = grain * 64 + i;
|
||||
if (frame >= 3 && frame < 771) expected = reference[256 + frame - 3];
|
||||
} else if (grain < 36) {
|
||||
const auto frame = (grain - 13) * 64 + i;
|
||||
if (!split && frame < 1279) expected = reference[1025 + frame];
|
||||
if (split && grain < 29 && frame < 1023) expected = reference[1025 + frame];
|
||||
const auto last = (grain - 29) * 64 + i;
|
||||
if (split && grain >= 29 && last < 255) expected = reference[2049 + last];
|
||||
} else {
|
||||
const auto frame = (grain - 36) * 64 + i;
|
||||
if (frame >= 3 && frame < 2051) expected = reference[256 + frame - 3];
|
||||
}
|
||||
Require(std::abs(output[i] - expected) < 1e-6f);
|
||||
}
|
||||
const auto expectedCallbacks = split ? (grain < 13 ? 0u : grain < 29 ? 1u : grain < 44 ? 3u : grain < 60 ? 4u : grain < 64 ? 5u : 6u)
|
||||
: grain < 13 ? 0u : grain < 28 ? 1u : grain < 44 ? 2u : grain < 64 ? 3u : 4u;
|
||||
Require(callbackFlags.size() == expectedCallbacks);
|
||||
}
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
const auto reference = Reference();
|
||||
TestSkipAndBlockEnd(reference);
|
||||
@@ -676,6 +739,8 @@ int main() {
|
||||
TestCorruptSuperframeAtPageEnd();
|
||||
TestStreamPartitions();
|
||||
TestRefill(reference);
|
||||
TestReplacement();
|
||||
TestStreamingReplay();
|
||||
TestStarvationAndTruncation(reference);
|
||||
TestAppendBoundaries(reference);
|
||||
TestAppendWithoutWaveform();
|
||||
|
||||
@@ -278,6 +278,7 @@ static void TestCustomSampler() {
|
||||
option.custom_rack_option.module[m].state_size = 16;
|
||||
}
|
||||
option.max_channel_works = 4;
|
||||
option.max_waveform_blocks = 1;
|
||||
|
||||
Ngs2ContextBufferInfo query{};
|
||||
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &option.custom_rack_option.rack_option, &query) == SCE_NGS2_OK);
|
||||
@@ -328,6 +329,7 @@ static void TestCustomFilter() {
|
||||
option.custom_rack_option.num_modules = 1;
|
||||
option.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER;
|
||||
option.max_channel_works = 1;
|
||||
option.max_waveform_blocks = 1;
|
||||
|
||||
auto invalid = option;
|
||||
invalid.custom_rack_option.module[0].state_size = 4;
|
||||
@@ -520,6 +522,7 @@ static void TestCustomRackChecks() {
|
||||
sampler.custom_rack_option.num_modules = 1;
|
||||
sampler.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_FILTER;
|
||||
sampler.max_channel_works = 1;
|
||||
sampler.max_waveform_blocks = 1;
|
||||
Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &sampler.custom_rack_option.rack_option, &info, &rack) == SCE_NGS2_ERROR_INVALID_NUM_BUFFERS);
|
||||
sampler.custom_rack_option.num_buffers = 1;
|
||||
sampler.custom_rack_option.module[0].extra_buffer_id = 1;
|
||||
@@ -739,6 +742,7 @@ static void TestCustomDelay(std::uint32_t count, const DelayOption& delay) {
|
||||
samplerOption.custom_rack_option = DelayRackOption(delay, count).custom_rack_option;
|
||||
samplerOption.custom_rack_option.rack_option.size = sizeof(samplerOption);
|
||||
samplerOption.max_channel_works = 1;
|
||||
samplerOption.max_waveform_blocks = 1;
|
||||
Require(CreateSamplerThrows<std::runtime_error>(samplerOption));
|
||||
for (const auto invalid : {DelayOption{19}, DelayOption{24}, DelayOption{20, 2}, DelayOption{20, 0, 0},
|
||||
DelayOption{20, 0, 9}, DelayOption{20, 0, 4, 0.0f}, DelayOption{20, 0, 4, 20001.0f},
|
||||
|
||||
@@ -379,12 +379,12 @@ static void TestPitchAndRepeat() {
|
||||
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.empty());
|
||||
Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT);
|
||||
|
||||
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);
|
||||
Require(callbackFlags.size() == 2 && callbackFlags[1] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
|
||||
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]);
|
||||
@@ -456,14 +456,14 @@ static void TestPcmCallbackReset() {
|
||||
Require(callback.calls == 1);
|
||||
Ngs2SamplerVoiceState state{};
|
||||
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] == 0);
|
||||
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == nullptr);
|
||||
for (std::uint32_t frame = 0; frame < Grain; ++frame) Require(first[frame] == (flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT && frame == 2 ? 16384 : 0));
|
||||
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
const auto second = RenderI16(system);
|
||||
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);
|
||||
Require(state.num_decoded_samples == Grain * 2 && state.waveform_data == callback.pcm.data() + Grain * 2);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
}
|
||||
@@ -487,22 +487,23 @@ static void TestPcmCallbackRefill() {
|
||||
Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
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));
|
||||
}
|
||||
for (std::uint32_t frame = 0; frame < RenderGrain; ++frame) Require(first[frame] == (frame == 3 ? pcm : 0));
|
||||
Ngs2SamplerVoiceState state{};
|
||||
Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
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);
|
||||
Require(state.num_decoded_samples == 1 && state.waveform_data == nullptr);
|
||||
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));
|
||||
const float gain = pause ? static_cast<float>(64 - frame) / 64.0f : 1.0f;
|
||||
Require(second[frame] == std::lrint(callback.pcm[frame + 1] * gain));
|
||||
}
|
||||
Require(callback.calls == 1);
|
||||
if (pause) Event(sampler, SCE_NGS2_VOICE_EVENT_RESUME);
|
||||
const auto third = RenderI16(system, RenderGrain);
|
||||
for (std::uint32_t frame = 0; frame < RenderGrain; ++frame) {
|
||||
const float gain = pause ? static_cast<float>(frame) / 64.0f : 1.0f;
|
||||
const float source = frame < 63 ? callback.pcm[65 + frame] : 0.0f;
|
||||
Require(third[frame] == std::lrint(source * gain));
|
||||
}
|
||||
Require(callback.calls == 2 && Flags(sampler) == 0);
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
}
|
||||
}
|
||||
@@ -545,10 +546,18 @@ static void TestPcmCallbackSetup() {
|
||||
std::vector<std::int16_t> output(Grain * channels, -1);
|
||||
const Ngs2RenderBufferInfo info{output.data(), output.size() * sizeof(std::int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels};
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
Require(callback.calls == 1 && std::all_of(output.begin(), output.end(), [](std::int16_t sample) { return sample == 0; }));
|
||||
Require(callback.calls == 1);
|
||||
for (std::uint32_t i = 0; i < output.size(); ++i) {
|
||||
const auto frame = i / channels;
|
||||
const auto channel = i % channels;
|
||||
const auto expected = frame >= 3 && frame < (flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT ? 5u : 4u)
|
||||
? channels == 2 ? static_cast<std::int16_t>(std::lrint(16384.0f * std::numbers::sqrt2_v<float> / 2.0f))
|
||||
: channel == 2 ? 16384 : 0 : 0;
|
||||
Require(output[i] == expected);
|
||||
}
|
||||
Ngs2SamplerVoiceState state{};
|
||||
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(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == nullptr);
|
||||
Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK);
|
||||
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]));
|
||||
|
||||
@@ -122,7 +122,110 @@ static void TestSampler(const Case& test) {
|
||||
usedBuffers = firstBuffer;
|
||||
}
|
||||
|
||||
struct Stream {
|
||||
std::uintptr_t voice;
|
||||
std::vector<std::int16_t> pcm;
|
||||
std::uint32_t mode;
|
||||
std::uint32_t callbacks = 0;
|
||||
};
|
||||
|
||||
static int SubmitStream(Stream& stream, std::uint32_t block, std::uint32_t flags) {
|
||||
const auto offset = block * (stream.mode == 2 ? 256u : 128u);
|
||||
const Ngs2WaveformBlock info{0, 256, 0, 0, stream.mode == 1 || stream.mode == 2 || stream.mode == 5 ? 320u : 64u, 0, stream.mode == 1 ? 0u : block};
|
||||
const Ngs2SamplerVoiceWaveformBlocksParam param{{sizeof(param), 0, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS},
|
||||
stream.pcm.data() + offset, flags, 1, &info};
|
||||
return sceNgs2VoiceControl(stream.voice, ¶m.header);
|
||||
}
|
||||
|
||||
static void APS5_VABI RefillStream(const Ngs2VoiceCallbackInfo* info) {
|
||||
auto& stream = *reinterpret_cast<Stream*>(info->callback_data);
|
||||
Require(info->flag == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END);
|
||||
Require(info->block_size == (stream.mode == 1 ? 1280u : 256u));
|
||||
++stream.callbacks;
|
||||
if (stream.mode < 3 || stream.mode == 5 || stream.callbacks >= 5) return;
|
||||
const auto flags = (stream.mode == 4 ? SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET : 0) |
|
||||
(stream.callbacks < 4 ? SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE : 0);
|
||||
Require(SubmitStream(stream, stream.callbacks, flags) == SCE_NGS2_OK);
|
||||
}
|
||||
|
||||
static void TestStreaming(std::uint32_t mode, std::uint32_t capacity = 2) {
|
||||
const auto firstBuffer = usedBuffers;
|
||||
const auto system = CreateSystem();
|
||||
Require(sceNgs2SystemSetGrainSamples(system, 64) == 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 = capacity;
|
||||
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);
|
||||
Stream stream{Voice(rack), std::vector<std::int16_t>(1280), mode};
|
||||
for (std::uint32_t i = 0; i < stream.pcm.size(); ++i) stream.pcm[i] = static_cast<std::int16_t>((i / 128 + 1) * 1000);
|
||||
Control(stream.voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP,
|
||||
Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}});
|
||||
Patch(stream.voice, master);
|
||||
Require(SubmitStream(stream, 0, SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE) == SCE_NGS2_OK);
|
||||
if (mode == 0) {
|
||||
const std::array<Ngs2WaveformBlock, 3> blocks{{{0, 256, 0, 0, 64, 0, 0}, {256, 256, 0, 0, 64, 0, 0}, {512, 256, 0, 0, 64, 0, 0}}};
|
||||
const Ngs2SamplerVoiceWaveformBlocksParam invalid{{sizeof(invalid), 0, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS},
|
||||
stream.pcm.data(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE, blocks.size(), blocks.data()};
|
||||
Require(sceNgs2VoiceControl(stream.voice, &invalid.header) == static_cast<int>(0x804A8054u));
|
||||
}
|
||||
Control(stream.voice, SCE_NGS2_VOICE_PARAM_CALLBACK,
|
||||
Ngs2VoiceCallbackParam{{}, RefillStream, reinterpret_cast<std::uintptr_t>(&stream), SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0});
|
||||
Event(stream.voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
std::array<float, 128> samples{};
|
||||
const Ngs2RenderBufferInfo output{samples.data(), sizeof(samples), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2};
|
||||
for (std::uint32_t grain = 0; grain < 15; ++grain) {
|
||||
if ((mode < 3 || mode == 5) && grain >= 1 && grain <= 4) {
|
||||
const auto flags = (mode == 1 || mode == 2 || mode == 5 ? SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND : 0) |
|
||||
(grain < 4 ? SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE : 0);
|
||||
Require(SubmitStream(stream, grain, flags) == SCE_NGS2_OK);
|
||||
}
|
||||
if (grain == 8) {
|
||||
Event(stream.voice, SCE_NGS2_VOICE_EVENT_STOP_IMM);
|
||||
Event(stream.voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
}
|
||||
Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK);
|
||||
for (std::uint32_t i = 0; i < samples.size(); ++i) {
|
||||
const auto frame = (grain < 8 ? grain : grain - 8) * 64 + i / 2;
|
||||
float expected = 0.0f;
|
||||
if (grain < 8 || mode == 1) {
|
||||
if (frame >= 3 && frame < 323) expected = static_cast<float>(((frame - 3) / 64 * (mode == 2 ? 2 : 1) + 1) * 1000) / 32768.0f;
|
||||
} else if (mode != 2 && mode != 5 && frame >= 3 && frame < (capacity == 3 ? 131u : 67u)) {
|
||||
expected = static_cast<float>(capacity == 3 ? ((frame - 3) / 64 + 4) * 1000 : 5000) / 32768.0f;
|
||||
}
|
||||
Require(samples[i] == expected);
|
||||
}
|
||||
auto expectedCallbacks = mode == 1 ? (grain < 4 ? 0u : grain < 12 ? 1u : 2u)
|
||||
: mode == 2 || mode == 5 ? (grain == 0 ? 0u : grain < 4 ? grain : grain < 8 ? 5u : 6u)
|
||||
: grain < 5 ? grain + 1 : grain < 8 ? 5u : 6u;
|
||||
if (capacity == 3 && grain >= 9) expectedCallbacks = 7;
|
||||
Require(stream.callbacks == expectedCallbacks);
|
||||
}
|
||||
Require(SubmitStream(stream, 0, 0) == static_cast<int>(0x804A8054u));
|
||||
option.max_waveform_blocks = 0;
|
||||
Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &common, &query) == static_cast<int>(0x804A8053u));
|
||||
Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK);
|
||||
usedBuffers = firstBuffer;
|
||||
}
|
||||
|
||||
int main() {
|
||||
for (const auto& test : Cases) TestSampler(test);
|
||||
for (std::uint32_t mode = 0; mode < 6; ++mode) TestStreaming(mode);
|
||||
TestStreaming(0, 3);
|
||||
return 0;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ static void TestStreamEndsAtDeclaredSamples() {
|
||||
Event(voice, SCE_NGS2_VOICE_EVENT_PLAY);
|
||||
auto out = RenderI16(system);
|
||||
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));
|
||||
Require(Flags(voice) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING | SCE_NGS2_VOICE_STATE_FLAG_EMPTY));
|
||||
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);
|
||||
@@ -63,15 +63,15 @@ static void TestStreamEndsAtDeclaredSamples() {
|
||||
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());
|
||||
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK && state.waveform_data == first.data() + 6);
|
||||
out = RenderI16(system);
|
||||
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 3 ? 0 : 8192));
|
||||
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 7 ? 8192 : 0));
|
||||
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 < 3 ? 8192 : 4096));
|
||||
for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 5 ? 4096 : 0));
|
||||
Require(Flags(voice) == 0);
|
||||
Require(sceNgs2VoiceGetState(voice, &state.voice_state, sizeof(state)) == SCE_NGS2_OK);
|
||||
Require(state.num_decoded_samples == 0 && state.decoded_data_size == 0 && state.waveform_data == nullptr);
|
||||
@@ -111,8 +111,10 @@ static void TestStreamRejections() {
|
||||
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); }));
|
||||
AddBlock(complete, pcm.data(), pcm.size() * 2, 6, 0);
|
||||
const Ngs2WaveformBlock closedBlock{0, pcm.size() * 2, 0, 0, 6, 0, 0};
|
||||
const Ngs2SamplerVoiceWaveformBlocksParam closed{{sizeof(closed), 0, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS}, pcm.data(), 3, 1, &closedBlock};
|
||||
Require(sceNgs2VoiceControl(complete, &closed.header) == static_cast<int>(0x804A8054u));
|
||||
|
||||
const auto repeating = StreamVoice(rack, 2, master);
|
||||
const Ngs2WaveformBlock repeated{0, pcm.size() * 2, 1, 0, 20, 0, 0};
|
||||
@@ -195,7 +197,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] == 0 && out[3] == 8192);
|
||||
Require(out[0] == 8192 && out[3] == 8192);
|
||||
Require(Flags(voice) == playing);
|
||||
|
||||
AddBlock(voice, pcm.data(), pcm.size() * 2, 4, 0);
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
|
||||
- [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 sampler streaming](../../../core/libs/prx/libSceNgs2.native/src/Sampler.cpp) - nonzero sampler setup channel masks and transitions on grains below 64 throw. Single-voice PCM/ATRAC9 comparisons cover outstanding block capacity, append coalescing with matching user data, starvation, decoder frame caches, replacement bounds, callback setup/reset and circular descriptor replay. Inter-voice block-callback ordering relative to UserFx has not been compared. 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)
|
||||
|
||||
Reference in new issue
Block a user