mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-23 09:03:22 -05:00
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.
122 lines
5.4 KiB
C++
122 lines
5.4 KiB
C++
#include "Ngs2Test.hpp"
|
|
|
|
#include "prx/libc/include/General.hpp"
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <vector>
|
|
|
|
static void Put16(std::vector<std::uint8_t>& out, std::uint32_t value) {
|
|
out.push_back(static_cast<std::uint8_t>(value));
|
|
out.push_back(static_cast<std::uint8_t>(value >> 8));
|
|
}
|
|
|
|
static void Put32(std::vector<std::uint8_t>& out, std::uint32_t value) {
|
|
Put16(out, value & 0xffff);
|
|
Put16(out, value >> 16);
|
|
}
|
|
|
|
static void PutTag(std::vector<std::uint8_t>& out, const char* tag) {
|
|
out.insert(out.end(), tag, tag + 4);
|
|
}
|
|
|
|
static std::vector<std::uint8_t> PcmFile(std::uint32_t channels, std::uint32_t sampleRate, std::uint32_t bits, std::uint32_t frames) {
|
|
const std::uint32_t frameBytes = channels * bits / 8;
|
|
std::vector<std::uint8_t> file;
|
|
PutTag(file, "RIFF");
|
|
Put32(file, 36 + frames * frameBytes);
|
|
PutTag(file, "WAVE");
|
|
PutTag(file, "fmt ");
|
|
Put32(file, 16);
|
|
Put16(file, 1);
|
|
Put16(file, channels);
|
|
Put32(file, sampleRate);
|
|
Put32(file, sampleRate * frameBytes);
|
|
Put16(file, frameBytes);
|
|
Put16(file, bits);
|
|
PutTag(file, "data");
|
|
Put32(file, frames * frameBytes);
|
|
file.resize(file.size() + frames * frameBytes, 0x11);
|
|
return file;
|
|
}
|
|
|
|
static void TestParsePcm() {
|
|
const auto file = PcmFile(2, 44100, 16, 100);
|
|
Ngs2WaveformInfo info{};
|
|
Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK);
|
|
Require(info.format.waveform_type == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && info.format.num_channels == 2 && info.format.sample_rate == 44100);
|
|
Require(info.data_offset == 44 && info.data_size == 400 && info.num_samples == 100);
|
|
Require(info.num_blocks == 1 && info.block[0].data_offset == 44 && info.block[0].data_size == 400 && info.block[0].num_samples == 100);
|
|
|
|
auto truncated = file;
|
|
truncated.resize(44 + 202);
|
|
Require(sceNgs2ParseWaveformData(truncated.data(), truncated.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA);
|
|
|
|
auto partialFrame = PcmFile(2, 44100, 16, 100);
|
|
partialFrame.resize(44 + 402, 0x11);
|
|
const std::uint32_t partialSize = 402;
|
|
std::memcpy(partialFrame.data() + 40, &partialSize, sizeof(partialSize));
|
|
Require(sceNgs2ParseWaveformData(partialFrame.data(), partialFrame.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA);
|
|
|
|
const auto eightBit = PcmFile(1, 44100, 8, 10);
|
|
Require(sceNgs2ParseWaveformData(eightBit.data(), eightBit.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT);
|
|
const auto noChannels = PcmFile(0, 44100, 16, 10);
|
|
Require(sceNgs2ParseWaveformData(noChannels.data(), noChannels.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT);
|
|
}
|
|
|
|
static void TestParseFile() {
|
|
const auto wave = PcmFile(1, 22050, 16, 64);
|
|
const char* const guestPath = "/aps5_ngs2_parse_file.wav";
|
|
const auto path = ResolvePath_nid_no_patch(guestPath);
|
|
{
|
|
std::ofstream out(path, std::ios::binary);
|
|
const char pad[3]{};
|
|
out.write(pad, sizeof(pad));
|
|
out.write(reinterpret_cast<const char*>(wave.data()), static_cast<std::streamsize>(wave.size()));
|
|
}
|
|
Ngs2WaveformInfo info{};
|
|
Require(sceNgs2ParseWaveformFile(guestPath, 3, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
|
Require(sceNgs2ParseWaveformFile(nullptr, 0, &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA);
|
|
Require(sceNgs2ParseWaveformFile(guestPath, 3, &info) == SCE_NGS2_OK);
|
|
Require(info.format.num_channels == 1 && info.format.sample_rate == 22050 && info.num_samples == 64);
|
|
Require(info.data_offset == 3 + 44 && info.block[0].data_offset == 3 + 44 && info.data_size == 128);
|
|
Require(sceNgs2ParseWaveformFile(guestPath, static_cast<std::uint32_t>(wave.size()) + 4, &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA);
|
|
Require(sceNgs2ParseWaveformFile(guestPath, 0, &info) == SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT);
|
|
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<std::uint32_t>(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<const char*>(first.data()), static_cast<std::streamsize>(first.size()));
|
|
out.write(reinterpret_cast<const char*>(second.data()), static_cast<std::streamsize>(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<std::uint32_t>(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;
|
|
}
|