From 90295d3733cb77ca78703ab942602829cdfd1d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Fri, 2 Oct 2026 06:21:07 +0200 Subject: [PATCH] feat(libSceNgs2.native): implement system, racks, voices and PCM rendering libSceNgs2 keeps its stubs; its types now come from the .native header. Co-Authored-By: Claude --- core/libs/SceTypes.hpp | 132 ------- .../libs/prx/libSceNgs2.native/CMakeLists.txt | 32 ++ core/libs/prx/libSceNgs2.native/Export.cpp | 218 ----------- .../libSceNgs2.native/include/Ngs2Types.hpp | 361 ++++++++++++++++++ .../libSceNgs2.native/src/Ngs2Internal.hpp | 96 +++++ core/libs/prx/libSceNgs2.native/src/Rack.cpp | 193 ++++++++++ .../libs/prx/libSceNgs2.native/src/Render.cpp | 189 +++++++++ .../libs/prx/libSceNgs2.native/src/System.cpp | 208 ++++++++++ .../libSceNgs2.native/src/Unimplemented.cpp | 78 ++++ core/libs/prx/libSceNgs2.native/src/Voice.cpp | 304 +++++++++++++++ .../libSceNgs2.native/tests/Ngs2Render.cpp | 272 +++++++++++++ core/libs/prx/libSceNgs2/Export.cpp | 195 +--------- 12 files changed, 1734 insertions(+), 544 deletions(-) create mode 100644 core/libs/prx/libSceNgs2.native/CMakeLists.txt delete mode 100644 core/libs/prx/libSceNgs2.native/Export.cpp create mode 100644 core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp create mode 100644 core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp create mode 100644 core/libs/prx/libSceNgs2.native/src/Rack.cpp create mode 100644 core/libs/prx/libSceNgs2.native/src/Render.cpp create mode 100644 core/libs/prx/libSceNgs2.native/src/System.cpp create mode 100644 core/libs/prx/libSceNgs2.native/src/Unimplemented.cpp create mode 100644 core/libs/prx/libSceNgs2.native/src/Voice.cpp create mode 100644 core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 14a4e72d5..42ff74398 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -511,138 +511,6 @@ struct AudioPropagationSystemMemory { std::size_t size_gpu_mem; }; -using Ngs2Handle = std::uintptr_t; -using Ngs2BufferAllocHandler = std::int32_t (*)(void*); -using Ngs2BufferFreeHandler = std::int32_t (*)(void*); - -struct Ngs2ContextBufferInfo { - void* host_buffer; - std::size_t host_buffer_size; - std::uintptr_t reserved[5]; - std::uintptr_t user_data; -}; - -struct Ngs2BufferAllocator { - Ngs2BufferAllocHandler alloc_handler; - Ngs2BufferFreeHandler free_handler; - std::uintptr_t user_data; -}; - -struct Ngs2SystemOption { - std::size_t size; - char name[16]; - std::uint32_t flags; - std::uint32_t max_grain_samples; - std::uint32_t num_grain_samples; - std::uint32_t sample_rate; - std::uint32_t reserved[6]; -}; - -struct Ngs2SystemInfo { - char name[16]; - Ngs2Handle system_handle; - Ngs2ContextBufferInfo buffer_info; - std::uint32_t uid; - std::uint32_t min_grain_samples; - std::uint32_t max_grain_samples; - std::uint32_t state_flags; - std::uint32_t rack_count; - float last_render_ratio; - std::int64_t last_render_tick; - std::int64_t render_count; - std::uint32_t sample_rate; - std::uint32_t num_grain_samples; -}; - -struct Ngs2RackOption { - std::size_t size; - char name[16]; - std::uint32_t flags; - std::uint32_t max_grain_samples; - std::uint32_t max_voices; - std::uint32_t max_input_delay_blocks; - std::uint32_t max_matrices; - std::uint32_t max_ports; - std::uint32_t reserved[20]; -}; - -struct Ngs2VoiceParamHeader { - std::uint16_t size; - std::int16_t next; - std::uint32_t id; -}; - -struct Ngs2RenderBufferInfo { - void* buffer; - std::size_t buffer_size; - std::uint32_t waveform_type; - std::uint32_t num_channels; -}; - -struct Ngs2VoiceState { - std::uint32_t state_flags; -}; - -struct Ngs2WaveformFormat { - std::uint32_t waveform_type; - std::uint32_t num_channels; - std::uint32_t sample_rate; - std::uint32_t config_data; - std::uint32_t frame_offset; - std::uint32_t frame_margin; -}; - -struct Ngs2WaveformBlock { - std::uint64_t data_offset; - std::uint64_t data_size; - std::uint32_t num_repeats; - std::uint32_t num_skip_samples; - std::uint32_t num_samples; - std::uint32_t reserved; - std::uintptr_t user_data; -}; - -struct Ngs2WaveformInfo { - Ngs2WaveformFormat format; - std::uint32_t data_offset; - std::uint32_t data_size; - std::uint32_t loop_begin_position; - std::uint32_t loop_end_position; - std::uint32_t num_samples; - std::uint32_t audio_unit_size; - std::uint32_t num_audio_unit_samples; - std::uint32_t num_audio_unit_per_frame; - std::uint32_t audio_frame_size; - std::uint32_t num_audio_frame_samples; - std::uint32_t num_delay_samples; - std::uint32_t num_blocks; - Ngs2WaveformBlock block[4]; -}; - -struct Ngs2PanParam { - std::uint32_t reserved[16]; -}; - -struct Ngs2PanWork { - std::uint32_t reserved[64]; -}; - -struct Ngs2GeomListenerParam { - std::uint32_t reserved[32]; -}; - -struct Ngs2GeomListenerWork { - std::uint32_t reserved[64]; -}; - -struct Ngs2GeomSourceParam { - std::uint32_t reserved[32]; -}; - -struct Ngs2GeomAttribute { - std::uint32_t reserved[32]; -}; - struct AvPlayerAudio { std::uint16_t channel_count; std::uint8_t reserved1[2]; diff --git a/core/libs/prx/libSceNgs2.native/CMakeLists.txt b/core/libs/prx/libSceNgs2.native/CMakeLists.txt new file mode 100644 index 000000000..ec776d2c1 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(libSceNgs2.native SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/src/Rack.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Render.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/System.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Unimplemented.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Voice.cpp +) +target_include_directories(libSceNgs2.native PRIVATE ${LIBS_INCLUDE_DIR}) +set_target_properties(libSceNgs2.native PROPERTIES + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) +configure_windows_unwind(libSceNgs2.native) + +if(MINGW) + foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) + string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + endforeach() +endif() + +if(BUILD_TESTING) + add_test_executable(ngs2_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Ngs2Render.cpp) + target_include_directories(ngs2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(ngs2_tests PRIVATE libSceNgs2.native libkernel libc) + configure_windows_unwind(ngs2_tests) + add_test(NAME ngs2_render COMMAND ngs2_tests) + if(WIN32) + set_tests_properties(ngs2_render PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNgs2.native/Export.cpp b/core/libs/prx/libSceNgs2.native/Export.cpp deleted file mode 100644 index d40346948..000000000 --- a/core/libs/prx/libSceNgs2.native/Export.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos, uint32_t num_samples, Ngs2WaveformBlock* block) { - (void)format; - (void)sample_pos; - (void)num_samples; - (void)block; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib, uint32_t flags) { - (void)listener; - (void)source; - (void)out_attrib; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* out_work, uint32_t flags) { - (void)param; - (void)out_work; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) { - (void)out_listener_param; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) { - (void)out_source_param; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix) { - (void)work; - (void)params; - (void)num_params; - (void)matrix_format; - (void)out_volume_matrix; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { - (void)work; - (void)speaker_angles; - (void)unit_angle; - (void)num_speakers; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) { - (void)data; - (void)data_size; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { - (void)system_handle; - (void)rack_id; - (void)option; - (void)buffer_info; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { - (void)system_handle; - (void)rack_id; - (void)option; - (void)allocator; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info) { - (void)rack_handle; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id, uintptr_t* handle) { - (void)rack_handle; - (void)voice_id; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackLock(uintptr_t rack_handle) { - (void)rack_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { - (void)rack_id; - (void)option; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackUnlock(uintptr_t rack_handle) { - (void)rack_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { - (void)option; - (void)buffer_info; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { - (void)option; - (void)allocator; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info) { - (void)system_handle; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info, size_t info_size) { - (void)system_handle; - (void)info; - (void)info_size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) { - (void)option; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) { - (void)system_handle; - (void)buffer_info; - (void)num_buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) { - (void)option; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples) { - (void)system_handle; - (void)num_samples; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { - (void)voice_handle; - (void)param_list; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { - (void)voice_handle; - (void)state; - (void)state_size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { - (void)voice_handle; - (void)state_flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const void* commands, uint32_t num_commands, uint32_t flags) { - (void)voice_handle; - (void)commands; - (void)num_commands; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp new file mode 100644 index 000000000..18681f4f0 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -0,0 +1,361 @@ +#ifndef CORE_LIBS_PRX_LIBSCENGS2_INCLUDE_NGS2TYPES_HPP +#define CORE_LIBS_PRX_LIBSCENGS2_INCLUDE_NGS2TYPES_HPP + +#include +#include + +#include "prx/libc/include/general/VabiMacros.hpp" + +static constexpr int SCE_NGS2_OK = 0; +static constexpr int SCE_NGS2_ERROR_INVALID_OUT_ADDRESS = static_cast(0x804A8010); +static constexpr int SCE_NGS2_ERROR_INVALID_OUT_SIZE = static_cast(0x804A8011); +static constexpr int SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE = static_cast(0x804A8201); +static constexpr int SCE_NGS2_ERROR_INVALID_RACK_HANDLE = static_cast(0x804A8202); + +static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000; +static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000; +static constexpr std::uint32_t SCE_NGS2_RACK_ID_MASTERING = 0x3000; + +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_I16L = 0x12; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_TYPE_PCM_F32L = 0x18; + +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_PLAY = 1; +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_STOP = 2; +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_STOP_IMM = 4; +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_KILL = 8; +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_PAUSE = 16; +static constexpr std::uint32_t SCE_NGS2_VOICE_EVENT_RESUME = 32; + +static constexpr std::uint32_t SCE_NGS2_VOICE_STATE_FLAG_INUSE = 1; +static constexpr std::uint32_t SCE_NGS2_VOICE_STATE_FLAG_PLAYING = 2; +static constexpr std::uint32_t SCE_NGS2_VOICE_STATE_FLAG_PAUSED = 4; +static constexpr std::uint32_t SCE_NGS2_VOICE_STATE_FLAG_STOPPED = 8; + +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS = 0x0001; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_PORT_VOLUME = 0x0002; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_PORT_MATRIX = 0x0003; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_PORT_DELAY = 0x0004; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_PATCH = 0x0005; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_EVENT = 0x0006; +static constexpr std::uint32_t SCE_NGS2_VOICE_PARAM_CALLBACK = 0x0007; +static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP = 0x10000000; +static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS = 0x10000001; +static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_EXIT_LOOP = 0x10000004; +static constexpr std::uint32_t SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH = 0x10000005; +static constexpr std::uint32_t SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP = 0x20000000; +static constexpr std::uint32_t SCE_NGS2_MASTERING_VOICE_PARAM_SETUP = 0x30000000; +static constexpr std::uint32_t SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT = 0x30000005; + +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE = 1; +static constexpr std::uint32_t SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET = 4; + +static constexpr std::uint32_t SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END = 1; +static constexpr std::uint32_t SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT = 2; + +using Ngs2Handle = std::uintptr_t; + +struct Ngs2ContextBufferInfo { + void* host_buffer; + std::size_t host_buffer_size; + std::uintptr_t reserved[5]; + std::uintptr_t user_data; +}; +static_assert(sizeof(Ngs2ContextBufferInfo) == 64); + +using Ngs2BufferAllocHandler = std::int32_t (APS5_VABI *)(Ngs2ContextBufferInfo*); +using Ngs2BufferFreeHandler = std::int32_t (APS5_VABI *)(Ngs2ContextBufferInfo*); + +struct Ngs2BufferAllocator { + Ngs2BufferAllocHandler alloc_handler; + Ngs2BufferFreeHandler free_handler; + std::uintptr_t user_data; +}; + +struct Ngs2SystemOption { + std::size_t size; + char name[64]; + std::uintptr_t job_scheduler_options[4]; + std::uint32_t flags; + std::uint32_t max_grain_samples; + std::uint32_t num_grain_samples; + std::uint32_t sample_rate; + std::uint32_t max_voice_channels; + std::uint32_t reserved[5]; +}; +static_assert(sizeof(Ngs2SystemOption) == 144); + +struct Ngs2SystemInfo { + char name[64]; + Ngs2Handle system_handle; + Ngs2ContextBufferInfo buffer_info; + std::uint32_t uid; + std::uint32_t min_grain_samples; + std::uint32_t max_grain_samples; + std::uint32_t state_flags; + std::uint32_t rack_count; + float last_render_ratio; + std::int64_t last_render_tick; + std::int64_t render_count; + std::uint32_t sample_rate; + std::uint32_t num_grain_samples; +}; +static_assert(sizeof(Ngs2SystemInfo) == 184); + +struct Ngs2RackOption { + std::size_t size; + char name[64]; + std::uint32_t flags; + std::uint32_t max_grain_samples; + std::uint32_t max_voices; + std::uint32_t max_input_delay_blocks; + std::uint32_t max_matrices; + std::uint32_t max_ports; + std::uint32_t max_voice_channels; + std::uint32_t max_output_channels; + std::uint32_t reserved[18]; +}; +static_assert(sizeof(Ngs2RackOption) == 176); + +struct Ngs2SamplerRackOption { + Ngs2RackOption rack_option; + std::uint32_t max_channel_works; + std::uint32_t max_codec_caches; + std::uint32_t max_waveform_blocks; + std::uint32_t max_envelope_points; + std::uint32_t max_filters; + std::uint32_t max_atrac9_decoders; + std::uint32_t max_atrac9_channel_works; + std::uint32_t max_ajm_atrac9_decoders; + std::uint32_t num_peak_meter_blocks; +}; + +struct Ngs2SubmixerRackOption { + Ngs2RackOption rack_option; + std::uint32_t max_channels; + std::uint32_t max_envelope_points; + std::uint32_t max_filters; + std::uint32_t max_inputs; + std::uint32_t num_peak_meter_blocks; +}; + +struct Ngs2MasteringRackOption { + Ngs2RackOption rack_option; + std::uint32_t max_channels; + std::uint32_t num_peak_meter_blocks; +}; + +struct Ngs2VoiceParamHeader { + std::uint16_t size; + std::int16_t next; + std::uint32_t id; +}; + +struct Ngs2VoiceMatrixLevelsParam { + Ngs2VoiceParamHeader header; + std::uint32_t matrix_id; + std::uint32_t num_levels; + const float* levels; +}; + +struct Ngs2VoicePortVolumeParam { + Ngs2VoiceParamHeader header; + std::uint32_t port; + float level; +}; + +struct Ngs2VoicePortMatrixParam { + Ngs2VoiceParamHeader header; + std::uint32_t port; + std::int32_t matrix_id; +}; + +struct Ngs2VoicePortDelayParam { + Ngs2VoiceParamHeader header; + std::uint32_t port; + std::uint32_t num_samples; +}; + +struct Ngs2VoicePatchParam { + Ngs2VoiceParamHeader header; + std::uint32_t port; + std::uint32_t dest_input_id; + Ngs2Handle dest_handle; +}; + +struct Ngs2VoiceEventParam { + Ngs2VoiceParamHeader header; + std::uint32_t event_id; +}; + +struct Ngs2VoiceCallbackInfo { + std::uintptr_t callback_data; + Ngs2Handle voice_handle; + std::uint32_t flag; + std::uint32_t reserved; + std::uintptr_t user_data; + const void* block_data; + std::uint32_t block_size; + std::uint32_t num_repeated; + std::uint32_t attributes; + std::uint32_t reserved2; +}; +static_assert(sizeof(Ngs2VoiceCallbackInfo) == 56); + +using Ngs2VoiceCallbackHandler = void (APS5_VABI *)(const Ngs2VoiceCallbackInfo*); + +struct Ngs2VoiceCallbackParam { + Ngs2VoiceParamHeader header; + Ngs2VoiceCallbackHandler callback; + std::uintptr_t callback_data; + std::uint32_t flags; + std::uint32_t reserved; +}; + +struct Ngs2WaveformFormat { + std::uint32_t waveform_type; + std::uint32_t num_channels; + std::uint32_t sample_rate; + std::uint32_t config_data; + std::uint32_t frame_offset; + std::uint32_t frame_margin; +}; +static_assert(sizeof(Ngs2WaveformFormat) == 24); + +struct Ngs2WaveformBlock { + std::uint64_t data_offset; + std::uint64_t data_size; + std::uint32_t num_repeats; + std::uint32_t num_skip_samples; + std::uint32_t num_samples; + std::uint32_t reserved; + std::uintptr_t user_data; +}; +static_assert(sizeof(Ngs2WaveformBlock) == 40); + +struct Ngs2SamplerVoiceSetupParam { + Ngs2VoiceParamHeader header; + Ngs2WaveformFormat format; +}; + +struct Ngs2SamplerVoiceWaveformBlocksParam { + Ngs2VoiceParamHeader header; + const void* data; + std::uint32_t flags; + std::uint32_t num_blocks; + const Ngs2WaveformBlock* blocks; +}; + +struct Ngs2SamplerVoicePitchParam { + Ngs2VoiceParamHeader header; + float ratio; +}; + +struct Ngs2SubmixerVoiceSetupParam { + Ngs2VoiceParamHeader header; + std::uint32_t num_io_channels; + std::uint32_t flags; +}; +static_assert(sizeof(Ngs2SubmixerVoiceSetupParam) == 16); + +struct Ngs2MasteringVoiceSetupParam { + Ngs2VoiceParamHeader header; + std::uint32_t num_io_channels; + std::uint32_t flags; +}; + +struct Ngs2MasteringVoiceOutputParam { + Ngs2VoiceParamHeader header; + std::uint32_t output_id; + std::uint32_t reserved; +}; + +struct Ngs2VoiceCommand { + std::uint32_t id; + std::uint8_t flags; + std::uint8_t type; + std::uint16_t count; + union { + std::uint32_t u; + std::int32_t i; + float f; + const float* levels; + } value; +}; +static_assert(sizeof(Ngs2VoiceCommand) == 16); + +struct Ngs2RenderBufferInfo { + void* buffer; + std::size_t buffer_size; + std::uint32_t waveform_type; + std::uint32_t num_channels; +}; + +struct Ngs2VoiceState { + std::uint32_t state_flags; + std::int32_t error_code; +}; +static_assert(sizeof(Ngs2VoiceState) == 8); + +struct Ngs2SubmixerVoiceState { + Ngs2VoiceState voice_state; + float envelope_height; + float peak_height; + float compressor_height; +}; +static_assert(sizeof(Ngs2SubmixerVoiceState) == 20); + +struct Ngs2SamplerVoiceState { + Ngs2VoiceState voice_state; + float envelope_height; + float peak_height; + std::uint32_t reserved; + std::uint64_t num_decoded_samples; + std::uint64_t decoded_data_size; + std::uintptr_t user_data; + const void* waveform_data; +}; +static_assert(sizeof(Ngs2SamplerVoiceState) == 56); + +struct Ngs2WaveformInfo { + Ngs2WaveformFormat format; + std::uint32_t data_offset; + std::uint32_t data_size; + std::uint32_t loop_begin_position; + std::uint32_t loop_end_position; + std::uint32_t num_samples; + std::uint32_t audio_unit_size; + std::uint32_t num_audio_unit_samples; + std::uint32_t num_audio_unit_per_frame; + std::uint32_t audio_frame_size; + std::uint32_t num_audio_frame_samples; + std::uint32_t num_delay_samples; + std::uint32_t num_blocks; + Ngs2WaveformBlock block[4]; +}; +static_assert(sizeof(Ngs2WaveformInfo) == 232); + +struct Ngs2PanParam { + std::uint32_t reserved[16]; +}; + +struct Ngs2PanWork { + std::uint32_t reserved[64]; +}; + +struct Ngs2GeomListenerParam { + std::uint32_t reserved[32]; +}; + +struct Ngs2GeomListenerWork { + std::uint32_t reserved[64]; +}; + +struct Ngs2GeomSourceParam { + std::uint32_t reserved[32]; +}; + +struct Ngs2GeomAttribute { + std::uint32_t reserved[32]; +}; + +#endif diff --git a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp new file mode 100644 index 000000000..f2e5683d4 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp @@ -0,0 +1,96 @@ +#ifndef CORE_LIBS_PRX_LIBSCENGS2_SRC_NGS2INTERNAL_HPP +#define CORE_LIBS_PRX_LIBSCENGS2_SRC_NGS2INTERNAL_HPP + +#include +#include +#include +#include +#include +#include + +#include "prx/libSceNgs2.native/include/Ngs2Types.hpp" + +static constexpr std::uint32_t NGS2_MAX_CHANNELS = 8; + +struct Ngs2System; +struct Ngs2Rack; + +enum class Ngs2PlayState : std::uint32_t { + Empty = 0, + Playing = SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING, + Paused = SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PAUSED, + Stopped = SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING | SCE_NGS2_VOICE_STATE_FLAG_STOPPED, +}; + +struct Ngs2Block { + const std::uint8_t* data; + Ngs2WaveformBlock info; + std::uint32_t cursor = 0; + std::uint32_t numRepeated = 0; +}; + +struct Ngs2Voice; + +struct Ngs2Port { + Ngs2Voice* dest = nullptr; + float volume = 1.0f; + std::int32_t matrix = -1; +}; + +struct Ngs2Voice { + Ngs2Rack* rack = nullptr; + Ngs2PlayState state = Ngs2PlayState::Empty; + std::uint32_t stateFlags = 0; + std::uint32_t channels = 0; + std::uint32_t sampleRate = 0; + float pitch = 1.0f; + std::uint64_t phase = 0; + std::deque blocks; + bool acceptsBlocks = true; + std::uint64_t decodedSamples = 0; + const std::uint8_t* waveformEnd = nullptr; + Ngs2VoiceCallbackHandler callback = nullptr; + std::uintptr_t callbackData = 0; + std::uint32_t callbackFlags = 0; + std::vector ports; + std::vector> matrices; + std::uint32_t outputId = 0; + std::vector samples; + bool rendering = false; + bool rendered = false; + bool hasSamples = false; + + void SetEvent(std::uint32_t eventId); + void ResetSetup(); + const std::uint8_t* WaveformData() const; +}; + +struct Ngs2Rack { + Ngs2System* system = nullptr; + std::uint32_t rackId = 0; + std::uint32_t maxChannels = 0; + Ngs2ContextBufferInfo bufferInfo{}; + Ngs2BufferAllocator allocator{}; + std::vector voices; +}; + +struct Ngs2System { + Ngs2SystemOption option{}; + Ngs2ContextBufferInfo bufferInfo{}; + Ngs2BufferAllocator allocator{}; + std::uint32_t uid = 0; + std::int64_t renderCount = 0; + std::vector racks; +}; + +std::string Ngs2Hex(std::uint32_t value); +std::recursive_mutex& Ngs2Mutex(); +Ngs2System* Ngs2FindSystem(Ngs2Handle handle); +Ngs2Rack* Ngs2FindRack(Ngs2Handle handle); +Ngs2Voice* Ngs2FindVoice(Ngs2Handle handle); +void* Ngs2Place(const Ngs2ContextBufferInfo* bufferInfo, std::size_t size, std::size_t alignment); +int Ngs2DestroyRack(Ngs2Rack& rack, Ngs2ContextBufferInfo* outBufferInfo); +int Ngs2ReleaseBuffer(const Ngs2BufferAllocator& allocator, Ngs2ContextBufferInfo bufferInfo, Ngs2ContextBufferInfo* outBufferInfo); +void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo); + +#endif diff --git a/core/libs/prx/libSceNgs2.native/src/Rack.cpp b/core/libs/prx/libSceNgs2.native/src/Rack.cpp new file mode 100644 index 000000000..385349f7c --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Rack.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" +#include "Ngs2Internal.hpp" + +union RackOptions { + Ngs2RackOption common; + Ngs2SamplerRackOption sampler; + Ngs2SubmixerRackOption submixer; + Ngs2MasteringRackOption mastering; +}; + +static std::size_t RackOptionSize(std::uint32_t rackId) { + switch (rackId) { + case SCE_NGS2_RACK_ID_SAMPLER: return sizeof(Ngs2SamplerRackOption); + case SCE_NGS2_RACK_ID_SUBMIXER: return sizeof(Ngs2SubmixerRackOption); + case SCE_NGS2_RACK_ID_MASTERING: return sizeof(Ngs2MasteringRackOption); + default: throw std::runtime_error("NGS2: rack id " + Ngs2Hex(rackId) + " is not implemented"); + } +} + +static RackOptions DefaultRackOption(std::uint32_t rackId) { + RackOptions options{}; + auto& common = options.common; + common.size = RackOptionSize(rackId); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_input_delay_blocks = 1; + common.max_matrices = 1; + common.max_ports = 8; + switch (rackId) { + case SCE_NGS2_RACK_ID_SAMPLER: + common.max_voices = 256; + common.max_input_delay_blocks = 0; + options.sampler.max_channel_works = 256; + options.sampler.max_codec_caches = 32; + options.sampler.max_waveform_blocks = 4; + options.sampler.max_envelope_points = 4; + options.sampler.max_filters = 8; + options.sampler.max_atrac9_decoders = 256; + options.sampler.max_atrac9_channel_works = 256; + options.sampler.num_peak_meter_blocks = 8; + break; + case SCE_NGS2_RACK_ID_SUBMIXER: + options.submixer.max_channels = 8; + options.submixer.max_envelope_points = 4; + options.submixer.max_filters = 8; + options.submixer.max_inputs = 1; + options.submixer.num_peak_meter_blocks = 8; + break; + default: + common.max_matrices = 0; + common.max_ports = 0; + options.mastering.max_channels = 8; + options.mastering.num_peak_meter_blocks = 8; + break; + } + return options; +} + +static std::uint32_t RackMaxChannels(std::uint32_t rackId, const RackOptions& options) { + if (rackId == SCE_NGS2_RACK_ID_SUBMIXER) return options.submixer.max_channels; + if (rackId == SCE_NGS2_RACK_ID_MASTERING) return options.mastering.max_channels; + return NGS2_MAX_CHANNELS; +} + +static RackOptions CheckedRackOption(std::uint32_t rackId, const Ngs2RackOption* option) { + const auto size = RackOptionSize(rackId); + if (option == nullptr) return DefaultRackOption(rackId); + if (option->size != size) throw std::invalid_argument("NGS2: unexpected rack option size " + std::to_string(option->size)); + RackOptions options{}; + std::memcpy(&options, option, size); + const auto maxChannels = RackMaxChannels(rackId, options); + if (options.common.max_voices == 0 || maxChannels == 0 || maxChannels > NGS2_MAX_CHANNELS) { + throw std::invalid_argument("NGS2: invalid rack voice or channel count"); + } + return options; +} + +static int CreateRack(Ngs2Handle systemHandle, std::uint32_t rackId, const RackOptions& options, const Ngs2ContextBufferInfo& bufferInfo, + const Ngs2BufferAllocator& allocator, Ngs2Handle* handle) { + auto* system = Ngs2FindSystem(systemHandle); + if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + auto* rack = new (Ngs2Place(&bufferInfo, sizeof(Ngs2Rack), alignof(Ngs2Rack))) Ngs2Rack{}; + rack->system = system; + rack->rackId = rackId; + rack->maxChannels = RackMaxChannels(rackId, options); + rack->bufferInfo = bufferInfo; + rack->allocator = allocator; + rack->voices.resize(options.common.max_voices); + for (auto& voice : rack->voices) { + voice.rack = rack; + voice.ports.resize(options.common.max_ports); + voice.matrices.resize(options.common.max_matrices); + } + system->racks.push_back(rack); + *handle = reinterpret_cast(rack); + return SCE_NGS2_OK; +} + +int Ngs2DestroyRack(Ngs2Rack& rack, Ngs2ContextBufferInfo* outBufferInfo) { + auto& racks = rack.system->racks; + std::erase(racks, &rack); + for (auto* other : racks) { + for (auto& voice : other->voices) { + for (auto& port : voice.ports) { + if (port.dest != nullptr && port.dest->rack == &rack) port.dest = nullptr; + } + } + } + const auto bufferInfo = rack.bufferInfo; + const auto allocator = rack.allocator; + std::destroy_at(&rack); + return Ngs2ReleaseBuffer(allocator, bufferInfo, outBufferInfo); +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { + if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + CheckedRackOption(rack_id, option); + *buffer_info = {}; + buffer_info->host_buffer_size = sizeof(Ngs2Rack); + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { + if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto options = CheckedRackOption(rack_id, option); + if (buffer_info == nullptr) APS5_INVALID_ARG_EX; + std::lock_guard lock(Ngs2Mutex()); + return CreateRack(system_handle, rack_id, options, *buffer_info, {}, handle); +} + +int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { + if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto options = CheckedRackOption(rack_id, option); + if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; + std::lock_guard lock(Ngs2Mutex()); + if (Ngs2FindSystem(system_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + Ngs2ContextBufferInfo bufferInfo{}; + bufferInfo.host_buffer_size = sizeof(Ngs2Rack); + bufferInfo.user_data = allocator->user_data; + const int result = allocator->alloc_handler(&bufferInfo); + if (result != SCE_NGS2_OK) throw std::runtime_error("NGS2: the allocator handler failed with " + std::to_string(result)); + return CreateRack(system_handle, rack_id, options, bufferInfo, *allocator, handle); +} + +int APS5_VABI sceNgs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info) { + std::lock_guard lock(Ngs2Mutex()); + if (buffer_info != nullptr) *buffer_info = {}; + auto* rack = Ngs2FindRack(rack_handle); + if (rack == nullptr) return SCE_NGS2_ERROR_INVALID_RACK_HANDLE; + return Ngs2DestroyRack(*rack, buffer_info); +} + +int APS5_VABI sceNgs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id, uintptr_t* handle) { + if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + std::lock_guard lock(Ngs2Mutex()); + auto* rack = Ngs2FindRack(rack_handle); + if (rack == nullptr) return SCE_NGS2_ERROR_INVALID_RACK_HANDLE; + if (voice_id >= rack->voices.size()) APS5_INVALID_ARG_EX; + *handle = reinterpret_cast(&rack->voices[voice_id]); + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2RackLock(uintptr_t rack_handle) { + Ngs2Mutex().lock(); + if (Ngs2FindRack(rack_handle) != nullptr) return SCE_NGS2_OK; + Ngs2Mutex().unlock(); + return SCE_NGS2_ERROR_INVALID_RACK_HANDLE; +} + +int APS5_VABI sceNgs2RackUnlock(uintptr_t rack_handle) { + std::lock_guard lock(Ngs2Mutex()); + if (Ngs2FindRack(rack_handle) == nullptr) return SCE_NGS2_ERROR_INVALID_RACK_HANDLE; + Ngs2Mutex().unlock(); + return SCE_NGS2_OK; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/src/Render.cpp b/core/libs/prx/libSceNgs2.native/src/Render.cpp new file mode 100644 index 000000000..765895c51 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Render.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" +#include "Ngs2Internal.hpp" + +static constexpr double PHASE_ONE = 4294967296.0; +static constexpr std::uint64_t PHASE_FRACTION = 0xffffffff; + +static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& block, std::uint32_t frame) { + return block.data + (static_cast(block.info.num_skip_samples) + frame) * voice.channels * sizeof(std::int16_t); +} + +static const std::uint8_t* NextFrame(const Ngs2Voice& voice) { + const auto& block = voice.blocks.front(); + if (block.cursor + 1 < block.info.num_samples) return FrameAt(voice, block, block.cursor + 1); + if (block.info.num_repeats != 0) return FrameAt(voice, block, 0); + if (voice.blocks.size() > 1) return FrameAt(voice, voice.blocks[1], 0); + return FrameAt(voice, block, block.cursor); +} + +static float ReadSample(const std::uint8_t* frame, std::uint32_t channel) { + std::int16_t sample; + std::memcpy(&sample, frame + channel * sizeof(sample), sizeof(sample)); + return sample; +} + +static void FinishBlock(Ngs2Voice& voice) { + auto& block = voice.blocks.front(); + 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; + } + const Ngs2VoiceCallbackInfo info{ + voice.callbackData, + reinterpret_cast(&voice), + repeat ? SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT : SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, + 0, + block.info.user_data, + block.data, + static_cast(block.info.data_size), + block.numRepeated, + 0, + 0, + }; + if (!repeat) { + voice.waveformEnd = FrameAt(voice, block, block.info.num_samples); + voice.blocks.pop_front(); + } + if (voice.callback != nullptr && (voice.callbackFlags & info.flag) != 0) voice.callback(&info); +} + +static void Advance(Ngs2Voice& voice, std::uint64_t frames) { + while (frames != 0 && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty()) { + auto& block = voice.blocks.front(); + const auto step = std::min(frames, block.info.num_samples - block.cursor); + block.cursor += static_cast(step); + voice.decodedSamples += step; + frames -= step; + if (block.cursor == block.info.num_samples) FinishBlock(voice); + } +} + +static void ConsumeSamples(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) { + const auto step = static_cast(std::llround(voice.sampleRate * static_cast(voice.pitch) / systemRate * PHASE_ONE)); + std::uint32_t written = 0; + for (; written < grain && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty(); written++) { + const auto& block = voice.blocks.front(); + const auto* current = FrameAt(voice, block, block.cursor); + const auto* next = NextFrame(voice); + const auto fraction = static_cast(static_cast(voice.phase) / PHASE_ONE); + for (std::uint32_t channel = 0; channel < voice.channels; channel++) { + voice.samples[channel * grain + written] = std::lerp(ReadSample(current, channel), ReadSample(next, channel), fraction) / 32768.0f; + } + voice.phase += step; + Advance(voice, voice.phase >> 32); + voice.phase &= PHASE_FRACTION; + } + voice.hasSamples = written != 0; + if (voice.state == Ngs2PlayState::Playing && voice.blocks.empty() && !voice.acceptsBlocks) voice.state = Ngs2PlayState::Empty; +} + +static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate); + +static void MixPort(Ngs2Voice& voice, const Ngs2Voice& source, const Ngs2Port& port, std::uint32_t grain) { + const auto* matrix = port.matrix < 0 ? nullptr : &source.matrices[port.matrix]; + const std::size_t outputs = matrix == nullptr ? voice.channels : std::min(voice.channels, matrix->size() / source.channels); + for (std::size_t dst = 0; dst < outputs; dst++) { + for (std::uint32_t src = 0; src < source.channels; src++) { + const float level = port.volume * (matrix == nullptr ? (src == dst ? 1.0f : 0.0f) : (*matrix)[dst * source.channels + src]); + if (level == 0.0f) continue; + for (std::uint32_t i = 0; i < grain; i++) voice.samples[dst * grain + i] += source.samples[src * grain + i] * level; + } + } +} + +static void MixInputs(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate) { + for (auto* source : voices) { + for (const auto& port : source->ports) { + if (port.dest != &voice || port.volume == 0.0f) continue; + RenderVoice(*source, voices, grain, systemRate); + if (!source->hasSamples) continue; + MixPort(voice, *source, port, grain); + voice.hasSamples = true; + } + } +} + +static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate) { + if (voice.rendered) return; + if (voice.rendering) throw std::invalid_argument("NGS2: the voice patches form a cycle"); + voice.rendering = true; + voice.samples.assign(static_cast(grain) * voice.channels, 0.0f); + voice.hasSamples = false; + if (voice.state == Ngs2PlayState::Playing && voice.channels != 0) { + if (voice.rack->rackId == SCE_NGS2_RACK_ID_SAMPLER) ConsumeSamples(voice, grain, systemRate); + else MixInputs(voice, voices, grain, systemRate); + } + voice.rendering = false; + voice.rendered = true; +} + +static std::size_t SampleBytes(std::uint32_t waveformType) { + if (waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) return sizeof(std::int16_t); + if (waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_F32L) return sizeof(float); + throw std::runtime_error("NGS2: render waveform type " + Ngs2Hex(waveformType) + " is not implemented"); +} + +static std::vector& OutputMix(std::vector>& mixes, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo, + const Ngs2Voice& voice, std::uint32_t grain) { + if (voice.outputId >= numBufferInfo) throw std::invalid_argument("NGS2: mastering output " + std::to_string(voice.outputId) + " has no render buffer"); + const auto& output = bufferInfo[voice.outputId]; + const auto sampleBytes = SampleBytes(output.waveform_type); + if (output.num_channels != voice.channels) throw std::runtime_error("NGS2: mixing " + std::to_string(voice.channels) + " mastering channels into " + std::to_string(output.num_channels) + " is not implemented"); + if (output.buffer == nullptr || output.buffer_size < static_cast(grain) * output.num_channels * sampleBytes) { + throw std::invalid_argument("NGS2: render buffer " + std::to_string(voice.outputId) + " is missing or too small"); + } + auto& mix = mixes[voice.outputId]; + if (mix.empty()) mix.resize(static_cast(grain) * output.num_channels); + return mix; +} + +static void WriteOutput(const Ngs2RenderBufferInfo& output, const std::vector& mix) { + if (output.waveform_type == SCE_NGS2_WAVEFORM_TYPE_PCM_F32L) { + std::memcpy(output.buffer, mix.data(), mix.size() * sizeof(float)); + return; + } + auto* pcm = static_cast(output.buffer); + for (std::size_t i = 0; i < mix.size(); i++) { + pcm[i] = static_cast(std::min(std::lrint(std::clamp(mix[i], -1.0f, 1.0f) * 32768.0f), 32767L)); + } +} + +void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo) { + const auto grain = system.option.num_grain_samples; + std::vector voices; + for (auto* rack : system.racks) { + for (auto& voice : rack->voices) { + voice.rendered = false; + voices.push_back(&voice); + } + } + std::vector> mixes(numBufferInfo); + for (auto* voice : voices) { + RenderVoice(*voice, voices, grain, system.option.sample_rate); + if (voice->rack->rackId != SCE_NGS2_RACK_ID_MASTERING || !voice->hasSamples) continue; + auto& mix = OutputMix(mixes, bufferInfo, numBufferInfo, *voice, grain); + for (std::uint32_t channel = 0; channel < voice->channels; channel++) { + for (std::uint32_t i = 0; i < grain; i++) mix[i * voice->channels + channel] += voice->samples[channel * grain + i]; + } + } + for (std::uint32_t i = 0; i < numBufferInfo; i++) { + if (bufferInfo[i].buffer != nullptr) std::memset(bufferInfo[i].buffer, 0, bufferInfo[i].buffer_size); + if (!mixes[i].empty()) WriteOutput(bufferInfo[i], mixes[i]); + } + for (auto* voice : voices) { + if (voice->state == Ngs2PlayState::Stopped) voice->state = Ngs2PlayState::Empty; + voice->stateFlags = static_cast(voice->state); + } + system.renderCount++; +} diff --git a/core/libs/prx/libSceNgs2.native/src/System.cpp b/core/libs/prx/libSceNgs2.native/src/System.cpp new file mode 100644 index 000000000..1bb850606 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/System.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" +#include "Ngs2Internal.hpp" + +static constexpr std::uint32_t MIN_GRAIN_SAMPLES = 64; + +static std::vector& Systems() { + static std::vector systems; + return systems; +} + +std::string Ngs2Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%x", value); + return text; +} + +std::recursive_mutex& Ngs2Mutex() { + static std::recursive_mutex mutex; + return mutex; +} + +Ngs2System* Ngs2FindSystem(Ngs2Handle handle) { + auto& systems = Systems(); + const auto found = std::find(systems.begin(), systems.end(), reinterpret_cast(handle)); + return found == systems.end() ? nullptr : *found; +} + +Ngs2Rack* Ngs2FindRack(Ngs2Handle handle) { + for (auto* system : Systems()) { + for (auto* rack : system->racks) { + if (reinterpret_cast(rack) == handle) return rack; + } + } + return nullptr; +} + +Ngs2Voice* Ngs2FindVoice(Ngs2Handle handle) { + for (auto* system : Systems()) { + for (auto* rack : system->racks) { + for (auto& voice : rack->voices) { + if (reinterpret_cast(&voice) == handle) return &voice; + } + } + } + return nullptr; +} + +void* Ngs2Place(const Ngs2ContextBufferInfo* bufferInfo, std::size_t size, std::size_t alignment) { + if (bufferInfo == nullptr || bufferInfo->host_buffer == nullptr || bufferInfo->host_buffer_size < size) { + throw std::invalid_argument("NGS2: the context buffer is missing or smaller than the queried size"); + } + if (reinterpret_cast(bufferInfo->host_buffer) % alignment != 0) { + throw std::invalid_argument("NGS2: the context buffer is not aligned to " + std::to_string(alignment) + " bytes"); + } + return bufferInfo->host_buffer; +} + +int Ngs2ReleaseBuffer(const Ngs2BufferAllocator& allocator, Ngs2ContextBufferInfo bufferInfo, Ngs2ContextBufferInfo* outBufferInfo) { + if (outBufferInfo != nullptr) *outBufferInfo = allocator.free_handler == nullptr ? bufferInfo : Ngs2ContextBufferInfo{}; + return allocator.free_handler == nullptr ? SCE_NGS2_OK : allocator.free_handler(&bufferInfo); +} + +static Ngs2ContextBufferInfo Allocate(const Ngs2BufferAllocator* allocator, std::size_t size) { + if (allocator == nullptr || allocator->alloc_handler == nullptr || allocator->free_handler == nullptr) APS5_INVALID_ARG_EX; + Ngs2ContextBufferInfo bufferInfo{}; + bufferInfo.host_buffer_size = size; + bufferInfo.user_data = allocator->user_data; + const int result = allocator->alloc_handler(&bufferInfo); + if (result != SCE_NGS2_OK) throw std::runtime_error("NGS2: the allocator handler failed with " + std::to_string(result)); + return bufferInfo; +} + +static Ngs2SystemOption DefaultSystemOption() { + Ngs2SystemOption option{}; + option.size = sizeof(Ngs2SystemOption); + option.max_grain_samples = 512; + option.num_grain_samples = 256; + option.sample_rate = 48000; + return option; +} + +static Ngs2SystemOption CheckedSystemOption(const Ngs2SystemOption* option) { + if (option == nullptr) return DefaultSystemOption(); + if (option->size != sizeof(Ngs2SystemOption)) throw std::invalid_argument("NGS2: unexpected system option size " + std::to_string(option->size)); + if (option->flags != 0) throw std::runtime_error("NGS2: system option flags are not implemented"); + if (option->sample_rate == 0 || option->max_grain_samples < MIN_GRAIN_SAMPLES || option->num_grain_samples == 0 || + option->num_grain_samples > option->max_grain_samples) { + throw std::invalid_argument("NGS2: invalid system grain or sample rate"); + } + return *option; +} + +static int CreateSystem(const Ngs2SystemOption& option, const Ngs2ContextBufferInfo& bufferInfo, const Ngs2BufferAllocator& allocator, Ngs2Handle* handle) { + static std::uint32_t nextUid = 1; + auto* system = new (Ngs2Place(&bufferInfo, sizeof(Ngs2System), alignof(Ngs2System))) Ngs2System{}; + system->option = option; + system->bufferInfo = bufferInfo; + system->allocator = allocator; + system->uid = nextUid++; + Systems().push_back(system); + *handle = reinterpret_cast(system); + return SCE_NGS2_OK; +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) { + if (buffer_info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + CheckedSystemOption(option); + *buffer_info = {}; + buffer_info->host_buffer_size = sizeof(Ngs2System); + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { + if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto checked = CheckedSystemOption(option); + if (buffer_info == nullptr) APS5_INVALID_ARG_EX; + std::lock_guard lock(Ngs2Mutex()); + return CreateSystem(checked, *buffer_info, {}, handle); +} + +int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { + if (handle == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + const auto checked = CheckedSystemOption(option); + const auto bufferInfo = Allocate(allocator, sizeof(Ngs2System)); + std::lock_guard lock(Ngs2Mutex()); + return CreateSystem(checked, bufferInfo, *allocator, handle); +} + +int APS5_VABI sceNgs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info) { + std::lock_guard lock(Ngs2Mutex()); + if (buffer_info != nullptr) *buffer_info = {}; + auto* system = Ngs2FindSystem(system_handle); + if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + while (!system->racks.empty()) { + const int result = Ngs2DestroyRack(*system->racks.back(), nullptr); + if (result != SCE_NGS2_OK) return result; + } + std::erase(Systems(), system); + const auto bufferInfo = system->bufferInfo; + const auto allocator = system->allocator; + std::destroy_at(system); + return Ngs2ReleaseBuffer(allocator, bufferInfo, buffer_info); +} + +int APS5_VABI sceNgs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info, size_t info_size) { + if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + if (info_size != sizeof(Ngs2SystemInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + std::lock_guard lock(Ngs2Mutex()); + const auto* system = Ngs2FindSystem(system_handle); + if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + *info = {}; + std::memcpy(info->name, system->option.name, sizeof(info->name)); + info->system_handle = system_handle; + info->buffer_info = system->bufferInfo; + info->uid = system->uid; + info->min_grain_samples = MIN_GRAIN_SAMPLES; + info->max_grain_samples = system->option.max_grain_samples; + info->state_flags = 1; + info->rack_count = static_cast(system->racks.size()); + info->render_count = system->renderCount; + info->sample_rate = system->option.sample_rate; + info->num_grain_samples = system->option.num_grain_samples; + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples) { + std::lock_guard lock(Ngs2Mutex()); + auto* system = Ngs2FindSystem(system_handle); + if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + if (num_samples == 0 || num_samples > system->option.max_grain_samples) APS5_INVALID_ARG_EX; + system->option.num_grain_samples = num_samples; + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) { + std::lock_guard lock(Ngs2Mutex()); + auto* system = Ngs2FindSystem(system_handle); + if (system == nullptr) return SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + if (buffer_info == nullptr || num_buffer_info == 0) APS5_INVALID_ARG_EX; + Ngs2RenderSystem(*system, buffer_info, num_buffer_info); + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) { + if (option == nullptr) APS5_INVALID_ARG_EX; + *option = DefaultSystemOption(); + return SCE_NGS2_OK; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/src/Unimplemented.cpp b/core/libs/prx/libSceNgs2.native/src/Unimplemented.cpp new file mode 100644 index 000000000..619dc4c8d --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Unimplemented.cpp @@ -0,0 +1,78 @@ +#include +#include + +#include "prx/libc/include/General.hpp" +#include "prx/libSceNgs2.native/include/Ngs2Types.hpp" + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos, uint32_t num_samples, Ngs2WaveformBlock* block) { + (void)format; + (void)sample_pos; + (void)num_samples; + (void)block; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib, uint32_t flags) { + (void)listener; + (void)source; + (void)out_attrib; + (void)flags; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* out_work, uint32_t flags) { + (void)param; + (void)out_work; + (void)flags; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) { + (void)out_listener_param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) { + (void)out_source_param; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix) { + (void)work; + (void)params; + (void)num_params; + (void)matrix_format; + (void)out_volume_matrix; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { + (void)work; + (void)speaker_angles; + (void)unit_angle; + (void)num_speakers; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) { + (void)data; + (void)data_size; + (void)info; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp new file mode 100644 index 000000000..acdbfc3a6 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -0,0 +1,304 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libc/include/General.hpp" +#include "Ngs2Internal.hpp" + +static constexpr std::uint32_t COMMAND_EVENT = 2; +static constexpr std::uint32_t COMMAND_MATRIX_LEVELS = 5; +static constexpr std::uint32_t COMMAND_PORT_VOLUME = 6; +static constexpr std::uint32_t COMMAND_PORT_MATRIX = 7; +static constexpr std::uint8_t COMMAND_TYPE_FLOAT = 1; +static constexpr std::uint8_t COMMAND_TYPE_INT = 3; +static constexpr std::uint8_t COMMAND_TYPE_UINT = 4; +static constexpr std::uint8_t COMMAND_TYPE_FLOAT_ARRAY = 0x11; + +void Ngs2Voice::SetEvent(std::uint32_t eventId) { + switch (eventId) { + case SCE_NGS2_VOICE_EVENT_PLAY: + if (state == Ngs2PlayState::Empty || state == Ngs2PlayState::Stopped) { + state = Ngs2PlayState::Playing; + stateFlags |= SCE_NGS2_VOICE_STATE_FLAG_INUSE; + } + break; + case SCE_NGS2_VOICE_EVENT_STOP: + if (state == Ngs2PlayState::Playing || state == Ngs2PlayState::Paused) state = Ngs2PlayState::Stopped; + break; + case SCE_NGS2_VOICE_EVENT_STOP_IMM: + case SCE_NGS2_VOICE_EVENT_KILL: + state = Ngs2PlayState::Empty; + break; + case SCE_NGS2_VOICE_EVENT_PAUSE: + if (state == Ngs2PlayState::Playing) state = Ngs2PlayState::Paused; + break; + case SCE_NGS2_VOICE_EVENT_RESUME: + if (state == Ngs2PlayState::Paused) state = Ngs2PlayState::Playing; + break; + default: throw std::invalid_argument("NGS2: unknown voice event " + Ngs2Hex(eventId)); + } +} + +void Ngs2Voice::ResetSetup() { + SetEvent(SCE_NGS2_VOICE_EVENT_STOP_IMM); + std::fill(ports.begin(), ports.end(), Ngs2Port{}); + for (auto& matrix : matrices) matrix.clear(); + channels = 0; + sampleRate = 0; + pitch = 1.0f; + phase = 0; + blocks.clear(); + acceptsBlocks = true; + decodedSamples = 0; + waveformEnd = nullptr; +} + +const std::uint8_t* Ngs2Voice::WaveformData() const { + if (blocks.empty()) return waveformEnd; + const auto& block = blocks.front(); + return block.data + (static_cast(block.info.num_skip_samples) + block.cursor) * channels * sizeof(std::int16_t); +} + +template +static const TParam& ParamAs(const Ngs2VoiceParamHeader& param) { + if (param.size < sizeof(TParam)) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " is too small"); + return reinterpret_cast(param); +} + +static Ngs2Port& PortAt(Ngs2Voice& voice, std::uint32_t port) { + if (port >= voice.ports.size()) throw std::invalid_argument("NGS2: voice port " + std::to_string(port) + " is out of range"); + return voice.ports[port]; +} + +static void SetMatrixLevels(Ngs2Voice& voice, std::uint32_t matrixId, const float* levels, std::uint32_t numLevels) { + if (matrixId >= voice.matrices.size() || numLevels > NGS2_MAX_CHANNELS * NGS2_MAX_CHANNELS || (levels == nullptr && numLevels != 0)) { + APS5_INVALID_ARG_EX; + } + voice.matrices[matrixId].assign(levels, levels + numLevels); +} + +static void SetPortMatrix(Ngs2Voice& voice, std::uint32_t port, std::int32_t matrixId) { + if (matrixId < -1 || matrixId >= static_cast(voice.matrices.size())) APS5_INVALID_ARG_EX; + PortAt(voice, port).matrix = matrixId; +} + +static void Patch(Ngs2Voice& voice, const Ngs2VoicePatchParam& patch) { + auto& port = PortAt(voice, patch.port); + if (patch.dest_input_id != 0) throw std::runtime_error("NGS2: voice input " + std::to_string(patch.dest_input_id) + " is not implemented"); + if (patch.dest_handle == 0) { + port.dest = nullptr; + return; + } + auto* dest = Ngs2FindVoice(patch.dest_handle); + if (dest == nullptr || dest->rack->system != voice.rack->system || dest->rack->rackId == SCE_NGS2_RACK_ID_SAMPLER) APS5_INVALID_ARG_EX; + port.dest = dest; +} + +static void ApplyCommonParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { + switch (param.id) { + case SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS: { + const auto& levels = ParamAs(param); + SetMatrixLevels(voice, levels.matrix_id, levels.levels, levels.num_levels); + break; + } + case SCE_NGS2_VOICE_PARAM_PORT_VOLUME: { + const auto& volume = ParamAs(param); + PortAt(voice, volume.port).volume = volume.level; + break; + } + case SCE_NGS2_VOICE_PARAM_PORT_MATRIX: { + const auto& matrix = ParamAs(param); + SetPortMatrix(voice, matrix.port, matrix.matrix_id); + break; + } + case SCE_NGS2_VOICE_PARAM_PORT_DELAY: { + const auto& delay = ParamAs(param); + PortAt(voice, delay.port); + if (delay.num_samples != 0) throw std::runtime_error("NGS2: port delay is not implemented"); + break; + } + case SCE_NGS2_VOICE_PARAM_PATCH: Patch(voice, ParamAs(param)); break; + case SCE_NGS2_VOICE_PARAM_EVENT: voice.SetEvent(ParamAs(param).event_id); break; + case SCE_NGS2_VOICE_PARAM_CALLBACK: { + const auto& callback = ParamAs(param); + voice.callback = callback.callback; + voice.callbackData = callback.callback_data; + voice.callbackFlags = callback.flags; + break; + } + default: throw std::runtime_error("NGS2: voice param " + Ngs2Hex(param.id) + " is not implemented"); + } +} + +static void SetupSampler(Ngs2Voice& voice, const Ngs2WaveformFormat& format) { + voice.ResetSetup(); + if (format.waveform_type == 0 && format.num_channels == 0 && format.sample_rate == 0) return; + if (format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) 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.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; +} + +static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlocksParam& param) { + constexpr std::uint32_t knownFlags = SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET; + if ((param.flags & ~knownFlags) != 0) throw std::runtime_error("NGS2: waveform block flags " + Ngs2Hex(param.flags) + " are not implemented"); + if (voice.channels == 0 || (param.num_blocks != 0 && (param.blocks == nullptr || param.data == nullptr))) APS5_INVALID_ARG_EX; + const bool reset = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET) != 0; + if (!voice.acceptsBlocks && !reset) throw std::invalid_argument("NGS2: the voice waveform was already closed"); + if (reset) { + voice.blocks.clear(); + voice.phase = 0; + voice.waveformEnd = nullptr; + } + voice.acceptsBlocks = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE) != 0; + const std::size_t frameBytes = voice.channels * sizeof(std::int16_t); + for (std::uint32_t i = 0; i < param.num_blocks; i++) { + const auto& block = param.blocks[i]; + if (block.num_samples == 0 && block.data_size == 0) continue; + if (block.num_samples == 0 || (static_cast(block.num_skip_samples) + block.num_samples) * frameBytes > block.data_size) { + throw std::invalid_argument("NGS2: waveform block " + std::to_string(i) + " does not fit its data"); + } + voice.blocks.push_back({static_cast(param.data) + block.data_offset, block}); + } +} + +static void ApplySamplerParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { + switch (param.id) { + case SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP: SetupSampler(voice, ParamAs(param).format); break; + case SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS: AddWaveformBlocks(voice, ParamAs(param)); break; + case SCE_NGS2_SAMPLER_VOICE_PARAM_EXIT_LOOP: { + const auto looping = std::find_if(voice.blocks.begin(), voice.blocks.end(), [](const Ngs2Block& block) { return block.info.num_repeats != 0; }); + if (looping != voice.blocks.end()) looping->info.num_repeats = 0; + break; + } + case SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH: { + const float ratio = ParamAs(param).ratio; + if (!std::isfinite(ratio) || ratio < 0.0f) APS5_INVALID_ARG_EX; + voice.pitch = ratio; + break; + } + default: throw std::runtime_error("NGS2: sampler voice param " + Ngs2Hex(param.id) + " is not implemented"); + } +} + +static void SetupMixer(Ngs2Voice& voice, std::uint32_t numChannels) { + if (numChannels == 0 || numChannels > voice.rack->maxChannels) APS5_INVALID_ARG_EX; + voice.ResetSetup(); + voice.channels = numChannels; +} + +static void ApplyParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { + const std::uint32_t rackId = param.id >> 16; + if (rackId == 0) { + ApplyCommonParam(voice, param); + return; + } + if (rackId != voice.rack->rackId) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + switch (rackId) { + case SCE_NGS2_RACK_ID_SAMPLER: ApplySamplerParam(voice, param); return; + case SCE_NGS2_RACK_ID_SUBMIXER: + if (param.id != SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP) break; + if (ParamAs(param).flags != 0) throw std::runtime_error("NGS2: submixer setup flags are not implemented"); + SetupMixer(voice, ParamAs(param).num_io_channels); + return; + case SCE_NGS2_RACK_ID_MASTERING: + if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_SETUP) { + SetupMixer(voice, ParamAs(param).num_io_channels); + return; + } + if (param.id == SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT) { + voice.outputId = ParamAs(param).output_id; + return; + } + break; + default: break; + } + throw std::runtime_error("NGS2: voice param " + Ngs2Hex(param.id) + " is not implemented"); +} + +static Ngs2Voice& CheckedVoice(Ngs2Handle handle) { + auto* voice = Ngs2FindVoice(handle); + if (voice == nullptr) throw std::invalid_argument("NGS2: invalid voice handle"); + return *voice; +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { + std::lock_guard lock(Ngs2Mutex()); + auto& voice = CheckedVoice(voice_handle); + if (param_list == nullptr) APS5_INVALID_ARG_EX; + for (const auto* param = param_list;; param = reinterpret_cast(reinterpret_cast(param) + param->next)) { + ApplyParam(voice, *param); + if (param->next == 0) break; + } + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCommand* commands, size_t num_commands) { + std::lock_guard lock(Ngs2Mutex()); + auto& voice = CheckedVoice(voice_handle); + if (commands == nullptr && num_commands != 0) APS5_INVALID_ARG_EX; + for (std::size_t i = 0; i < num_commands; i++) { + const auto& command = commands[i]; + const std::uint32_t index = command.id >> 24; + const std::uint32_t id = command.id & 0xffffff; + const std::uint8_t expectedType = id == COMMAND_EVENT ? COMMAND_TYPE_UINT + : id == COMMAND_MATRIX_LEVELS ? COMMAND_TYPE_FLOAT_ARRAY + : id == COMMAND_PORT_VOLUME ? COMMAND_TYPE_FLOAT + : id == COMMAND_PORT_MATRIX ? COMMAND_TYPE_INT : 0; + if (expectedType == 0) throw std::runtime_error("NGS2: voice command " + Ngs2Hex(command.id) + " is not implemented"); + if (command.type != expectedType) throw std::invalid_argument("NGS2: voice command " + Ngs2Hex(command.id) + " has value type " + Ngs2Hex(command.type)); + if (id == COMMAND_EVENT) voice.SetEvent(command.value.u); + else if (id == COMMAND_MATRIX_LEVELS) SetMatrixLevels(voice, index, command.value.levels, command.count); + else if (id == COMMAND_PORT_VOLUME) PortAt(voice, index).volume = command.value.f; + else SetPortMatrix(voice, index, command.value.i); + } + return SCE_NGS2_OK; +} + +int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { + if (state == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + std::lock_guard lock(Ngs2Mutex()); + const auto& voice = CheckedVoice(voice_handle); + switch (voice.rack->rackId) { + case SCE_NGS2_RACK_ID_SAMPLER: { + if (state_size != sizeof(Ngs2SamplerVoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + auto& sampler = *reinterpret_cast(state); + sampler = {}; + sampler.voice_state.state_flags = voice.stateFlags; + sampler.envelope_height = 1.0f; + sampler.num_decoded_samples = voice.decodedSamples; + sampler.decoded_data_size = voice.decodedSamples * voice.channels * sizeof(std::int16_t); + sampler.user_data = voice.blocks.empty() ? 0 : voice.blocks.front().info.user_data; + sampler.waveform_data = voice.WaveformData(); + return SCE_NGS2_OK; + } + case SCE_NGS2_RACK_ID_SUBMIXER: { + if (state_size != sizeof(Ngs2SubmixerVoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; + auto& submixer = *reinterpret_cast(state); + submixer = {}; + submixer.voice_state.state_flags = voice.stateFlags; + return SCE_NGS2_OK; + } + default: throw std::runtime_error("NGS2: voice state of rack " + Ngs2Hex(voice.rack->rackId) + " is not implemented"); + } +} + +int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { + if (state_flags == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; + std::lock_guard lock(Ngs2Mutex()); + *state_flags = CheckedVoice(voice_handle).stateFlags; + return SCE_NGS2_OK; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp new file mode 100644 index 000000000..5caa27592 --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp @@ -0,0 +1,272 @@ +#include "prx/libSceNgs2.native/include/Ngs2Types.hpp" + +#include +#include +#include +#include + +extern "C" { +int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption*); +int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption*, Ngs2ContextBufferInfo*); +int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption*, const Ngs2ContextBufferInfo*, uintptr_t*); +int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption*, const Ngs2BufferAllocator*, uintptr_t*); +int APS5_VABI sceNgs2SystemDestroy(uintptr_t, Ngs2ContextBufferInfo*); +int APS5_VABI sceNgs2SystemGetInfo(uintptr_t, Ngs2SystemInfo*, size_t); +int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t, uint32_t); +int APS5_VABI sceNgs2SystemRender(uintptr_t, const Ngs2RenderBufferInfo*, uint32_t); +int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t, const Ngs2RackOption*, Ngs2ContextBufferInfo*); +int APS5_VABI sceNgs2RackCreate(uintptr_t, uint32_t, const Ngs2RackOption*, const Ngs2ContextBufferInfo*, uintptr_t*); +int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t, uint32_t, const Ngs2RackOption*, const Ngs2BufferAllocator*, uintptr_t*); +int APS5_VABI sceNgs2RackDestroy(uintptr_t, Ngs2ContextBufferInfo*); +int APS5_VABI sceNgs2RackGetVoiceHandle(uintptr_t, uint32_t, uintptr_t*); +int APS5_VABI sceNgs2VoiceControl(uintptr_t, const Ngs2VoiceParamHeader*); +int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t, const Ngs2VoiceCommand*, size_t); +int APS5_VABI sceNgs2VoiceGetState(uintptr_t, Ngs2VoiceState*, size_t); +int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t, uint32_t*); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "NGS2 check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr std::uint32_t Grain = 8; + +static std::vector buffers[16]; +static std::size_t usedBuffers = 0; + +static Ngs2ContextBufferInfo Buffer(const Ngs2ContextBufferInfo& query) { + auto& storage = buffers[usedBuffers++]; + storage.resize(query.host_buffer_size / sizeof(std::uint64_t) + 1); + Ngs2ContextBufferInfo info{}; + info.host_buffer = storage.data(); + info.host_buffer_size = query.host_buffer_size; + return info; +} + +static uintptr_t CreateSystem() { + Ngs2ContextBufferInfo query{}; + Require(sceNgs2SystemQueryBufferSize(nullptr, &query) == SCE_NGS2_OK && query.host_buffer_size != 0); + const auto info = Buffer(query); + uintptr_t system = 0; + Require(sceNgs2SystemCreate(nullptr, &info, &system) == SCE_NGS2_OK && system != 0); + Require(sceNgs2SystemSetGrainSamples(system, Grain) == SCE_NGS2_OK); + return system; +} + +static uintptr_t CreateRack(uintptr_t system, std::uint32_t rackId) { + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(rackId, nullptr, &query) == SCE_NGS2_OK); + const auto info = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, rackId, nullptr, &info, &rack) == SCE_NGS2_OK && rack != 0); + return rack; +} + +static uintptr_t Voice(uintptr_t rack) { + uintptr_t voice = 0; + Require(sceNgs2RackGetVoiceHandle(rack, 0, &voice) == SCE_NGS2_OK && voice != 0); + return voice; +} + +template +static void Control(uintptr_t voice, std::uint32_t id, TParam param) { + param.header = {static_cast(sizeof(TParam)), 0, id}; + Require(sceNgs2VoiceControl(voice, ¶m.header) == SCE_NGS2_OK); +} + +static void Event(uintptr_t voice, std::uint32_t eventId) { + Control(voice, SCE_NGS2_VOICE_PARAM_EVENT, Ngs2VoiceEventParam{{}, eventId}); +} + +static void Patch(uintptr_t source, uintptr_t dest) { + Control(source, SCE_NGS2_VOICE_PARAM_PATCH, Ngs2VoicePatchParam{{}, 0, 0, dest}); +} + +static uintptr_t Mastering(uintptr_t system, std::uint32_t channels) { + const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_MASTERING)); + Control(voice, SCE_NGS2_MASTERING_VOICE_PARAM_SETUP, Ngs2MasteringVoiceSetupParam{{}, channels, 0}); + Control(voice, SCE_NGS2_MASTERING_VOICE_PARAM_OUTPUT, Ngs2MasteringVoiceOutputParam{{}, 0, 0}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + return voice; +} + +static uintptr_t Sampler(uintptr_t system, const std::vector& pcm, std::uint32_t repeats) { + const auto voice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER)); + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_SETUP, Ngs2SamplerVoiceSetupParam{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1, 48000, 0, 0, 0}}); + const Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), repeats, 0, static_cast(pcm.size()), 0, 0x55}; + Control(voice, SCE_NGS2_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + return voice; +} + +static std::vector callbackFlags; +static void APS5_VABI OnBlock(const Ngs2VoiceCallbackInfo* info) { + Require(info->callback_data == 7 && info->user_data == 0x55); + callbackFlags.push_back(info->flag); +} + +static std::uint32_t Flags(uintptr_t voice) { + std::uint32_t flags = 0xffffffff; + Require(sceNgs2VoiceGetStateFlags(voice, &flags) == SCE_NGS2_OK); + return flags; +} + +static std::vector RenderI16(uintptr_t system) { + std::vector out(Grain, -1); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(std::int16_t), SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 1}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + return out; +} + +static void TestErrorsAndInfo() { + Ngs2SystemOption option{}; + Require(sceNgs2SystemResetOption(&option) == SCE_NGS2_OK); + Require(option.size == sizeof(option) && option.max_grain_samples == 512 && option.num_grain_samples == 256 && option.sample_rate == 48000); + Require(sceNgs2SystemQueryBufferSize(&option, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_SAMPLER, nullptr, nullptr) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + + Ngs2SystemInfo info{}; + Require(sceNgs2SystemGetInfo(0x1234, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE); + Require(sceNgs2RackDestroy(0x1234, nullptr) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE); + + const auto system = CreateSystem(); + CreateRack(system, SCE_NGS2_RACK_ID_SAMPLER); + Require(sceNgs2SystemGetInfo(system, nullptr, sizeof(info)) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS); + Require(sceNgs2SystemGetInfo(system, &info, sizeof(info) - 1) == SCE_NGS2_ERROR_INVALID_OUT_SIZE); + Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_OK); + Require(info.system_handle == system && info.uid != 0 && info.rack_count == 1 && info.sample_rate == 48000); + Require(info.num_grain_samples == Grain && info.max_grain_samples == 512 && info.render_count == 0); + + Ngs2ContextBufferInfo released{}; + Require(sceNgs2SystemDestroy(system, &released) == SCE_NGS2_OK && released.host_buffer != nullptr); + Require(sceNgs2SystemGetInfo(system, &info, sizeof(info)) == SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE); +} + +static void TestPcmBlockEnd() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + std::vector pcm; + for (int i = 0; i < 12; i++) pcm.push_back(static_cast(i * 1000 - 4000)); + const auto sampler = Sampler(system, pcm, 0); + Patch(sampler, master); + Control(sampler, SCE_NGS2_VOICE_PARAM_CALLBACK, Ngs2VoiceCallbackParam{{}, OnBlock, 7, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0}); + Require(Flags(sampler) == 0); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + Require(Flags(sampler) == SCE_NGS2_VOICE_STATE_FLAG_INUSE); + + callbackFlags.clear(); + auto out = RenderI16(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == pcm[i]); + Require(Flags(sampler) == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING) && callbackFlags.empty()); + + Ngs2SamplerVoiceState state{}; + Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state) - 8) == SCE_NGS2_ERROR_INVALID_OUT_SIZE); + Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK); + Require(state.num_decoded_samples == Grain && state.decoded_data_size == Grain * 2 && state.user_data == 0x55); + Require(state.waveform_data == pcm.data() + Grain); + + out = RenderI16(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == (i < 4 ? pcm[Grain + i] : 0)); + Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END); + Require(Flags(sampler) == 0); + Require(sceNgs2VoiceGetState(sampler, &state.voice_state, sizeof(state)) == SCE_NGS2_OK); + Require(state.num_decoded_samples == pcm.size() && state.waveform_data == pcm.data() + pcm.size()); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestPitchAndRepeat() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + const std::vector pcm{0, 1000, 2000, 3000}; + const auto sampler = Sampler(system, pcm, 1); + Patch(sampler, master); + Control(sampler, SCE_NGS2_SAMPLER_VOICE_PARAM_PITCH, Ngs2SamplerVoicePitchParam{{}, 0.5f}); + Control(sampler, SCE_NGS2_VOICE_PARAM_CALLBACK, + Ngs2VoiceCallbackParam{{}, OnBlock, 7, SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END | SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT, 0}); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + + callbackFlags.clear(); + const std::int16_t first[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 1500}; + auto out = RenderI16(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == first[i]); + Require(callbackFlags.size() == 1 && callbackFlags[0] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT); + + const std::int16_t second[Grain] = {0, 500, 1000, 1500, 2000, 2500, 3000, 3000}; + out = RenderI16(system); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i] == second[i]); + Require(callbackFlags.size() == 2 && callbackFlags[1] == SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END); + Require(Flags(sampler) == 0); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static void TestSubmixerMatrix() { + const auto system = CreateSystem(); + const auto master = Mastering(system, 2); + const auto submixer = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER)); + Control(submixer, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 2, 0}); + Patch(submixer, master); + const Ngs2VoiceCommand play{2, 0, 4, 0, {.u = SCE_NGS2_VOICE_EVENT_PLAY}}; + Require(sceNgs2VoiceRunCommands(submixer, &play, 1) == SCE_NGS2_OK); + + const std::vector pcm(Grain, 16384); + const auto sampler = Sampler(system, pcm, 0); + Patch(sampler, submixer); + const float levels[2] = {1.0f, 0.5f}; + Control(sampler, SCE_NGS2_VOICE_PARAM_MATRIX_LEVELS, Ngs2VoiceMatrixLevelsParam{{}, 0, 2, levels}); + Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_MATRIX, Ngs2VoicePortMatrixParam{{}, 0, 0}); + Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, 0.5f}); + Require(sceNgs2VoiceRunCommands(sampler, &play, 1) == SCE_NGS2_OK); + + std::vector out(Grain * 2, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + for (std::uint32_t i = 0; i < Grain; i++) Require(out[i * 2] == 0.25f && out[i * 2 + 1] == 0.125f); + + Ngs2SubmixerVoiceState state{}; + Require(sceNgs2VoiceGetState(submixer, &state.voice_state, sizeof(state)) == SCE_NGS2_OK); + Require(state.voice_state.state_flags == (SCE_NGS2_VOICE_STATE_FLAG_INUSE | SCE_NGS2_VOICE_STATE_FLAG_PLAYING)); + + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + for (float sample : out) Require(sample == 0.0f); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); +} + +static int allocations = 0; +static std::int32_t APS5_VABI Allocate(Ngs2ContextBufferInfo* info) { + Require(info->host_buffer == nullptr && info->host_buffer_size != 0 && info->user_data == 9); + info->host_buffer = std::calloc(1, info->host_buffer_size); + allocations++; + return SCE_NGS2_OK; +} +static std::int32_t APS5_VABI Release(Ngs2ContextBufferInfo* info) { + Require(info->host_buffer != nullptr && info->user_data == 9); + std::free(info->host_buffer); + allocations--; + return SCE_NGS2_OK; +} + +static void TestAllocator() { + const Ngs2BufferAllocator allocator{Allocate, Release, 9}; + uintptr_t system = 0; + Require(sceNgs2SystemCreateWithAllocator(nullptr, &allocator, &system) == SCE_NGS2_OK && allocations == 1); + uintptr_t sampler = 0; + uintptr_t master = 0; + Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_SAMPLER, nullptr, &allocator, &sampler) == SCE_NGS2_OK); + Require(sceNgs2RackCreateWithAllocator(system, SCE_NGS2_RACK_ID_MASTERING, nullptr, &allocator, &master) == SCE_NGS2_OK && allocations == 3); + Ngs2ContextBufferInfo released{}; + Require(sceNgs2RackDestroy(sampler, &released) == SCE_NGS2_OK && allocations == 2 && released.host_buffer == nullptr); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK && allocations == 0); + Require(sceNgs2RackDestroy(master, nullptr) == SCE_NGS2_ERROR_INVALID_RACK_HANDLE); +} + +int main() { + TestErrorsAndInfo(); + TestPcmBlockEnd(); + TestPitchAndRepeat(); + TestSubmixerMatrix(); + TestAllocator(); + return 0; +} diff --git a/core/libs/prx/libSceNgs2/Export.cpp b/core/libs/prx/libSceNgs2/Export.cpp index 43ff3d5cc..fb34206c2 100644 --- a/core/libs/prx/libSceNgs2/Export.cpp +++ b/core/libs/prx/libSceNgs2/Export.cpp @@ -5,199 +5,6 @@ extern "C" { -int APS5_VABI sceNgs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos, uint32_t num_samples, Ngs2WaveformBlock* block) { - (void)format; - (void)sample_pos; - (void)num_samples; - (void)block; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib, uint32_t flags) { - (void)listener; - (void)source; - (void)out_attrib; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* out_work, uint32_t flags) { - (void)param; - (void)out_work; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) { - (void)out_listener_param; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) { - (void)out_source_param; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix) { - (void)work; - (void)params; - (void)num_params; - (void)matrix_format; - (void)out_volume_matrix; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { - (void)work; - (void)speaker_angles; - (void)unit_angle; - (void)num_speakers; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) { - (void)data; - (void)data_size; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { - (void)system_handle; - (void)rack_id; - (void)option; - (void)buffer_info; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { - (void)system_handle; - (void)rack_id; - (void)option; - (void)allocator; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info) { - (void)rack_handle; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id, uintptr_t* handle) { - (void)rack_handle; - (void)voice_id; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackLock(uintptr_t rack_handle) { - (void)rack_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { - (void)rack_id; - (void)option; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2RackUnlock(uintptr_t rack_handle) { - (void)rack_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { - (void)option; - (void)buffer_info; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { - (void)option; - (void)allocator; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info) { - (void)system_handle; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info, size_t info_size) { - (void)system_handle; - (void)info; - (void)info_size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) { - (void)option; - (void)buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) { - (void)system_handle; - (void)buffer_info; - (void)num_buffer_info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples) { - (void)system_handle; - (void)num_samples; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { - (void)voice_handle; - (void)param_list; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { - (void)voice_handle; - (void)state; - (void)state_size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNgs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { - (void)voice_handle; - (void)state_flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} +APS5_DUMMY_FUN }