#include "libs/audio.h" #include #include "common/assert.h" #include "common/common.h" #include "common/emulatorConfig.h" #include "common/logging/log.h" #include "common/stringUtils.h" #include "common/threads.h" #include "kernel/pthread.h" #include "kernel/semaphore.h" #include "libs/ajm/atrac9_decoder.h" #include "libs/audio_internal.h" #include "libs/errno.h" #include "libs/libs.h" #include #include #include #include #include #include #include #include #include #include #include "libatrac9.h" namespace Libs::Audio { namespace { constexpr int AUDIO_OUT_PORT_TYPE_MAIN = 0; constexpr int AUDIO_OUT_PORT_TYPE_BGM = 1; constexpr int AUDIO_OUT_PORT_TYPE_VOICE = 2; constexpr int AUDIO_OUT_PORT_TYPE_PERSONAL = 3; constexpr int AUDIO_OUT_PORT_TYPE_PADSPK = 4; constexpr int AUDIO_OUT_PORT_TYPE_VIBRATION = 10; constexpr int AUDIO_OUT_PORT_TYPE_AUDIO3D = 126; constexpr int AUDIO_OUT_PORT_TYPE_AUX = 127; constexpr uint32_t AUDIO_OUT_PARAM_FORMAT_MASK = 0x000000ffu; constexpr int AUDIO_IN_SILENT_STATE_DEVICE_NONE = 0x1; constexpr uint32_t AUDIO_IN_GRAIN_MAX_ASYNC = 384; static bool audio_out_port_type_is_valid(int type) { return (type >= AUDIO_OUT_PORT_TYPE_MAIN && type <= AUDIO_OUT_PORT_TYPE_PADSPK) || type == AUDIO_OUT_PORT_TYPE_VIBRATION || type == AUDIO_OUT_PORT_TYPE_AUDIO3D || type == AUDIO_OUT_PORT_TYPE_AUX; } } // namespace class Audio { public: using Format = AudioInternal::Format; class Id { public: explicit Id(int id): m_id(id - 1) {} [[nodiscard]] int ToInt() const { return m_id + 1; } [[nodiscard]] bool IsValid() const { return m_id >= 0; } friend class Audio; private: Id() = default; static Id Invalid() { return {}; } static Id Create(int audio_id) { Id r; r.m_id = audio_id; return r; } [[nodiscard]] int GetId() const { return m_id; } int m_id = -1; }; struct OutputParam { Id handle; const void* data = nullptr; }; Audio() = default; virtual ~Audio(); KYTY_CLASS_NO_COPY(Audio); Id AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format); bool AudioOutClose(Id handle); bool AudioOutValid(Id handle); bool AudioOutHasDevice(Id handle); bool AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume); uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true); bool AudioOutGetStatus(Id handle, int* type, int* channels_num); Id AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous); int AudioInClose(Id handle); int AudioInGetSilentState(Id handle); int AudioInInput(Id handle, void* dest); static constexpr int OUT_PORTS_MAX = 32; static constexpr int IN_PORTS_MAX = 8; private: struct PortOut { bool used = false; int type = 0; uint32_t samples_num = 0; uint32_t freq = 0; Format format = Format::Unknown; uint64_t last_output_time = 0; int channels_num = 0; int volume[12] = {}; SDL_AudioStream* stream = nullptr; }; struct PortIn { bool used = false; bool busy = false; bool asynchronous = false; uint32_t samples_num = 0; uint32_t freq = 0; uint32_t bytes_per_frame = 0; uint64_t last_input_time = 0; SDL_AudioStream* stream = nullptr; SDL_AudioDeviceID device = 0; }; PortIn* GetAudioInPort(Id handle); // Caller holds m_mutex. Common::Mutex m_mutex; PortOut m_out_ports[OUT_PORTS_MAX]; PortIn m_in_ports[IN_PORTS_MAX]; static bool FormatIsFloat(Format format); static bool FormatIsStd(Format format); static uint32_t BytesPerSample(Format format); static uint32_t OutputChannels(const PortOut& port); static SDL_AudioFormat SdlFormat(Format format); static bool OpenSdlDevice(PortOut* port); static void CloseSdlDevice(PortOut* port); static void OpenSdlDevice(PortIn* port, Format format); static void CloseSdlDevice(PortIn* port); static const void* PrepareOutputBuffer(const PortOut& port, const void* data, std::vector* buffer); static bool QueueSdlAudio(PortOut* port, const void* data, bool blocking); }; static Audio* g_audio = nullptr; namespace AudioInternal { int AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format) { if (g_audio == nullptr) { return 0; } auto id = g_audio->AudioOutOpen(type, samples_num, freq, format); return id.IsValid() ? id.ToInt() : 0; } void AudioOutClose(int handle) { if (g_audio != nullptr && handle > 0) { (void)g_audio->AudioOutClose(Audio::Id(handle)); } } bool AudioOutHasDevice(int handle) { return g_audio != nullptr && handle > 0 && g_audio->AudioOutHasDevice(Audio::Id(handle)); } uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) { if (g_audio == nullptr || params == nullptr || num == 0) { return 0; } std::vector output_params; output_params.reserve(num); for (uint32_t i = 0; i < num; i++) { if (params[i].handle > 0 && params[i].data != nullptr) { output_params.push_back( Audio::OutputParam {Audio::Id(params[i].handle), params[i].data}); } } if (output_params.empty()) { return 0; } return g_audio->AudioOutOutputs(output_params.data(), static_cast(output_params.size()), blocking); } } // namespace AudioInternal void Initialize() { EXIT_IF(g_audio != nullptr); g_audio = new Audio; } void Shutdown() { delete g_audio; g_audio = nullptr; } Audio::~Audio() { for (auto& port: m_out_ports) { CloseSdlDevice(&port); } for (auto& port: m_in_ports) { CloseSdlDevice(&port); } } bool Audio::FormatIsFloat(Format format) { switch (format) { case Format::FloatMono: case Format::FloatStereo: case Format::Float8Ch: case Format::Float8ChStd: case Format::Float12Ch: return true; default: return false; } } bool Audio::FormatIsStd(Format format) { return (format == Format::Signed16bit8ChStd || format == Format::Float8ChStd); } uint32_t Audio::BytesPerSample(Format format) { return FormatIsFloat(format) ? sizeof(float) : sizeof(int16_t); } uint32_t Audio::OutputChannels(const PortOut& port) { // SDL only takes up to 8 channels. Keep the guest buffer's channel count separate. return std::min(port.channels_num, 8); } SDL_AudioFormat Audio::SdlFormat(Format format) { return FormatIsFloat(format) ? SDL_AUDIO_F32 : SDL_AUDIO_S16; } bool Audio::OpenSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioOut: SDL audio init failed: %s\n", SDL_GetError()); return false; } SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(port->format); desired.channels = static_cast(OutputChannels(*port)); port->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &desired, nullptr, nullptr); if (port->stream == nullptr) { LOGF("AudioOut: SDL_OpenAudioDeviceStream failed: %s\n", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return false; } if (!SDL_ResumeAudioStreamDevice(port->stream)) { LOGF("AudioOut: SDL_ResumeAudioStreamDevice failed: %s\n", SDL_GetError()); CloseSdlDevice(port); return false; } LOGF("AudioOut: opened SDL stream (%d Hz, %d ch, format 0x%04x)\n", desired.freq, desired.channels, static_cast(desired.format)); return true; } void Audio::CloseSdlDevice(PortOut* port) { EXIT_IF(port == nullptr); if (port->stream != nullptr) { SDL_DestroyAudioStream(port->stream); SDL_QuitSubSystem(SDL_INIT_AUDIO); } port->stream = nullptr; } const void* Audio::PrepareOutputBuffer(const PortOut& port, const void* data, std::vector* buffer) { EXIT_IF(data == nullptr); EXIT_IF(buffer == nullptr); const auto frames = port.samples_num; const auto channels = static_cast(port.channels_num); const auto output_channels = OutputChannels(port); const auto bytes_per_sample = BytesPerSample(port.format); const bool reorder = channels >= 8 && !FormatIsStd(port.format); bool volume_changed = false; for (uint32_t ch = 0; ch < channels; ch++) { if (port.volume[ch] != 32768) { volume_changed = true; break; } } if (!volume_changed && !reorder) { return data; } buffer->resize(frames * output_channels * bytes_per_sample); // SDL wants back speakers before side speakers; non-STD PCM has them reversed. static constexpr uint32_t SDL_8CH_MAP[8] = {0, 1, 2, 3, 6, 7, 4, 5}; if (FormatIsFloat(port.format)) { auto* dst = reinterpret_cast(buffer->data()); const auto* src = static_cast(data); for (uint32_t frame = 0; frame < frames; frame++) { for (uint32_t ch = 0; ch < output_channels; ch++) { const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch; dst[frame * output_channels + ch] = src[frame * channels + src_ch] * (static_cast(port.volume[src_ch]) / 32768.0f); } if (channels == 12) { // Add the four top channels to their front/back channels, turning 12 into 8. // SDL handles the rest if the output device has fewer channels. static constexpr uint32_t HEIGHT_DST[4] = {0, 1, 4, 5}; for (uint32_t ch = 0; ch < 4; ch++) { dst[frame * output_channels + HEIGHT_DST[ch]] += src[frame * channels + 8 + ch] * (static_cast(port.volume[8 + ch]) / 32768.0f); } } } } else { auto* dst = reinterpret_cast(buffer->data()); const auto* src = static_cast(data); for (uint32_t frame = 0; frame < frames; frame++) { for (uint32_t ch = 0; ch < output_channels; ch++) { const auto src_ch = reorder ? SDL_8CH_MAP[ch] : ch; int64_t sample = static_cast(src[frame * channels + src_ch]) * port.volume[src_ch] / 32768; if (sample > std::numeric_limits::max()) { sample = std::numeric_limits::max(); } else if (sample < std::numeric_limits::min()) { sample = std::numeric_limits::min(); } dst[frame * output_channels + ch] = static_cast(sample); } } } return buffer->data(); } bool Audio::QueueSdlAudio(PortOut* port, const void* data, bool blocking) { EXIT_IF(port == nullptr); if (port->stream == nullptr || data == nullptr) { return false; } std::vector prepared_buffer; const void* prepared_data = PrepareOutputBuffer(*port, data, &prepared_buffer); const auto output_channels = OutputChannels(*port); const auto prepared_size = BytesPerSample(port->format) * output_channels * port->samples_num; if (blocking) { constexpr uint64_t target_latency_us = 40000; const auto buffer_us = port->freq != 0 ? (1000000ULL * port->samples_num) / port->freq : 0; const auto buffers = buffer_us != 0 ? static_cast((target_latency_us + buffer_us - 1) / buffer_us) : 2u; const auto min_queued_size = prepared_size * std::clamp(buffers, 2u, 16u); const auto wait_start = LibKernel::KernelGetProcessTime(); while (SDL_GetAudioStreamQueued(port->stream) > static_cast(min_queued_size)) { if (LibKernel::KernelGetProcessTime() - wait_start > 200000) { SDL_ClearAudioStream(port->stream); break; } Common::Thread::SleepMicro(1000); } } if (!SDL_PutAudioStreamData(port->stream, prepared_data, static_cast(prepared_size))) { LOGF("AudioOut: SDL_PutAudioStreamData failed: %s\n", SDL_GetError()); return false; } return true; } Audio::Id Audio::AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format) { Common::LockGuard lock(m_mutex); for (int id = 0; id < OUT_PORTS_MAX; id++) { if (!m_out_ports[id].used) { auto& port = m_out_ports[id]; port.used = true; port.type = type; port.samples_num = samples_num; port.freq = freq; port.format = format; port.last_output_time = 0; switch (format) { case Format::Signed16bitMono: case Format::FloatMono: port.channels_num = 1; break; case Format::Signed16bitStereo: case Format::FloatStereo: port.channels_num = 2; break; case Format::Signed16bit8Ch: case Format::Float8Ch: case Format::Signed16bit8ChStd: case Format::Float8ChStd: port.channels_num = 8; break; case Format::Float12Ch: port.channels_num = 12; break; default: EXIT("unknown format"); } for (int i = 0; i < port.channels_num; i++) { port.volume[i] = 32768; } if (type != AUDIO_OUT_PORT_TYPE_VIBRATION) { OpenSdlDevice(&port); } return Id::Create(id); } } return Id::Invalid(); } bool Audio::AudioOutClose(Id handle) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; CloseSdlDevice(&port); port = {}; return true; } return false; } bool Audio::AudioOutValid(Id handle) { Common::LockGuard lock(m_mutex); return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && m_out_ports[handle.GetId()].used); } bool Audio::AudioOutHasDevice(Id handle) { Common::LockGuard lock(m_mutex); return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX && m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].stream != nullptr); } bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; *type = port.type; *channels_num = port.channels_num; return true; } return false; } bool Audio::AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume) { Common::LockGuard lock(m_mutex); if (AudioOutValid(handle)) { auto& port = m_out_ports[handle.GetId()]; for (int i = 0; i < port.channels_num; i++, bitflag >>= 1u) { auto bit = bitflag & 0x1u; if (bit == 1) { port.volume[i] = volume[i]; LOGF("\t port.volume[%d] = volume[%d] (%d)\n", i, i, volume[i]); } } return true; } return false; } uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking) { EXIT_NOT_IMPLEMENTED(num == 0); EXIT_NOT_IMPLEMENTED(!AudioOutValid(params[0].handle)); const auto& first_port = m_out_ports[params[0].handle.GetId()]; uint64_t block_time = (1000000 * first_port.samples_num) / first_port.freq; uint64_t current_time = LibKernel::KernelGetProcessTime(); uint64_t max_wait_time = 0; for (uint32_t i = 0; i < num; i++) { uint64_t next_time = m_out_ports[params[i].handle.GetId()].last_output_time + block_time; uint64_t wait_time = (next_time > current_time ? next_time - current_time : 0); max_wait_time = (wait_time > max_wait_time ? wait_time : max_wait_time); } bool any_port_has_device = false; for (uint32_t i = 0; i < num; i++) { if (m_out_ports[params[i].handle.GetId()].stream != nullptr) { any_port_has_device = true; break; } } // One real output device is enough to pace the whole synchronized batch. Applying the fallback // when a vibration port is present would rate-limit the device-backed ports to exactly 1x and // prevent their SDL queues from building an underrun cushion. if (blocking && max_wait_time != 0 && !any_port_has_device) { Common::Thread::SleepMicro(max_wait_time); } for (uint32_t i = 0; i < num; i++) { auto& port = m_out_ports[params[i].handle.GetId()]; QueueSdlAudio(&port, params[i].data, blocking); } for (uint32_t i = 0; i < num; i++) { m_out_ports[params[i].handle.GetId()].last_output_time = LibKernel::KernelGetProcessTime(); } return first_port.samples_num; } static bool RecordingDevicePresent(SDL_AudioDeviceID device) { int count = 0; SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&count); bool present = false; for (int i = 0; i < count; i++) { if (devices[i] == device) { present = true; break; } } SDL_free(devices); return present; } void Audio::OpenSdlDevice(PortIn* port, Format format) { const auto& name = Config::GetAudioInputDevice(); if (name.empty()) { return; } if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { LOGF("AudioIn: SDL init failed: %s\n", SDL_GetError()); return; } int device_count = 0; SDL_AudioDeviceID* devices = SDL_GetAudioRecordingDevices(&device_count); SDL_AudioDeviceID device = 0; for (int i = 0; i < device_count; i++) { const char* device_name = SDL_GetAudioDeviceName(devices[i]); if (device_name != nullptr && name == device_name) { device = devices[i]; break; } } SDL_free(devices); if (device == 0) { LOGF("AudioIn: cannot find '%s'; using silence\n", name.c_str()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } SDL_AudioSpec desired {}; desired.freq = static_cast(port->freq); desired.format = SdlFormat(format); desired.channels = static_cast(port->bytes_per_frame / BytesPerSample(format)); port->stream = SDL_OpenAudioDeviceStream(device, &desired, nullptr, nullptr); if (port->stream == nullptr) { LOGF("AudioIn: cannot open '%s': %s; using silence\n", name.c_str(), SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } port->device = device; LOGF("AudioIn: opened '%s' (%u Hz, %d ch)\n", name.c_str(), port->freq, desired.channels); } void Audio::CloseSdlDevice(PortIn* port) { if (port->stream != nullptr) { SDL_DestroyAudioStream(port->stream); SDL_QuitSubSystem(SDL_INIT_AUDIO); port->stream = nullptr; port->device = 0; } } Audio::Id Audio::AudioInOpen(uint32_t samples_num, uint32_t freq, Format format, bool asynchronous) { Common::LockGuard lock(m_mutex); for (int id = 0; id < IN_PORTS_MAX; id++) { if (!m_in_ports[id].used) { auto& port = m_in_ports[id]; port = {}; port.used = true; port.asynchronous = asynchronous; port.samples_num = samples_num; port.freq = freq; port.bytes_per_frame = BytesPerSample(format); if (format == Format::Signed16bitStereo || format == Format::FloatStereo) { port.bytes_per_frame *= 2; } OpenSdlDevice(&port, format); return Id::Create(id); } } return Id::Invalid(); } Audio::PortIn* Audio::GetAudioInPort(Id handle) { if (handle.GetId() < 0 || handle.GetId() >= IN_PORTS_MAX || !m_in_ports[handle.GetId()].used) { return nullptr; } return &m_in_ports[handle.GetId()]; } int Audio::AudioInClose(Id handle) { Common::LockGuard lock(m_mutex); auto* port = GetAudioInPort(handle); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->busy) { return AUDIO_IN_ERROR_BUSY; } CloseSdlDevice(port); *port = {}; return OK; } int Audio::AudioInGetSilentState(Id handle) { Common::LockGuard lock(m_mutex); auto* port = GetAudioInPort(handle); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->stream == nullptr || !RecordingDevicePresent(port->device)) { return AUDIO_IN_SILENT_STATE_DEVICE_NONE; } return 0; } int Audio::AudioInInput(Id handle, void* dest) { PortIn snapshot; { Common::LockGuard lock(m_mutex); auto* port = GetAudioInPort(handle); if (port == nullptr) { return AUDIO_IN_ERROR_INVALID_HANDLE; } if (port->busy) { return AUDIO_IN_ERROR_BUSY; } port->busy = true; snapshot = *port; } bool failed = snapshot.stream != nullptr && dest != nullptr && (!RecordingDevicePresent(snapshot.device) || !SDL_ResumeAudioStreamDevice(snapshot.stream)); const uint64_t block_time = (1000000ULL * snapshot.samples_num) / snapshot.freq; uint64_t wait_time = 0; if (snapshot.asynchronous) { if (dest != nullptr) { wait_time = block_time; } } else if (snapshot.last_input_time != 0 && (snapshot.stream == nullptr || dest == nullptr)) { const auto now = LibKernel::KernelGetProcessTime(); const auto next = snapshot.last_input_time + block_time; if (next > now) { wait_time = next - now; } } Common::Thread::SleepMicro(wait_time); uint32_t frames = snapshot.samples_num; bool timed_out = false; if (dest != nullptr) { if (snapshot.stream != nullptr && !failed) { const int requested = static_cast(frames * snapshot.bytes_per_frame); const auto deadline = LibKernel::KernelGetProcessTime() + std::max(20000, block_time * 4); const auto device = SDL_GetAudioStreamDevice(snapshot.stream); int available = SDL_GetAudioStreamAvailable(snapshot.stream); while (!snapshot.asynchronous && available >= 0 && available < requested && device != 0 && !SDL_AudioDevicePaused(device) && LibKernel::KernelGetProcessTime() < deadline) { Common::Thread::SleepMicro(1000); available = SDL_GetAudioStreamAvailable(snapshot.stream); } failed = available < 0 || device == 0 || SDL_AudioDevicePaused(device); timed_out = !snapshot.asynchronous && available < requested; if (!failed && !timed_out) { if (snapshot.asynchronous) { frames = AUDIO_IN_GRAIN_MAX_ASYNC; } const int bytes = SDL_GetAudioStreamData( snapshot.stream, dest, static_cast(frames * snapshot.bytes_per_frame)); failed = bytes < 0; if (!failed) { frames = static_cast(bytes) / snapshot.bytes_per_frame; } } } if (snapshot.stream == nullptr || failed || timed_out) { std::memset(dest, 0, frames * snapshot.bytes_per_frame); } } else if (snapshot.stream != nullptr) { SDL_PauseAudioStreamDevice(snapshot.stream); SDL_ClearAudioStream(snapshot.stream); } { Common::LockGuard lock(m_mutex); auto& port = m_in_ports[handle.GetId()]; if (failed) { LOGF("AudioIn: capture stopped; using silence\n"); CloseSdlDevice(&port); } port.last_input_time = dest != nullptr ? LibKernel::KernelGetProcessTime() : 0; port.busy = false; } return dest != nullptr ? static_cast(frames) : 0; } namespace AudioOut { LIB_NAME("AudioOut", "AudioOut"); struct AudioOutOutputParam { int handle; const void* ptr; }; struct AudioOutPortState { uint16_t output; uint8_t channel; uint8_t reserved1[1]; int16_t volume; uint16_t reroute_counter; uint64_t flag; uint64_t reserved2[2]; }; int KYTY_SYSV_ABI AudioOutInit() { PRINT_NAME(); return OK; } int KYTY_SYSV_ABI AudioOutOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); LOGF("\t user_id = %d\n" "\t type = %d\n" "\t index = %d\n" "\t len = %u\n" "\t freq = %u\n", user_id, type, index, len, freq); if (!audio_out_port_type_is_valid(type)) { return AUDIO_OUT_ERROR_INVALID_PORT_TYPE; } EXIT_NOT_IMPLEMENTED(index != 0); Audio::Format format = Audio::Format::Unknown; const auto format_param = param & AUDIO_OUT_PARAM_FORMAT_MASK; switch (format_param) { case 0: format = Audio::Format::Signed16bitMono; break; case 1: format = Audio::Format::Signed16bitStereo; break; case 2: format = Audio::Format::Signed16bit8Ch; break; case 3: format = Audio::Format::FloatMono; break; case 4: format = Audio::Format::FloatStereo; break; case 5: format = Audio::Format::Float8Ch; break; case 6: format = Audio::Format::Signed16bit8ChStd; break; case 7: format = Audio::Format::Float8ChStd; break; default:; } LOGF("\t param = %u (format=%u, %s)\n", param, format_param, magic_enum::enum_name(format)); EXIT_NOT_IMPLEMENTED(format == Audio::Format::Unknown); EXIT_IF(g_audio == nullptr); auto id = g_audio->AudioOutOpen(type, len, freq, format); if (!id.IsValid()) { return AUDIO_OUT_ERROR_PORT_FULL; } return id.ToInt(); } int KYTY_SYSV_ABI AudioOutClose(int handle) { PRINT_NAME(); if (!g_audio->AudioOutClose(Audio::Id(handle))) { return AUDIO_OUT_ERROR_INVALID_PORT; } return OK; } int KYTY_SYSV_ABI AudioOutGetPortState(int handle, AudioOutPortState* state) { PRINT_NAME(); int type = 0; int channels_num = 0; if (!g_audio->AudioOutGetStatus(Audio::Id(handle), &type, &channels_num)) { return AUDIO_OUT_ERROR_INVALID_PORT; } EXIT_NOT_IMPLEMENTED(state == nullptr); state->reroute_counter = 0; state->volume = 127; switch (type) { case AUDIO_OUT_PORT_TYPE_MAIN: case AUDIO_OUT_PORT_TYPE_BGM: case AUDIO_OUT_PORT_TYPE_AUDIO3D: state->output = 1; state->channel = (channels_num > 2 ? 2 : channels_num); break; case AUDIO_OUT_PORT_TYPE_VOICE: case AUDIO_OUT_PORT_TYPE_PERSONAL: state->output = 0x40; state->channel = 1; break; case AUDIO_OUT_PORT_TYPE_PADSPK: case AUDIO_OUT_PORT_TYPE_VIBRATION: state->output = 4; state->channel = 1; break; case AUDIO_OUT_PORT_TYPE_AUX: state->output = 0x80; state->channel = 0; break; default: EXIT("unknown port type: %d\n", type); } LOGF("\t output = %" PRIu16 "\n" "\t channel = %" PRIu8 "\n", state->output, state->channel); return OK; } int KYTY_SYSV_ABI AudioOutSetVolume(int handle, uint32_t flag, int* vol) { PRINT_NAME(); LOGF("\t handle = %d\n" "\t flag = %u\n", handle, flag); EXIT_IF(g_audio == nullptr); EXIT_NOT_IMPLEMENTED(vol == nullptr); if (!g_audio->AudioOutSetVolume(Audio::Id(handle), flag, vol)) { return AUDIO_OUT_ERROR_INVALID_PORT; } return OK; } int KYTY_SYSV_ABI AudioOutOutputs(AudioOutOutputParam* param, uint32_t num) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(param == nullptr); Audio::OutputParam params[Audio::OUT_PORTS_MAX]; EXIT_IF(g_audio == nullptr); for (uint32_t i = 0; i < num; i++) { params[i].handle = Audio::Id(param[i].handle); params[i].data = param[i].ptr; if (!g_audio->AudioOutValid(params[i].handle)) { return AUDIO_OUT_ERROR_INVALID_PORT; } } return static_cast(g_audio->AudioOutOutputs(params, num)); } int KYTY_SYSV_ABI AudioOutOutput(int handle, const void* ptr) { // EXIT_NOT_IMPLEMENTED(ptr == nullptr); Audio::OutputParam params[1]; EXIT_IF(g_audio == nullptr); params[0].handle = Audio::Id(handle); params[0].data = ptr; if (!g_audio->AudioOutValid(params[0].handle)) { return AUDIO_OUT_ERROR_INVALID_PORT; } return static_cast(g_audio->AudioOutOutputs(params, 1)); } } // namespace AudioOut namespace AudioIn { LIB_NAME("AudioIn", "AudioIn"); static int OpenPort(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param, bool asynchronous) { LOGF("\t user_id = %d\n" "\t type = %d\n" "\t index = %d\n" "\t len = %u\n" "\t freq = %u\n", user_id, type, index, len, freq); if (type != 0 && type != 1) { return AUDIO_IN_ERROR_INVALID_TYPE; } if (index != 0) { return AUDIO_IN_ERROR_INVALID_PARAM; } if (len != 128 && (asynchronous || len != 256)) { return AUDIO_IN_ERROR_INVALID_SIZE; } if (freq != 48000 && (asynchronous || freq != 16000)) { return AUDIO_IN_ERROR_INVALID_FREQ; } Audio::Format format = Audio::Format::Unknown; switch (param) { case 1: format = Audio::Format::Signed16bitMono; break; case 2: format = Audio::Format::Signed16bitStereo; break; case 0x10: case 0x11: format = Audio::Format::FloatMono; break; case 0x12: format = Audio::Format::FloatStereo; break; default: return AUDIO_IN_ERROR_INVALID_PARAM; } LOGF("\t param = %u (%s)\n", param, magic_enum::enum_name(format)); EXIT_IF(g_audio == nullptr); auto id = g_audio->AudioInOpen(len, freq, format, asynchronous); if (!id.IsValid()) { return AUDIO_IN_ERROR_PORT_FULL; } return id.ToInt(); } int KYTY_SYSV_ABI AudioInOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); return OpenPort(user_id, type, index, len, freq, param, false); } int KYTY_SYSV_ABI AudioInHqOpen(int user_id, int type, int index, uint32_t len, uint32_t freq, uint32_t param) { PRINT_NAME(); return OpenPort(user_id, type, index, len, freq, param, true); } int KYTY_SYSV_ABI AudioInClose(int handle) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInClose(Audio::Id(handle)); } int KYTY_SYSV_ABI AudioInInput(int handle, void* dest) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInInput(Audio::Id(handle), dest); } int KYTY_SYSV_ABI AudioInGetSilentState(int handle) { PRINT_NAME(); EXIT_IF(g_audio == nullptr); if (handle <= 0) { return AUDIO_IN_ERROR_INVALID_HANDLE; } return g_audio->AudioInGetSilentState(Audio::Id(handle)); } } // namespace AudioIn namespace VoiceQoS { LIB_NAME("VoiceQoS", "VoiceQoS"); int KYTY_SYSV_ABI VoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_type) { PRINT_NAME(); LOGF("\t mem_block = %016" PRIx64 "\n" "\t mem_size = %" PRIu32 "\n" "\t app_type = %" PRId32 "\n", reinterpret_cast(mem_block), mem_size, app_type); return OK; } } // namespace VoiceQoS namespace Acm { LIB_NAME("Acm", "Acm"); struct AcmBatchInfo { void* buffer; size_t offset; size_t buffer_size; }; struct AcmBatchError { uint32_t reserved[8]; }; static std::atomic_uint32_t g_acm_next_context {1}; static std::atomic_uint32_t g_acm_next_batch {1}; static void acm_advance_batch(AcmBatchInfo* info, size_t bytes) { if (info == nullptr || info->buffer == nullptr || info->buffer_size == 0) { return; } info->offset = std::min(info->buffer_size, info->offset + bytes); } int KYTY_SYSV_ABI AcmContextCreate(AcmContextId* context) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(context == nullptr); *context = g_acm_next_context.fetch_add(1, std::memory_order_relaxed); LOGF("\t context = %" PRIu32 "\n", *context); return OK; } int KYTY_SYSV_ABI AcmContextDestroy(AcmContextId context) { PRINT_NAME(); LOGF("\t context = %" PRIu32 "\n", context); return OK; } int KYTY_SYSV_ABI AcmBatchStartBuffer(AcmContextId context, const void* batch_commands, size_t batch_size, AcmBatchError* batch_error, AcmBatchId* batch) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(batch == nullptr); if (batch_error != nullptr) { std::memset(batch_error, 0, sizeof(AcmBatchError)); } *batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed); return OK; } int KYTY_SYSV_ABI AcmBatchStartBuffers(AcmContextId context, uint32_t batch_info_count, const AcmBatchInfo* const batch_info[], AcmBatchError* batch_error, AcmBatchId* batch) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(batch_info_count != 0 && batch_info == nullptr); EXIT_NOT_IMPLEMENTED(batch == nullptr); if (batch_error != nullptr) { std::memset(batch_error, 0, sizeof(AcmBatchError)); } *batch = g_acm_next_batch.fetch_add(1, std::memory_order_relaxed); return OK; } int KYTY_SYSV_ABI AcmBatchWait(AcmContextId context, AcmBatchId batch, uint32_t timeout) { return OK; } int KYTY_SYSV_ABI AcmBatchJobNotification(AcmBatchInfo* batch_info) { PRINT_NAME(); acm_advance_batch(batch_info, 2 * 16); return OK; } int KYTY_SYSV_ABI AcmConvReverbSharedInput(AcmBatchInfo* batch_info, uint32_t block_count, void* in, uint32_t count, const void* const ir[], const float* gain, void* const out[]) { PRINT_NAME(); (void)block_count; (void)in; (void)count; (void)ir; (void)gain; (void)out; acm_advance_batch(batch_info, 1024); return OK; } int KYTY_SYSV_ABI AcmConvReverbSharedIr(AcmBatchInfo* batch_info, uint32_t block_count, const void* ir, uint32_t count, void* const in[], const float* gain, void* const out[]) { PRINT_NAME(); (void)block_count; (void)ir; (void)count; (void)in; (void)gain; (void)out; acm_advance_batch(batch_info, 1024); return OK; } int KYTY_SYSV_ABI AcmFft(AcmBatchInfo* batch_info, int size, int count, int input_format, const void* const input[], int output_format, void* const output[], uint32_t flags) { PRINT_NAME(); (void)size; (void)count; (void)input_format; (void)input; (void)output_format; (void)output; (void)flags; acm_advance_batch(batch_info, 256); return OK; } int KYTY_SYSV_ABI AcmIfft(AcmBatchInfo* batch_info, int size, int count, int input_format, const void* const input[], int output_format, void* const output[], uint32_t flags) { PRINT_NAME(); (void)size; (void)count; (void)input_format; (void)input; (void)output_format; (void)output; (void)flags; acm_advance_batch(batch_info, 256); return OK; } int KYTY_SYSV_ABI AcmPanner(AcmBatchInfo* batch_info, uint32_t in_count, const float* const in[], uint32_t biquad_count, uint32_t biquad_update_count, uint32_t out_count, const void* const parameter[], void* const state[], const float* const out_init[], float* const out[]) { PRINT_NAME(); (void)in_count; (void)in; (void)biquad_count; (void)biquad_update_count; (void)out_count; (void)parameter; (void)state; (void)out_init; (void)out; acm_advance_batch(batch_info, 512); return OK; } } // namespace Acm namespace Audio3d { LIB_NAME("Audio3d", "Audio3d"); namespace Semaphore = LibKernel::Semaphore; struct Audio3dOpenParameters { size_t size = 0x20; uint32_t granularity = 256; uint32_t rate = 0; uint32_t max_objects = 512; uint32_t queue_depth = 2; uint32_t buffer_mode = 2; uint32_t pad = 0; // uint32_t num_beds; }; struct Audio3dData { enum class State { Empty, Ready, Play }; std::atomic state = State::Empty; }; struct Audio3dInternal { Audio3dData* data = nullptr; Common::Mutex* data_mutex = nullptr; uint64_t data_delay = 0; Semaphore::KernelSema playback_sema = nullptr; Audio3dOpenParameters params = {}; int user_id = 0; float late_reverb_level = 0.0f; float downmix_spread_radius = 2.0f; int downmix_spread_height_aware = 0; uint32_t data_index = 0; bool used = false; std::atomic_bool playback_finished = false; }; constexpr uint32_t MAX_PORTS = 4; static Audio3dInternal g_ports[MAX_PORTS] = {}; static void playback_simulate(void* arg) { auto* port = static_cast(arg); EXIT_IF(port == nullptr); EXIT_IF(port->data_mutex == nullptr); EXIT_IF(port->data == nullptr); for (;;) { int result = Semaphore::KernelWaitSema(port->playback_sema, 1, nullptr); if (result != OK) { break; } Audio3dData* play_data = nullptr; port->data_mutex->Lock(); { for (uint32_t i = 0; i < port->params.queue_depth; i++) { uint32_t index = (port->data_index + i) % port->params.queue_depth; if (port->data[index].state == Audio3dData::State::Play) { play_data = &port->data[index]; break; } } } port->data_mutex->Unlock(); EXIT_IF(play_data == nullptr); if (play_data != nullptr) { // TODO(): Audio output is not yet implemented, so simulate audio delay Common::Thread::SleepMicro(port->data_delay); play_data->state = Audio3dData::State::Empty; } } port->playback_finished = true; } int KYTY_SYSV_ABI Audio3dInitialize(int64_t reserved) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(reserved != 0); return OK; } void KYTY_SYSV_ABI Audio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(sizeof(Audio3dOpenParameters) != 0x20); *p = Audio3dOpenParameters(); } int KYTY_SYSV_ABI Audio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, uint32_t* id) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(parameters == nullptr); EXIT_NOT_IMPLEMENTED(id == nullptr); EXIT_NOT_IMPLEMENTED(parameters->size != 0x20); LOGF("\t user_id = %d\n" "\t granularity = %u\n" "\t rate = %u\n" "\t max_objects = %u\n" "\t queue_depth = %u\n" "\t buffer_mode = %u\n", user_id, parameters->granularity, parameters->rate, parameters->max_objects, parameters->queue_depth, parameters->buffer_mode); EXIT_NOT_IMPLEMENTED(parameters->buffer_mode != 2); EXIT_NOT_IMPLEMENTED(user_id != 255 && user_id != 1); uint32_t port = 0; for (; port < MAX_PORTS; port++) { if (!g_ports[port].used) { break; } } EXIT_NOT_IMPLEMENTED(port >= MAX_PORTS); g_ports[port].user_id = user_id; g_ports[port].params = *parameters; g_ports[port].used = true; EXIT_IF(g_ports[port].data != nullptr); EXIT_IF(g_ports[port].data_mutex != nullptr); EXIT_IF(g_ports[port].playback_sema != nullptr); g_ports[port].data = new Audio3dData[parameters->queue_depth]; g_ports[port].data_index = 0; g_ports[port].data_mutex = new Common::Mutex; g_ports[port].data_delay = (1000000 * static_cast(parameters->granularity)) / 48000; for (uint32_t d = 0; d < parameters->queue_depth; d++) { g_ports[port].data[d].state = Audio3dData::State::Empty; } int result = Semaphore::KernelCreateSema(&g_ports[port].playback_sema, "audio3d_play", 0x01, 0, static_cast(parameters->queue_depth), nullptr); EXIT_NOT_IMPLEMENTED(result != OK); g_ports[port].playback_finished = false; Common::Thread playback_thread(playback_simulate, &g_ports[port]); playback_thread.Detach(); *id = port; return OK; } int KYTY_SYSV_ABI Audio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS); EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used); EXIT_NOT_IMPLEMENTED(attribute == nullptr); LOGF("\t attribute_id = 0x%" PRIx32 "\n", attribute_id); switch (attribute_id) { case 0x10001: EXIT_NOT_IMPLEMENTED(attribute_size != 4); g_ports[port_id].late_reverb_level = *static_cast(attribute); LOGF("\t late_reverb_level = %f\n", g_ports[port_id].late_reverb_level); break; case 0x10002: EXIT_NOT_IMPLEMENTED(attribute_size != 4); g_ports[port_id].downmix_spread_radius = *static_cast(attribute); LOGF("\t downmix_spread_radius = %f\n", g_ports[port_id].downmix_spread_radius); break; case 0x10003: EXIT_NOT_IMPLEMENTED(attribute_size != 4); g_ports[port_id].downmix_spread_height_aware = *static_cast(attribute); LOGF("\t downmix_spread_height_aware = %d\n", g_ports[port_id].downmix_spread_height_aware); break; default: EXIT("unknown attribute: 0x%" PRIx32 "\n", attribute_id); } return OK; } int KYTY_SYSV_ABI Audio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level, uint32_t* queue_available) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS); EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used); EXIT_NOT_IMPLEMENTED(queue_level == nullptr && queue_available == nullptr); auto* port = &g_ports[port_id]; uint32_t empty_num = 0; port->data_mutex->Lock(); { for (uint32_t i = 0; i < port->params.queue_depth; i++) { uint32_t index = (port->data_index + i) % port->params.queue_depth; if (port->data[index].state == Audio3dData::State::Empty) { empty_num++; } else { break; } } } port->data_mutex->Unlock(); EXIT_IF(empty_num > port->params.queue_depth); LOGF("\t queue_available = %u\n", empty_num); if (queue_level != nullptr) { *queue_level = port->params.queue_depth - empty_num; } if (queue_available != nullptr) { *queue_available = empty_num; } return OK; } int KYTY_SYSV_ABI Audio3dPortAdvance(uint32_t port_id) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS); EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used); auto* port = &g_ports[port_id]; port->data_mutex->Lock(); { uint32_t current_index = port->data_index; uint32_t next_index = (current_index + 1) % port->params.queue_depth; if (port->data[current_index].state == Audio3dData::State::Empty) { port->data[current_index].state = Audio3dData::State::Ready; } EXIT_NOT_IMPLEMENTED(port->data[current_index].state != Audio3dData::State::Ready); port->data_index = next_index; LOGF("\t %u -> %u\n", current_index, next_index); } port->data_mutex->Unlock(); return OK; } int KYTY_SYSV_ABI Audio3dPortPush(uint32_t port_id, uint32_t blocking) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(port_id >= MAX_PORTS); EXIT_NOT_IMPLEMENTED(!g_ports[port_id].used); auto* port = &g_ports[port_id]; EXIT_NOT_IMPLEMENTED(blocking != 1); LOGF("\t blocking = %u\n", blocking); int data_num = 0; Audio3dData* first_data = nullptr; port->data_mutex->Lock(); { first_data = port->data + port->data_index; for (uint32_t i = 0; i < port->params.queue_depth; i++) { uint32_t index = (port->data_index + i) % port->params.queue_depth; if (port->data[index].state == Audio3dData::State::Ready) { port->data[index].state = Audio3dData::State::Play; data_num++; } } } port->data_mutex->Unlock(); LOGF("\t push num = %d\n", data_num); if (data_num > 0) { Semaphore::KernelSignalSema(port->playback_sema, data_num); if (blocking == 1) { auto wait_time = port->data_delay / 8; while (first_data->state != Audio3dData::State::Empty) { Common::Thread::SleepMicro(wait_time); } } } return OK; } } // namespace Audio3d namespace Ngs2 { LIB_NAME("Ngs2", "Ngs2"); constexpr int32_t NGS2_ERROR_INVALID_OUT_ADDRESS = static_cast(0x804a8010u); constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_DATA = static_cast(0x804a8430u); constexpr int32_t NGS2_ERROR_INVALID_WAVEFORM_FORMAT = static_cast(0x804a8431u); constexpr int32_t NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = static_cast(0x804a8432u); constexpr uint32_t NGS2_WAVEFORM_TYPE_ATRAC9 = 0x40; struct Ngs2SystemOption { size_t size = 0; char name[64] = {}; uintptr_t job_scheduler_options[4] = {}; uint32_t flags = 0; uint32_t max_grain_samples = 0; uint32_t num_grain_samples = 0; uint32_t sample_rate = 0; uint32_t max_voice_channels = 0; uint32_t reserved[5] = {}; }; struct Ngs2RackOption { size_t size = 0; char name[64] = {}; uint32_t flags = 0; uint32_t max_grain_samples = 0; uint32_t max_voices = 0; uint32_t max_input_delay_blocks = 0; uint32_t max_matrices = 0; uint32_t max_ports = 0; uint32_t max_voice_channels = 0; uint32_t max_output_channels = 0; uint32_t reserved[18] = {}; }; struct Ngs2MasteringRackOption { Ngs2RackOption rack_option; uint32_t max_channels = 0; uint32_t num_peak_meter_blocks = 0; }; struct Ngs2SubmixerRackOption { Ngs2RackOption rack_option; uint32_t max_channels = 0; uint32_t max_envelope_points = 0; uint32_t max_filters = 0; uint32_t max_inputs = 0; uint32_t num_peak_meter_blocks = 0; }; struct Ngs2SamplerRackOption { Ngs2RackOption rack_option; uint32_t max_channel_works = 0; uint32_t max_codec_caches = 0; uint32_t max_waveform_blocks = 0; uint32_t max_envelope_points = 0; uint32_t max_filters = 0; uint32_t max_atrac9_decoders = 0; uint32_t max_atrac9_channel_works = 0; uint32_t max_ajm_atrac9_decoders = 0; uint32_t num_peak_meter_blocks = 0; }; struct Ngs2ReverbRackOption { Ngs2RackOption rack_option; uint32_t max_channels = 0; uint32_t reverb_size = 0; }; struct Ngs2CustomModuleOption { uint32_t size = 0; }; struct Ngs2CustomRackModuleInfo { const Ngs2CustomModuleOption* option = nullptr; uint32_t module_id = 0; uint32_t source_buffer_id = 0; uint32_t extra_buffer_id = 0; uint32_t dest_buffer_id = 0; uint32_t state_offset = 0; uint32_t state_size = 0; uint32_t reserved = 0; uint32_t reserved2 = 0; }; struct Ngs2CustomRackPortInfo { uint32_t source_buffer_id = 0; uint32_t reserved = 0; }; struct Ngs2CustomRackOption { Ngs2RackOption rack_option; uint32_t state_size = 0; uint32_t num_buffers = 0; uint32_t num_modules = 0; uint32_t reserved = 0; Ngs2CustomRackModuleInfo module[24]; Ngs2CustomRackPortInfo port[16]; }; struct Ngs2CustomSubmixerRackOption { Ngs2CustomRackOption custom_rack_option; uint32_t max_channels = 0; uint32_t max_inputs = 0; }; struct Ngs2CustomMasteringRackOption { Ngs2CustomRackOption custom_rack_option; uint32_t max_channels = 0; uint32_t max_inputs = 0; }; struct Ngs2CustomSamplerRackOption { Ngs2CustomRackOption custom_rack_option; uint32_t max_channel_works = 0; uint32_t max_waveform_blocks = 0; uint32_t max_atrac9_decoders = 0; uint32_t max_atrac9_channel_works = 0; uint32_t max_ajm_atrac9_decoders = 0; uint32_t max_codec_caches = 0; }; union Ngs2RackOptionUnion { Ngs2RackOption common; Ngs2SamplerRackOption sampler; Ngs2MasteringRackOption mastering; Ngs2SubmixerRackOption submixer; Ngs2ReverbRackOption reverb; Ngs2CustomSubmixerRackOption custom_submixer; Ngs2CustomMasteringRackOption custom_mastering; Ngs2CustomSamplerRackOption custom_sampler; }; struct Ngs2ContextBufferInfo { void* host_buffer = nullptr; size_t host_buffer_size = 0; uintptr_t reserved[5] = {}; uintptr_t user_data = 0; }; struct Ngs2SystemInfo { char name[64] = {}; uintptr_t system_handle = 0; Ngs2ContextBufferInfo buffer_info; uint32_t uid = 0; uint32_t min_grain_samples = 0; uint32_t max_grain_samples = 0; uint32_t state_flags = 0; uint32_t rack_count = 0; float last_render_ratio = 0.0f; uint64_t last_render_tick = 0; uint64_t render_count = 0; uint32_t sample_rate = 0; uint32_t num_grain_samples = 0; }; struct Ngs2RenderBufferInfo { void* buffer = nullptr; size_t buffer_size = 0; uint32_t waveform_type = 0; uint32_t num_channels = 0; }; struct Ngs2WaveformFormat { uint32_t waveform_type = 0; uint32_t num_channels = 0; uint32_t sample_rate = 0; uint32_t config_data = 0; uint32_t frame_margin = 0; uint32_t frame_offset = 0; }; struct Ngs2WaveformBlock { uintptr_t data_offset = 0; size_t data_size = 0; uint32_t num_repeats = 0; uint32_t num_skip_samples = 0; uint32_t num_samples = 0; uint32_t reserved = 0; uintptr_t user_data = 0; }; struct Ngs2WaveformInfo { Ngs2WaveformFormat format; uint32_t data_offset = 0; uint32_t data_size = 0; uint32_t loop_begin_position = 0; uint32_t loop_end_position = 0; uint32_t num_samples = 0; uint32_t audio_unit_size = 0; uint32_t num_audio_unit_samples = 0; uint32_t num_audio_unit_per_frame = 0; uint32_t audio_frame_size = 0; uint32_t num_audio_frame_samples = 0; uint32_t num_delay_samples = 0; uint32_t num_blocks = 0; Ngs2WaveformBlock blocks[4]; }; struct Ngs2PanParam { float angle = 0.0f; float distance = 0.0f; float fbw_level = 0.0f; float lfe_level = 0.0f; }; struct Ngs2PanWork { float speaker_angles[8] = {}; float unit_angle = 0.0f; uint32_t num_speakers = 0; }; struct Ngs2GeomVector { float x = 0.0f; float y = 0.0f; float z = 0.0f; }; struct Ngs2GeomCone { float inner_level = 0.0f; float inner_angle = 0.0f; float outer_level = 0.0f; float outer_angle = 0.0f; }; struct Ngs2GeomRolloff { uint32_t model = 0; float max_distance = 0.0f; float rolloff_factor = 0.0f; float reference_distance = 0.0f; }; struct Ngs2GeomListenerParam { Ngs2GeomVector position; Ngs2GeomVector orient_front; Ngs2GeomVector orient_up; Ngs2GeomVector velocity; float sound_speed = 0.0f; uint32_t reserved[2] = {}; }; struct Ngs2GeomListenerWork { float matrix[4][4] = {}; Ngs2GeomVector velocity; float sound_speed = 0.0f; uint32_t coordinate = 0; uint32_t reserved[3] = {}; }; struct Ngs2GeomSourceParam { Ngs2GeomVector position; Ngs2GeomVector velocity; Ngs2GeomVector direction; Ngs2GeomCone cone; Ngs2GeomRolloff rolloff; float doppler_factor = 0.0f; float fbw_level = 0.0f; float lfe_level = 0.0f; float max_level = 0.0f; float min_level = 0.0f; float radius = 0.0f; uint32_t num_speakers = 0; uint32_t matrix_format = 0; uint32_t reserved[2] = {}; }; struct Ngs2GeomA3dAttribute { Ngs2GeomVector position; float volume = 0.0f; uint32_t reserved[4] = {}; }; struct Ngs2GeomAttribute { float pitch_ratio = 0.0f; float level[64] = {}; Ngs2GeomA3dAttribute a3d_attrib; uint32_t reserved[4] = {}; }; using Ngs2BufferAllocHandler = int32_t KYTY_SYSV_ABI (*)(Ngs2ContextBufferInfo*); using Ngs2BufferFreeHandler = int32_t KYTY_SYSV_ABI (*)(Ngs2ContextBufferInfo*); struct Ngs2BufferAllocator { Ngs2BufferAllocHandler alloc_handler = nullptr; Ngs2BufferFreeHandler free_handler = nullptr; uintptr_t user_data = 0; }; struct Ngs2Internal { Ngs2SystemOption option; Ngs2ContextBufferInfo buffer_info; Ngs2BufferAllocator allocator; Ngs2Internal* next = nullptr; uint64_t render_count = 0; uint32_t uid = 0; Common::Mutex mutex; }; enum class Ngs2RackType { Sampler, Submixer, Mastering, Reverb, CustomSubmixer, CustomMastering, CustomSampler, }; struct Ngs2UserFxContext { void* common; void* param; void* work; uintptr_t user_data; uint32_t max_voices; uint32_t voice_index; uint64_t reserved[4] {}; }; struct Ngs2UserFxProcessContext { float** channels; void* common; const void* param; void* work; void* state; uintptr_t user_data; uint32_t flags; uint32_t input_channels; uint32_t output_channels; uint32_t grain_samples; uint32_t sample_rate; uint32_t reserved {}; uint64_t reserved2[4] {}; }; struct Ngs2UserFxOption { Ngs2CustomModuleOption header; int KYTY_SYSV_ABI (*setup)(Ngs2UserFxContext*); int KYTY_SYSV_ABI (*cleanup)(Ngs2UserFxContext*); uintptr_t control; int KYTY_SYSV_ABI (*process)(Ngs2UserFxProcessContext*); size_t common_size; size_t param_size; size_t work_size; uintptr_t user_data; }; struct Ngs2RackInternal { Ngs2Internal* ngs = nullptr; Ngs2RackInternal* next = nullptr; Ngs2RackType type = Ngs2RackType::Sampler; Ngs2RackOptionUnion option; Ngs2ContextBufferInfo buffer_info; Ngs2BufferAllocator allocator; std::array, 24> common; std::array fx {}; }; enum class Ngs2VoicePlayState { Empty, Playing, Paused, Stopped }; enum class Ngs2VoicePlayEvent { None, Play, Pause, Resume, Stop, StopImm, Kill }; struct Ngs2VoiceInternal { struct Port { Ngs2VoiceInternal* dest = nullptr; uint32_t input = 0; float volume = 1.0f; int32_t matrix = -1; }; struct Block { const uint8_t* data; Ngs2WaveformBlock info; uint32_t cursor = 0; size_t data_cursor = 0; uint32_t skip_remaining = info.num_skip_samples; }; struct Module { std::vector param, work, state; uint32_t flags = 1; }; Ngs2VoicePlayEvent event = Ngs2VoicePlayEvent::None; Ngs2VoicePlayState state = Ngs2VoicePlayState::Empty; Ngs2RackInternal* rack = nullptr; uintptr_t callback = 0; uintptr_t callback_data = 0; uint32_t callback_flags = 0; std::deque blocks; std::vector ports; std::vector> matrices; std::vector modules; std::vector samples; std::vector output_matrix; uint32_t channels = 0; uint32_t output_id = 0; bool rendering = false; bool rendered = false; bool has_samples = false; std::unique_ptr decoder; std::vector decoded_frame; bool accepts_blocks = true; void SetupSampler(const Ngs2WaveformFormat& format) { EXIT_NOT_IMPLEMENTED(format.sample_rate != rack->ngs->option.sample_rate || format.frame_margin != 0 || format.frame_offset != 0); channels = format.num_channels; blocks.clear(); decoder.reset(); decoded_frame.clear(); accepts_blocks = true; if (format.waveform_type == NGS2_WAVEFORM_TYPE_ATRAC9) { decoder = std::make_unique(channels, format.sample_rate, Ajm::AjmSampleEncoding::Float, 0); const auto result = decoder->Initialize(&format.config_data, sizeof(format.config_data)); EXIT_NOT_IMPLEMENTED(result.result != OK); EXIT_NOT_IMPLEMENTED(result.format.channel_num != channels || result.format.sampling_frequency != format.sample_rate); Ajm::AjmSidebandDecAt9CodecInfo info {}; decoder->WriteCodecInfo(&info, sizeof(info), result); EXIT_NOT_IMPLEMENTED(info.frame_samples > std::numeric_limits::max()); decoded_frame.resize(info.frame_samples * channels); } else { EXIT_NOT_IMPLEMENTED(format.waveform_type != 0x12); } } void SetMatrix(uint32_t index, const float* levels, uint32_t count) { EXIT_NOT_IMPLEMENTED(index >= matrices.size()); matrices[index].assign(levels, levels + count); } void SetVolume(uint32_t index, float volume) { EXIT_NOT_IMPLEMENTED(index >= ports.size()); ports[index].volume = volume; } void SetPortMatrix(uint32_t index, int32_t matrix) { EXIT_NOT_IMPLEMENTED(index >= ports.size()); ports[index].matrix = matrix; } void SetEvent(uint32_t id) { switch (id) { case 1: event = Ngs2VoicePlayEvent::Play; break; case 2: event = Ngs2VoicePlayEvent::Stop; break; case 4: event = Ngs2VoicePlayEvent::StopImm; break; case 8: event = Ngs2VoicePlayEvent::Kill; break; case 16: event = Ngs2VoicePlayEvent::Pause; break; case 32: event = Ngs2VoicePlayEvent::Resume; break; default: EXIT("unknown event_id: 0x%08" PRIx32 "\n", id); } } }; struct Ngs2VoiceParamHeader { uint16_t size; int16_t next; uint32_t id; }; struct Ngs2VoiceEventParam { Ngs2VoiceParamHeader header; uint32_t event_id; }; struct Ngs2VoicePatchParam { Ngs2VoiceParamHeader header; uint32_t port; uint32_t dest_input_id; uintptr_t dest_handle; }; struct Ngs2VoiceMatrixLevelsParam { Ngs2VoiceParamHeader header; uint32_t matrix_id; uint32_t num_levels; const float* levels; }; struct Ngs2VoicePortMatrixParam { Ngs2VoiceParamHeader header; uint32_t port; int32_t matrix_id; }; struct Ngs2VoicePortVolumeParam { Ngs2VoiceParamHeader header; uint32_t port; float level; }; struct Ngs2VoicePortDelayParam { Ngs2VoiceParamHeader header; uint32_t port; uint32_t num_samples; }; struct Ngs2VoiceCallbackParam { Ngs2VoiceParamHeader header; uintptr_t callback; uintptr_t callback_data; uint32_t flags; uint32_t reserved; }; struct Ngs2VoiceState { uint32_t state_flags; int32_t error_code; }; struct Ngs2SubmixerVoiceState { Ngs2VoiceState voice_state; float envelope_height; float peak_height; float compressor_height; }; struct Ngs2CustomMasteringVoiceState { Ngs2VoiceState voice_state; uint32_t reserved; uint32_t reserved2; }; struct Ngs2SamplerVoiceState { Ngs2VoiceState voice_state; float envelope_height; float peak_height; uint32_t reserved; uint64_t num_decoded_samples; uint64_t decoded_data_size; uint64_t user_data; const void* waveform_data; }; static Ngs2Internal* g_ngs_list = nullptr; static Ngs2RackInternal* g_racks_list = nullptr; static std::atomic_uint32_t g_next_ngs_uid = 1; static Common::Mutex g_racks_mutex; static_assert(sizeof(Ngs2UserFxContext) == 72); static_assert(sizeof(Ngs2UserFxProcessContext) == 104); static_assert(sizeof(Ngs2UserFxOption) == 72); static_assert(sizeof(Ngs2SystemOption) == 144); static_assert(sizeof(Ngs2SystemInfo) == 184); static_assert(sizeof(Ngs2RackOption) == 176); static_assert(sizeof(Ngs2VoiceState) == 8); static_assert(sizeof(Ngs2SubmixerVoiceState) == 20); static_assert(sizeof(Ngs2CustomMasteringVoiceState) == 16); static_assert(sizeof(Ngs2SamplerVoiceState) == 56); static_assert(sizeof(Ngs2WaveformFormat) == 24); static_assert(sizeof(Ngs2WaveformBlock) == 40); static_assert(sizeof(Ngs2WaveformInfo) == 232); static uint32_t Ngs2GetStateFlags(const Ngs2VoiceInternal* voice) { switch (voice->state) { case Ngs2VoicePlayState::Empty: return 0; case Ngs2VoicePlayState::Playing: return 0x3; case Ngs2VoicePlayState::Paused: return 0x5; case Ngs2VoicePlayState::Stopped: return 0xb; } return 0; } static Ngs2SystemOption Ngs2DefaultSystemOption() { Ngs2SystemOption option {}; option.size = sizeof(Ngs2SystemOption); option.max_grain_samples = 512; option.num_grain_samples = 256; option.sample_rate = 48000; return option; } int KYTY_SYSV_ABI Ngs2SystemResetOption(Ngs2SystemOption* option) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(option == nullptr); *option = Ngs2DefaultSystemOption(); return OK; } static Ngs2Internal* Ngs2CreateSystemInternal(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info) { auto* ngs = new (buffer_info->host_buffer) Ngs2Internal; ngs->option = *option; ngs->buffer_info = *buffer_info; ngs->uid = g_next_ngs_uid.fetch_add(1, std::memory_order_relaxed); ngs->next = g_ngs_list; g_ngs_list = ngs; return ngs; } static bool Ngs2RackIsCustom(Ngs2RackType type) { switch (type) { case Ngs2RackType::CustomSubmixer: case Ngs2RackType::CustomMastering: case Ngs2RackType::CustomSampler: return true; default: return false; } } int KYTY_SYSV_ABI Ngs2SystemQueryBufferSize(const Ngs2SystemOption* option, Ngs2ContextBufferInfo* buffer_info) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buffer_info == nullptr); auto default_option = Ngs2DefaultSystemOption(); if (option == nullptr) { option = &default_option; LOGF("\t option = nullptr, using reset defaults\n"); } EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2SystemOption)); std::memset(buffer_info, 0, sizeof(Ngs2ContextBufferInfo)); buffer_info->host_buffer_size = sizeof(Ngs2Internal); return OK; } int KYTY_SYSV_ABI Ngs2SystemCreate(const Ngs2SystemOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buffer_info == nullptr); EXIT_NOT_IMPLEMENTED(handle == nullptr); EXIT_NOT_IMPLEMENTED(buffer_info->host_buffer == nullptr); EXIT_NOT_IMPLEMENTED(buffer_info->host_buffer_size < sizeof(Ngs2Internal)); auto default_option = Ngs2DefaultSystemOption(); if (option == nullptr) { option = &default_option; LOGF("\t option = nullptr, using reset defaults\n"); } EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2SystemOption)); auto* ngs = Ngs2CreateSystemInternal(option, buffer_info); *handle = reinterpret_cast(ngs); return OK; } static void Ngs2FillDefaultRackOption(uint32_t rack_id, Ngs2RackOptionUnion* option) { EXIT_NOT_IMPLEMENTED(option == nullptr); *option = {}; switch (rack_id) { case 0x1000: option->sampler.rack_option.size = sizeof(Ngs2SamplerRackOption); option->sampler.rack_option.max_grain_samples = 512; option->sampler.rack_option.max_voices = 256; option->sampler.rack_option.max_input_delay_blocks = 0; option->sampler.rack_option.max_matrices = 1; option->sampler.rack_option.max_ports = 8; option->sampler.max_channel_works = 256; option->sampler.max_codec_caches = 32; option->sampler.max_waveform_blocks = 4; option->sampler.max_envelope_points = 4; option->sampler.max_filters = 8; option->sampler.max_atrac9_decoders = 256; option->sampler.max_atrac9_channel_works = 256; option->sampler.max_ajm_atrac9_decoders = 0; option->sampler.num_peak_meter_blocks = 8; break; case 0x2000: option->submixer.rack_option.size = sizeof(Ngs2SubmixerRackOption); option->submixer.rack_option.max_grain_samples = 512; option->submixer.rack_option.max_voices = 1; option->submixer.rack_option.max_input_delay_blocks = 1; option->submixer.rack_option.max_matrices = 1; option->submixer.rack_option.max_ports = 8; option->submixer.max_channels = 8; option->submixer.max_envelope_points = 4; option->submixer.max_filters = 8; option->submixer.max_inputs = 1; option->submixer.num_peak_meter_blocks = 8; break; case 0x2001: option->reverb.rack_option.size = sizeof(Ngs2ReverbRackOption); option->reverb.rack_option.max_grain_samples = 512; option->reverb.rack_option.max_voices = 1; option->reverb.rack_option.max_input_delay_blocks = 1; option->reverb.rack_option.max_matrices = 1; option->reverb.rack_option.max_ports = 8; option->reverb.max_channels = 8; option->reverb.reverb_size = 1; break; case 0x3000: option->mastering.rack_option.size = sizeof(Ngs2MasteringRackOption); option->mastering.rack_option.max_grain_samples = 512; option->mastering.rack_option.max_voices = 1; option->mastering.rack_option.max_input_delay_blocks = 1; option->mastering.rack_option.max_matrices = 0; option->mastering.rack_option.max_ports = 0; option->mastering.max_channels = 8; option->mastering.num_peak_meter_blocks = 8; break; case 0x4002: // FIXME: Temporary PS5 progress fallback. This mirrors Prospero reset helper's // common custom-submixer defaults, but the custom module internals are still stubbed. option->custom_submixer.custom_rack_option.rack_option.size = sizeof(Ngs2CustomSubmixerRackOption); option->custom_submixer.custom_rack_option.rack_option.max_grain_samples = 512; option->custom_submixer.custom_rack_option.rack_option.max_voices = 1; option->custom_submixer.custom_rack_option.rack_option.max_input_delay_blocks = 1; option->custom_submixer.custom_rack_option.rack_option.max_matrices = 1; option->custom_submixer.custom_rack_option.rack_option.max_ports = 8; option->custom_submixer.custom_rack_option.num_buffers = 1; option->custom_submixer.max_channels = 8; option->custom_submixer.max_inputs = 1; break; case 0x4001: option->custom_sampler.custom_rack_option.rack_option.size = sizeof(Ngs2CustomSamplerRackOption); option->custom_sampler.custom_rack_option.rack_option.max_grain_samples = 512; option->custom_sampler.custom_rack_option.rack_option.max_voices = 256; option->custom_sampler.custom_rack_option.rack_option.max_input_delay_blocks = 0; option->custom_sampler.custom_rack_option.rack_option.max_matrices = 1; option->custom_sampler.custom_rack_option.rack_option.max_ports = 8; option->custom_sampler.custom_rack_option.num_buffers = 1; option->custom_sampler.max_channel_works = 256; option->custom_sampler.max_waveform_blocks = 4; option->custom_sampler.max_atrac9_decoders = 256; option->custom_sampler.max_atrac9_channel_works = 256; option->custom_sampler.max_ajm_atrac9_decoders = 0; option->custom_sampler.max_codec_caches = 32; break; default: EXIT("unknown rack_id for default option: 0x%" PRIx32 "\n", rack_id); } } int KYTY_SYSV_ABI Ngs2RackQueryBufferSize(uint32_t rack_id, const Ngs2RackOption* option, Ngs2ContextBufferInfo* buffer_info) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buffer_info == nullptr); Ngs2RackOptionUnion default_option {}; if (option == nullptr) { Ngs2FillDefaultRackOption(rack_id, &default_option); option = &default_option.common; LOGF("\t option = nullptr, using reset defaults for rack_id 0x%" PRIx32 "\n", rack_id); } LOGF("\t rack_id = 0x%" PRIx32 "\n" "\t max_voices = %u\n", rack_id, option->max_voices); buffer_info->host_buffer_size = sizeof(Ngs2RackInternal) + sizeof(Ngs2VoiceInternal) * option->max_voices; return OK; } int KYTY_SYSV_ABI Ngs2SystemCreateWithAllocator(const Ngs2SystemOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(allocator == nullptr); EXIT_NOT_IMPLEMENTED(handle == nullptr); EXIT_NOT_IMPLEMENTED(allocator->alloc_handler == nullptr); EXIT_NOT_IMPLEMENTED(allocator->free_handler == nullptr); auto default_option = Ngs2DefaultSystemOption(); if (option == nullptr) { option = &default_option; LOGF("\t option = nullptr, using reset defaults\n"); } EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2SystemOption)); LOGF("\t name = %.64s\n" "\t flags = %u\n" "\t max_grain_samples = %u\n" "\t num_grain_samples = %u\n" "\t sample_rate = %u\n" "\t max_voice_channels = %u\n" "\t alloc_handler = 0x%016" PRIx64 "\n" "\t free_handler = 0x%016" PRIx64 "\n" "\t user_data = 0x%016" PRIx64 "\n", option->name, option->flags, option->max_grain_samples, option->num_grain_samples, option->sample_rate, option->max_voice_channels, reinterpret_cast(allocator->alloc_handler), reinterpret_cast(allocator->free_handler), static_cast(allocator->user_data)); Ngs2ContextBufferInfo buf {}; buf.host_buffer = nullptr; buf.host_buffer_size = sizeof(Ngs2Internal); buf.user_data = allocator->user_data; int result = allocator->alloc_handler(&buf); EXIT_NOT_IMPLEMENTED(result != OK); EXIT_NOT_IMPLEMENTED(buf.host_buffer == nullptr); auto* ngs = Ngs2CreateSystemInternal(option, &buf); ngs->allocator = *allocator; *handle = reinterpret_cast(ngs); return OK; } int KYTY_SYSV_ABI Ngs2SystemGetInfo(uintptr_t system_handle, Ngs2SystemInfo* info, size_t info_size) { constexpr int32_t ERROR_INVALID_OUT_ADDRESS = static_cast(0x804a8010u); constexpr int32_t ERROR_INVALID_OUT_SIZE = static_cast(0x804a8011u); constexpr int32_t ERROR_INVALID_SYSTEM_HANDLE = static_cast(0x804a8201u); if (info == nullptr) { return ERROR_INVALID_OUT_ADDRESS; } if (info_size != sizeof(Ngs2SystemInfo)) { return ERROR_INVALID_OUT_SIZE; } auto* ngs = reinterpret_cast(system_handle); auto* current = g_ngs_list; while (current != nullptr && current != ngs) { current = current->next; } if (current == nullptr) { return ERROR_INVALID_SYSTEM_HANDLE; } Common::LockGuard lock(ngs->mutex); *info = {}; std::memcpy(info->name, ngs->option.name, sizeof(info->name)); info->system_handle = system_handle; info->buffer_info = ngs->buffer_info; info->uid = ngs->uid; info->min_grain_samples = 64; info->max_grain_samples = ngs->option.max_grain_samples; info->state_flags = 1; info->render_count = ngs->render_count; info->sample_rate = ngs->option.sample_rate; info->num_grain_samples = ngs->option.num_grain_samples; Common::LockGuard racks_lock(g_racks_mutex); for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) { if (rack->ngs == ngs) { info->rack_count++; } } return OK; } int KYTY_SYSV_ABI Ngs2SystemSetGrainSamples(uintptr_t system_handle, uint32_t num_samples) { PRINT_NAME(); LOGF("\t system_handle = 0x%016" PRIx64 "\n" "\t num_samples = %u\n", static_cast(system_handle), num_samples); EXIT_NOT_IMPLEMENTED(system_handle == 0); auto* ngs = reinterpret_cast(system_handle); ngs->option.num_grain_samples = num_samples; return OK; } int KYTY_SYSV_ABI Ngs2SystemDestroy(uintptr_t system_handle, Ngs2ContextBufferInfo* buffer_info) { PRINT_NAME(); LOGF("\t system_handle = 0x%016" PRIx64 "\n", static_cast(system_handle)); if (buffer_info != nullptr) { std::memset(buffer_info, 0, sizeof(Ngs2ContextBufferInfo)); } return OK; } static void Ngs2InitModules(Ngs2RackInternal& rack) { if (!Ngs2RackIsCustom(rack.type)) { return; } const auto& custom = rack.option.custom_sampler.custom_rack_option; EXIT_NOT_IMPLEMENTED(custom.num_buffers != 1); auto* voices = reinterpret_cast(&rack + 1); for (uint32_t m = 0; m < custom.num_modules; ++m) { if (custom.module[m].module_id != 0x1f) { continue; } auto& fx = rack.fx[m]; fx = *reinterpret_cast(custom.module[m].option); rack.common[m].resize(fx.common_size); for (uint32_t i = 0; i < rack.option.common.max_voices; ++i) { auto& module = voices[i].modules[m]; module.param.resize(fx.param_size); module.work.resize(fx.work_size); module.state.resize(custom.module[m].state_size); if (fx.setup != nullptr) { Ngs2UserFxContext context { rack.common[m].data(), module.param.data(), module.work.data(), fx.user_data, rack.option.common.max_voices, i}; EXIT_NOT_IMPLEMENTED(fx.setup(&context) != OK); } } } } int KYTY_SYSV_ABI Ngs2RackCreate(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2ContextBufferInfo* buffer_info, uintptr_t* handle) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(buffer_info == nullptr); EXIT_NOT_IMPLEMENTED(handle == nullptr); EXIT_NOT_IMPLEMENTED(buffer_info->host_buffer == nullptr); EXIT_NOT_IMPLEMENTED(buffer_info->host_buffer_size == 0); EXIT_NOT_IMPLEMENTED(system_handle == 0); Ngs2RackOptionUnion default_option {}; if (option == nullptr) { Ngs2FillDefaultRackOption(rack_id, &default_option); option = &default_option.common; LOGF("\t option = nullptr, using reset defaults for rack_id 0x%" PRIx32 "\n", rack_id); } EXIT_NOT_IMPLEMENTED(option->size < sizeof(Ngs2RackOption)); LOGF("\t rack_id = 0x%" PRIx32 "\n" "\t name = %.64s\n" "\t flags = %u\n" "\t max_grain_samples = %u\n" "\t max_voices = %u\n" "\t max_input_delay_blocks = %u\n" "\t max_matrices = %u\n" "\t max_ports = %u\n" "\t max_voice_channels = %u\n" "\t max_output_channels = %u\n" "\t host_buffer = 0x%016" PRIx64 "\n" "\t host_buffer_size = 0x%016" PRIx64 "\n", rack_id, option->name, option->flags, option->max_grain_samples, option->max_voices, option->max_input_delay_blocks, option->max_matrices, option->max_ports, option->max_voice_channels, option->max_output_channels, reinterpret_cast(buffer_info->host_buffer), static_cast(buffer_info->host_buffer_size)); auto* ngs = reinterpret_cast(system_handle); auto* rack = new (buffer_info->host_buffer) Ngs2RackInternal {}; auto* voices = reinterpret_cast(rack + 1); Common::LockGuard lock(ngs->mutex); switch (rack_id) { case 0x1000: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2SamplerRackOption)); rack->option.sampler = *reinterpret_cast(option); rack->type = Ngs2RackType::Sampler; break; case 0x2000: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2SubmixerRackOption)); rack->option.submixer = *reinterpret_cast(option); rack->type = Ngs2RackType::Submixer; break; case 0x2001: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2ReverbRackOption)); rack->option.reverb = *reinterpret_cast(option); rack->type = Ngs2RackType::Reverb; break; case 0x3000: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2MasteringRackOption)); rack->option.mastering = *reinterpret_cast(option); rack->type = Ngs2RackType::Mastering; break; case 0x4002: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2CustomSubmixerRackOption)); rack->option.custom_submixer = *reinterpret_cast(option); rack->type = Ngs2RackType::CustomSubmixer; break; case 0x4003: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2CustomMasteringRackOption)); rack->option.custom_mastering = *reinterpret_cast(option); rack->type = Ngs2RackType::CustomMastering; break; case 0x4001: EXIT_NOT_IMPLEMENTED(option->size != sizeof(Ngs2CustomSamplerRackOption)); rack->option.custom_sampler = *reinterpret_cast(option); rack->type = Ngs2RackType::CustomSampler; break; default: EXIT("unknown rack_id: 0x%" PRIx32 "\n", rack_id); } LOGF("\t type = %s\n", magic_enum::enum_name(rack->type)); rack->allocator = Ngs2BufferAllocator(); rack->buffer_info = *buffer_info; rack->ngs = ngs; { Common::LockGuard racks_lock(g_racks_mutex); rack->next = g_racks_list; g_racks_list = rack; } for (uint32_t i = 0; i < option->max_voices; i++) { auto& voice = *std::construct_at(voices + i); voice.rack = rack; voice.ports.resize(option->max_ports); voice.matrices.resize(option->max_matrices); if (Ngs2RackIsCustom(rack->type)) { voice.modules.resize(rack->option.custom_sampler.custom_rack_option.num_modules); } } Ngs2InitModules(*rack); *handle = reinterpret_cast(rack); return OK; } int KYTY_SYSV_ABI Ngs2RackCreateWithAllocator(uintptr_t system_handle, uint32_t rack_id, const Ngs2RackOption* option, const Ngs2BufferAllocator* allocator, uintptr_t* handle) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(allocator == nullptr); EXIT_NOT_IMPLEMENTED(handle == nullptr); EXIT_NOT_IMPLEMENTED(allocator->alloc_handler == nullptr); EXIT_NOT_IMPLEMENTED(allocator->free_handler == nullptr); EXIT_NOT_IMPLEMENTED(system_handle == 0); Ngs2RackOptionUnion default_option {}; if (option == nullptr) { Ngs2FillDefaultRackOption(rack_id, &default_option); option = &default_option.common; LOGF("\t option = nullptr, using reset defaults for rack_id 0x%" PRIx32 "\n", rack_id); } EXIT_NOT_IMPLEMENTED(option->size < sizeof(Ngs2RackOption)); LOGF("\t alloc_handler = 0x%016" PRIx64 "\n" "\t free_handler = 0x%016" PRIx64 "\n" "\t user_data = 0x%016" PRIx64 "\n", reinterpret_cast(allocator->alloc_handler), reinterpret_cast(allocator->free_handler), static_cast(allocator->user_data)); Ngs2ContextBufferInfo buf {}; buf.host_buffer = nullptr; buf.host_buffer_size = 0; buf.user_data = allocator->user_data; Ngs2RackQueryBufferSize(rack_id, option, &buf); EXIT_NOT_IMPLEMENTED(buf.host_buffer_size == 0); int result = allocator->alloc_handler(&buf); EXIT_NOT_IMPLEMENTED(result != OK); EXIT_NOT_IMPLEMENTED(buf.host_buffer == nullptr); result = Ngs2RackCreate(system_handle, rack_id, option, &buf, handle); if (result == OK) { auto* rack = static_cast(buf.host_buffer); rack->allocator = *allocator; } return result; } int KYTY_SYSV_ABI Ngs2RackDestroy(uintptr_t rack_handle, Ngs2ContextBufferInfo* buffer_info) { constexpr int32_t ERROR_INVALID_RACK_HANDLE = static_cast(0x804a8202u); PRINT_NAME(); LOGF("\t rack_handle = 0x%016" PRIx64 "\n", static_cast(rack_handle)); if (buffer_info != nullptr) { *buffer_info = {}; } if (rack_handle == 0) { return ERROR_INVALID_RACK_HANDLE; } auto* rack = reinterpret_cast(rack_handle); Ngs2Internal* ngs = nullptr; { Common::LockGuard racks_lock(g_racks_mutex); for (auto* current = g_racks_list; current != nullptr; current = current->next) { if (current == rack) { ngs = current->ngs; break; } } } if (ngs == nullptr) { return ERROR_INVALID_RACK_HANDLE; } Ngs2ContextBufferInfo context_buffer; Ngs2BufferAllocator allocator; { Common::LockGuard lock(ngs->mutex); { Common::LockGuard racks_lock(g_racks_mutex); auto** link = &g_racks_list; while (*link != nullptr && *link != rack) { link = &(*link)->next; } if (*link == nullptr) { return ERROR_INVALID_RACK_HANDLE; } *link = rack->next; } auto* voices = reinterpret_cast(rack + 1); for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) { for (size_t m = 0; m < voices[i].modules.size(); ++m) { const auto& fx = rack->fx[m]; if (fx.cleanup != nullptr) { auto& module = voices[i].modules[m]; Ngs2UserFxContext context {rack->common[m].data(), module.param.data(), module.work.data(), fx.user_data, rack->option.common.max_voices, i}; EXIT_NOT_IMPLEMENTED(fx.cleanup(&context) != OK); } } std::destroy_at(voices + i); } context_buffer = rack->buffer_info; allocator = rack->allocator; std::destroy_at(rack); } if (allocator.free_handler != nullptr) { return allocator.free_handler(&context_buffer); } if (buffer_info != nullptr) { *buffer_info = context_buffer; } return OK; } int KYTY_SYSV_ABI Ngs2RackLock(uintptr_t rack_handle) { PRINT_NAME(); LOGF("\t rack_handle = 0x%016" PRIx64 "\n", static_cast(rack_handle)); EXIT_NOT_IMPLEMENTED(rack_handle == 0); auto* rack = reinterpret_cast(rack_handle); EXIT_NOT_IMPLEMENTED(rack->ngs == nullptr); rack->ngs->mutex.Lock(); return OK; } int KYTY_SYSV_ABI Ngs2RackUnlock(uintptr_t rack_handle) { PRINT_NAME(); LOGF("\t rack_handle = 0x%016" PRIx64 "\n", static_cast(rack_handle)); EXIT_NOT_IMPLEMENTED(rack_handle == 0); auto* rack = reinterpret_cast(rack_handle); EXIT_NOT_IMPLEMENTED(rack->ngs == nullptr); rack->ngs->mutex.Unlock(); return OK; } static void Ngs2ApplyEvent(Ngs2VoiceInternal& voice) { switch (voice.event) { case Ngs2VoicePlayEvent::None: break; case Ngs2VoicePlayEvent::Play: if (voice.state == Ngs2VoicePlayState::Empty) { voice.state = Ngs2VoicePlayState::Playing; } break; case Ngs2VoicePlayEvent::Pause: if (voice.state == Ngs2VoicePlayState::Playing) { voice.state = Ngs2VoicePlayState::Paused; } break; case Ngs2VoicePlayEvent::Resume: if (voice.state == Ngs2VoicePlayState::Paused) { voice.state = Ngs2VoicePlayState::Playing; } break; case Ngs2VoicePlayEvent::Stop: if (voice.state == Ngs2VoicePlayState::Playing) { voice.state = Ngs2VoicePlayState::Stopped; } break; case Ngs2VoicePlayEvent::StopImm: case Ngs2VoicePlayEvent::Kill: voice.state = Ngs2VoicePlayState::Empty; break; } voice.event = Ngs2VoicePlayEvent::None; } static void Ngs2ConsumeSamples(Ngs2VoiceInternal& voice, uint32_t grain) { uint32_t output = 0; while (output < grain && !voice.blocks.empty()) { auto& block = voice.blocks.front(); auto count = std::min(grain - output, block.info.num_samples - block.cursor); const int16_t* pcm = nullptr; const float* decoded = nullptr; if (voice.decoder != nullptr) { const auto skip = std::min(block.skip_remaining, static_cast(voice.decoded_frame.size() / voice.channels)); Ajm::AjmGaplessState gapless; gapless.Set({block.info.num_samples - block.cursor, static_cast(skip), 0}, true); const auto result = voice.decoder->Decode( block.data + block.data_cursor, block.info.data_size - block.data_cursor, voice.decoded_frame.data(), voice.decoded_frame.size() * sizeof(float), false, &gapless); EXIT_NOT_IMPLEMENTED(result.result != OK || result.input_consumed == 0); block.data_cursor += result.input_consumed; block.skip_remaining -= skip - gapless.current.skip_samples; const auto decoded_samples = static_cast(result.output_written / (sizeof(float) * voice.channels)); if (decoded_samples == 0) { continue; } EXIT_NOT_IMPLEMENTED(decoded_samples > count); count = decoded_samples; decoded = voice.decoded_frame.data(); } else { pcm = reinterpret_cast(block.data) + (block.info.num_skip_samples + block.cursor) * voice.channels; } for (uint32_t c = 0; c < voice.channels; ++c) { for (uint32_t i = 0; i < count; ++i) { const auto index = i * voice.channels + c; voice.samples[c * grain + output + i] = decoded != nullptr ? decoded[index] : pcm[index] / 32768.0f; } } output += count; block.cursor += count; if (block.cursor == block.info.num_samples) { struct CallbackInfo { uintptr_t data, voice; uint32_t flag, reserved; uintptr_t user; const void* block_data; size_t size; uint32_t repeats, attributes; } info {voice.callback_data, reinterpret_cast(&voice), 1, 0, block.info.user_data, block.data, block.info.data_size, 0, 0}; static_assert(sizeof(CallbackInfo) == 56); voice.blocks.pop_front(); if (voice.blocks.empty() && !voice.accepts_blocks) { voice.state = Ngs2VoicePlayState::Empty; } if (voice.callback != 0 && (voice.callback_flags & 1u) != 0) { reinterpret_cast(voice.callback)( &info); } } } voice.has_samples = output != 0; } static void Ngs2RenderVoice(Ngs2VoiceInternal& voice, const std::vector& voices, uint32_t grain) { if (voice.rendered) { return; } EXIT_NOT_IMPLEMENTED(voice.rendering); voice.rendering = true; voice.samples.assign(grain * voice.channels, 0.0f); voice.has_samples = false; if (voice.state == Ngs2VoicePlayState::Playing) { if (voice.rack->type == Ngs2RackType::CustomSampler) { Ngs2ConsumeSamples(voice, grain); } for (auto* source: voices) { for (const auto& port: source->ports) { if (port.dest != &voice || port.volume == 0.0f) { continue; } Ngs2RenderVoice(*source, voices, grain); if (!source->has_samples) { continue; } EXIT_NOT_IMPLEMENTED(port.input != 0); const auto* matrix = port.matrix < 0 ? nullptr : &source->matrices.at(port.matrix); for (uint32_t dst = 0; dst < voice.channels; ++dst) { for (uint32_t src = 0; src < source->channels; ++src) { const float level = port.volume * (matrix == nullptr ? (src == dst ? 1.0f : 0.0f) : matrix->at(dst * source->channels + src)); for (uint32_t i = 0; i < grain; ++i) { voice.samples[dst * grain + i] += source->samples[src * grain + i] * level; } } } voice.has_samples = true; } } if (voice.has_samples) { const auto& custom = voice.rack->option.custom_sampler.custom_rack_option; for (size_t m = 0; m < voice.modules.size(); ++m) { EXIT_NOT_IMPLEMENTED(custom.module[m].module_id != 0x1f); EXIT_NOT_IMPLEMENTED(custom.module[m].source_buffer_id != 0 || custom.module[m].dest_buffer_id != 0); auto& module = voice.modules[m]; const auto& fx = voice.rack->fx[m]; std::vector channels(voice.channels); for (uint32_t c = 0; c < voice.channels; ++c) { channels[c] = voice.samples.data() + c * grain; } Ngs2UserFxProcessContext context {channels.data(), voice.rack->common[m].data(), module.param.data(), module.work.data(), module.state.data(), fx.user_data, module.flags, voice.channels, voice.channels, grain, voice.rack->ngs->option.sample_rate}; EXIT_NOT_IMPLEMENTED(fx.process(&context) != OK); module.flags = 0; } } } voice.rendering = false; voice.rendered = true; } int KYTY_SYSV_ABI Ngs2SystemRender(uintptr_t system_handle, const Ngs2RenderBufferInfo* buffer_info, uint32_t num_buffer_info) { EXIT_NOT_IMPLEMENTED(buffer_info == nullptr || system_handle == 0 || num_buffer_info == 0); auto* ngs = reinterpret_cast(system_handle); Common::LockGuard lock(ngs->mutex); std::vector voices; { Common::LockGuard racks_lock(g_racks_mutex); for (auto* rack = g_racks_list; rack != nullptr; rack = rack->next) { if (rack->ngs != ngs) { continue; } auto* items = reinterpret_cast(rack + 1); for (uint32_t i = 0; i < rack->option.common.max_voices; ++i) { Ngs2ApplyEvent(items[i]); items[i].rendered = false; if (items[i].channels != 0) { voices.push_back(items + i); } } } } for (uint32_t i = 0; i < num_buffer_info; ++i) { if (buffer_info[i].buffer != nullptr && buffer_info[i].buffer_size != 0) { std::memset(buffer_info[i].buffer, 0, buffer_info[i].buffer_size); } } const auto grain = ngs->option.num_grain_samples; for (auto* voice: voices) { Ngs2RenderVoice(*voice, voices, grain); if (voice->rack->type != Ngs2RackType::Mastering || !voice->has_samples) { continue; } EXIT_NOT_IMPLEMENTED(voice->output_id >= num_buffer_info); const auto& output = buffer_info[voice->output_id]; EXIT_NOT_IMPLEMENTED(output.buffer == nullptr); EXIT_NOT_IMPLEMENTED(output.waveform_type != 0x18 || output.num_channels != voice->channels); EXIT_NOT_IMPLEMENTED(output.buffer_size < grain * output.num_channels * sizeof(float)); auto* pcm = static_cast(output.buffer); for (uint32_t dst = 0; dst < output.num_channels; ++dst) { for (uint32_t src = 0; src < voice->channels; ++src) { const float level = voice->output_matrix.empty() ? (src == dst ? 1.0f : 0.0f) : voice->output_matrix.at(src * output.num_channels + dst); for (uint32_t i = 0; i < grain; ++i) { pcm[i * output.num_channels + dst] += voice->samples[src * grain + i] * level; } } } } ++ngs->render_count; return OK; } static uint16_t Ngs2ReadLe16(const uint8_t* data) { return static_cast(data[0]) | (static_cast(data[1]) << 8u); } static uint32_t Ngs2ReadLe32(const uint8_t* data) { return static_cast(data[0]) | (static_cast(data[1]) << 8u) | (static_cast(data[2]) << 16u) | (static_cast(data[3]) << 24u); } static bool Ngs2FourCcEquals(const uint8_t* data, const char* four_cc) { return std::memcmp(data, four_cc, 4) == 0; } static int Ngs2ParseAtrac9Riff(const void* data, size_t data_size, Ngs2WaveformInfo* info) { static constexpr uint8_t ATRAC9_GUID[16] = {0xd2, 0x42, 0xe1, 0x47, 0xba, 0x36, 0x8d, 0x4d, 0x88, 0xfc, 0x61, 0x65, 0x4f, 0x8c, 0x83, 0x6c}; const auto* bytes = static_cast(data); if (bytes == nullptr || data_size < 12) { return NGS2_ERROR_INVALID_WAVEFORM_DATA; } if (!Ngs2FourCcEquals(bytes, "RIFF") || !Ngs2FourCcEquals(bytes + 8, "WAVE")) { return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; } const uint64_t riff_end64 = 8ull + Ngs2ReadLe32(bytes + 4); if (riff_end64 < 12 || riff_end64 > data_size) { return NGS2_ERROR_INVALID_WAVEFORM_DATA; } const auto riff_end = static_cast(riff_end64); const uint8_t* format = nullptr; const uint8_t* fact = nullptr; size_t waveform_offset = 0; uint32_t waveform_size = 0; for (size_t offset = 12; offset + 8 <= riff_end;) { const auto* chunk = bytes + offset; const auto chunk_size = static_cast(Ngs2ReadLe32(chunk + 4)); const auto payload = offset + 8; if (chunk_size > riff_end - payload) { return NGS2_ERROR_INVALID_WAVEFORM_DATA; } if (Ngs2FourCcEquals(chunk, "fmt ")) { if (chunk_size < 52) { return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } format = bytes + payload; } else if (Ngs2FourCcEquals(chunk, "fact")) { if (chunk_size < 12) { return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } fact = bytes + payload; } else if (Ngs2FourCcEquals(chunk, "data")) { if (payload > std::numeric_limits::max()) { return NGS2_ERROR_INVALID_WAVEFORM_DATA; } waveform_offset = payload; waveform_size = static_cast(chunk_size); } const uint64_t next = static_cast(payload) + chunk_size + (chunk_size & 1u); if (next > riff_end) { return NGS2_ERROR_INVALID_WAVEFORM_DATA; } offset = static_cast(next); } if (format == nullptr || fact == nullptr || waveform_offset == 0) { return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } if (Ngs2ReadLe16(format) != 0xfffe || std::memcmp(format + 24, ATRAC9_GUID, sizeof(ATRAC9_GUID)) != 0) { return NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT; } std::array config {}; std::memcpy(config.data(), format + 44, config.size()); if (config[0] != 0xfe || (config[1] & 1u) != 0 || ((config[1] >> 1u) & 7u) >= 6u) { return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } Atrac9CodecInfo codec {}; void* decoder = Atrac9GetHandle(); const bool valid_codec = decoder != nullptr && Atrac9InitDecoder(decoder, config.data()) == 0 && Atrac9GetCodecInfo(decoder, &codec) == 0 && codec.channels > 0 && codec.samplingRate > 0 && codec.superframeSize > 0 && codec.framesInSuperframe > 0 && codec.frameSamples > 0 && codec.superframeSize % codec.framesInSuperframe == 0; if (decoder != nullptr) { Atrac9ReleaseHandle(decoder); } const uint64_t frame_samples = static_cast(codec.frameSamples) * static_cast(codec.framesInSuperframe); if (!valid_codec || codec.channels != Ngs2ReadLe16(format + 2) || codec.samplingRate != static_cast(Ngs2ReadLe32(format + 4)) || codec.superframeSize != Ngs2ReadLe16(format + 12) || frame_samples != Ngs2ReadLe16(format + 18) || frame_samples > std::numeric_limits::max()) { return NGS2_ERROR_INVALID_WAVEFORM_FORMAT; } info->format.waveform_type = NGS2_WAVEFORM_TYPE_ATRAC9; info->format.num_channels = static_cast(codec.channels); info->format.sample_rate = static_cast(codec.samplingRate); std::memcpy(&info->format.config_data, config.data(), config.size()); info->data_offset = static_cast(waveform_offset); info->data_size = waveform_size; info->num_samples = Ngs2ReadLe32(fact); info->audio_unit_size = static_cast(codec.superframeSize / codec.framesInSuperframe); info->num_audio_unit_samples = static_cast(codec.frameSamples); info->num_audio_unit_per_frame = static_cast(codec.framesInSuperframe); info->audio_frame_size = static_cast(codec.superframeSize); info->num_audio_frame_samples = static_cast(frame_samples); info->num_delay_samples = Ngs2ReadLe32(fact + 4); info->num_blocks = 1; info->blocks[0].data_offset = waveform_offset; info->blocks[0].data_size = waveform_size; info->blocks[0].num_skip_samples = Ngs2ReadLe32(fact + 8); info->blocks[0].num_samples = info->num_samples; return OK; } int KYTY_SYSV_ABI Ngs2ParseWaveformData(const void* data, size_t data_size, Ngs2WaveformInfo* info) { PRINT_NAME(); LOGF("\t data = 0x%016" PRIx64 ", data_size = 0x%016" PRIx64 "\n", reinterpret_cast(data), static_cast(data_size)); if (info == nullptr) { return NGS2_ERROR_INVALID_OUT_ADDRESS; } std::memset(info, 0, sizeof(Ngs2WaveformInfo)); return Ngs2ParseAtrac9Riff(data, data_size, info); } int KYTY_SYSV_ABI Ngs2CalcWaveformBlock(const Ngs2WaveformFormat* format, uint32_t sample_pos, uint32_t num_samples, Ngs2WaveformBlock* block) { PRINT_NAME(); LOGF("\t format = 0x%016" PRIx64 ", sample_pos = %" PRIu32 ", num_samples = %" PRIu32 "\n", reinterpret_cast(format), sample_pos, num_samples); EXIT_NOT_IMPLEMENTED(block == nullptr); std::memset(block, 0, sizeof(Ngs2WaveformBlock)); block->num_samples = num_samples; return OK; } int KYTY_SYSV_ABI Ngs2PanInit(Ngs2PanWork* work, const float* speaker_angles, float unit_angle, uint32_t num_speakers) { PRINT_NAME(); LOGF("\t work = 0x%016" PRIx64 ", num_speakers = %" PRIu32 "\n", reinterpret_cast(work), num_speakers); EXIT_NOT_IMPLEMENTED(work == nullptr); std::memset(work, 0, sizeof(Ngs2PanWork)); work->unit_angle = unit_angle; work->num_speakers = std::min(num_speakers, 8); if (speaker_angles != nullptr) { for (uint32_t i = 0; i < work->num_speakers; i++) { work->speaker_angles[i] = speaker_angles[i]; } } return OK; } int KYTY_SYSV_ABI Ngs2PanGetVolumeMatrix(Ngs2PanWork* work, const Ngs2PanParam* params, uint32_t num_params, uint32_t matrix_format, float* out_volume_matrix) { PRINT_NAME(); LOGF("\t work = 0x%016" PRIx64 ", params = 0x%016" PRIx64 ", num_params = %" PRIu32 ", matrix_format = %" PRIu32 "\n", reinterpret_cast(work), reinterpret_cast(params), num_params, matrix_format); EXIT_NOT_IMPLEMENTED(out_volume_matrix == nullptr && num_params != 0); const auto channels = (matrix_format == 0 ? 2u : std::min(matrix_format, 8)); for (uint32_t p = 0; p < num_params; p++) { for (uint32_t c = 0; c < channels; c++) { out_volume_matrix[p * channels + c] = (c == 0 ? 1.0f : 0.0f); } } return OK; } int KYTY_SYSV_ABI Ngs2GeomResetListenerParam(Ngs2GeomListenerParam* out_listener_param) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(out_listener_param == nullptr); std::memset(out_listener_param, 0, sizeof(Ngs2GeomListenerParam)); out_listener_param->orient_front.z = 1.0f; out_listener_param->orient_up.y = 1.0f; out_listener_param->sound_speed = 343.0f; return OK; } int KYTY_SYSV_ABI Ngs2GeomResetSourceParam(Ngs2GeomSourceParam* out_source_param) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(out_source_param == nullptr); std::memset(out_source_param, 0, sizeof(Ngs2GeomSourceParam)); out_source_param->direction.z = 1.0f; out_source_param->cone.inner_level = 1.0f; out_source_param->cone.inner_angle = 360.0f; out_source_param->cone.outer_level = 1.0f; out_source_param->cone.outer_angle = 360.0f; out_source_param->rolloff.model = 0; out_source_param->rolloff.max_distance = 1000000.0f; out_source_param->rolloff.rolloff_factor = 1.0f; out_source_param->rolloff.reference_distance = 1.0f; out_source_param->doppler_factor = 1.0f; out_source_param->fbw_level = 1.0f; out_source_param->lfe_level = 1.0f; out_source_param->max_level = 1.0f; out_source_param->min_level = 0.0f; out_source_param->num_speakers = 2; out_source_param->matrix_format = 2; return OK; } int KYTY_SYSV_ABI Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* out_work, uint32_t flags) { PRINT_NAME(); LOGF("\t flags = 0x%08" PRIx32 "\n", flags); EXIT_NOT_IMPLEMENTED(param == nullptr); EXIT_NOT_IMPLEMENTED(out_work == nullptr); std::memset(out_work, 0, sizeof(Ngs2GeomListenerWork)); for (uint32_t i = 0; i < 4; i++) { out_work->matrix[i][i] = 1.0f; } out_work->velocity = param->velocity; out_work->sound_speed = (param->sound_speed > 0.0f ? param->sound_speed : 343.0f); out_work->coordinate = flags & 0x1u; return OK; } int KYTY_SYSV_ABI Ngs2GeomApply(const Ngs2GeomListenerWork* listener, const Ngs2GeomSourceParam* source, Ngs2GeomAttribute* out_attrib, uint32_t flags) { PRINT_NAME(); LOGF("\t flags = 0x%08" PRIx32 "\n", flags); EXIT_NOT_IMPLEMENTED(listener == nullptr); EXIT_NOT_IMPLEMENTED(source == nullptr); EXIT_NOT_IMPLEMENTED(out_attrib == nullptr); std::memset(out_attrib, 0, sizeof(Ngs2GeomAttribute)); out_attrib->pitch_ratio = 1.0f; out_attrib->a3d_attrib.position = source->position; out_attrib->a3d_attrib.volume = std::max(source->min_level, source->max_level); const auto channels = std::min((source->matrix_format == 0 ? 2u : source->matrix_format), 8); const auto level = (source->max_level > 0.0f ? source->max_level : 1.0f); for (uint32_t ch = 0; ch < channels; ch++) { out_attrib->level[ch * 8 + ch] = level; } return OK; } int KYTY_SYSV_ABI Ngs2RackGetVoiceHandle(uintptr_t rack_handle, uint32_t voice_id, uintptr_t* handle) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(handle == nullptr); EXIT_NOT_IMPLEMENTED(rack_handle == 0); LOGF("\t voice_id = %u\n", voice_id); auto* rack = reinterpret_cast(rack_handle); auto* voices = reinterpret_cast(rack_handle + sizeof(Ngs2RackInternal)); if (voice_id >= rack->option.common.max_voices) { LOGF("\t warning: voice_id %u >= max_voices %u, using last available stub voice\n", voice_id, rack->option.common.max_voices); if (rack->option.common.max_voices == 0) { return -1; } voice_id = rack->option.common.max_voices - 1; } EXIT_IF(voices[voice_id].rack != rack); *handle = reinterpret_cast(voices + voice_id); return OK; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) int KYTY_SYSV_ABI Ngs2VoiceControl(uintptr_t voice_handle, const Ngs2VoiceParamHeader* param_list) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(param_list == nullptr); EXIT_NOT_IMPLEMENTED(voice_handle == 0); auto* voice = reinterpret_cast(voice_handle); Common::LockGuard lock(voice->rack->ngs->mutex); const auto* param = param_list; for (;;) { LOGF("\t id = 0x%08" PRIx32 "\n" "\t size = %" PRIu16 "\n" "\t next = %" PRId16 "\n", param->id, param->size, param->next); auto rack_id = param->id >> 16u; EXIT_NOT_IMPLEMENTED(((param->id >> 15u) & 0x1u) != 0); switch (rack_id) { case 0x0000: { auto cid = param->id & 0x7fffu; switch (cid) { case 0x0001: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceMatrixLevelsParam)); const auto* ml = reinterpret_cast(param); voice->SetMatrix(ml->matrix_id, ml->levels, ml->num_levels); LOGF("\t matrix_id = %u\n" "\t num_levels = %u\n" "\t levels = 0x%016" PRIx64 "\n", ml->matrix_id, ml->num_levels, reinterpret_cast(ml->levels)); break; } case 0x0002: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortVolumeParam)); const auto* volume = reinterpret_cast(param); voice->SetVolume(volume->port, volume->level); LOGF("\t port = %u\n" "\t level = %f\n", volume->port, volume->level); break; } case 0x0003: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortMatrixParam)); const auto* pm = reinterpret_cast(param); voice->SetPortMatrix(pm->port, pm->matrix_id); LOGF("\t port = %u\n" "\t matrix_id = %d\n", pm->port, pm->matrix_id); break; } case 0x0004: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePortDelayParam)); const auto* delay = reinterpret_cast(param); LOGF("\t port = %u\n" "\t num_samples = %u\n", delay->port, delay->num_samples); break; } case 0x0005: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoicePatchParam)); const auto* patch = reinterpret_cast(param); EXIT_NOT_IMPLEMENTED(patch->port >= voice->ports.size()); voice->ports[patch->port].dest = reinterpret_cast(patch->dest_handle); voice->ports[patch->port].input = patch->dest_input_id; LOGF("\t connect->port = %u\n" "\t connect->dest_input_id = %u\n" "\t connect->dest_handle = 0x%016" PRIx64 "\n", patch->port, patch->dest_input_id, patch->dest_handle); break; } case 0x0006: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceEventParam)); const auto* event = reinterpret_cast(param); voice->SetEvent(event->event_id); LOGF("\t event = %u\n", event->event_id); break; } case 0x0007: { EXIT_NOT_IMPLEMENTED(param->size != sizeof(Ngs2VoiceCallbackParam)); const auto* callback = reinterpret_cast(param); voice->callback = callback->callback; voice->callback_data = callback->callback_data; voice->callback_flags = callback->flags; LOGF("\t callback = 0x%016" PRIx64 "\n" "\t callback_data = 0x%016" PRIx64 "\n" "\t flags = 0x%08" PRIx32 "\n", static_cast(voice->callback), static_cast(voice->callback_data), voice->callback_flags); break; } default: EXIT("unknown id: 0x%04" PRIx32 "\n", cid); } break; } case 0x1000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Sampler); break; case 0x2000: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Submixer); break; case 0x2001: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Reverb); break; case 0x3000: { EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::Mastering); const auto* values = reinterpret_cast(param + 1); switch (param->id & 0xffffu) { case 0: voice->channels = values[0]; break; case 1: { const auto* matrix = reinterpret_cast(param); EXIT_NOT_IMPLEMENTED(matrix->matrix_id != 5); voice->output_matrix.assign(matrix->levels, matrix->levels + matrix->num_levels); break; } case 5: voice->output_id = values[0]; break; default: EXIT("unsupported mastering control: 0x%08" PRIx32 "\n", param->id); } break; } case 0x4000: { EXIT_NOT_IMPLEMENTED(!Ngs2RackIsCustom(voice->rack->type)); const auto index = param->id & 0x1fu; EXIT_NOT_IMPLEMENTED((param->id & 0xffffffe0u) != 0x40001f00u || index >= voice->modules.size()); struct FxParam { Ngs2VoiceParamHeader header; const void* data; size_t size; }; const auto& fx = *reinterpret_cast(param); auto& module = voice->modules[index]; EXIT_NOT_IMPLEMENTED(voice->rack->fx[index].control != 0 || fx.size != module.param.size()); std::memcpy(module.param.data(), fx.data, fx.size); module.flags |= 2; break; } case 0x4001: { EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSampler); switch (param->id & 0xffffu) { case 0: { const auto& format = *reinterpret_cast(param + 1); voice->SetupSampler(format); break; } case 1: { struct BlocksParam { Ngs2VoiceParamHeader header; const uint8_t* data; uint32_t flags, count; const Ngs2WaveformBlock* blocks; }; const auto& blocks = *reinterpret_cast(param); EXIT_NOT_IMPLEMENTED(!voice->accepts_blocks || (blocks.flags != 0 && blocks.flags != 0x11)); voice->accepts_blocks = (blocks.flags & 1u) != 0; for (uint32_t i = 0; i < blocks.count; ++i) { const auto& block = blocks.blocks[i]; EXIT_NOT_IMPLEMENTED(block.num_repeats != 0 || block.num_samples == 0); EXIT_NOT_IMPLEMENTED( voice->decoder == nullptr && (uint64_t(block.num_skip_samples) + block.num_samples) * voice->channels * 2 > block.data_size); voice->blocks.push_back({blocks.data + block.data_offset, block}); } break; } case 5: EXIT_NOT_IMPLEMENTED(*reinterpret_cast(param + 1) != 1.0f); break; default: EXIT("unsupported sampler control: 0x%08" PRIx32 "\n", param->id); } break; } case 0x4002: { EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomSubmixer || (param->id & 0xffffu) != 0); const auto* channels = reinterpret_cast(param + 1); EXIT_NOT_IMPLEMENTED(channels[0] != channels[1]); voice->channels = channels[0]; break; } case 0x4003: EXIT_NOT_IMPLEMENTED(voice->rack->type != Ngs2RackType::CustomMastering); break; default: EXIT("unknown rack_id: 0x%" PRIx32 "\n", rack_id); } if (param->next == 0) { break; } param = reinterpret_cast(reinterpret_cast(param) + param->next); } return OK; } int KYTY_SYSV_ABI Ngs2VoiceRunCommands(uintptr_t voice_handle, const void* commands, size_t num_commands) { struct Command { uint32_t id; uint8_t flags, type; uint16_t count; union { uint32_t u; int32_t i; float f; const float* levels; } value; }; auto* voice = reinterpret_cast(voice_handle); Common::LockGuard lock(voice->rack->ngs->mutex); const auto* params = static_cast(commands); for (size_t i = 0; i < num_commands; ++i) { const auto& p = params[i]; const auto index = p.id >> 24; switch (p.id & 0xffffffu) { case 2: EXIT_NOT_IMPLEMENTED(p.type != 4); voice->SetEvent(p.value.u); break; case 5: EXIT_NOT_IMPLEMENTED(p.type != 0x11); voice->SetMatrix(index, p.value.levels, p.count); break; case 6: EXIT_NOT_IMPLEMENTED(p.type != 1); voice->SetVolume(index, p.value.f); break; case 7: EXIT_NOT_IMPLEMENTED(p.type != 3); voice->SetPortMatrix(index, p.value.i); break; default: EXIT("unknown command: 0x%08" PRIx32 "\n", p.id); } } return OK; } int KYTY_SYSV_ABI Ngs2VoiceGetState(uintptr_t voice_handle, Ngs2VoiceState* state, size_t state_size) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(state == nullptr); EXIT_NOT_IMPLEMENTED(voice_handle == 0); auto* voice = reinterpret_cast(voice_handle); Common::LockGuard lock(voice->rack->ngs->mutex); switch (voice->rack->type) { case Ngs2RackType::Submixer: { EXIT_NOT_IMPLEMENTED(state_size != sizeof(Ngs2SubmixerVoiceState)); auto* submixer = reinterpret_cast(state); *submixer = {}; submixer->voice_state.state_flags = Ngs2GetStateFlags(voice); break; } case Ngs2RackType::CustomMastering: { const auto configured_size = voice->rack->option.custom_mastering.custom_rack_option.state_size; EXIT_NOT_IMPLEMENTED(configured_size < sizeof(Ngs2CustomMasteringVoiceState)); EXIT_NOT_IMPLEMENTED(state_size != configured_size); std::memset(state, 0, state_size); auto* mastering = reinterpret_cast(state); mastering->voice_state.state_flags = Ngs2GetStateFlags(voice); break; } case Ngs2RackType::Sampler: case Ngs2RackType::CustomSampler: { if (state_size != sizeof(Ngs2SamplerVoiceState)) { LOGF("\t warning: sampler state_size = 0x%016" PRIx64 ", expected 0x%016" PRIx64 "\n", static_cast(state_size), static_cast(sizeof(Ngs2SamplerVoiceState))); } std::memset(state, 0, state_size); state->state_flags = Ngs2GetStateFlags(voice); if (state_size < sizeof(Ngs2SamplerVoiceState)) { break; } auto* sampler = reinterpret_cast(state); sampler->envelope_height = 1.0f; sampler->peak_height = 0.0f; sampler->reserved = 0; sampler->num_decoded_samples = 0; sampler->user_data = 0; sampler->waveform_data = nullptr; break; } default: EXIT("unknown type: %s\n", magic_enum::enum_name(voice->rack->type)); } return OK; } int KYTY_SYSV_ABI Ngs2VoiceGetStateFlags(uintptr_t voice_handle, uint32_t* state_flags) { PRINT_NAME(); EXIT_NOT_IMPLEMENTED(state_flags == nullptr); EXIT_NOT_IMPLEMENTED(voice_handle == 0); auto* voice = reinterpret_cast(voice_handle); Common::LockGuard lock(voice->rack->ngs->mutex); *state_flags = Ngs2GetStateFlags(voice); return OK; } } // namespace Ngs2 } // namespace Libs::Audio