// Vita3K emulator project // Copyright (C) 2022 Vita3K team // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along // with this program; if not, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. #include #include #include #include "SceNgs.h" #define SCE_NGS_MAX_SYSTEM_CHANNELS 2 using SceNgsSynthSystemHandle = Ptr; using SceNgsRackHandle = Ptr; using SceNgsVoiceHandle = Ptr; using SceNgsPatchHandle = Ptr; struct SceNgsVolumeMatrix { SceFloat32 matrix[SCE_NGS_MAX_SYSTEM_CHANNELS][SCE_NGS_MAX_SYSTEM_CHANNELS]; }; struct SceNgsPatchAudioPropInfo { SceInt32 out_channels; SceInt32 in_channels; SceNgsVolumeMatrix volume_matrix; }; struct SceNgsVoiceInfo { SceUInt32 voice_state; SceUInt32 num_modules; SceUInt32 num_inputs; SceUInt32 num_outputs; SceUInt32 num_patches_per_output; SceUInt32 update_passed; }; static_assert(sizeof(SceNgsPatchAudioPropInfo) == 24); struct SceNgsPatchDeliveryInfo { SceNgsVoiceHandle source_voice_handle; SceInt32 output_index; SceInt32 output_subindex; SceNgsVoiceHandle dest_voice_handle; SceInt32 input_index; }; static_assert(sizeof(SceNgsPatchDeliveryInfo) == 20); static constexpr SceUInt32 SCE_NGS_OK = 0; static constexpr SceUInt32 SCE_NGS_ERROR = 0x804A0001; static constexpr SceUInt32 SCE_NGS_ERROR_INVALID_ARG = 0x804A0002; static constexpr SceUInt32 SCE_NGS_ERROR_INVALID_STATE = 0x804A0010; static constexpr SceUInt32 SCE_NGS_ERROR_PARAM_OUT_OF_RANGE = 0x804A0009; static constexpr SceUInt32 SCE_NGS_SIZE_MISMATCH = 0x804A000D; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_AVAILABLE = 0; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_ACTIVE = 1 << 0; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_FINALIZE = 1 << 2; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_UNLOADING = 1 << 3; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_PENDING = 1 << 4; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_PAUSED = 1 << 5; static constexpr SceUInt32 SCE_NGS_VOICE_STATE_KEY_OFF = 1 << 6; static constexpr SceUInt32 SCE_NGS_MODULE_FLAG_NOT_BYPASSED = 0; static constexpr SceUInt32 SCE_NGS_MODULE_FLAG_BYPASSED = 2; static constexpr SceUInt32 SCE_NGS_VOICE_INIT_BASE = 0; static constexpr SceUInt32 SCE_NGS_VOICE_INIT_ROUTING = 1; static constexpr SceUInt32 SCE_NGS_VOICE_INIT_PRESET = 2; static constexpr SceUInt32 SCE_NGS_VOICE_INIT_CALLBACKS = 4; static constexpr SceUInt32 SCE_NGS_VOICE_INIT_ALL = 7; static constexpr SceInt32 SCE_NGS_SAMPLE_OFFSET_FROM_AT9_HEADER = 1 << 31; EXPORT(int, sceNgsAT9GetSectionDetails, std::uint32_t samples_start, const std::uint32_t num_samples, const std::uint32_t config_data, ngs::atrac9::SkipBufferInfo *info) { if (host.cfg.current_config.disable_ngs) { return -1; } if (!info) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } // Check magic! if ((config_data & 0xFF) != 0xFE && info) { return RET_ERROR(SCE_NGS_ERROR); } samples_start &= ~SCE_NGS_SAMPLE_OFFSET_FROM_AT9_HEADER; get_buffer_parameter(samples_start, num_samples, config_data, *info); return 0; } EXPORT(int, sceNgsModuleGetNumPresets) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsModuleGetPreset) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsPatchCreateRouting, ngs::PatchSetupInfo *patch_info, SceNgsPatchHandle *handle) { if (host.cfg.current_config.disable_ngs) { return 0; } if (!patch_info || !handle) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); // Make the scheduler order this right based on dependencies request ngs::Voice *source = patch_info->source.get(host.mem); if (!source) return RET_ERROR(SCE_NGS_ERROR); LOG_TRACE("Patching 0x{:X}:{}:{} to 0x{:X}:{}", patch_info->source.address(), patch_info->source_output_index, patch_info->source_output_index, patch_info->dest.address(), patch_info->dest_input_index); *handle = source->rack->system->voice_scheduler.patch(host.mem, patch_info); if (!*handle) { return RET_ERROR(SCE_NGS_ERROR); } return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsPatchGetInfo, SceNgsPatchHandle patch_handle, SceNgsPatchAudioPropInfo *prop_info, SceNgsPatchDeliveryInfo *deli_info) { // Always stereo if (prop_info) { prop_info->in_channels = 2; prop_info->out_channels = 2; } if (host.cfg.current_config.disable_ngs) return SCE_NGS_OK; ngs::Patch *patch = patch_handle.get(host.mem); if (!patch) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (prop_info) { prop_info->volume_matrix.matrix[0][0] = patch->volume_matrix[0][0]; prop_info->volume_matrix.matrix[0][1] = patch->volume_matrix[0][1]; prop_info->volume_matrix.matrix[1][0] = patch->volume_matrix[1][0]; prop_info->volume_matrix.matrix[1][1] = patch->volume_matrix[1][1]; } if (deli_info) { deli_info->input_index = patch->dest_index; deli_info->output_index = patch->output_index; deli_info->output_subindex = patch->output_sub_index; deli_info->source_voice_handle = Ptr(patch->source, host.mem); deli_info->dest_voice_handle = Ptr(patch->dest, host.mem); } return SCE_NGS_OK; } EXPORT(int, sceNgsPatchRemoveRouting, SceNgsPatchHandle patch_handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Patch *patch = patch_handle.get(host.mem); if (!patch_handle.valid(host.mem) || !patch) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (!patch->source->remove_patch(host.mem, patch_handle)) { return RET_ERROR(SCE_NGS_ERROR); } return 0; } EXPORT(int, sceNgsRackGetRequiredMemorySize, SceNgsSynthSystemHandle sys_handle, ngs::RackDescription *description, uint32_t *size) { if (host.cfg.current_config.disable_ngs) { *size = 1; return 0; } *size = ngs::Rack::get_required_memspace_size(host.mem, description); return 0; } EXPORT(SceUInt32, sceNgsRackGetVoiceHandle, SceNgsRackHandle rack_handle, const std::uint32_t index, SceNgsVoiceHandle *voice_handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Rack *rack = rack_handle.get(host.mem); if (!rack || !voice_handle) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (index >= rack->voices.size()) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } *voice_handle = rack->voices[index]; return SCE_NGS_OK; } EXPORT(SceUInt32, sceNgsRackInit, SceNgsSynthSystemHandle sys_handle, ngs::BufferParamsInfo *info, const ngs::RackDescription *description, SceNgsRackHandle *handle) { if (host.cfg.current_config.disable_ngs) { return 0; } assert(sys_handle); assert(info); assert(description); ngs::System *system = sys_handle.get(host.mem); if (!ngs::init_rack(host.ngs, host.mem, system, info, description)) { return RET_ERROR(SCE_NGS_ERROR); } *handle = info->data.cast(); return SCE_NGS_OK; } EXPORT(int, sceNgsRackRelease) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsRackSetParamErrorCallback) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsSystemGetRequiredMemorySize, ngs::SystemInitParameters *params, uint32_t *size) { if (host.cfg.current_config.disable_ngs) { *size = 1; return 0; } *size = ngs::System::get_required_memspace_size(params); // System struct size return 0; } EXPORT(SceUInt32, sceNgsSystemInit, Ptr memspace, const std::uint32_t memspace_size, ngs::SystemInitParameters *params, SceNgsSynthSystemHandle *handle) { if (host.cfg.current_config.disable_ngs) { return 0; } if (!ngs::init_system(host.ngs, host.mem, params, memspace, memspace_size)) { return RET_ERROR(SCE_NGS_ERROR); // TODO: Better error code } *handle = memspace.cast(); return SCE_NGS_OK; } EXPORT(int, sceNgsSystemLock) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsSystemRelease) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsSystemSetFlags) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsSystemSetParamErrorCallback) { return UNIMPLEMENTED(); } EXPORT(int, sceNgsSystemUnlock) { return UNIMPLEMENTED(); } EXPORT(SceUInt32, sceNgsSystemUpdate, SceNgsSynthSystemHandle handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::System *sys = handle.get(host.mem); sys->voice_scheduler.update(host.kernel, host.mem, thread_id); return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceBypassModule, SceNgsVoiceHandle voice_handle, const SceUInt32 module, const SceUInt32 bypass_flag) { if (host.cfg.current_config.disable_ngs) { return 0; } if (!voice_handle) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); ngs::Voice *voice = voice_handle.get(host.mem); ngs::ModuleData *storage = voice->module_storage(module); if (!storage) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); // no need to lock a mutex for this storage->is_bypassed = (bypass_flag & SCE_NGS_MODULE_FLAG_BYPASSED); return SCE_NGS_OK; } EXPORT(Ptr, sceNgsVoiceDefGetAtrac9Voice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_ATRAC9); } EXPORT(Ptr, sceNgsVoiceDefGetCompressorBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_COMPRESSOR); } EXPORT(Ptr, sceNgsVoiceDefGetCompressorSideChainBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SIDE_CHAIN_COMPRESSOR); } EXPORT(Ptr, sceNgsVoiceDefGetDelayBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_DELAY); } EXPORT(Ptr, sceNgsVoiceDefGetDistortionBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_DISTORTION); } EXPORT(Ptr, sceNgsVoiceDefGetEnvelopeBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_ENVELOPE); } EXPORT(Ptr, sceNgsVoiceDefGetEqBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_EQUALIZATION); } EXPORT(Ptr, sceNgsVoiceDefGetMasterBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_MASTER); } EXPORT(Ptr, sceNgsVoiceDefGetMixerBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_MIXER); } EXPORT(Ptr, sceNgsVoiceDefGetPauserBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_PAUSER); } EXPORT(Ptr, sceNgsVoiceDefGetPitchShiftBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_PITCH_SHIFT); } EXPORT(Ptr, sceNgsVoiceDefGetReverbBuss) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_REVERB); } EXPORT(Ptr, sceNgsVoiceDefGetSasEmuVoice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SAS_EMULATION); } EXPORT(Ptr, sceNgsVoiceDefGetScreamAtrac9Voice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SCREAM_ATRAC9); } EXPORT(Ptr, sceNgsVoiceDefGetScreamVoice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SCREAM); } EXPORT(Ptr, sceNgsVoiceDefGetSimpleAtrac9Voice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SIMPLE_ATRAC9); } EXPORT(Ptr, sceNgsVoiceDefGetSimpleVoice) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_SIMPLE); } EXPORT(Ptr, sceNgsVoiceDefGetTemplate1) { if (host.cfg.current_config.disable_ngs) { return Ptr(0); } return get_voice_definition(host.ngs, host.mem, ngs::BussType::BUSS_NORMAL_PLAYER); } static SceUInt32 ngsVoiceStateFromHLEState(const ngs::Voice *voice) { SceUInt32 state; switch (voice->state) { case ngs::VoiceState::VOICE_STATE_AVAILABLE: state = SCE_NGS_VOICE_STATE_AVAILABLE; break; case ngs::VoiceState::VOICE_STATE_ACTIVE: state = SCE_NGS_VOICE_STATE_ACTIVE; break; case ngs::VoiceState::VOICE_STATE_FINALIZING: state = SCE_NGS_VOICE_STATE_FINALIZE; break; case ngs::VoiceState::VOICE_STATE_UNLOADING: state = SCE_NGS_VOICE_STATE_UNLOADING; break; default: assert(false && "Invalid voice state to translate"); return 0; } if (voice->is_pending) state |= SCE_NGS_VOICE_STATE_PENDING; if (voice->is_paused) state |= SCE_NGS_VOICE_STATE_PAUSED; if (voice->is_keyed_off) state |= SCE_NGS_VOICE_STATE_KEY_OFF; return state; } EXPORT(SceInt32, sceNgsVoiceGetInfo, SceNgsVoiceHandle handle, SceNgsVoiceInfo *info) { if (host.cfg.current_config.disable_ngs) return SCE_NGS_OK; ngs::Voice *voice = handle.get(host.mem); if (!voice || !info) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } const std::lock_guard guard(*voice->voice_mutex); info->voice_state = ngsVoiceStateFromHLEState(voice); info->num_modules = static_cast(voice->datas.size()); info->num_inputs = static_cast(voice->inputs.inputs.size()); info->num_outputs = voice->rack->vdef->output_count(); info->num_patches_per_output = static_cast(voice->rack->patches_per_output); info->update_passed = voice->frame_count; return SCE_NGS_OK; } EXPORT(int, sceNgsVoiceGetModuleBypass, SceNgsVoiceHandle voice_handle, const SceUInt32 module, SceUInt32 *bypass_flag) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); ngs::ModuleData *storage = voice->module_storage(module); if (!storage) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); if (storage->is_bypassed) *bypass_flag = SCE_NGS_MODULE_FLAG_BYPASSED; else *bypass_flag = SCE_NGS_MODULE_FLAG_NOT_BYPASSED; return SCE_NGS_OK; } EXPORT(int, sceNgsVoiceGetModuleType) { return UNIMPLEMENTED(); } EXPORT(SceInt32, sceNgsVoiceGetOutputPatch, SceNgsVoiceHandle voice_handle, const SceInt32 output_index, const SceInt32 output_subindex, SceNgsPatchHandle *handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice || !handle) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if ((output_subindex < 0) || (output_index < 0)) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if ((output_index >= static_cast(voice->rack->vdef->output_count())) || (output_subindex >= voice->rack->patches_per_output)) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } *handle = voice->patches[output_index][output_subindex]; if (!(*handle) || (handle->get(host.mem))->output_sub_index == -1) { LOG_WARN("Getting non-existen output patch port {}:{}", output_index, output_subindex); *handle = nullptr; } return 0; } EXPORT(int, sceNgsVoiceGetParamsOutOfRange) { return UNIMPLEMENTED(); } EXPORT(SceInt32, sceNgsVoiceGetStateData, SceNgsVoiceHandle voice_handle, const SceUInt32 module, void *mem, const SceUInt32 mem_size) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); ngs::ModuleData *storage = voice->module_storage(module); if (!storage) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } std::memcpy(mem, storage->voice_state_data.data(), std::min(mem_size, storage->voice_state_data.size())); return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceInit, SceNgsVoiceHandle voice_handle, const ngs::VoicePreset *preset, const SceUInt32 init_flags) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); if (voice->state == ngs::VoiceState::VOICE_STATE_ACTIVE) return RET_ERROR(SCE_NGS_ERROR_INVALID_STATE); std::lock_guard guard(*voice->voice_mutex); if (init_flags == SCE_NGS_VOICE_INIT_BASE || init_flags == SCE_NGS_VOICE_INIT_ALL) { voice->state = ngs::VoiceState::VOICE_STATE_AVAILABLE; } if (init_flags & SCE_NGS_VOICE_INIT_ROUTING) { // reset all patches for (auto &patches : voice->patches) { for (auto &patch : patches) { if (patch) { patch.get(host.mem)->output_sub_index = -1; } } } } if (init_flags & SCE_NGS_VOICE_INIT_PRESET) { if (!preset) { STUBBED("Default preset not implemented"); } else if (!voice->set_preset(host.mem, preset)) { return RET_ERROR(SCE_NGS_ERROR); } } if (init_flags & SCE_NGS_VOICE_INIT_CALLBACKS) { for (auto &module_data : voice->datas) { module_data.callback = Ptr(); } } return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceKeyOff, SceNgsVoiceHandle voice_handle) { if (host.cfg.current_config.disable_ngs) { return SCE_NGS_OK; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } voice->is_keyed_off = true; voice->rack->system->voice_scheduler.off(voice); // call the finish callback, I got no idea what the module id should be in this case voice->invoke_callback(host.kernel, host.mem, thread_id, voice->finished_callback, voice->finished_callback_user_data, 0); voice->is_keyed_off = false; voice->rack->system->voice_scheduler.stop(voice); return SCE_NGS_OK; } EXPORT(int, sceNgsVoiceKill, SceNgsVoiceHandle voice_handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } voice->rack->system->voice_scheduler.stop(voice); return 0; } EXPORT(SceUInt32, sceNgsVoiceLockParams, SceNgsVoiceHandle voice_handle, SceUInt32 module, SceUInt32 unk2, Ptr buf) { if (host.cfg.current_config.disable_ngs) { auto *buffer_info = buf.get(host.mem); buffer_info->data = alloc(host.mem, 10, "SceNgs buffer stub"); buffer_info->size = 10; return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } ngs::ModuleData *data = voice->module_storage(module); if (!data) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } ngs::BufferParamsInfo *info = data->lock_params(host.mem); if (!info) { return RET_ERROR(SCE_NGS_ERROR); } *(buf.get(host.mem)) = *info; return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoicePatchSetVolume, SceNgsPatchHandle patch_handle, const SceInt32 output_channel, const SceInt32 input_channel, const SceFloat32 vol) { if (host.cfg.current_config.disable_ngs) return SCE_NGS_OK; ngs::Patch *patch = patch_handle.get(host.mem); if (!patch || patch->output_sub_index == -1) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); patch->volume_matrix[output_channel][input_channel] = vol; return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoicePatchSetVolumes, SceNgsPatchHandle patch_handle, const SceInt32 output_channel, const SceFloat32 *volumes, const SceInt32 vols) { if (host.cfg.current_config.disable_ngs) return SCE_NGS_OK; ngs::Patch *patch = patch_handle.get(host.mem); if (!patch || patch->output_sub_index == -1) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); for (int i = 0; i < std::min(vols, 2); i++) patch->volume_matrix[output_channel][i] = volumes[i]; return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoicePatchSetVolumesMatrix, SceNgsPatchHandle patch_handle, const SceNgsVolumeMatrix *matrix) { if (host.cfg.current_config.disable_ngs) return 0; ngs::Patch *patch = patch_handle.get(host.mem); if (!patch || patch->output_sub_index == -1) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); patch->volume_matrix[0][0] = matrix->matrix[0][0]; patch->volume_matrix[0][1] = matrix->matrix[0][1]; patch->volume_matrix[1][0] = matrix->matrix[1][0]; patch->volume_matrix[1][1] = matrix->matrix[1][1]; return SCE_NGS_OK; } EXPORT(int, sceNgsVoicePause, SceNgsVoiceHandle handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (voice->is_paused) return RET_ERROR(SCE_NGS_ERROR_INVALID_STATE); if (!voice->rack->system->voice_scheduler.pause(voice)) { return RET_ERROR(SCE_NGS_ERROR); } return SCE_NGS_OK; } EXPORT(SceUInt32, sceNgsVoicePlay, SceNgsVoiceHandle handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } voice->is_pending = true; if (!voice->rack->system->voice_scheduler.play(host.mem, voice)) { return RET_ERROR(SCE_NGS_ERROR); } voice->is_pending = false; return SCE_NGS_OK; } EXPORT(int, sceNgsVoiceResume, SceNgsVoiceHandle handle) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = handle.get(host.mem); if (!voice) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (!voice->is_paused) return RET_ERROR(SCE_NGS_ERROR_INVALID_STATE); if (!voice->rack->system->voice_scheduler.resume(host.mem, voice)) { return RET_ERROR(SCE_NGS_ERROR); } return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceSetFinishedCallback, SceNgsVoiceHandle voice_handle, Ptr callback, Ptr user_data) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); voice->finished_callback = callback; voice->finished_callback_user_data = user_data; return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceSetModuleCallback, SceNgsVoiceHandle voice_handle, const SceUInt32 module, Ptr callback, Ptr user_data) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); ngs::ModuleData *storage = voice->module_storage(module); if (!storage) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } storage->callback = callback; storage->user_data = user_data; return 0; } EXPORT(SceInt32, sceNgsVoiceSetParamsBlock, SceNgsVoiceHandle voice_handle, const ngs::ModuleParameterHeader *header, const SceUInt32 size, SceInt32 *num_error) { if (host.cfg.current_config.disable_ngs) return SCE_NGS_OK; ngs::Voice *voice = voice_handle.get(host.mem); if (!voice) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); const std::lock_guard guard(*voice->voice_mutex); *num_error = voice->parse_params_block(host.mem, header, size); if (*num_error == 0) return SCE_NGS_OK; else return RET_ERROR(SCE_NGS_ERROR); } EXPORT(SceInt32, sceNgsVoiceSetPreset, SceNgsVoiceHandle voice_handle, const ngs::VoicePreset *preset) { if (host.cfg.current_config.disable_ngs) { return 0; } if (!voice_handle || !preset) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); ngs::Voice *voice = voice_handle.get(host.mem); const std::lock_guard guard(*voice->voice_mutex); if (!voice->set_preset(host.mem, preset)) return RET_ERROR(SCE_NGS_ERROR); return SCE_NGS_OK; } EXPORT(SceInt32, sceNgsVoiceUnlockParams, SceNgsVoiceHandle handle, const SceUInt32 module_index) { if (host.cfg.current_config.disable_ngs) { return 0; } ngs::Voice *voice = handle.get(host.mem); if (!voice) return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); ngs::ModuleData *data = voice->module_storage(module_index); if (!data) { return RET_ERROR(SCE_NGS_ERROR_INVALID_ARG); } if (!data->unlock_params()) { return RET_ERROR(SCE_NGS_ERROR); } return SCE_NGS_OK; } EXPORT(int, sceSulphaNgsGetDefaultConfig) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsGetNeededMemory) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsInit) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsSetRackName) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsSetSampleName) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsSetSynthName) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsSetVoiceName) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsShutdown) { return UNIMPLEMENTED(); } EXPORT(int, sceSulphaNgsTrace) { return UNIMPLEMENTED(); } BRIDGE_IMPL(sceNgsAT9GetSectionDetails) BRIDGE_IMPL(sceNgsModuleGetNumPresets) BRIDGE_IMPL(sceNgsModuleGetPreset) BRIDGE_IMPL(sceNgsPatchCreateRouting) BRIDGE_IMPL(sceNgsPatchGetInfo) BRIDGE_IMPL(sceNgsPatchRemoveRouting) BRIDGE_IMPL(sceNgsRackGetRequiredMemorySize) BRIDGE_IMPL(sceNgsRackGetVoiceHandle) BRIDGE_IMPL(sceNgsRackInit) BRIDGE_IMPL(sceNgsRackRelease) BRIDGE_IMPL(sceNgsRackSetParamErrorCallback) BRIDGE_IMPL(sceNgsSystemGetRequiredMemorySize) BRIDGE_IMPL(sceNgsSystemInit) BRIDGE_IMPL(sceNgsSystemLock) BRIDGE_IMPL(sceNgsSystemRelease) BRIDGE_IMPL(sceNgsSystemSetFlags) BRIDGE_IMPL(sceNgsSystemSetParamErrorCallback) BRIDGE_IMPL(sceNgsSystemUnlock) BRIDGE_IMPL(sceNgsSystemUpdate) BRIDGE_IMPL(sceNgsVoiceBypassModule) BRIDGE_IMPL(sceNgsVoiceDefGetAtrac9Voice) BRIDGE_IMPL(sceNgsVoiceDefGetCompressorBuss) BRIDGE_IMPL(sceNgsVoiceDefGetCompressorSideChainBuss) BRIDGE_IMPL(sceNgsVoiceDefGetDelayBuss) BRIDGE_IMPL(sceNgsVoiceDefGetDistortionBuss) BRIDGE_IMPL(sceNgsVoiceDefGetEnvelopeBuss) BRIDGE_IMPL(sceNgsVoiceDefGetEqBuss) BRIDGE_IMPL(sceNgsVoiceDefGetMasterBuss) BRIDGE_IMPL(sceNgsVoiceDefGetMixerBuss) BRIDGE_IMPL(sceNgsVoiceDefGetPauserBuss) BRIDGE_IMPL(sceNgsVoiceDefGetPitchShiftBuss) BRIDGE_IMPL(sceNgsVoiceDefGetReverbBuss) BRIDGE_IMPL(sceNgsVoiceDefGetSasEmuVoice) BRIDGE_IMPL(sceNgsVoiceDefGetScreamAtrac9Voice) BRIDGE_IMPL(sceNgsVoiceDefGetScreamVoice) BRIDGE_IMPL(sceNgsVoiceDefGetSimpleAtrac9Voice) BRIDGE_IMPL(sceNgsVoiceDefGetSimpleVoice) BRIDGE_IMPL(sceNgsVoiceDefGetTemplate1) BRIDGE_IMPL(sceNgsVoiceGetInfo) BRIDGE_IMPL(sceNgsVoiceGetModuleBypass) BRIDGE_IMPL(sceNgsVoiceGetModuleType) BRIDGE_IMPL(sceNgsVoiceGetOutputPatch) BRIDGE_IMPL(sceNgsVoiceGetParamsOutOfRange) BRIDGE_IMPL(sceNgsVoiceGetStateData) BRIDGE_IMPL(sceNgsVoiceInit) BRIDGE_IMPL(sceNgsVoiceKeyOff) BRIDGE_IMPL(sceNgsVoiceKill) BRIDGE_IMPL(sceNgsVoiceLockParams) BRIDGE_IMPL(sceNgsVoicePatchSetVolume) BRIDGE_IMPL(sceNgsVoicePatchSetVolumes) BRIDGE_IMPL(sceNgsVoicePatchSetVolumesMatrix) BRIDGE_IMPL(sceNgsVoicePause) BRIDGE_IMPL(sceNgsVoicePlay) BRIDGE_IMPL(sceNgsVoiceResume) BRIDGE_IMPL(sceNgsVoiceSetFinishedCallback) BRIDGE_IMPL(sceNgsVoiceSetModuleCallback) BRIDGE_IMPL(sceNgsVoiceSetParamsBlock) BRIDGE_IMPL(sceNgsVoiceSetPreset) BRIDGE_IMPL(sceNgsVoiceUnlockParams) BRIDGE_IMPL(sceSulphaNgsGetDefaultConfig) BRIDGE_IMPL(sceSulphaNgsGetNeededMemory) BRIDGE_IMPL(sceSulphaNgsInit) BRIDGE_IMPL(sceSulphaNgsSetRackName) BRIDGE_IMPL(sceSulphaNgsSetSampleName) BRIDGE_IMPL(sceSulphaNgsSetSynthName) BRIDGE_IMPL(sceSulphaNgsSetVoiceName) BRIDGE_IMPL(sceSulphaNgsShutdown) BRIDGE_IMPL(sceSulphaNgsTrace)