Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/libs/prx/libSceNgs2.native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/Ngs2Library.cmake)
add_sce_ngs2_library(libSceNgs2.native)

if(BUILD_TESTING)
foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents)
foreach(ngs2Test IN ITEMS Render Atrac9 Hevag Filter Custom Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents)
string(TOLOWER ${ngs2Test} testName)
add_test_executable(ngs2_${testName}_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2${ngs2Test}.cpp)
target_include_directories(ngs2_${testName}_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src)
Expand Down
11 changes: 9 additions & 2 deletions core/libs/prx/libSceNgs2.native/Ngs2Library.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
enable_language(C)

function(add_sce_ngs2_library target)
if(NOT TARGET ngs2_hevag)
add_library(ngs2_hevag STATIC ${CMAKE_SOURCE_DIR}/3rdparty/vgmstream/src/coding/psv_decoder.c)
set_target_properties(ngs2_hevag PROPERTIES POSITION_INDEPENDENT_CODE ON C_VISIBILITY_PRESET hidden)
endif()
set(ngs2Dir ${CMAKE_CURRENT_FUNCTION_LIST_DIR})
add_library(${target} SHARED EXCLUDE_FROM_ALL
${ngs2Dir}/src/Atrac9.cpp
${ngs2Dir}/src/Hevag.cpp
${ngs2Dir}/src/Custom.cpp
${ngs2Dir}/src/Geom.cpp
${ngs2Dir}/src/Pan.cpp
Expand All @@ -11,8 +18,8 @@ function(add_sce_ngs2_library target)
${ngs2Dir}/src/System.cpp
${ngs2Dir}/src/Voice.cpp
)
target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src)
target_link_libraries(${target} PRIVATE atrac9 libc libkernel)
target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src ${CMAKE_SOURCE_DIR}/3rdparty/vgmstream/src)
target_link_libraries(${target} PRIVATE atrac9 ngs2_hevag libc libkernel)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(${target} PRIVATE LINKER:--exclude-libs,ALL)
endif()
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I16L = 0x12;
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I16B = 0x13;
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F32L = 0x18;
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F32B = 0x19;
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_VAG = 0x1c;
static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_ATRAC9 = 0x40;

static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_PLAY = 1;
Expand Down
13 changes: 9 additions & 4 deletions core/libs/prx/libSceNgs2.native/src/Atrac9.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ static constexpr std::size_t FACT_ATRAC9_SIZE = 12;
static constexpr std::uint32_t MAX_SAMPLE_RATE = 192000;
static constexpr std::uint32_t WAVEFORM_TYPE_PCM_FIRST = 0x10;
static constexpr std::uint32_t WAVEFORM_TYPE_PCM_LAST = 0x1b;
static constexpr std::uint32_t WAVEFORM_TYPE_VAG = 0x1c;
static constexpr std::uint32_t VAG_FRAME_SAMPLES = 28;
static constexpr std::uint32_t ATRAC9_FRAME_SAMPLES[16] = {0, 64, 0, 0, 128, 0, 0, 256, 0, 0, 0, 0, 0, 0, 0, 0};
static constexpr std::uint32_t PCM_BYTES_PER_SAMPLE[] = {1, 1, 2, 2, 3, 3, 4, 4, 4, 4, 8, 8};
Expand Down Expand Up @@ -411,6 +410,7 @@ int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2W
*info = {};
if (data == nullptr) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto* bytes = static_cast<const std::uint8_t*>(data);
if (data_size >= 4 && std::memcmp(bytes, "VAGp", 4) == 0) return Ngs2ParseHevag(bytes, data_size, *info);
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;
Expand All @@ -436,8 +436,13 @@ int APS5_VABI sceNgs2ParseWaveformFile(const char* path, uint32_t offset, Ngs2Wa
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);
int result;
if (length >= 4 && std::memcmp(readBytes(0, 4), "VAGp", 4) == 0) {
result = length < 0x30 ? SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA : Ngs2ParseHevag(readBytes(0, 0x30), length, *info);
} else {
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;
Expand All @@ -453,7 +458,7 @@ int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_
*block = PcmBlock(type, format->num_channels, sample_pos, num_samples);
return SCE_NGS2_OK;
}
if (type == WAVEFORM_TYPE_VAG) {
if (type == SCE_NGS2_WAVEFORM_TYPE_VAG) {
*block = VagBlock(format->num_channels, sample_pos, num_samples);
return SCE_NGS2_OK;
}
Expand Down
83 changes: 83 additions & 0 deletions core/libs/prx/libSceNgs2.native/src/Hevag.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <limits>

#include "Ngs2Internal.hpp"

extern "C" {
#include "coding/coding.h"
}

namespace {

struct FrameStream {
STREAMFILE stream{};
const std::uint8_t* frame;
};

std::size_t ReadFrame(STREAMFILE* stream, std::uint8_t* destination, offv_t offset, std::size_t size) {
if (offset < 0 || offset >= 16) return 0;
size = std::min(size, 16 - static_cast<std::size_t>(offset));
std::memcpy(destination, reinterpret_cast<FrameStream*>(stream)->frame + offset, size);
return size;
}

std::uint32_t ReadBe32(const std::uint8_t* data) {
return (static_cast<std::uint32_t>(data[0]) << 24) | (static_cast<std::uint32_t>(data[1]) << 16)
| (static_cast<std::uint32_t>(data[2]) << 8) | data[3];
}

}

int Ngs2ParseHevag(const std::uint8_t* header, std::size_t size, Ngs2WaveformInfo& info) {
if (size < 0x30) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const auto version = ReadBe32(header + 4);
if (version != 0x20001 && version != 0x30000) return SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT;
if (ReadBe32(header + 0x18) != 0 || (ReadBe32(header + 0x1c) & 0xffff00ffu) != 0)
throw std::runtime_error("NGS2: alternate VAG header layout is not implemented");
const std::uint32_t channels = header[0x1e] == 0 ? 1 : header[0x1e];
const auto rate = ReadBe32(header + 0x10);
if (channels > NGS2_MAX_CHANNELS || rate == 0 || rate > 192000) return SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT;
const auto bytes = ReadBe32(header + 0xc);
const auto frameBytes = 16 * channels;
if (bytes > size - 0x30 || bytes % frameBytes != 0) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
const std::uint64_t samples = static_cast<std::uint64_t>(bytes / frameBytes) * 28;
if (samples > std::numeric_limits<std::uint32_t>::max()) return SCE_NGS2_ERROR_INVALID_WAVEFORM_DATA;
info.format = {SCE_NGS2_WAVEFORM_TYPE_VAG, channels, rate, 0, 0, 0};
info.data_offset = 0x30;
info.data_size = bytes;
info.num_samples = static_cast<std::uint32_t>(samples);
info.audio_unit_size = frameBytes;
info.num_audio_unit_samples = 28;
info.num_audio_unit_per_frame = 1;
info.audio_frame_size = frameBytes;
info.num_audio_frame_samples = 28;
info.num_blocks = 1;
info.block[0] = {0x30, bytes, 0, 0, info.num_samples, 0, 0};
return SCE_NGS2_OK;
}

void Ngs2DecodeHevag(Ngs2Block& block, std::uint32_t channels) {
const std::uint64_t samples = static_cast<std::uint64_t>(block.info.num_skip_samples) + block.info.num_samples;
const auto frames = (samples + 27) / 28;
if (frames > block.info.data_size / (16 * channels)) throw std::invalid_argument("NGS2: truncated HE-VAG block");
block.decoded.resize(static_cast<std::size_t>(frames * 28 * channels));
std::array<VGMSTREAMCHANNEL, NGS2_MAX_CHANNELS> histories{};
FrameStream input{};
input.stream.read = ReadFrame;
for (std::uint64_t frame = 0; frame < frames; ++frame) {
for (std::uint32_t channel = 0; channel < channels; ++channel) {
input.frame = block.data + (frame * channels + channel) * 16;
const auto predictor = (input.frame[1] & 0xf0) | (input.frame[0] >> 4);
const auto flags = input.frame[1] & 0xf;
if (predictor >= 128 || (input.frame[0] & 0xf) > 12 || flags > 7)
throw std::invalid_argument("NGS2: invalid HE-VAG frame");
if (flags != 0 && flags != 1 && flags != 7)
throw std::runtime_error("NGS2: HE-VAG embedded loops are not implemented");
auto& history = histories[channel];
history.streamfile = &input.stream;
decode_hevag(&history, block.decoded.data() + frame * 28 * channels + channel, static_cast<int>(channels), 0, 28);
}
}
}
3 changes: 3 additions & 0 deletions core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct Ngs2Block {
std::uint64_t availableFrames = 0;
std::vector<Ngs2Piece> pieces;
bool started = false;
std::vector<std::int16_t> decoded;
};

struct Ngs2Atrac9DecoderDeleter {
Expand Down Expand Up @@ -237,6 +238,8 @@ void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param);
std::size_t Ngs2Atrac9BlockBytes(const Ngs2Voice& voice, const Ngs2WaveformBlock& block);
void Ngs2RestartAtrac9(Ngs2Voice& voice);
void Ngs2ConsumeAtrac9(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate);
void Ngs2DecodeHevag(Ngs2Block& block, std::uint32_t channels);
int Ngs2ParseHevag(const std::uint8_t* header, std::size_t size, Ngs2WaveformInfo& info);
bool Ngs2FinishBlock(Ngs2Voice& voice);
int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option, std::uint32_t maxInputs);
void Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option);
Expand Down
12 changes: 10 additions & 2 deletions core/libs/prx/libSceNgs2.native/src/Render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ static constexpr std::uint64_t PHASE_FRACTION = 0xffffffff;
static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& block, std::uint32_t frame) {
const std::size_t frameBytes = voice.channels * sizeof(std::int16_t);
if (block.data == nullptr) return nullptr;
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG)
return reinterpret_cast<const std::uint8_t*>(block.decoded.data()) + (static_cast<std::size_t>(block.info.num_skip_samples) + frame) * frameBytes;
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;
Expand Down Expand Up @@ -74,7 +76,8 @@ 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.waveformEnd = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG ? (block.data == nullptr ? nullptr : block.data + block.info.data_size)
: 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();
}
const auto revision = voice.waveformRevision;
Expand All @@ -91,9 +94,14 @@ static void Advance(Ngs2Voice& voice, std::uint64_t frames) {
const std::uint64_t limit = block.streaming ? std::min<std::uint64_t>(block.info.num_samples, block.availableFrames) : block.info.num_samples;
if (block.cursor >= limit && block.cursor != block.info.num_samples && !(block.streaming && !voice.acceptsBlocks)) break;
const auto step = std::min<std::uint64_t>(frames, limit - block.cursor);
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG && block.data != nullptr) {
const auto before = block.cursor == 0 ? 0 : (static_cast<std::uint64_t>(block.info.num_skip_samples) + block.cursor + 27) / 28;
const auto after = (static_cast<std::uint64_t>(block.info.num_skip_samples) + block.cursor + step + 27) / 28;
voice.decodedBytes += (after - before) * 16 * voice.channels;
}
block.cursor += static_cast<std::uint32_t>(step);
voice.decodedSamples += step;
if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) voice.decodedBytes += step * voice.channels * sizeof(std::int16_t);
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) voice.decodedBytes += step * voice.channels * sizeof(std::int16_t);
frames -= step;
if (block.streaming) {
while (block.pieces.size() > 1 && block.cursor >= block.pieces.front().firstFrame + block.pieces.front().frames) block.pieces.erase(block.pieces.begin());
Expand Down
18 changes: 17 additions & 1 deletion core/libs/prx/libSceNgs2.native/src/Voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block
const std::uint8_t* Ngs2Voice::WaveformData() const {
if (blocks.empty()) return waveformEnd;
const auto& block = blocks.front();
if (waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG)
return block.data + ((static_cast<std::size_t>(block.info.num_skip_samples) + block.cursor) / 28) * channels * 16;
if (waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return block.data + block.dataCursor;
if (block.streaming) {
for (const auto& piece : block.pieces) {
Expand Down Expand Up @@ -187,10 +189,13 @@ static void SetupSampler(Ngs2Voice& voice, const Ngs2WaveformFormat& format) {
return;
}
voice.ResetSetup();
if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) {
if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L && format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9 &&
format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_VAG) {
throw std::runtime_error("NGS2: waveform type " + Ngs2Hex(format.waveform_type) + " is not implemented");
}
if (format.frame_offset != 0 || format.frame_margin != 0) throw std::runtime_error("NGS2: waveform frame offset and margin are not implemented");
if (format.waveform_type == SCE_NGS2_WAVEFORM_TYPE_VAG && format.config_data != 0)
throw std::runtime_error("NGS2: HE-VAG configuration data is not implemented");
if (format.num_channels == 0 || format.num_channels > NGS2_MAX_CHANNELS || format.sample_rate == 0) APS5_INVALID_ARG_EX;
voice.channels = format.num_channels;
voice.sampleRate = format.sample_rate;
Expand All @@ -199,6 +204,7 @@ 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_VAG) throw std::runtime_error("NGS2: streaming HE-VAG blocks are not implemented");
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; });
Expand Down Expand Up @@ -251,11 +257,13 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl
const auto& block = param.blocks[i];
if (block.num_samples == 0 && block.data_size == 0) continue;
if (allowsSilence && block.data_size == 0) {
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG) throw std::runtime_error("NGS2: dataless HE-VAG blocks are not implemented");
if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) throw std::runtime_error("NGS2: a dataless waveform block under the silence flag is not implemented for ATRAC9 voices");
voice.blocks.push_back({nullptr, block});
continue;
}
const std::uint64_t bytes = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? Ngs2Atrac9BlockBytes(voice, block)
: voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG ? ((static_cast<std::uint64_t>(block.num_skip_samples) + block.num_samples + 27) / 28) * 16 * voice.channels
: (static_cast<std::uint64_t>(block.num_skip_samples) + block.num_samples) * frameBytes;
const bool streaming = block.num_samples != 0 && bytes > block.data_size && voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L &&
block.num_skip_samples == 0 && block.num_repeats == 0 && block.data_size >= frameBytes && block.data_size % frameBytes == 0;
Expand All @@ -264,6 +272,14 @@ 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 (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_VAG) {
try {
Ngs2DecodeHevag(voice.blocks.back(), voice.channels);
} catch (...) {
voice.blocks.pop_back();
throw;
}
}
if (streaming) {
auto& stream = voice.blocks.back();
stream.streaming = true;
Expand Down
Loading
Loading