From 132715e4e2e517f6259c7d6b22c4182253199773 Mon Sep 17 00:00:00 2001 From: gookie <36005381+gookie-dev@users.noreply.github.com> Date: Sun, 11 Oct 2026 02:29:13 +0200 Subject: [PATCH] perf(libSceNgs2.native): parse waveform files without reading their samples sceNgs2ParseWaveformFile read everything from the offset to the end of the file into a zero-filled buffer and parsed that, although parsing uses only the RIFF chunk headers and the fmt, fact and smpl payloads. A waveform at an offset in a sound bank cost a read of the rest of the bank on each call. ReadChunks now takes the bytes through an accessor. Waveform data passes its buffer; a waveform file seeks to and reads the headers and those payloads alone, with the same checks against the length from the offset to the end of the file. The format dispatch both use moves to ParseWaveform. ngs2_parse adds a bank of two waveforms in one file, the second with a chunk after its samples, and parses each at its offset. PPSA21402 (Balatro) parses its five 33 MB music tracks at start: one call took 8.3 to 26.7 ms and allocated 33 MB, and now takes 0.01 to 0.04 ms with the same result. --- .../libs/prx/libSceNgs2.native/src/Atrac9.cpp | 56 ++++++++++++------- .../prx/libSceNgs2.native/tests/Ngs2Parse.cpp | 27 +++++++++ 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp index 240c0a7a9..b508ac56a 100644 --- a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp @@ -217,14 +217,16 @@ struct RiffChunks { std::size_t dataSize = 0; }; -static int ReadChunks(const std::uint8_t* bytes, std::size_t size, RiffChunks& chunks) { +template +static int ReadChunks(std::size_t size, TRead&& read, RiffChunks& chunks) { if (size < 12) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; - if (std::memcmp(bytes, "RIFF", 4) != 0 || std::memcmp(bytes + 8, "WAVE", 4) != 0) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; - const std::size_t riffEnd = 8 + static_cast(ReadLe32(bytes + 4)); + const auto* header = read(0, 12); + if (std::memcmp(header, "RIFF", 4) != 0 || std::memcmp(header + 8, "WAVE", 4) != 0) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; + const std::size_t riffEnd = 8 + static_cast(ReadLe32(header + 4)); if (riffEnd < 12) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; const std::size_t end = std::min(size, riffEnd); for (std::size_t offset = 12; offset + 8 <= end;) { - const auto* chunk = bytes + offset; + const auto* chunk = read(offset, 8); const std::size_t payload = offset + 8; const std::size_t chunkSize = ReadLe32(chunk + 4); if (chunkSize > riffEnd - payload) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; @@ -236,13 +238,13 @@ static int ReadChunks(const std::uint8_t* bytes, std::size_t size, RiffChunks& c } if (chunkSize > end - payload) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; if (std::memcmp(chunk, "fmt ", 4) == 0) { - chunks.format = bytes + payload; + chunks.format = read(payload, chunkSize); chunks.formatSize = chunkSize; } else if (std::memcmp(chunk, "fact", 4) == 0) { - chunks.fact = bytes + payload; + chunks.fact = read(payload, chunkSize); chunks.factSize = chunkSize; } else if (std::memcmp(chunk, "smpl", 4) == 0) { - chunks.sampler = bytes + payload; + chunks.sampler = read(payload, chunkSize); chunks.samplerSize = chunkSize; } offset = payload + chunkSize + (chunkSize & 1); @@ -349,6 +351,17 @@ static int ParsePcm16(const RiffChunks& chunks, std::size_t size, Ngs2WaveformIn return SCE_NGS2_OK; } +static int ParseWaveform(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo& info) { + if (chunks.formatSize < 2) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; + const std::uint16_t tag = ReadLe16(chunks.format); + if (tag == WAVE_FORMAT_EXTENSIBLE && chunks.formatSize >= FMT_GUID_OFFSET + sizeof(ATRAC9_GUID) && + std::memcmp(chunks.format + FMT_GUID_OFFSET, ATRAC9_GUID, sizeof(ATRAC9_GUID)) == 0) { + return ParseAtrac9(chunks, info); + } + if (tag == WAVE_FORMAT_PCM) return ParsePcm16(chunks, size, info); + throw std::runtime_error("NGS2: parsing waveform format tag " + Ngs2Hex(tag) + " is not implemented"); +} + #pragma GCC visibility push(default) extern "C" { @@ -357,17 +370,11 @@ int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2W if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; *info = {}; if (data == nullptr) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; + const auto* bytes = static_cast(data); RiffChunks chunks; - const int result = ReadChunks(static_cast(data), data_size, chunks); + const int result = ReadChunks(data_size, [bytes](std::size_t at, std::size_t) { return bytes + at; }, chunks); if (result != SCE_NGS2_OK) return result; - if (chunks.formatSize < 2) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT; - const std::uint16_t tag = ReadLe16(chunks.format); - if (tag == WAVE_FORMAT_EXTENSIBLE && chunks.formatSize >= FMT_GUID_OFFSET + sizeof(ATRAC9_GUID) && - std::memcmp(chunks.format + FMT_GUID_OFFSET, ATRAC9_GUID, sizeof(ATRAC9_GUID)) == 0) { - return ParseAtrac9(chunks, *info); - } - if (tag == WAVE_FORMAT_PCM) return ParsePcm16(chunks, data_size, *info); - throw std::runtime_error("NGS2: parsing waveform format tag " + Ngs2Hex(tag) + " is not implemented"); + return ParseWaveform(chunks, data_size, *info); } int APS5_VABI sceNgs2ParseWaveformFile(const char* path, uint32_t offset, Ngs2WaveformInfo* info) { @@ -379,11 +386,18 @@ int APS5_VABI sceNgs2ParseWaveformFile(const char* path, uint32_t offset, Ngs2Wa if (!file) throw std::runtime_error(std::string(__func__) + ": cannot open " + host.string()); const auto fileSize = static_cast(file.tellg()); if (offset > fileSize) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA; - const std::uint64_t length = fileSize - offset; - std::vector bytes(static_cast(length)); - file.seekg(offset); - if (!file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) throw std::runtime_error(std::string(__func__) + ": read failed for " + host.string()); - const int result = sceNgs2ParseWaveformData(bytes.data(), bytes.size(), info); + const auto length = static_cast(fileSize - offset); + const std::string function = __func__; + std::vector> pieces; + const auto readBytes = [&](std::size_t at, std::size_t count) { + auto& piece = pieces.emplace_back(std::max(count, 1)); + file.seekg(static_cast(offset + at)); + if (!file.read(reinterpret_cast(piece.data()), static_cast(count))) throw std::runtime_error(function + ": read failed for " + host.string()); + return static_cast(piece.data()); + }; + RiffChunks chunks; + int result = ReadChunks(length, readBytes, chunks); + if (result == SCE_NGS2_OK) result = ParseWaveform(chunks, length, *info); if (result != SCE_NGS2_OK) return result; info->data_offset += offset; for (std::uint32_t i = 0; i < info->num_blocks; i++) info->block[i].data_offset += offset; diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp index 396881d46..a2545f52e 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp @@ -87,8 +87,35 @@ static void TestParseFile() { std::filesystem::remove(path); } +static void TestParseFileBank() { + const auto first = PcmFile(2, 48000, 16, 300); + auto second = PcmFile(1, 32000, 16, 50); + PutTag(second, "LIST"); + Put32(second, 4); + PutTag(second, "INFO"); + const std::uint32_t riffSize = static_cast(second.size() - 8); + std::memcpy(second.data() + 4, &riffSize, sizeof(riffSize)); + const char* const guestPath = "/aps5_ngs2_parse_bank.bin"; + const auto path = ResolvePath_nid_no_patch(guestPath); + { + std::ofstream out(path, std::ios::binary); + out.write(reinterpret_cast(first.data()), static_cast(first.size())); + out.write(reinterpret_cast(second.data()), static_cast(second.size())); + } + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformFile(guestPath, 0, &info) == SCE_NGS2_OK); + Require(info.format.num_channels == 2 && info.format.sample_rate == 48000 && info.num_samples == 300); + Require(info.data_offset == 44 && info.data_size == 1200 && info.block[0].data_offset == 44); + const auto secondOffset = static_cast(first.size()); + Require(sceNgs2ParseWaveformFile(guestPath, secondOffset, &info) == SCE_NGS2_OK); + Require(info.format.num_channels == 1 && info.format.sample_rate == 32000 && info.num_samples == 50); + Require(info.data_offset == secondOffset + 44 && info.data_size == 100 && info.block[0].data_offset == secondOffset + 44); + std::filesystem::remove(path); +} + int main() { TestParsePcm(); TestParseFile(); + TestParseFileBank(); return 0; }