#include #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; static constexpr float MAX_MATRIX_LEVEL = 4.0f; 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; if (userFxHandler) userFxFlags = 1; } 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; Ngs2ClearReverb(*this); 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() { ++waveformRevision; SetEvent(SCE_NGS2_VOICE_EVENT_STOP_IMM); std::fill(ports.begin(), ports.end(), Ngs2Port{}); for (auto& matrix : matrices) matrix.clear(); filters.clear(); fbwLevel = 1.0f; lfeLevel = 1.0f; channels = 0; sampleRate = 0; waveformType = 0; atrac9 = {}; pitch = 1.0f; phase = 0; blocks.clear(); acceptsBlocks = true; decodedSamples = 0; decodedBytes = 0; waveformEnd = nullptr; userFxHandler = nullptr; userFxData = {}; userFxFlags = 0; } const std::uint8_t* Ngs2StreamEnd(const Ngs2Voice& voice, const Ngs2Block& block) { const auto& last = block.pieces.back(); return last.data + last.frames * voice.channels * sizeof(std::int16_t); } const std::uint8_t* Ngs2Voice::WaveformData() const { if (blocks.empty()) return waveformEnd; const auto& block = blocks.front(); if (waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) return block.data + block.dataCursor; if (block.streaming) { for (const auto& piece : block.pieces) { if (block.cursor >= piece.firstFrame && block.cursor - piece.firstFrame < piece.frames) return piece.data + (block.cursor - piece.firstFrame) * channels * sizeof(std::int16_t); } return Ngs2StreamEnd(*this, block); } return block.data + (static_cast(block.info.num_skip_samples) + block.cursor) * channels * sizeof(std::int16_t); } 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 == 0 || numLevels > NGS2_MAX_CHANNELS * NGS2_MAX_CHANNELS || levels == nullptr) { APS5_INVALID_ARG_EX; } auto& matrix = voice.matrices[matrixId]; matrix.resize(numLevels); for (std::uint32_t i = 0; i < numLevels; i++) matrix[i] = std::clamp(levels[i], -MAX_MATRIX_LEVEL, MAX_MATRIX_LEVEL); } 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 && format.waveform_type != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) { 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; voice.waveformType = format.waveform_type; if (format.waveform_type == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2SetupAtrac9(voice, format); } static void AppendStreamData(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlocksParam& param, std::size_t frameBytes) { 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; }); if (!pendingWaveform) throw std::invalid_argument("NGS2: appending compressed data requires a waveform"); for (std::uint32_t i = 0; i < param.num_blocks; ++i) { auto block = param.blocks[i]; if (block.data_size == 0) continue; block.num_samples = 0; block.num_skip_samples = 0; block.num_repeats = 0; voice.blocks.push_back({static_cast(param.data) + block.data_offset, block}); } return; } if (voice.blocks.empty() || !voice.blocks.back().streaming) throw std::runtime_error("NGS2: appending waveform data to a voice without a streaming waveform is not implemented"); auto& stream = voice.blocks.back(); for (std::uint32_t i = 0; i < param.num_blocks; i++) { const auto& block = param.blocks[i]; if (block.data_size == 0) continue; if (block.data_size % frameBytes != 0 || block.num_skip_samples != 0 || block.num_repeats != 0) { throw std::runtime_error("NGS2: appending a waveform block with skipped samples, repeats or a partial frame is not implemented"); } stream.pieces.push_back({static_cast(param.data) + block.data_offset, stream.availableFrames, block.data_size / frameBytes}); stream.availableFrames += block.data_size / frameBytes; } } static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBlocksParam& param) { constexpr std::uint32_t knownFlags = SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE | SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND | 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.waveformRevision; voice.blocks.clear(); voice.phase = 0; voice.waveformEnd = nullptr; if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) Ngs2RestartAtrac9(voice); } const std::size_t frameBytes = voice.channels * sizeof(std::int16_t); if ((param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_APPEND) != 0) { AppendStreamData(voice, param, frameBytes); voice.acceptsBlocks = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE) != 0; return; } voice.acceptsBlocks = (param.flags & SCE_NGS2_WAVEFORM_BLOCKS_FLAG_CONTINUE) != 0; 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; const std::uint64_t bytes = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? Ngs2Atrac9BlockBytes(voice, block) : (static_cast(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; const bool compressedStream = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 && voice.acceptsBlocks && block.num_repeats == 0; if (block.num_samples == 0 || (bytes > block.data_size && !streaming && !compressedStream)) { 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}); if (streaming) { auto& stream = voice.blocks.back(); stream.streaming = true; stream.availableFrames = block.data_size / frameBytes; stream.pieces.push_back({stream.data, 0, stream.availableFrames}); } } } static void SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) { if (param.index >= voice.rack->maxFilters) APS5_INVALID_ARG_EX; if (voice.filters.size() <= param.index) voice.filters.resize(param.index + 1); auto& filter = voice.filters[param.index]; if (param.type == 0) { filter = {}; return; } if (param.location != 1 || param.type != 1) { throw std::runtime_error("NGS2: sampler filter location " + std::to_string(param.location) + " type " + std::to_string(param.type) + " is not implemented"); } if (!std::isfinite(param.frequency) || param.frequency < 0.0f || !std::isfinite(param.q) || param.q <= 0.0f || !std::isfinite(param.level) || param.level < 0.0f) { APS5_INVALID_ARG_EX; } const double rate = voice.rack->system->option.sample_rate; filter.enabled = true; filter.bypassMask = param.channel_mask; filter.history.resize(voice.channels); if (param.frequency == 0.0f || param.frequency >= rate * 0.5) { filter.b0 = param.frequency == 0.0f ? 0.0 : param.level; filter.b1 = filter.b2 = filter.a1 = filter.a2 = 0.0; return; } const double omega = 2.0 * std::numbers::pi * param.frequency / rate; const double cosine = std::cos(omega); const double alpha = std::sin(omega) / (2.0 * param.q); const double inverseA0 = 1.0 / (1.0 + alpha); filter.b0 = (1.0 - cosine) * 0.5 * inverseA0 * param.level; filter.b1 = 2.0 * filter.b0; filter.b2 = filter.b0; filter.a1 = -2.0 * cosine * inverseA0; filter.a2 = (1.0 - alpha) * inverseA0; } 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; } case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: SetFilter(voice, ParamAs(param)); 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 == (SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX2 >> 16) || rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER) { Ngs2ApplyCustomParam(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_USER_FX) { const auto& fx = ParamAs(param); voice.userFxHandler = fx.handler; voice.userFxData = {fx.user_data0, fx.user_data1, fx.user_data2}; voice.userFxFlags = fx.handler ? 1 : 0; return; } 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_REVERB: if (param.id == SCE_NGS2_REVERB_VOICE_PARAM_SETUP) { const auto& setup = ParamAs(param); if (setup.flags != 0) throw std::runtime_error("NGS2: reverb setup flags are not implemented"); if (setup.num_input_channels != setup.num_output_channels) throw std::runtime_error("NGS2: reverb channel conversion is not implemented"); if (setup.num_output_channels != 1 && setup.num_output_channels != 2 && setup.num_output_channels != 6 && setup.num_output_channels != 8) APS5_INVALID_ARG_EX; SetupMixer(voice, setup.num_output_channels); Ngs2SetupReverb(voice); return; } if (param.id == SCE_NGS2_REVERB_VOICE_PARAM_I3DL2) { Ngs2SetReverbParams(voice, ParamAs(param).i3dl2); return; } break; 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_GAIN) { const auto& gain = ParamAs(param); if (!std::isfinite(gain.fbw_level) || !std::isfinite(gain.lfe_level)) APS5_INVALID_ARG_EX; voice.fbwLevel = gain.fbw_level; voice.lfeLevel = gain.lfe_level; 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.decodedBytes; 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_REVERB: { if (state_size != sizeof(Ngs2VoiceState)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; *state = {}; state->state_flags = voice.stateFlags; 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 sceNgs2VoiceGetPortInfo(uintptr_t voice_handle, uint32_t port, Ngs2VoicePortInfo* info, size_t info_size) { if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; if (info_size != sizeof(Ngs2VoicePortInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; std::lock_guard lock(Ngs2Mutex()); auto& voice = CheckedVoice(voice_handle); const auto& source = PortAt(voice, port); *info = {}; info->matrix_id = source.matrix; info->volume = source.volume; info->dest_handle = reinterpret_cast(source.dest); return SCE_NGS2_OK; } int APS5_VABI sceNgs2VoiceQueryInfo(uintptr_t voice_handle, uint32_t info_id, void* info, size_t info_size) { if (info == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS; std::lock_guard lock(Ngs2Mutex()); const auto& voice = CheckedVoice(voice_handle); switch (info_id) { case SCE_NGS2_VOICE_INFO_CHANNELS: { if (info_size != sizeof(Ngs2VoiceChannelsInfo)) return SCE_NGS2_ERROR_INVALID_OUT_SIZE; *static_cast(info) = {voice.channels, 0}; return SCE_NGS2_OK; } default: throw std::runtime_error("NGS2: voice info " + Ngs2Hex(info_id) + " 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