Files
AnyPS5/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp
T
gookie 132715e4e2 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.
2026-10-11 02:29:13 +02:00

430 lines
23 KiB
C++

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cmath>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <limits>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include "libatrac9.h"
#include "prx/libc/include/General.hpp"
#include "Ngs2Internal.hpp"
static constexpr std::uint16_t WAVE_FORMAT_PCM = 0x1;
static constexpr std::uint16_t WAVE_FORMAT_EXTENSIBLE = 0xfffe;
static constexpr std::uint8_t ATRAC9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36, 0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65, 0x4f, 0x8c, 0x83, 0x6c};
static constexpr std::size_t FMT_ATRAC9_SIZE = 52;
static constexpr std::size_t FMT_GUID_OFFSET = 24;
static constexpr std::size_t FMT_CONFIG_OFFSET = 44;
static constexpr std::size_t FACT_ATRAC9_SIZE = 12;
static constexpr std::uint32_t MAX_SAMPLE_RATE = 192000;
void Ngs2Atrac9DecoderDeleter::operator()(void* handle) const {
Atrac9ReleaseHandle(handle);
}
static void ConfigBytes(std::uint32_t configData, std::uint8_t* config) {
for (int i = 0; i < 4; i++) config[i] = static_cast<std::uint8_t>(configData >> (24 - 8 * i));
}
static bool CodecInfo(const std::uint8_t* config, Atrac9CodecInfo& info) {
std::unique_ptr<void, Ngs2Atrac9DecoderDeleter> decoder(Atrac9GetHandle());
std::uint8_t copy[4];
std::memcpy(copy, config, sizeof(copy));
info = {};
return decoder != nullptr && Atrac9InitDecoder(decoder.get(), copy) == 0 && Atrac9GetCodecInfo(decoder.get(), &info) == 0 && info.channels > 0 &&
info.framesInSuperframe > 0 && info.frameSamples > 0 && info.superframeSize % info.framesInSuperframe == 0;
}
static bool MatchingCodecInfo(const Ngs2WaveformFormat& format, Atrac9CodecInfo& info) {
std::uint8_t config[4];
ConfigBytes(format.config_data, config);
return CodecInfo(config, info) && static_cast<std::uint32_t>(info.channels) == format.num_channels && static_cast<std::uint32_t>(info.samplingRate) == format.sample_rate;
}
void Ngs2SetupAtrac9(Ngs2Voice& voice, const Ngs2WaveformFormat& format) {
Atrac9CodecInfo info{};
if (!MatchingCodecInfo(format, info)) throw std::invalid_argument("NGS2: the ATRAC9 config does not match the waveform format");
ConfigBytes(format.config_data, voice.atrac9.config);
voice.atrac9.frameSamples = static_cast<std::uint32_t>(info.frameSamples);
voice.atrac9.framesInSuperframe = static_cast<std::uint32_t>(info.framesInSuperframe);
voice.atrac9.superframeBytes = static_cast<std::uint32_t>(info.superframeSize);
Ngs2RestartAtrac9(voice);
}
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block) {
const std::uint64_t samples = static_cast<std::uint64_t>(block.num_skip_samples) + block.num_samples;
const std::uint64_t superframeSamples = static_cast<std::uint64_t>(voice.atrac9.frameSamples) * voice.atrac9.framesInSuperframe;
return static_cast<std::size_t>((samples + superframeSamples - 1) / superframeSamples * voice.atrac9.superframeBytes);
}
void Ngs2RestartAtrac9(Ngs2Voice& voice) {
auto& atrac9 = voice.atrac9;
atrac9.decoder.reset(Atrac9GetHandle());
if (atrac9.decoder == nullptr || Atrac9InitDecoder(atrac9.decoder.get(), atrac9.config) != 0) throw std::runtime_error("NGS2: ATRAC9 decoder init failed");
atrac9.window.clear();
atrac9.windowCursor = 0;
atrac9.input.clear();
atrac9.remainingSamples = 0;
atrac9.skipSamples = 0;
atrac9.finishOnDrain = false;
}
static std::size_t BufferedFrames(const Ngs2Voice& voice) {
return voice.atrac9.window.size() / voice.channels - voice.atrac9.windowCursor;
}
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;
}
return true;
}
static bool DecodeSuperframe(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;
}
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);
}
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,
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;
}
return true;
}
static bool EnsureFrames(Ngs2Voice& voice, std::size_t count) {
auto& atrac9 = voice.atrac9;
while (voice.state == Ngs2PlayState::Playing && 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;
}
return voice.state == Ngs2PlayState::Playing;
}
static bool AdvanceAtrac9(Ngs2Voice& voice) {
while ((voice.phase >> 32) != 0 && voice.state == Ngs2PlayState::Playing) {
if (!EnsureFrames(voice, 1)) return false;
const auto count = std::min<std::uint64_t>(voice.phase >> 32, BufferedFrames(voice));
voice.atrac9.windowCursor += static_cast<std::uint32_t>(count);
voice.decodedSamples += count;
voice.phase -= count << 32;
if (BufferedFrames(voice) == 0 && voice.atrac9.finishOnDrain) {
voice.atrac9.finishOnDrain = false;
if (!Ngs2FinishBlock(voice)) return false;
}
}
return voice.state == Ngs2PlayState::Playing;
}
void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) {
constexpr double phaseOne = 4294967296.0;
const auto initialRevision = voice.waveformRevision;
const auto step = static_cast<std::uint64_t>(std::llround(voice.sampleRate * static_cast<double>(voice.pitch) / systemRate * phaseOne));
std::uint32_t written = 0;
for (; written < grain; ++written) {
if (!AdvanceAtrac9(voice) || !EnsureFrames(voice, 1)) break;
const auto revision = voice.waveformRevision;
const auto fraction = static_cast<float>(voice.phase / phaseOne);
if (fraction != 0.0f) EnsureFrames(voice, 2);
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != revision) break;
const auto* current = voice.atrac9.window.data() + static_cast<std::size_t>(voice.atrac9.windowCursor) * voice.channels;
const auto* next = BufferedFrames(voice) > 1 ? current + voice.channels : current;
for (std::uint32_t channel = 0; channel < voice.channels; ++channel)
voice.samples[static_cast<std::size_t>(channel) * grain + written] = std::lerp(current[channel], next[channel], fraction);
voice.phase += step;
}
voice.hasSamples = written != 0;
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != initialRevision) return;
AdvanceAtrac9(voice);
if (voice.state != Ngs2PlayState::Playing || voice.waveformRevision != initialRevision) return;
if (!voice.acceptsBlocks && voice.blocks.empty() && BufferedFrames(voice) == 0 && voice.atrac9.remainingSamples != 0)
throw std::invalid_argument("NGS2: the ATRAC9 stream ends before its declared samples");
}
static std::uint16_t ReadLe16(const std::uint8_t* data) {
return static_cast<std::uint16_t>(data[0] | data[1] << 8);
}
static std::uint32_t ReadLe32(const std::uint8_t* data) {
return static_cast<std::uint32_t>(data[0]) | static_cast<std::uint32_t>(data[1]) << 8 | static_cast<std::uint32_t>(data[2]) << 16 | static_cast<std::uint32_t>(data[3]) << 24;
}
struct RiffChunks {
const std::uint8_t* format = nullptr;
std::size_t formatSize = 0;
const std::uint8_t* fact = nullptr;
std::size_t factSize = 0;
const std::uint8_t* sampler = nullptr;
std::size_t samplerSize = 0;
std::size_t dataOffset = 0;
std::size_t dataSize = 0;
};
template <class TRead>
static int ReadChunks(std::size_t size, TRead&& read, RiffChunks& chunks) {
if (size < 12) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
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<std::size_t>(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 = 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;
const bool data = std::memcmp(chunk, "data", 4) == 0;
if (data) {
chunks.dataOffset = payload;
chunks.dataSize = chunkSize;
if (chunkSize > end - payload) break;
}
if (chunkSize > end - payload) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
if (std::memcmp(chunk, "fmt ", 4) == 0) {
chunks.format = read(payload, chunkSize);
chunks.formatSize = chunkSize;
} else if (std::memcmp(chunk, "fact", 4) == 0) {
chunks.fact = read(payload, chunkSize);
chunks.factSize = chunkSize;
} else if (std::memcmp(chunk, "smpl", 4) == 0) {
chunks.sampler = read(payload, chunkSize);
chunks.samplerSize = chunkSize;
}
offset = payload + chunkSize + (chunkSize & 1);
}
if (chunks.format == nullptr || chunks.dataOffset == 0 || chunks.dataOffset > std::numeric_limits<std::uint32_t>::max()) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
return SCE_NGS2_OK;
}
static bool AddAtrac9Range(Ngs2WaveformInfo& info, std::uint64_t begin, std::uint32_t samples, std::uint32_t repeats) {
if (samples == 0) return true;
const auto preroll = std::min<std::uint64_t>(begin, info.num_audio_unit_samples);
const auto firstFrame = (begin - preroll) / info.num_audio_frame_samples;
const auto skip = begin - firstFrame * info.num_audio_frame_samples;
const auto frames = (skip + samples + info.num_audio_frame_samples - 1) / info.num_audio_frame_samples;
const auto offset = firstFrame * info.audio_frame_size;
const auto bytes = frames * info.audio_frame_size;
if (offset > info.data_size || bytes > info.data_size - offset) return false;
auto& block = info.block[info.num_blocks++];
block = {info.data_offset + offset, bytes, repeats, static_cast<std::uint32_t>(skip), samples, 0, 0};
return true;
}
static int ParseAtrac9Loop(const RiffChunks& chunks, Ngs2WaveformInfo& info) {
if (chunks.sampler == nullptr) return SCE_NGS2_OK;
if (chunks.samplerSize < 36) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto count = ReadLe32(chunks.sampler + 28);
if (count > (chunks.samplerSize - 36) / 24) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
if (count == 0) return SCE_NGS2_OK;
if (count != 1) throw std::runtime_error("NGS2: multiple waveform loops are not implemented");
const auto* loop = chunks.sampler + 36;
if (ReadLe32(loop + 4) != 0 || ReadLe32(loop + 16) != 0)
throw std::runtime_error("NGS2: non-forward or fractional waveform loops are not implemented");
const std::uint64_t begin = ReadLe32(loop + 8);
const std::uint64_t end = static_cast<std::uint64_t>(ReadLe32(loop + 12)) + 1;
const auto skip = info.block[0].num_skip_samples;
const std::uint64_t waveformEnd = static_cast<std::uint64_t>(skip) + info.num_samples;
if (begin < skip || begin >= end || end > waveformEnd) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto plays = ReadLe32(loop + 20);
info.loop_begin_position = static_cast<std::uint32_t>(begin - skip);
info.loop_end_position = static_cast<std::uint32_t>(end - skip);
info.num_blocks = 0;
info.block[0] = {};
if (!AddAtrac9Range(info, skip, info.loop_begin_position, 0) ||
!AddAtrac9Range(info, begin, static_cast<std::uint32_t>(end - begin), plays == 0 ? UINT32_MAX : plays - 1) ||
!AddAtrac9Range(info, end, static_cast<std::uint32_t>(waveformEnd - end), 0))
return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
return SCE_NGS2_OK;
}
static int ParseAtrac9(const RiffChunks& chunks, Ngs2WaveformInfo& info) {
const auto* format = chunks.format;
if (chunks.formatSize < FMT_ATRAC9_SIZE || chunks.fact == nullptr || chunks.factSize < FACT_ATRAC9_SIZE) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
std::uint8_t config[4];
std::memcpy(config, format + FMT_CONFIG_OFFSET, sizeof(config));
Atrac9CodecInfo codec{};
if (!CodecInfo(config, codec) || ReadLe16(format + 2) != codec.channels || ReadLe32(format + 4) != static_cast<std::uint32_t>(codec.samplingRate) ||
ReadLe16(format + 12) != codec.superframeSize || ReadLe16(format + 18) != static_cast<std::uint32_t>(codec.frameSamples * codec.framesInSuperframe)) {
return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
info.format = {SCE_NGS2_WAVEFORM_TYPE_ATRAC9, static_cast<std::uint32_t>(codec.channels), static_cast<std::uint32_t>(codec.samplingRate),
static_cast<std::uint32_t>(config[0]) << 24 | static_cast<std::uint32_t>(config[1]) << 16 | static_cast<std::uint32_t>(config[2]) << 8 | config[3], 0, 0};
info.data_offset = static_cast<std::uint32_t>(chunks.dataOffset);
info.data_size = static_cast<std::uint32_t>(chunks.dataSize);
info.num_samples = ReadLe32(chunks.fact);
info.audio_unit_size = static_cast<std::uint32_t>(codec.superframeSize / codec.framesInSuperframe);
info.num_audio_unit_samples = static_cast<std::uint32_t>(codec.frameSamples);
info.num_audio_unit_per_frame = static_cast<std::uint32_t>(codec.framesInSuperframe);
info.audio_frame_size = static_cast<std::uint32_t>(codec.superframeSize);
info.num_audio_frame_samples = static_cast<std::uint32_t>(codec.frameSamples * codec.framesInSuperframe);
info.num_delay_samples = ReadLe32(chunks.fact + 4);
info.num_blocks = 1;
info.block[0].data_offset = chunks.dataOffset;
info.block[0].data_size = chunks.dataSize;
info.block[0].num_skip_samples = ReadLe32(chunks.fact + 8);
info.block[0].num_samples = info.num_samples;
return ParseAtrac9Loop(chunks, info);
}
static int ParsePcm16(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo& info) {
const auto* format = chunks.format;
if (chunks.formatSize < 16) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
const std::uint32_t channels = ReadLe16(format + 2);
const std::uint32_t sampleRate = ReadLe32(format + 4);
const std::uint32_t bytesPerFrame = channels * sizeof(std::int16_t);
if (ReadLe16(format + 14) != 16 || channels == 0 || channels > NGS2_MAX_CHANNELS || sampleRate == 0 || sampleRate > MAX_SAMPLE_RATE || ReadLe16(format + 12) != bytesPerFrame) {
return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
if (chunks.sampler != nullptr && chunks.samplerSize >= 32 && ReadLe32(chunks.sampler + 28) != 0) throw std::runtime_error("NGS2: parsing looped waveforms is not implemented");
if (chunks.dataSize > size - chunks.dataOffset || chunks.dataSize % bytesPerFrame != 0) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const std::size_t dataSize = chunks.dataSize;
info.format = {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, channels, sampleRate, 0, 0, 0};
info.data_offset = static_cast<std::uint32_t>(chunks.dataOffset);
info.data_size = static_cast<std::uint32_t>(dataSize);
info.num_samples = static_cast<std::uint32_t>(dataSize / bytesPerFrame);
info.audio_unit_size = bytesPerFrame;
info.num_audio_unit_samples = 1;
info.num_audio_unit_per_frame = 1;
info.audio_frame_size = bytesPerFrame;
info.num_audio_frame_samples = 1;
info.num_blocks = 1;
info.block[0].data_offset = chunks.dataOffset;
info.block[0].data_size = dataSize;
info.block[0].num_samples = info.num_samples;
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" {
int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) {
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<const std::uint8_t*>(data);
RiffChunks 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;
return ParseWaveform(chunks, data_size, *info);
}
int APS5_VABI sceNgs2ParseWaveformFile(const char* path, uint32_t offset, Ngs2WaveformInfo* info) {
if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
*info = {};
if (path == nullptr) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto host = ResolvePath_nid_no_patch(path);
std::ifstream file(host, std::ios::binary | std::ios::ate);
if (!file) throw std::runtime_error(std::string(__func__) + ": cannot open " + host.string());
const auto fileSize = static_cast<std::uint64_t>(file.tellg());
if (offset > fileSize) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto length = static_cast<std::size_t>(fileSize - offset);
const std::string function = __func__;
std::vector<std::vector<std::uint8_t>> pieces;
const auto readBytes = [&](std::size_t at, std::size_t count) {
auto& piece = pieces.emplace_back(std::max<std::size_t>(count, 1));
file.seekg(static_cast<std::streamoff>(offset + at));
if (!file.read(reinterpret_cast<char*>(piece.data()), static_cast<std::streamsize>(count))) throw std::runtime_error(function + ": read failed for " + host.string());
return static_cast<const std::uint8_t*>(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;
return SCE_NGS2_OK;
}
int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos, uint32_t num_samples, Ngs2WaveformBlock* block) {
if (block == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
*block = {};
if (format == nullptr || format->num_channels == 0 || format->num_channels > NGS2_MAX_CHANNELS || format->sample_rate == 0 || format->sample_rate > MAX_SAMPLE_RATE) {
return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
}
if (format->waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) throw std::runtime_error("NGS2: waveform blocks of type " + Ngs2Hex(format->waveform_type) + " are not implemented");
Atrac9CodecInfo codec{};
if (!MatchingCodecInfo(*format, codec)) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
const std::uint64_t superframeSamples = static_cast<std::uint64_t>(codec.frameSamples) * codec.framesInSuperframe;
const std::uint64_t skip = sample_pos % superframeSamples;
block->data_offset = sample_pos / superframeSamples * codec.superframeSize;
if (num_samples != 0) {
block->data_size = (skip + num_samples + superframeSamples - 1) / superframeSamples * codec.superframeSize;
block->num_skip_samples = static_cast<std::uint32_t>(skip);
}
block->num_samples = num_samples;
return SCE_NGS2_OK;
}
}
#pragma GCC visibility pop