From 2b32823764cf3a6bfc5740e09824e616000c5a90 Mon Sep 17 00:00:00 2001 From: Gursukh Date: Sat, 3 Jan 2026 22:41:45 +0000 Subject: [PATCH 1/3] Implement rack management and streaming support in NGS2 library - Added RackInternal and VoiceInternal structures to manage racks and voices. - Introduced methods for creating and destroying racks, including default value handling. - Implemented a ring buffer for streaming audio data within VoiceInternal. - Enhanced SystemInternal to manage multiple racks and their associated voices. - Updated SystemSetup and SystemCleanup functions to accommodate new rack management. - Added utility functions for rack ID to index conversion and buffer management. - Introduced WaveformBlockInternal for handling waveform streaming. --- src/core/libraries/ngs2/README.md | 324 +++++++++++ src/core/libraries/ngs2/ngs2.cpp | 714 +++++++++++++++++++++++- src/core/libraries/ngs2/ngs2_impl.cpp | 138 ++++- src/core/libraries/ngs2/ngs2_impl.h | 20 + src/core/libraries/ngs2/ngs2_internal.h | 198 +++++++ 5 files changed, 1360 insertions(+), 34 deletions(-) create mode 100644 src/core/libraries/ngs2/README.md create mode 100644 src/core/libraries/ngs2/ngs2_internal.h diff --git a/src/core/libraries/ngs2/README.md b/src/core/libraries/ngs2/README.md new file mode 100644 index 000000000..5a378ec23 --- /dev/null +++ b/src/core/libraries/ngs2/README.md @@ -0,0 +1,324 @@ +# NGS2 HLE Implementation + +## Overview + +**libSceNgs2** (Next Generation Sound 2) is the PlayStation 4's high-level audio library, responsible for audio synthesis, mixing, and effects processing. This HLE (High-Level Emulation) implementation provides the core functionality needed to render audio in PS4 games. + +### Architecture + +The NGS2 system follows a hierarchical structure: + +``` +System (OrbisNgs2Handle) + └── Racks (OrbisNgs2Handle) + └── Voices (OrbisNgs2Handle) +``` + +- **System**: The top-level audio context that manages sample rate, grain size, and all child racks +- **Rack**: A processing unit of a specific type (Sampler, Submixer, Mastering, etc.) containing multiple voices +- **Voice**: An individual audio channel that can be configured, played, paused, and stopped + +### Audio Flow + +``` +Sampler Racks → Submixer Racks → Mastering Rack → Output Buffers +``` + +--- + +## Implementation Status + +### ✅ Fully Implemented + +| Feature | Description | +|---------|-------------| +| System Create/Destroy | System lifecycle with buffer allocation | +| System Query Buffer Size | Calculate required buffer sizes | +| Rack Create/Destroy | Sampler rack creation with voice allocation | +| Rack Query Buffer Size | Calculate rack buffer requirements | +| Voice Handle Retrieval | Get voice handles from rack | +| Voice State Management | State flags, play/pause/stop/kill/resume | +| PCM16 Playback | Decode and render 16-bit PCM audio | +| Streaming Audio | Ring buffer-based streaming with 3-slot circular buffer | +| One-Shot Playback | Single-buffer audio playback | +| Pitch Control | Variable playback speed via pitch ratio | +| Port Volume | Per-voice volume control | +| Sample Rate Conversion | Resampling with linear interpolation | +| Multi-channel Support | Mono to 8-channel audio | +| Pan Volume Matrix | Basic stereo panning calculations | + +### 🚧 Partially Implemented (Stubbed) + +| Feature | Status | Notes | +|---------|--------|-------| +| Mastering Rack | Stubbed | Params accepted but not processed | +| Submixer Rack | Stubbed | Created but no mixing logic | +| Matrix Levels | Stubbed | Returns identity matrix | +| Port Matrix | Stubbed | Param accepted, no effect | +| Port Delay | Stubbed | Param accepted, no effect | +| Voice Patch | Stubbed | Routing not implemented | +| Voice Callback | Stubbed | Callbacks not invoked | +| Envelope | Stubbed | Always returns height 1.0 | +| Peak Meter | Stubbed | Always returns peak 1.0 | + +### ❌ Not Implemented + +| Feature | Notes | +|---------|-------| +| ATRAC9 Decoding | Compressed audio codec (0x40) | +| Reverb Rack (0x4001) | Effects processing | +| Equalizer Rack (0x4002) | Frequency band adjustment | +| Custom Rack (0x4003) | User-defined processing modules | +| Filter Processing | Biquad, lowpass, etc. | +| Compressor/Limiter | Dynamics processing | +| Distortion | Audio distortion effect | +| Chorus/Delay | Time-based effects | +| LFE (Low Frequency Effects) | Subwoofer channel handling | +| 3D Geometry (sceNgs2Geom*) | Spatial audio positioning | +| FFT Functions | Frequency analysis | +| Stream API | sceNgs2Stream* functions | +| Job Scheduler | Multi-threaded processing | +| Report Handlers | Debug/profiling callbacks | + +--- + +## Rack IDs +``` +0x1000 = Sampler (index 0) +0x2000 = Submixer (index 2) +0x2001 = Submixer alt (index 3) +0x3000 = Mastering (index 1) +0x4001 = Reverb (index 4) +0x4002 = Equalizer (index 5) +0x4003 = Custom (index 6) +``` + +## Sample Rates (valid values) +``` +11025, 12000, 22050, 24000, 44100, 48000, 88200, 96000, 176400, 192000 +``` + +## Waveform Types +Valid if: `(type & 0xFFFFFFF8) == 0x80 || type == 0x40 || (type - 0x10) < 0xD` +- `0x40` = ATRAC9 +- `0x10-0x1C` = PCM variants +- `0x80-0x87` = PCM variants + +Render buffer types: `0x12, 0x13, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D` + +## Voice Control Params (ID → size) +``` +1 → 0x18 MatrixLevels +2 → 0x10 PortVolume +3 → 0x10 PortMatrix +4 → 0x10 PortDelay +5 → 0x18 Patch +6 → 0x0C Event +7 → 0x20 Callback +0xC001 → 0x20 Custom +``` + +## Voice Events (param ID 6) +``` +0 = Reset (clears state) +1 = Pause (sets 0x200) +2 = Stop (sets 0x400) +3 = Kill (sets 0x800) +4 = Resume A (sets 0x1000) +5 = Resume B (sets 0x2000) +``` + +## State Flags (bitfield) +``` +0x01 = Playing +0x200 = Paused +0x400 = Stopped +0x800 = Killed +0x1000 = Resume A +0x2000 = Resume B +``` +Init sets: `flags = (flags & 0xDF000000) | 0x20000101` +GetStateFlags returns: `flags & 0xFF` + +## Module IDs (Custom Racks) +``` +0x10=Envelope 0x11=Compressor 0x12=Distortion 0x13=Filter +0x14=Chorus 0x15=Delay 0x16=Reverb 0x17=Sampler +0x18=PitchShift 0x19=Unknown 0x1A=Limiter 0x1B=UserFx +0x1C=Mixer 0x1D=Generator 0x1E=EQ 0x70=Passthrough +``` + +## Callback Flags +Valid: `0, 1, 2, 3` + +## Key Errors +``` +0x804a0230 = Invalid system handle +0x804a0260 = Invalid rack ID +0x804a0300 = Invalid voice handle +0x804a0303 = Invalid voice event +0x804a0308 = Invalid voice control ID +0x804a030a = Invalid voice control size +0x804a0402 = Invalid waveform type +``` + +--- + +## Implementation + +### Existing Structures (DO NOT REDEFINE) +The following are already defined in ngs2 headers: +- `OrbisNgs2Handle` → ngs2_impl.h +- `OrbisNgs2RackDEBUG` → ngs2_impl.h +- `OrbisNgs2RackOption` → ngs2.h +- `OrbisNgs2SystemDEBUG` → ngs2_impl.h +- `OrbisNgs2SystemOption` → ngs2_impl.h +- `OrbisNgs2VoiceState` → ngs2.h +- `OrbisNgs2VoicePortDEBUG` → ngs2.h +- `OrbisNgs2VoiceMatrixDEBUG` → ngs2.h +- `OrbisNgs2VoiceParamHeader` → ngs2.h +- `OrbisNgs2Sampler*` → ngs2_sampler.h +- `OrbisNgs2Submixer*` → ngs2_submixer.h +- `OrbisNgs2Mastering*` → ngs2_mastering.h +- `OrbisNgs2Reverb*` → ngs2_reverb.h +- `OrbisNgs2Custom*` → ngs2_custom.h +- `OrbisNgs2Eq*` → ngs2_eq.h +- `HandleInternal` → ngs2_impl.h +- `SystemInternal` → ngs2_impl.h +- `StackBuffer` → ngs2_impl.h + +### New Internal Structures (add to ngs2_impl.h) +```cpp +struct RackInternal { + HandleInternal handle; + OrbisNgs2RackDEBUG DEBUG; // use existing struct + SystemInternal* ownerSystem; + std::vector> voices; + u32 rackType; +}; + +struct VoiceInternal { + HandleInternal handle; + RackInternal* ownerRack; + u32 voiceIndex; + u32 stateFlags; + std::vector ports; // use existing struct + std::vector matrices; // use existing struct +}; + +// Add field to existing SystemInternal: +std::vector racks; +``` + +### Phase 1: Handles +- `sceNgs2SystemCreate`: Allocate `SystemInternal`, return as handle +- `sceNgs2SystemDestroy`: Free system and all racks + +### Phase 2: Racks +- `sceNgs2RackQueryBufferSize`: Return size based on rack type +- `sceNgs2RackCreate`: Allocate `RackInternal`, map rackId→index, add to system +- `sceNgs2RackGetVoiceHandle`: Return `rack->voices[voiceIndex]` + +### Phase 3: Voices +- `sceNgs2VoiceControl`: Parse linked list by param ID, apply per switch above +- `sceNgs2VoiceGetState`: Copy voice state struct +- `sceNgs2VoiceGetStateFlags`: Return `stateFlags & 0xFF` + +### Phase 4: Render +```cpp +s32 sceNgs2SystemRender(handle, bufferDEBUG, numBufferDEBUG) { + // Validate: numBufferDEBUG in 1-16, buffers non-null, types valid + // Process racks: Samplers → Submixers → Mastering + // Write output: size = grainSamples * channels * bytesPerSample + return ORBIS_OK; +} +``` + +### Minimal Stub +```cpp +// SystemCreate +auto* sys = new SystemInternal(); +*outHandle = reinterpret_cast(sys); + +// RackCreate +auto* rack = new RackInternal(); +rack->voices.resize(option->maxVoices); +*outHandle = reinterpret_cast(rack); + +// VoiceGetStateFlags +*out = 0; // Not playing + +// SystemRender +for (u32 i = 0; i < numBufferDEBUG; i++) + memset(bufferDEBUG[i].buffer, 0, bufferDEBUG[i].bufferSize); +``` + +## Files +| File | Purpose | +|------|---------| +| ngs2.cpp | Main API entry points and render loop | +| ngs2.h | Public API structures and types | +| ngs2_impl.cpp | System and rack lifecycle management | +| ngs2_impl.h | Core types (SystemInternal, HandleInternal) | +| ngs2_internal.h | Internal structures (VoiceInternal, RackInternal, RingBufferSlot) | +| ngs2_sampler.h | Sampler rack structures | +| ngs2_mastering.h | Mastering rack structures | +| ngs2_submixer.h | Submixer rack structures | +| ngs2_reverb.h | Reverb rack structures | +| ngs2_custom.h | Custom rack and module structures | +| ngs2_eq.h | Equalizer structures | +| ngs2_pan.h | Pan work and param structures | +| ngs2_geom.h | 3D geometry/spatial audio structures | +| ngs2_report.h | Report handler structures | +| ngs2_error.h | Error code definitions | + +--- + +## Areas for Improvement + +### High Priority +1. **ATRAC9 Decoding**: Many games use ATRAC9 compressed audio. Integration with an ATRAC9 decoder is critical for broader game compatibility. +2. **Mastering Rack Processing**: Currently stubbed - should apply gain, limiting, and LFE filtering to final output. +3. **Voice Routing (Patch)**: Voices should be able to route to submixers instead of directly to output. +4. **Streaming Buffer Gap**: Reduce/eliminate the audio gap between buffer consumption and refill. Currently there can be a brief silence when the ring buffer empties before the game refills it. Consider pre-buffering or predictive starvation signaling. + +### Medium Priority +1. **Submixer Processing**: Implement actual mixing of multiple input voices with envelope and effects. +2. **Filter Implementation**: Biquad filters for EQ, lowpass, highpass processing. +3. **Envelope Processing**: ADSR envelopes for volume shaping. +4. **Better Resampling**: Current linear interpolation could be upgraded to sinc or polyphase for higher quality. + +### Low Priority +1. **Reverb/Delay Effects**: Time-based audio effects for spatial depth. +2. **3D Geometry API**: Spatial positioning with distance attenuation and panning. +3. **Compressor/Limiter**: Dynamics processing for mastering. +4. **Peak Metering**: Accurate level measurement for debugging. +5. **Multi-threaded Rendering**: Job scheduler for parallel voice processing. + +### Code Quality +1. **Thread Safety**: Add proper mutex locking for multi-threaded game access. +2. **Memory Management**: Consider using the game-provided buffer allocator instead of `new`. +3. **Waveform Block Repeats**: Loop handling for blocks with `numRepeats > 0`. +4. **Callback Invocation**: Actually invoke registered callbacks on buffer events. + +--- + +## Audio Data Management + +In the context of the NGS2 HLE implementation, audio data is processed primarily through the **Sampler Rack (0x1000)**. The system differentiates between short, memory-resident sounds (One-Shots) and longer, buffered content (Streaming) based on how the waveform data is supplied and managed during the `sceNgs2SystemRender` cycle. + +#### One-Shot Playback +One-shots are typically used for UI sounds, sound effects, or short musical stings. + +* **Memory Layout:** The entire audio payload (whether raw PCM or a complete compressed ATRAC9 block) is loaded into a contiguous block of RAM by the application before playback begins. +* **Voice Handling:** When the voice is triggered, the `VoiceInternal` structure maintains a read cursor relative to the start of this static memory region. +* **Rendering:** During the render pass, the system decodes or copies data starting from the current cursor position. If the sound is looped, the cursor simply jumps back to a defined loop-start offset upon reaching the end. Since the data is static, no synchronization with game-side file I/O is required. + +#### Streamed Playback +Streaming is used for background music (BGM) or long speech tracks to conserve memory. + +* **Ring Buffer Mechanism:** Instead of a linear buffer, the voice utilizes a fixed-size circular buffer (3 slots). The application (Producer) and the NGS2 renderer (Consumer) operate concurrently on this buffer. + * **The Game's Role:** Periodically fills sections of the buffer that have already been played, ensuring the "Write Head" stays ahead of the "Read Head." + * **NGS2's Role:** The `SystemRender` function consumes data from the "Read Head" position and signals starvation when buffers run low. +* **Starvation Handling:** When the ring buffer count drops to the threshold, the stateFlags `0x80` bit is set to signal the game to provide more data. +* **Decoding State:** For compressed formats like ATRAC9 (`0x40`), the decoder context must be preserved seamlessly across buffer transitions to prevent audio artifacts. \ No newline at end of file diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 2f785f9a0..ea24e58bb 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" @@ -9,11 +12,267 @@ #include "core/libraries/ngs2/ngs2_error.h" #include "core/libraries/ngs2/ngs2_geom.h" #include "core/libraries/ngs2/ngs2_impl.h" +#include "core/libraries/ngs2/ngs2_internal.h" #include "core/libraries/ngs2/ngs2_pan.h" #include "core/libraries/ngs2/ngs2_report.h" +#include "core/libraries/ngs2/ngs2_sampler.h" namespace Libraries::Ngs2 { +// ============================================================================= +// Audio Decoder Interface +// ============================================================================= + +// Waveform type constants +// Add new formats here as they are discovered/implemented +enum WaveformType : u32 { + PCM16 = 0x12, // 16-bit PCM little-endian (confirmed working) + // TODO: Add more formats +}; + +// Check if waveform type is supported +static bool IsWaveformTypeSupported(u32 waveformType) { + switch (waveformType) { + case WaveformType::PCM16: + return true; + // TODO: Add cases for new formats here + default: + return false; + } +} + +// Get bytes per sample for a waveform type +static u32 GetBytesPerSample(u32 waveformType, u32 numChannels) { + switch (waveformType) { + case WaveformType::PCM16: + return 2 * numChannels; + // TODO: Add cases for new formats here + // case WaveformType::FLOAT: + // return 4 * numChannels; + default: + return 2 * numChannels; + } +} + +// ============================================================================= +// PCM16 Decoder +// ============================================================================= + +static float DecodePCM16Sample(const void* data, u32 sampleIdx, u32 channel, u32 numChannels, + u32 totalSamples) { + if (sampleIdx >= totalSamples) { + return 0.0f; + } + + const s16* srcData = reinterpret_cast(data); + s16 sample = srcData[sampleIdx * numChannels + channel]; + + return static_cast(sample) / 32768.0f; +} + +// ============================================================================= +// Add new decoder functions here +// ============================================================================= +// Example: +// static float DecodeFloatSample(const void* data, u32 sampleIdx, u32 channel, +// u32 numChannels, u32 totalSamples) { +// const float* srcData = reinterpret_cast(data); +// return srcData[sampleIdx * numChannels + channel]; +// } + +// ============================================================================= +// Unified Sample Decoder +// ============================================================================= + +static float DecodeSample(const void* data, u32 sampleIdx, u32 channel, u32 numChannels, + u32 totalSamples, u32 waveformType) { + switch (waveformType) { + case WaveformType::PCM16: + return DecodePCM16Sample(data, sampleIdx, channel, numChannels, totalSamples); + // TODO: Add cases for new formats here + default: + return 0.0f; + } +} + +// ============================================================================= +// Voice Renderer +// ============================================================================= + +struct RenderContext { + const OrbisNgs2RenderBufferInfo* bufferInfo; + u32 numBufferInfo; + u32 grainSamples; + u32 outputSampleRate; +}; + +static bool RenderVoice(VoiceInternal* voice, const RenderContext& ctx) { + if (!voice || !voice->isSetup) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Voice not setup or invalid"); + return false; + } + + // Check if voice is playing + if (!(voice->stateFlags & 0x01)) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Voice not playing"); + return false; + } + + // Check if voice is paused, stopped, or killed + if (voice->stateFlags & (0x200 | 0x400 | 0x800)) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Voice paused, stopped, or killed"); + return false; + } + + u32 waveformType = voice->format.waveformType; + if (!IsWaveformTypeSupported(waveformType)) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Unsupported waveform type: {:#x}", waveformType); + return false; + } + + // Get current slot from ring buffer + RingBufferSlot* currentSlot = voice->getCurrentSlot(); + if (!currentSlot || !currentSlot->valid || currentSlot->consumed) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) No valid buffer in ring"); + return false; + } + + voice->currentBufferPtr = currentSlot->basePtr; + + u32 numChannels = voice->format.numChannels; + if (numChannels == 0 || numChannels > 8) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Invalid number of channels: {}", numChannels); + return false; + } + + // Calculate sample rate ratio for resampling + u32 sourceSampleRate = voice->format.sampleRate; + if (sourceSampleRate == 0) { + sourceSampleRate = 48000; + } + + float pitchRatio = voice->pitchRatio; + if (pitchRatio <= 0.0f) { + pitchRatio = 1.0f; + } + + float sampleRateRatio = (static_cast(sourceSampleRate) * pitchRatio) / + static_cast(ctx.outputSampleRate); + + // Find matching output buffer and render + for (u32 bufIdx = 0; bufIdx < ctx.numBufferInfo; bufIdx++) { + const auto& bufInfo = ctx.bufferInfo[bufIdx]; + if (!bufInfo.buffer || bufInfo.bufferSize == 0) { + continue; + } + + const void* srcData = currentSlot->data; + u32 totalSamples = currentSlot->numSamples; + if (totalSamples == 0) { + continue; + } + + // Determine output format + // Output buffer format detection - use raw values since we only know PCM16 for sure + float* dstFloat = nullptr; + s16* dstS16 = nullptr; + u32 dstChannels = 2; + bool outputIsFloat = false; + + if (bufInfo.waveformType == WaveformType::PCM16) { + dstS16 = reinterpret_cast(bufInfo.buffer); + dstChannels = bufInfo.numChannels > 0 ? bufInfo.numChannels : numChannels; + outputIsFloat = false; + } else { + // Default to float output for unknown types + dstFloat = reinterpret_cast(bufInfo.buffer); + dstChannels = bufInfo.numChannels > 0 ? bufInfo.numChannels : 2; + outputIsFloat = true; + } + + // Render samples + float currentPos = voice->samplePosFloat; + bool voiceStopped = false; + + for (u32 outSample = 0; outSample < ctx.grainSamples && !voiceStopped; outSample++) { + u32 sampleInt = static_cast(currentPos); + float frac = currentPos - static_cast(sampleInt); + + // Check if we've reached the end of current buffer + if (sampleInt >= totalSamples) { + voice->lastConsumedBuffer = currentSlot->basePtr; + voice->totalDecodedSamples += totalSamples; + voice->advanceReadIndex(); + + if (voice->getReadyBufferCount() <= VoiceInternal::STARVATION_THRESHOLD) { + voice->stateFlags |= 0x80; + } + + currentSlot = voice->getCurrentSlot(); + if (currentSlot && currentSlot->valid && !currentSlot->consumed) { + srcData = currentSlot->data; + totalSamples = currentSlot->numSamples; + currentPos = 0.0f; + sampleInt = 0; + frac = 0.0f; + voice->isStreaming = true; + voice->currentBufferPtr = currentSlot->basePtr; + } else { + if (voice->isStreaming) { + currentPos = 0.0f; + break; + } else { + voice->stateFlags &= ~0x01; + voice->stateFlags |= 0x400; + voiceStopped = true; + break; + } + } + } + + // Decode and interpolate samples for each output channel + for (u32 ch = 0; ch < dstChannels; ch++) { + u32 srcCh = ch < numChannels ? ch : 0; + + float sample0 = DecodeSample(srcData, sampleInt, srcCh, numChannels, totalSamples, + waveformType); + float sample1 = DecodeSample(srcData, sampleInt + 1, srcCh, numChannels, + totalSamples, waveformType); + float sample = sample0 + frac * (sample1 - sample0); + + // Apply port volume + sample *= voice->portVolume; + + // Write to output buffer + u32 dstIdx = outSample * dstChannels + ch; + if (outputIsFloat) { + if (dstIdx * sizeof(float) < bufInfo.bufferSize) { + dstFloat[dstIdx] += sample; + dstFloat[dstIdx] = std::clamp(dstFloat[dstIdx], -1.0f, 1.0f); + } + } else { + if (dstIdx * 2 < bufInfo.bufferSize) { + s32 mixed = dstS16[dstIdx] + static_cast(sample * 32768.0f); + dstS16[dstIdx] = static_cast(std::clamp(mixed, -32768, 32767)); + } + } + } + + currentPos += sampleRateRatio; + } + + voice->samplePosFloat = currentPos; + voice->currentSamplePos = static_cast(currentPos); + return true; + } + + return false; +} + +// ============================================================================= +// API Functions +// ============================================================================= + // Ngs2 s32 PS4_SYSV_ABI sceNgs2CalcWaveformBlock(const OrbisNgs2WaveformFormat* format, u32 samplePos, @@ -51,30 +310,45 @@ s32 PS4_SYSV_ABI sceNgs2RackCreate(OrbisNgs2Handle systemHandle, u32 rackId, const OrbisNgs2RackOption* option, const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle) { - LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); + LOG_DEBUG(Lib_Ngs2, "rackId = {:#x}, maxVoices = {}", rackId, option ? option->maxVoices : 0); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - return ORBIS_OK; + + auto* system = reinterpret_cast(systemHandle); + return RackCreate(system, rackId, option, bufferInfo, outHandle); } s32 PS4_SYSV_ABI sceNgs2RackCreateWithAllocator(OrbisNgs2Handle systemHandle, u32 rackId, const OrbisNgs2RackOption* option, const OrbisNgs2BufferAllocator* allocator, OrbisNgs2Handle* outHandle) { - LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); + LOG_DEBUG(Lib_Ngs2, "rackId = {:#x}, maxVoices = {}", rackId, option ? option->maxVoices : 0); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - return ORBIS_OK; + + if (!outHandle) { + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + auto* system = reinterpret_cast(systemHandle); + + // Create rack with null buffer info (allocator version doesn't use pre-allocated buffer) + return RackCreate(system, rackId, option, nullptr, outHandle); } s32 PS4_SYSV_ABI sceNgs2RackDestroy(OrbisNgs2Handle rackHandle, OrbisNgs2ContextBufferInfo* outBufferInfo) { - LOG_ERROR(Lib_Ngs2, "called"); - return ORBIS_OK; + LOG_DEBUG(Lib_Ngs2, "called"); + if (!rackHandle) { + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + + auto* rack = reinterpret_cast(rackHandle); + return RackDestroy(rack, outBufferInfo); } s32 PS4_SYSV_ABI sceNgs2RackGetInfo(OrbisNgs2Handle rackHandle, OrbisNgs2RackInfo* outInfo, @@ -90,7 +364,21 @@ s32 PS4_SYSV_ABI sceNgs2RackGetUserData(OrbisNgs2Handle rackHandle, uintptr_t* o s32 PS4_SYSV_ABI sceNgs2RackGetVoiceHandle(OrbisNgs2Handle rackHandle, u32 voiceIndex, OrbisNgs2Handle* outHandle) { - LOG_DEBUG(Lib_Ngs2, "(STUBBED) voiceIndex = {}", voiceIndex); + LOG_DEBUG(Lib_Ngs2, "voiceIndex = {}", voiceIndex); + if (!rackHandle) { + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + + auto* rack = reinterpret_cast(rackHandle); + if (voiceIndex >= rack->voices.size()) { + LOG_ERROR(Lib_Ngs2, "Invalid voice index {} (max {})", voiceIndex, rack->voices.size()); + return ORBIS_NGS2_ERROR_INVALID_VOICE_INDEX; + } + + if (outHandle) { + *outHandle = reinterpret_cast(rack->voices[voiceIndex].get()); + } + return ORBIS_OK; } @@ -101,7 +389,48 @@ s32 PS4_SYSV_ABI sceNgs2RackLock(OrbisNgs2Handle rackHandle) { s32 PS4_SYSV_ABI sceNgs2RackQueryBufferSize(u32 rackId, const OrbisNgs2RackOption* option, OrbisNgs2ContextBufferInfo* outBufferInfo) { - LOG_ERROR(Lib_Ngs2, "rackId = {}", rackId); + LOG_DEBUG(Lib_Ngs2, "rackId = {:#x}, option = {}", rackId, static_cast(option)); + + if (!outBufferInfo) { + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + u32 rackIndex = RackIdToIndex(rackId); + if (rackIndex == 0xFF) { + LOG_ERROR(Lib_Ngs2, "Invalid rack ID: {:#x}", rackId); + return ORBIS_NGS2_ERROR_INVALID_RACK_ID; + } + + // Use defaults if option is NULL - based on libSceNgs2.c analysis + u32 maxVoices = 1; + u32 maxPorts = 1; + u32 maxMatrices = 1; + + if (option && option->size >= sizeof(OrbisNgs2RackOption)) { + maxVoices = option->maxVoices > 0 ? option->maxVoices : 1; + maxPorts = option->maxPorts > 0 ? option->maxPorts : 1; + maxMatrices = option->maxMatrices > 0 ? option->maxMatrices : 1; + } else { + // Sampler rack (0x1000) defaults to 256 voices + if (rackId == 0x1000) { + maxVoices = 256; + } + } + + // Calculate required buffer size + size_t baseSize = sizeof(RackInternal); + size_t voiceSize = sizeof(VoiceInternal) * maxVoices; + size_t portSize = sizeof(OrbisNgs2VoicePortInfo) * maxPorts * maxVoices; + size_t matrixSize = sizeof(OrbisNgs2VoiceMatrixInfo) * maxMatrices * maxVoices; + + size_t totalSize = baseSize + voiceSize + portSize + matrixSize; + totalSize = (totalSize + 0xFF) & ~0xFF; // Align to 256 bytes + + outBufferInfo->hostBuffer = nullptr; + outBufferInfo->hostBufferSize = totalSize; + std::memset(outBufferInfo->reserved, 0, sizeof(outBufferInfo->reserved)); + + LOG_DEBUG(Lib_Ngs2, "Required buffer size: {} bytes", totalSize); return ORBIS_OK; } @@ -144,7 +473,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreate(const OrbisNgs2SystemOption* option, // TODO: API reporting? - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); return result; } @@ -179,7 +508,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o result = ORBIS_NGS2_ERROR_INVALID_BUFFER_ALLOCATOR; LOG_ERROR(Lib_Ngs2, "Invalid system buffer allocator {}", (void*)allocator); } - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); return result; } @@ -189,7 +518,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemDestroy(OrbisNgs2Handle systemHandle, LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -237,7 +566,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemQueryBufferSize(const OrbisNgs2SystemOption* optio s32 result; if (outBufferInfo) { result = SystemSetup(option, outBufferInfo, 0, 0); - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); } else { result = ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; LOG_ERROR(Lib_Ngs2, "Invalid system buffer info {}", (void*)outBufferInfo); @@ -249,11 +578,56 @@ s32 PS4_SYSV_ABI sceNgs2SystemQueryBufferSize(const OrbisNgs2SystemOption* optio s32 PS4_SYSV_ABI sceNgs2SystemRender(OrbisNgs2Handle systemHandle, const OrbisNgs2RenderBufferInfo* aBufferInfo, u32 numBufferInfo) { - LOG_DEBUG(Lib_Ngs2, "(STUBBED) numBufferInfo = {}", numBufferInfo); if (!systemHandle) { LOG_ERROR(Lib_Ngs2, "systemHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } + + if (!aBufferInfo || numBufferInfo == 0 || numBufferInfo > 16) { + LOG_ERROR(Lib_Ngs2, "Invalid buffer info: ptr={}, count={}", + static_cast(aBufferInfo), numBufferInfo); + return ORBIS_NGS2_ERROR_INVALID_BUFFER_ADDRESS; + } + + auto* system = reinterpret_cast(systemHandle); + + // Clear all output buffers first + for (u32 i = 0; i < numBufferInfo; i++) { + if (aBufferInfo[i].buffer && aBufferInfo[i].bufferSize > 0) { + std::memset(aBufferInfo[i].buffer, 0, aBufferInfo[i].bufferSize); + } + } + + // Setup render context + RenderContext ctx{}; + ctx.bufferInfo = aBufferInfo; + ctx.numBufferInfo = numBufferInfo; + ctx.grainSamples = system->numGrainSamples > 0 ? system->numGrainSamples : 256; + ctx.outputSampleRate = system->sampleRate > 0 ? system->sampleRate : 48000; + + u32 voicesRendered = 0; + + // Process each rack + for (auto* rack : system->racks) { + if (!rack) { + continue; + } + + // Process sampler racks (0x1000) + if (rack->rackId == 0x1000) { + for (auto& voice : rack->voices) { + if (RenderVoice(voice.get(), ctx)) { + voicesRendered++; + } + } + } + // TODO: Process submixer racks (0x2000, 0x2001) + // TODO: Process mastering racks (0x3000) + // TODO: Process effect racks (0x4001, 0x4002, 0x4003) + } + + system->renderCount++; + return ORBIS_OK; } @@ -267,7 +641,7 @@ static s32 PS4_SYSV_ABI sceNgs2SystemResetOption(OrbisNgs2SystemOption* outOptio } *outOption = option; - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -309,13 +683,201 @@ s32 PS4_SYSV_ABI sceNgs2SystemUnlock(OrbisNgs2Handle systemHandle) { s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, const OrbisNgs2VoiceParamHeader* paramList) { - LOG_ERROR(Lib_Ngs2, "called"); + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "voiceHandle is nullptr"); + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + + auto* voice = reinterpret_cast(voiceHandle); + + const OrbisNgs2VoiceParamHeader* current = paramList; + while (current != nullptr) { + + switch (current->id) { + // Sampler rack-specific params (0x1000000X) + case 0x10000000: { // Sampler Voice Setup + auto* setup = reinterpret_cast(current); + voice->format = setup->format; + voice->flags = setup->flags; + voice->isSetup = true; + break; + } + case 0x10000001: { // Sampler Waveform Blocks + auto* blocks = + reinterpret_cast(current); + + u32 totalDataSize = 0; + u32 totalSamples = 0; + u32 numChannels = voice->format.numChannels > 0 ? voice->format.numChannels : 2; + + if (blocks->numBlocks > 0 && blocks->aBlock) { + for (u32 i = 0; i < blocks->numBlocks; i++) { + totalDataSize += blocks->aBlock[i].dataSize; + totalSamples += blocks->aBlock[i].numSamples; + } + + voice->waveformBlocks.resize(blocks->numBlocks); + for (u32 i = 0; i < blocks->numBlocks; i++) { + voice->waveformBlocks[i].dataOffset = blocks->aBlock[i].dataOffset; + voice->waveformBlocks[i].dataSize = blocks->aBlock[i].dataSize; + voice->waveformBlocks[i].numRepeats = blocks->aBlock[i].numRepeats; + voice->waveformBlocks[i].numSkipSamples = blocks->aBlock[i].numSkipSamples; + voice->waveformBlocks[i].numSamples = blocks->aBlock[i].numSamples; + voice->waveformBlocks[i].currentRepeat = 0; + } + } + + const u8* basePtr = reinterpret_cast(blocks->data); + u32 dataOffset = + (blocks->numBlocks > 0 && blocks->aBlock) ? blocks->aBlock[0].dataOffset : 0; + const void* actualDataPtr = basePtr + dataOffset; + + bool isFirstSetup = (voice->ringBufferCount == 0) && !voice->isStreaming; + + if (isFirstSetup) { + voice->resetRing(); + voice->addToRing(blocks->data, actualDataPtr, totalDataSize, totalSamples); + voice->lastConsumedBuffer = nullptr; + voice->currentBufferPtr = actualDataPtr; + voice->stateFlags &= ~0x80; + voice->currentSamplePos = 0; + voice->samplePosFloat = 0.0f; + voice->currentBlockIndex = 0; + voice->isStreaming = false; + } else { + bool added = + voice->addToRing(blocks->data, actualDataPtr, totalDataSize, totalSamples); + + if (added) { + voice->isStreaming = true; + voice->stateFlags &= ~0x80; + } + } + break; + } + case 0x10000005: { // Sampler Pitch + auto* pitch = reinterpret_cast(current); + voice->pitchRatio = pitch->ratio; + break; + } + + // Mastering rack-specific params (0x3000000X) - not yet implemented + case 0x30000000: + case 0x30000001: + case 0x30000002: + case 0x30000003: + case 0x30000004: + case 0x30000005: + case 0x30000006: + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Mastering param ID: {:#x}", current->id); + break; + + // Generic voice params + case 0x06: { // Voice Event + auto* event = reinterpret_cast(current); + switch (event->eventId) { + case 0: // Reset + voice->stateFlags = (voice->stateFlags & 0xDF000000) | 0x20000101; + voice->totalDecodedSamples = 0; + voice->currentBufferPtr = nullptr; + voice->lastConsumedBuffer = nullptr; + voice->currentSamplePos = 0; + voice->samplePosFloat = 0.0f; + voice->currentBlockIndex = 0; + voice->isStreaming = false; + voice->waveformBlocks.clear(); + voice->resetRing(); + break; + case 1: // Pause + voice->stateFlags |= 0x200; + break; + case 2: // Stop + voice->stateFlags &= ~0x01; + voice->stateFlags |= 0x400; + break; + case 3: // Kill + voice->stateFlags &= ~0x01; + voice->stateFlags |= 0x800; + voice->resetRing(); + break; + case 4: // Resume A + voice->stateFlags = (voice->stateFlags & ~0x200) | 0x1000 | 0x01; + break; + case 5: // Resume B + voice->stateFlags = (voice->stateFlags & ~0x200) | 0x2000 | 0x01; + break; + default: + LOG_WARNING(Lib_Ngs2, "Unknown voice event ID: {}", event->eventId); + break; + } + break; + } + case 0x01: { // Matrix Levels - not yet implemented + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Matrix levels param"); + break; + } + case 0x02: { // Port Volume + auto* portVol = reinterpret_cast(current); + voice->portVolume = portVol->level; + break; + } + case 0x03: { // Port Matrix - not yet implemented + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Port matrix param"); + break; + } + case 0x04: { // Port Delay - not yet implemented + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Port delay param"); + break; + } + case 0x05: { // Patch - not yet implemented + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Patch param"); + break; + } + case 0x07: { // Callback - not yet implemented + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Callback param"); + break; + } + default: + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Unhandled voice param ID: {:#x}", current->id); + break; + } + + // Move to next parameter + if (current->next == 0 || current->next == -1) { + break; + } + current = reinterpret_cast( + reinterpret_cast(current) + current->next); + } + return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetMatrixInfo(OrbisNgs2Handle voiceHandle, u32 matrixId, OrbisNgs2VoiceMatrixInfo* outInfo, size_t outInfoSize) { - LOG_ERROR(Lib_Ngs2, "matrixId = {}, outInfoSize = {}", matrixId, outInfoSize); + if (!voiceHandle) { + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + + if (!outInfo || outInfoSize < sizeof(OrbisNgs2VoiceMatrixInfo)) { + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + auto* voice = reinterpret_cast(voiceHandle); + + // Return default matrix info - identity matrix for stereo (1.0 on diagonal) + outInfo->numLevels = voice->format.numChannels * voice->format.numChannels; + if (outInfo->numLevels == 0) { + outInfo->numLevels = 4; // Default stereo 2x2 + } + + // Initialize to identity-like matrix + std::memset(outInfo->aLevel, 0, sizeof(outInfo->aLevel)); + u32 channels = voice->format.numChannels > 0 ? voice->format.numChannels : 2; + for (u32 i = 0; i < channels && i < 8; i++) { + outInfo->aLevel[i * channels + i] = 1.0f; + } + return ORBIS_OK; } @@ -333,12 +895,80 @@ s32 PS4_SYSV_ABI sceNgs2VoiceGetPortInfo(OrbisNgs2Handle voiceHandle, u32 port, s32 PS4_SYSV_ABI sceNgs2VoiceGetState(OrbisNgs2Handle voiceHandle, OrbisNgs2VoiceState* outState, size_t stateSize) { - LOG_ERROR(Lib_Ngs2, "stateSize = {}", stateSize); + if (!outState) { + LOG_ERROR(Lib_Ngs2, "Invalid voice state address"); + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + // Only accept valid state sizes: 0x4 (basic) or 0x30 (sampler) + if (stateSize != sizeof(OrbisNgs2VoiceState) && + stateSize != sizeof(OrbisNgs2SamplerVoiceState)) { + LOG_ERROR(Lib_Ngs2, "Invalid voice state size: {:#x}", stateSize); + return ORBIS_NGS2_ERROR_INVALID_OUT_SIZE; + } + + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "Invalid voice handle"); + // On invalid handle, zero out the state (LLE behavior) + if (stateSize == sizeof(OrbisNgs2VoiceState)) { + outState->stateFlags = 0; + } else { + std::memset(outState, 0, sizeof(OrbisNgs2SamplerVoiceState)); + } + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + + auto* voice = reinterpret_cast(voiceHandle); + + if (stateSize == sizeof(OrbisNgs2VoiceState)) { + outState->stateFlags = voice->stateFlags; + return ORBIS_OK; + } + + auto* samplerState = reinterpret_cast(outState); + samplerState->voiceState.stateFlags = voice->stateFlags; + + if (voice->isStreaming && voice->getReadyBufferCount() <= VoiceInternal::STARVATION_THRESHOLD) { + samplerState->userData = 1; + } else { + samplerState->userData = 0; + } + samplerState->envelopeHeight = 1.0f; + samplerState->peakHeight = 1.0f; + samplerState->reserved = 0; + + samplerState->numDecodedSamples = voice->totalDecodedSamples; + + u32 bytesPerSample = 2 * (voice->format.numChannels > 0 ? voice->format.numChannels : 2); + samplerState->decodedDataSize = samplerState->numDecodedSamples * bytesPerSample; + + samplerState->waveformData = voice->currentBufferPtr; + return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2VoiceGetStateFlags(OrbisNgs2Handle voiceHandle, u32* outStateFlags) { - LOG_ERROR(Lib_Ngs2, "called"); + if (!outStateFlags) { + LOG_ERROR(Lib_Ngs2, "Invalid voice state address"); + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + if (!voiceHandle) { + LOG_ERROR(Lib_Ngs2, "Invalid voice handle"); + *outStateFlags = 0; // LLE behavior: zero out on invalid handle + return ORBIS_NGS2_ERROR_INVALID_VOICE_HANDLE; + } + + auto* voice = reinterpret_cast(voiceHandle); + + u32 flags = voice->stateFlags & 0xFF; + + if (voice->isStreaming && voice->getReadyBufferCount() <= VoiceInternal::STARVATION_THRESHOLD) { + flags |= 0x80; + } + + *outStateFlags = flags; + return ORBIS_OK; } @@ -380,14 +1010,52 @@ s32 PS4_SYSV_ABI sceNgs2GeomApply(const OrbisNgs2GeomListenerWork* listener, s32 PS4_SYSV_ABI sceNgs2PanInit(OrbisNgs2PanWork* work, const float* aSpeakerAngle, float unitAngle, u32 numSpeakers) { - LOG_ERROR(Lib_Ngs2, "unitAngle = {}, numSpeakers = {}", unitAngle, numSpeakers); + LOG_DEBUG(Lib_Ngs2, "unitAngle = {}, numSpeakers = {}", unitAngle, numSpeakers); return ORBIS_OK; } s32 PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix(OrbisNgs2PanWork* work, const OrbisNgs2PanParam* aParam, u32 numParams, u32 matrixFormat, float* outVolumeMatrix) { - LOG_ERROR(Lib_Ngs2, "numParams = {}, matrixFormat = {}", numParams, matrixFormat); + if (!outVolumeMatrix) { + return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; + } + + // matrixFormat: 1 = mono, 2 = stereo, etc. + u32 numOutputChannels = matrixFormat; + if (numOutputChannels == 0) + numOutputChannels = 2; + if (numOutputChannels > 8) + numOutputChannels = 8; + + // Initialize volume matrix to identity/center pan + // For stereo output (format 2), we create a simple center pan + for (u32 p = 0; p < numParams; p++) { + float* matrix = outVolumeMatrix + p * numOutputChannels; + + if (aParam && numParams > 0) { + // Use the pan angle to compute left/right levels + float angle = aParam[p].angle; + // Simple stereo panning: angle 0 = center, -1 = left, +1 = right + float leftLevel = 0.5f * (1.0f - angle); + float rightLevel = 0.5f * (1.0f + angle); + + if (numOutputChannels >= 2) { + matrix[0] = leftLevel; + matrix[1] = rightLevel; + for (u32 ch = 2; ch < numOutputChannels; ch++) { + matrix[ch] = 0.0f; + } + } else { + matrix[0] = 1.0f; + } + } else { + for (u32 ch = 0; ch < numOutputChannels; ch++) { + matrix[ch] = 1.0f / numOutputChannels; + } + } + } + return ORBIS_OK; } @@ -395,7 +1063,7 @@ s32 PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix(OrbisNgs2PanWork* work, const OrbisNg s32 PS4_SYSV_ABI sceNgs2ReportRegisterHandler(u32 reportType, OrbisNgs2ReportHandler handler, uintptr_t userData, OrbisNgs2Handle* outHandle) { - LOG_INFO(Lib_Ngs2, "reportType = {}, userData = {}", reportType, userData); + LOG_DEBUG(Lib_Ngs2, "reportType = {}, userData = {}", reportType, userData); if (!handler) { LOG_ERROR(Lib_Ngs2, "handler is nullptr"); return ORBIS_NGS2_ERROR_INVALID_REPORT_HANDLE; @@ -408,7 +1076,7 @@ s32 PS4_SYSV_ABI sceNgs2ReportUnregisterHandler(OrbisNgs2Handle reportHandle) { LOG_ERROR(Lib_Ngs2, "reportHandle is nullptr"); return ORBIS_NGS2_ERROR_INVALID_REPORT_HANDLE; } - LOG_INFO(Lib_Ngs2, "called"); + LOG_DEBUG(Lib_Ngs2, "called"); return ORBIS_OK; } @@ -588,4 +1256,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("AbYvTOZ8Pts", "libSceNgs2", 1, "libSceNgs2", sceNgs2VoiceRunCommands); }; -} // namespace Libraries::Ngs2 +} // namespace Libraries::Ngs2 \ No newline at end of file diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index 141ac41ba..b7d9560f7 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -3,10 +3,12 @@ #include "ngs2_error.h" #include "ngs2_impl.h" +#include "ngs2_internal.h" #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/kernel.h" +#include using namespace Libraries::Kernel; @@ -101,8 +103,23 @@ s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* optio } if (outSystem) { - // dummy handle - outSystem->systemHandle = 1; + // Initialize system + std::memset(outSystem, 0, sizeof(SystemInternal)); + outSystem->systemHandle = reinterpret_cast(outSystem); + outSystem->sampleRate = sampleRate; + outSystem->currentSampleRate = sampleRate; + outSystem->maxGrainSamples = static_cast(maxGrainSamples); + outSystem->minGrainSamples = 64; + outSystem->numGrainSamples = static_cast(numGrainSamples); + outSystem->currentNumGrainSamples = numGrainSamples; + outSystem->renderCount = 0; + outSystem->rackCount = 0; + outSystem->isActive = 1; + + if (option && option->name[0] != '\0') { + std::strncpy(outSystem->name, option->name, ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1); + outSystem->name[ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1] = '\0'; + } } return ORBIS_OK; @@ -157,31 +174,130 @@ s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* // Setup StackBufferOpen(&stackBuffer, hostBufferInfo->hostBuffer, hostBufferInfo->hostBufferSize, &systemList, optionFlags); - result = SystemSetupCore(&stackBuffer, option, &setupResult); + + // Allocate SystemInternal from the buffer + auto* system = new SystemInternal(); + result = SystemSetupCore(&stackBuffer, option, system); if (result < 0) { + delete system; return result; } StackBufferClose(&stackBuffer, &requiredBufferSize); - // Copy buffer results - setupResult.bufferInfo = *hostBufferInfo; - setupResult.hostFree = hostFree; - // TODO - // setupResult.systemList = systemList; + system->bufferInfo = *hostBufferInfo; + system->hostFree = hostFree; + system->systemHandle = reinterpret_cast(system); - OrbisNgs2Handle systemHandle = setupResult.systemHandle; if (hostBufferInfo->hostBufferSize >= requiredBufferSize) { - *outHandle = systemHandle; + *outHandle = system->systemHandle; return ORBIS_OK; } - SystemCleanup(systemHandle, 0); + delete system; LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", hostBufferInfo->hostBufferSize, requiredBufferSize); return ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; } +u32 RackIdToIndex(u32 rackId) { + switch (rackId) { + case 0x1000: return 0; // Sampler + case 0x3000: return 1; // Mastering + case 0x2000: return 2; // Submixer + case 0x2001: return 3; // Submixer alt + case 0x4001: return 4; // Reverb + case 0x4002: return 5; // Equalizer + case 0x4003: return 6; // Custom + default: return 0xFF; + } +} + +s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* option, + const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle) { + if (!system) { + LOG_ERROR(Lib_Ngs2, "RackCreate: Invalid system handle"); + return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; + } + + u32 rackIndex = RackIdToIndex(rackId); + if (rackIndex == 0xFF) { + LOG_ERROR(Lib_Ngs2, "Invalid rack ID: {:#x}", rackId); + return ORBIS_NGS2_ERROR_INVALID_RACK_ID; + } + + auto* rack = new RackInternal(); + rack->ownerSystem = system; + rack->rackType = rackIndex; + rack->rackId = rackId; + rack->handle.systemData = system; + + // Setup rack info with defaults or from option + rack->info.rackHandle = reinterpret_cast(rack); + rack->info.ownerSystemHandle = system->systemHandle; + rack->info.type = rackIndex; + rack->info.rackId = rackId; + rack->info.minGrainSamples = 64; + rack->info.stateFlags = 0; + + // Use option values if provided, otherwise use defaults + if (option && option->size >= sizeof(OrbisNgs2RackOption)) { + std::strncpy(rack->info.name, option->name, ORBIS_NGS2_RACK_NAME_LENGTH - 1); + rack->info.name[ORBIS_NGS2_RACK_NAME_LENGTH - 1] = '\0'; + rack->info.maxVoices = option->maxVoices > 0 ? option->maxVoices : 1; + rack->info.maxGrainSamples = option->maxGrainSamples > 0 ? option->maxGrainSamples : 512; + } else { + // Default values when option is NULL - based on libSceNgs2.c analysis + rack->info.name[0] = '\0'; + // Sampler rack (0x1000) defaults to 0x100 (256) voices, others default to 1 + if (rackId == 0x1000) { + rack->info.maxVoices = 256; // Sampler default + } else { + rack->info.maxVoices = 1; + } + rack->info.maxGrainSamples = 512; + } + + // Allocate voices + u32 numVoices = rack->info.maxVoices; + rack->voices.reserve(numVoices); + for (u32 i = 0; i < numVoices; i++) { + auto voice = std::make_unique(); + voice->ownerRack = rack; + voice->voiceIndex = i; + voice->handle.systemData = system; + voice->stateFlags = 0; // Not playing + rack->voices.push_back(std::move(voice)); + } + + system->racks.push_back(rack); + system->rackCount++; + + if (outHandle) { + *outHandle = reinterpret_cast(rack); + } + + return ORBIS_OK; +} + +s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* outBufferInfo) { + if (!rack) { + return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; + } + + SystemInternal* system = rack->ownerSystem; + if (system) { + auto it = std::find(system->racks.begin(), system->racks.end(), rack); + if (it != system->racks.end()) { + system->racks.erase(it); + system->rackCount--; + } + } + + delete rack; + return ORBIS_OK; +} + } // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_impl.h b/src/core/libraries/ngs2/ngs2_impl.h index a433e84fd..e684d5bc0 100644 --- a/src/core/libraries/ngs2/ngs2_impl.h +++ b/src/core/libraries/ngs2/ngs2_impl.h @@ -3,7 +3,10 @@ #pragma once +#include "common/types.h" #include "core/libraries/kernel/threads/pthread.h" +#include +#include namespace Libraries::Ngs2 { @@ -92,6 +95,10 @@ struct StackBuffer { char padding[7]; }; +// Forward declarations for types used before definition +struct RackInternal; +struct VoiceInternal; + struct SystemInternal { // setup init char name[ORBIS_NGS2_SYSTEM_NAME_LENGTH]; // 0 @@ -154,6 +161,9 @@ struct SystemInternal { u32 rackCount; // 336 float lastRenderRatio; // 340 float cpuLoad; // 344 + + // Rack management + std::vector racks; }; struct HandleInternal { @@ -176,4 +186,14 @@ s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outI s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* hostBufferInfo, OrbisNgs2BufferFreeHandler hostFree, OrbisNgs2Handle* outHandle); +// Forward declarations for internal types +struct RackInternal; +struct SystemInternal; +struct OrbisNgs2RackOption; + +u32 RackIdToIndex(u32 rackId); +s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* option, + const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle); +s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* outBufferInfo); + } // namespace Libraries::Ngs2 diff --git a/src/core/libraries/ngs2/ngs2_internal.h b/src/core/libraries/ngs2/ngs2_internal.h new file mode 100644 index 000000000..586319fcf --- /dev/null +++ b/src/core/libraries/ngs2/ngs2_internal.h @@ -0,0 +1,198 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "ngs2.h" +#include "ngs2_impl.h" +#include +#include +#include + +namespace Libraries::Ngs2 { + +// Forward declarations +struct RackInternal; +struct VoiceInternal; + +// Waveform block for streaming support +struct WaveformBlockInternal { + u32 dataOffset; // Offset into the base data pointer + u32 dataSize; // Size of this block in bytes + u32 numRepeats; // Number of times to repeat this block (0 = once) + u32 numSkipSamples; // Samples to skip at start + u32 numSamples; // Total samples in this block + u32 currentRepeat; // Current repeat iteration + + WaveformBlockInternal() : dataOffset(0), dataSize(0), numRepeats(0), + numSkipSamples(0), numSamples(0), currentRepeat(0) {} +}; + +// Ring buffer slot for streaming +struct RingBufferSlot { + const void* basePtr; // Original base pointer provided by game + const void* data; // Actual data pointer (with offset applied) + u32 dataSize; + u32 numSamples; + bool valid; // Whether this slot contains valid data + bool consumed; // Whether this slot has been fully consumed + + RingBufferSlot() : basePtr(nullptr), data(nullptr), dataSize(0), numSamples(0), + valid(false), consumed(false) {} + + void set(const void* base, const void* d, u32 ds, u32 ns) { + basePtr = base; + data = d; + dataSize = ds; + numSamples = ns; + valid = true; + consumed = false; + } + + void markConsumed() { + consumed = true; + } + + void reset() { + basePtr = nullptr; + data = nullptr; + dataSize = 0; + numSamples = 0; + valid = false; + consumed = false; + } +}; + +struct VoiceInternal { + HandleInternal handle; + RackInternal* ownerRack; + u32 voiceIndex; + u32 stateFlags; + std::vector ports; + std::vector matrices; + + // Sampler-specific data + OrbisNgs2WaveformFormat format; + float pitchRatio; + float portVolume; // Volume level for the voice (0.0 to 1.0+) + bool isSetup; + + // Playback position (in samples, not bytes) + u32 currentSamplePos; // Integer sample position + float samplePosFloat; // Floating point position for resampling + + // Block-based streaming support + std::vector waveformBlocks; + u32 currentBlockIndex; // Which block we're reading from + u32 flags; + bool isStreaming; + + // Ring buffer for streaming audio + // The game provides buffers in a circular fashion (A->B->C->A->B->C...) + // We track which slots have data and which have been consumed + // Game typically refills with 3 buffers at once, so ring must accommodate that + static constexpr u32 MAX_RING_SLOTS = 3; // Support game's 3-buffer refill pattern + static constexpr u32 STARVATION_THRESHOLD = 0; // Signal when down to 1 buffer (game adds 3) + RingBufferSlot ringBuffer[MAX_RING_SLOTS]; + u32 ringWriteIndex; // Next slot to write to (from game) + u32 ringReadIndex; // Current slot being read (by renderer) + u32 ringBufferCount; // Number of valid buffers in ring + const void* lastConsumedBuffer = nullptr; // Last buffer we finished reading (for game to check) + u64 totalDecodedSamples; // Total samples decoded so far + const void* currentBufferPtr = nullptr; // Add this + + VoiceInternal() : ownerRack(nullptr), voiceIndex(0), stateFlags(0), + pitchRatio(1.0f), portVolume(1.0f), isSetup(false), + currentSamplePos(0), samplePosFloat(0.0f), + currentBlockIndex(0), flags(0), isStreaming(false), + ringWriteIndex(0), ringReadIndex(0), ringBufferCount(0), + lastConsumedBuffer(nullptr) { + handle.selfPtr = &handle; + handle.systemData = nullptr; + handle.refCount = 1; + handle.handleType = 3; // Voice + handle.handleID = 0; + std::memset(&format, 0, sizeof(format)); + for (u32 i = 0; i < MAX_RING_SLOTS; i++) { + ringBuffer[i].reset(); + } + } + + // Get the current buffer being read + RingBufferSlot* getCurrentSlot() { + // We use the consumed flag to determine if the current read head is valid data + if (ringBuffer[ringReadIndex].valid && !ringBuffer[ringReadIndex].consumed) { + return &ringBuffer[ringReadIndex]; + } + return nullptr; + } + + // Advance to the next buffer in the ring + void advanceReadIndex() { + if (!ringBuffer[ringReadIndex].valid) return; + + ringBuffer[ringReadIndex].consumed = true; + lastConsumedBuffer = ringBuffer[ringReadIndex].basePtr; + ringReadIndex = (ringReadIndex + 1) % MAX_RING_SLOTS; + + if (ringBufferCount > 0) { + ringBufferCount--; + } + } + + // Add a buffer to the ring + bool addToRing(const void* basePtr, const void* data, u32 dataSize, u32 numSamples) { + if (ringBufferCount >= MAX_RING_SLOTS) { + return false; + } + + RingBufferSlot* slot = &ringBuffer[ringWriteIndex]; + if (slot->valid && !slot->consumed) { + return false; + } + + slot->set(basePtr, data, dataSize, numSamples); + ringWriteIndex = (ringWriteIndex + 1) % MAX_RING_SLOTS; + ringBufferCount++; + + return true; + } + + // Reset the ring buffer state + void resetRing() { + for (u32 i = 0; i < MAX_RING_SLOTS; i++) { + ringBuffer[i].reset(); + } + ringWriteIndex = 0; + ringReadIndex = 0; + ringBufferCount = 0; + lastConsumedBuffer = nullptr; + currentBufferPtr = nullptr; + totalDecodedSamples = 0; + } + + // Get number of buffers ready to read + u32 getReadyBufferCount() const { + return ringBufferCount; + } +}; + +struct RackInternal { + HandleInternal handle; + OrbisNgs2RackInfo info; + SystemInternal* ownerSystem; + std::vector> voices; + u32 rackType; + u32 rackId; + + RackInternal() : ownerSystem(nullptr), rackType(0), rackId(0) { + handle.selfPtr = &handle; + handle.systemData = nullptr; + handle.refCount = 1; + handle.handleType = 2; // Rack + handle.handleID = 0; + std::memset(&info, 0, sizeof(info)); + } +}; + +} // namespace Libraries::Ngs2 From 0f4e5ffee0e2f571ce6b7cf871b3246d3f07915e Mon Sep 17 00:00:00 2001 From: Gursukh Date: Sat, 3 Jan 2026 23:06:00 +0000 Subject: [PATCH 2/3] Chore: Changed var names to snake case --- src/core/libraries/ngs2/ngs2.cpp | 337 +++++++++++++------------- src/core/libraries/ngs2/ngs2_impl.cpp | 221 ++++++++--------- src/core/libraries/ngs2/ngs2_impl.h | 24 +- 3 files changed, 291 insertions(+), 291 deletions(-) diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index ea24e58bb..db615225b 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -31,8 +31,8 @@ enum WaveformType : u32 { }; // Check if waveform type is supported -static bool IsWaveformTypeSupported(u32 waveformType) { - switch (waveformType) { +static bool IsWaveformTypeSupported(u32 waveform_type) { + switch (waveform_type) { case WaveformType::PCM16: return true; // TODO: Add cases for new formats here @@ -42,15 +42,15 @@ static bool IsWaveformTypeSupported(u32 waveformType) { } // Get bytes per sample for a waveform type -static u32 GetBytesPerSample(u32 waveformType, u32 numChannels) { - switch (waveformType) { +static u32 GetBytesPerSample(u32 waveform_type, u32 num_channels) { + switch (waveform_type) { case WaveformType::PCM16: - return 2 * numChannels; + return 2 * num_channels; // TODO: Add cases for new formats here // case WaveformType::FLOAT: - // return 4 * numChannels; + // return 4 * num_channels; default: - return 2 * numChannels; + return 2 * num_channels; } } @@ -58,14 +58,14 @@ static u32 GetBytesPerSample(u32 waveformType, u32 numChannels) { // PCM16 Decoder // ============================================================================= -static float DecodePCM16Sample(const void* data, u32 sampleIdx, u32 channel, u32 numChannels, - u32 totalSamples) { - if (sampleIdx >= totalSamples) { +static float DecodePCM16Sample(const void* data, u32 sample_idx, u32 channel, u32 num_channels, + u32 total_samples) { + if (sample_idx >= total_samples) { return 0.0f; } - const s16* srcData = reinterpret_cast(data); - s16 sample = srcData[sampleIdx * numChannels + channel]; + const s16* src_data = reinterpret_cast(data); + s16 sample = src_data[sample_idx * num_channels + channel]; return static_cast(sample) / 32768.0f; } @@ -74,21 +74,21 @@ static float DecodePCM16Sample(const void* data, u32 sampleIdx, u32 channel, u32 // Add new decoder functions here // ============================================================================= // Example: -// static float DecodeFloatSample(const void* data, u32 sampleIdx, u32 channel, -// u32 numChannels, u32 totalSamples) { -// const float* srcData = reinterpret_cast(data); -// return srcData[sampleIdx * numChannels + channel]; +// static float DecodeFloatSample(const void* data, u32 sample_idx, u32 channel, +// u32 num_channels, u32 total_samples) { +// const float* src_data = reinterpret_cast(data); +// return src_data[sample_idx * num_channels + channel]; // } // ============================================================================= // Unified Sample Decoder // ============================================================================= -static float DecodeSample(const void* data, u32 sampleIdx, u32 channel, u32 numChannels, - u32 totalSamples, u32 waveformType) { - switch (waveformType) { +static float DecodeSample(const void* data, u32 sample_idx, u32 channel, u32 num_channels, + u32 total_samples, u32 waveform_type) { + switch (waveform_type) { case WaveformType::PCM16: - return DecodePCM16Sample(data, sampleIdx, channel, numChannels, totalSamples); + return DecodePCM16Sample(data, sample_idx, channel, num_channels, total_samples); // TODO: Add cases for new formats here default: return 0.0f; @@ -100,10 +100,10 @@ static float DecodeSample(const void* data, u32 sampleIdx, u32 channel, u32 numC // ============================================================================= struct RenderContext { - const OrbisNgs2RenderBufferInfo* bufferInfo; - u32 numBufferInfo; - u32 grainSamples; - u32 outputSampleRate; + const OrbisNgs2RenderBufferInfo* buffer_info; + u32 num_buffer_info; + u32 grain_samples; + u32 output_sample_rate; }; static bool RenderVoice(VoiceInternal* voice, const RenderContext& ctx) { @@ -124,145 +124,145 @@ static bool RenderVoice(VoiceInternal* voice, const RenderContext& ctx) { return false; } - u32 waveformType = voice->format.waveformType; - if (!IsWaveformTypeSupported(waveformType)) { - LOG_DEBUG(Lib_Ngs2, "(STUBBED) Unsupported waveform type: {:#x}", waveformType); + u32 waveform_type = voice->format.waveformType; + if (!IsWaveformTypeSupported(waveform_type)) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Unsupported waveform type: {:#x}", waveform_type); return false; } // Get current slot from ring buffer - RingBufferSlot* currentSlot = voice->getCurrentSlot(); - if (!currentSlot || !currentSlot->valid || currentSlot->consumed) { + RingBufferSlot* current_slot = voice->getCurrentSlot(); + if (!current_slot || !current_slot->valid || current_slot->consumed) { LOG_DEBUG(Lib_Ngs2, "(STUBBED) No valid buffer in ring"); return false; } - voice->currentBufferPtr = currentSlot->basePtr; + voice->currentBufferPtr = current_slot->basePtr; - u32 numChannels = voice->format.numChannels; - if (numChannels == 0 || numChannels > 8) { - LOG_DEBUG(Lib_Ngs2, "(STUBBED) Invalid number of channels: {}", numChannels); + u32 num_channels = voice->format.numChannels; + if (num_channels == 0 || num_channels > 8) { + LOG_DEBUG(Lib_Ngs2, "(STUBBED) Invalid number of channels: {}", num_channels); return false; } // Calculate sample rate ratio for resampling - u32 sourceSampleRate = voice->format.sampleRate; - if (sourceSampleRate == 0) { - sourceSampleRate = 48000; + u32 source_sample_rate = voice->format.sampleRate; + if (source_sample_rate == 0) { + source_sample_rate = 48000; } - float pitchRatio = voice->pitchRatio; - if (pitchRatio <= 0.0f) { - pitchRatio = 1.0f; + float pitch_ratio = voice->pitchRatio; + if (pitch_ratio <= 0.0f) { + pitch_ratio = 1.0f; } - float sampleRateRatio = (static_cast(sourceSampleRate) * pitchRatio) / - static_cast(ctx.outputSampleRate); + float sample_rate_ratio = (static_cast(source_sample_rate) * pitch_ratio) / + static_cast(ctx.output_sample_rate); // Find matching output buffer and render - for (u32 bufIdx = 0; bufIdx < ctx.numBufferInfo; bufIdx++) { - const auto& bufInfo = ctx.bufferInfo[bufIdx]; - if (!bufInfo.buffer || bufInfo.bufferSize == 0) { + for (u32 buf_idx = 0; buf_idx < ctx.num_buffer_info; buf_idx++) { + const auto& buf_info = ctx.buffer_info[buf_idx]; + if (!buf_info.buffer || buf_info.bufferSize == 0) { continue; } - const void* srcData = currentSlot->data; - u32 totalSamples = currentSlot->numSamples; - if (totalSamples == 0) { + const void* src_data = current_slot->data; + u32 total_samples = current_slot->numSamples; + if (total_samples == 0) { continue; } // Determine output format // Output buffer format detection - use raw values since we only know PCM16 for sure - float* dstFloat = nullptr; - s16* dstS16 = nullptr; - u32 dstChannels = 2; - bool outputIsFloat = false; + float* dst_float = nullptr; + s16* dst_s16 = nullptr; + u32 dst_channels = 2; + bool output_is_float = false; - if (bufInfo.waveformType == WaveformType::PCM16) { - dstS16 = reinterpret_cast(bufInfo.buffer); - dstChannels = bufInfo.numChannels > 0 ? bufInfo.numChannels : numChannels; - outputIsFloat = false; + if (buf_info.waveformType == WaveformType::PCM16) { + dst_s16 = reinterpret_cast(buf_info.buffer); + dst_channels = buf_info.numChannels > 0 ? buf_info.numChannels : num_channels; + output_is_float = false; } else { // Default to float output for unknown types - dstFloat = reinterpret_cast(bufInfo.buffer); - dstChannels = bufInfo.numChannels > 0 ? bufInfo.numChannels : 2; - outputIsFloat = true; + dst_float = reinterpret_cast(buf_info.buffer); + dst_channels = buf_info.numChannels > 0 ? buf_info.numChannels : 2; + output_is_float = true; } // Render samples - float currentPos = voice->samplePosFloat; - bool voiceStopped = false; + float current_pos = voice->samplePosFloat; + bool voice_stopped = false; - for (u32 outSample = 0; outSample < ctx.grainSamples && !voiceStopped; outSample++) { - u32 sampleInt = static_cast(currentPos); - float frac = currentPos - static_cast(sampleInt); + for (u32 out_sample = 0; out_sample < ctx.grain_samples && !voice_stopped; out_sample++) { + u32 sample_int = static_cast(current_pos); + float frac = current_pos - static_cast(sample_int); // Check if we've reached the end of current buffer - if (sampleInt >= totalSamples) { - voice->lastConsumedBuffer = currentSlot->basePtr; - voice->totalDecodedSamples += totalSamples; + if (sample_int >= total_samples) { + voice->lastConsumedBuffer = current_slot->basePtr; + voice->totalDecodedSamples += total_samples; voice->advanceReadIndex(); if (voice->getReadyBufferCount() <= VoiceInternal::STARVATION_THRESHOLD) { voice->stateFlags |= 0x80; } - currentSlot = voice->getCurrentSlot(); - if (currentSlot && currentSlot->valid && !currentSlot->consumed) { - srcData = currentSlot->data; - totalSamples = currentSlot->numSamples; - currentPos = 0.0f; - sampleInt = 0; + current_slot = voice->getCurrentSlot(); + if (current_slot && current_slot->valid && !current_slot->consumed) { + src_data = current_slot->data; + total_samples = current_slot->numSamples; + current_pos = 0.0f; + sample_int = 0; frac = 0.0f; voice->isStreaming = true; - voice->currentBufferPtr = currentSlot->basePtr; + voice->currentBufferPtr = current_slot->basePtr; } else { if (voice->isStreaming) { - currentPos = 0.0f; + current_pos = 0.0f; break; } else { voice->stateFlags &= ~0x01; voice->stateFlags |= 0x400; - voiceStopped = true; + voice_stopped = true; break; } } } // Decode and interpolate samples for each output channel - for (u32 ch = 0; ch < dstChannels; ch++) { - u32 srcCh = ch < numChannels ? ch : 0; + for (u32 ch = 0; ch < dst_channels; ch++) { + u32 src_ch = ch < num_channels ? ch : 0; - float sample0 = DecodeSample(srcData, sampleInt, srcCh, numChannels, totalSamples, - waveformType); - float sample1 = DecodeSample(srcData, sampleInt + 1, srcCh, numChannels, - totalSamples, waveformType); + float sample0 = DecodeSample(src_data, sample_int, src_ch, num_channels, + total_samples, waveform_type); + float sample1 = DecodeSample(src_data, sample_int + 1, src_ch, num_channels, + total_samples, waveform_type); float sample = sample0 + frac * (sample1 - sample0); // Apply port volume sample *= voice->portVolume; // Write to output buffer - u32 dstIdx = outSample * dstChannels + ch; - if (outputIsFloat) { - if (dstIdx * sizeof(float) < bufInfo.bufferSize) { - dstFloat[dstIdx] += sample; - dstFloat[dstIdx] = std::clamp(dstFloat[dstIdx], -1.0f, 1.0f); + u32 dst_idx = out_sample * dst_channels + ch; + if (output_is_float) { + if (dst_idx * sizeof(float) < buf_info.bufferSize) { + dst_float[dst_idx] += sample; + dst_float[dst_idx] = std::clamp(dst_float[dst_idx], -1.0f, 1.0f); } } else { - if (dstIdx * 2 < bufInfo.bufferSize) { - s32 mixed = dstS16[dstIdx] + static_cast(sample * 32768.0f); - dstS16[dstIdx] = static_cast(std::clamp(mixed, -32768, 32767)); + if (dst_idx * 2 < buf_info.bufferSize) { + s32 mixed = dst_s16[dst_idx] + static_cast(sample * 32768.0f); + dst_s16[dst_idx] = static_cast(std::clamp(mixed, -32768, 32767)); } } } - currentPos += sampleRateRatio; + current_pos += sample_rate_ratio; } - voice->samplePosFloat = currentPos; - voice->currentSamplePos = static_cast(currentPos); + voice->samplePosFloat = current_pos; + voice->currentSamplePos = static_cast(current_pos); return true; } @@ -395,42 +395,42 @@ s32 PS4_SYSV_ABI sceNgs2RackQueryBufferSize(u32 rackId, const OrbisNgs2RackOptio return ORBIS_NGS2_ERROR_INVALID_OUT_ADDRESS; } - u32 rackIndex = RackIdToIndex(rackId); - if (rackIndex == 0xFF) { + u32 rack_index = RackIdToIndex(rackId); + if (rack_index == 0xFF) { LOG_ERROR(Lib_Ngs2, "Invalid rack ID: {:#x}", rackId); return ORBIS_NGS2_ERROR_INVALID_RACK_ID; } // Use defaults if option is NULL - based on libSceNgs2.c analysis - u32 maxVoices = 1; - u32 maxPorts = 1; - u32 maxMatrices = 1; + u32 max_voices = 1; + u32 max_ports = 1; + u32 max_matrices = 1; if (option && option->size >= sizeof(OrbisNgs2RackOption)) { - maxVoices = option->maxVoices > 0 ? option->maxVoices : 1; - maxPorts = option->maxPorts > 0 ? option->maxPorts : 1; - maxMatrices = option->maxMatrices > 0 ? option->maxMatrices : 1; + max_voices = option->maxVoices > 0 ? option->maxVoices : 1; + max_ports = option->maxPorts > 0 ? option->maxPorts : 1; + max_matrices = option->maxMatrices > 0 ? option->maxMatrices : 1; } else { // Sampler rack (0x1000) defaults to 256 voices if (rackId == 0x1000) { - maxVoices = 256; + max_voices = 256; } } // Calculate required buffer size - size_t baseSize = sizeof(RackInternal); - size_t voiceSize = sizeof(VoiceInternal) * maxVoices; - size_t portSize = sizeof(OrbisNgs2VoicePortInfo) * maxPorts * maxVoices; - size_t matrixSize = sizeof(OrbisNgs2VoiceMatrixInfo) * maxMatrices * maxVoices; + size_t base_size = sizeof(RackInternal); + size_t voice_size = sizeof(VoiceInternal) * max_voices; + size_t port_size = sizeof(OrbisNgs2VoicePortInfo) * max_ports * max_voices; + size_t matrix_size = sizeof(OrbisNgs2VoiceMatrixInfo) * max_matrices * max_voices; - size_t totalSize = baseSize + voiceSize + portSize + matrixSize; - totalSize = (totalSize + 0xFF) & ~0xFF; // Align to 256 bytes + size_t total_size = base_size + voice_size + port_size + matrix_size; + total_size = (total_size + 0xFF) & ~0xFF; // Align to 256 bytes outBufferInfo->hostBuffer = nullptr; - outBufferInfo->hostBufferSize = totalSize; + outBufferInfo->hostBufferSize = total_size; std::memset(outBufferInfo->reserved, 0, sizeof(outBufferInfo->reserved)); - LOG_DEBUG(Lib_Ngs2, "Required buffer size: {} bytes", totalSize); + LOG_DEBUG(Lib_Ngs2, "Required buffer size: {} bytes", total_size); return ORBIS_OK; } @@ -448,7 +448,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreate(const OrbisNgs2SystemOption* option, const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle) { s32 result; - OrbisNgs2ContextBufferInfo localInfo; + OrbisNgs2ContextBufferInfo local_info; if (!bufferInfo || !outHandle) { if (!bufferInfo) { result = ORBIS_NGS2_ERROR_INVALID_BUFFER_INFO; @@ -461,14 +461,14 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreate(const OrbisNgs2SystemOption* option, // TODO: Report errors? } else { // Make bufferInfo copy - localInfo.hostBuffer = bufferInfo->hostBuffer; - localInfo.hostBufferSize = bufferInfo->hostBufferSize; + local_info.hostBuffer = bufferInfo->hostBuffer; + local_info.hostBufferSize = bufferInfo->hostBufferSize; for (int i = 0; i < 5; i++) { - localInfo.reserved[i] = bufferInfo->reserved[i]; + local_info.reserved[i] = bufferInfo->reserved[i]; } - localInfo.userData = bufferInfo->userData; + local_info.userData = bufferInfo->userData; - result = SystemSetup(option, &localInfo, 0, outHandle); + result = SystemSetup(option, &local_info, 0, outHandle); } // TODO: API reporting? @@ -482,20 +482,19 @@ s32 PS4_SYSV_ABI sceNgs2SystemCreateWithAllocator(const OrbisNgs2SystemOption* o OrbisNgs2Handle* outHandle) { s32 result; if (allocator && allocator->allocHandler != 0) { - OrbisNgs2BufferAllocHandler hostAlloc = allocator->allocHandler; + OrbisNgs2BufferAllocHandler host_alloc = allocator->allocHandler; if (outHandle) { - OrbisNgs2BufferFreeHandler hostFree = allocator->freeHandler; - OrbisNgs2ContextBufferInfo bufferInfo; - result = SystemSetup(option, &bufferInfo, 0, 0); + OrbisNgs2BufferFreeHandler host_free = allocator->freeHandler; + OrbisNgs2ContextBufferInfo buffer_info; + result = SystemSetup(option, &buffer_info, 0, 0); if (result >= 0) { - uintptr_t sysUserData = allocator->userData; - result = Core::ExecuteGuest(hostAlloc, &bufferInfo); + result = Core::ExecuteGuest(host_alloc, &buffer_info); if (result >= 0) { - OrbisNgs2Handle* handleCopy = outHandle; - result = SystemSetup(option, &bufferInfo, hostFree, handleCopy); + OrbisNgs2Handle* handle_copy = outHandle; + result = SystemSetup(option, &buffer_info, host_free, handle_copy); if (result < 0) { - if (hostFree) { - Core::ExecuteGuest(hostFree, &bufferInfo); + if (host_free) { + Core::ExecuteGuest(host_free, &buffer_info); } } } @@ -600,12 +599,12 @@ s32 PS4_SYSV_ABI sceNgs2SystemRender(OrbisNgs2Handle systemHandle, // Setup render context RenderContext ctx{}; - ctx.bufferInfo = aBufferInfo; - ctx.numBufferInfo = numBufferInfo; - ctx.grainSamples = system->numGrainSamples > 0 ? system->numGrainSamples : 256; - ctx.outputSampleRate = system->sampleRate > 0 ? system->sampleRate : 48000; + ctx.buffer_info = aBufferInfo; + ctx.num_buffer_info = numBufferInfo; + ctx.grain_samples = system->numGrainSamples > 0 ? system->numGrainSamples : 256; + ctx.output_sample_rate = system->sampleRate > 0 ? system->sampleRate : 48000; - u32 voicesRendered = 0; + u32 voices_rendered = 0; // Process each rack for (auto* rack : system->racks) { @@ -617,7 +616,7 @@ s32 PS4_SYSV_ABI sceNgs2SystemRender(OrbisNgs2Handle systemHandle, if (rack->rackId == 0x1000) { for (auto& voice : rack->voices) { if (RenderVoice(voice.get(), ctx)) { - voicesRendered++; + voices_rendered++; } } } @@ -706,14 +705,14 @@ s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, auto* blocks = reinterpret_cast(current); - u32 totalDataSize = 0; - u32 totalSamples = 0; - u32 numChannels = voice->format.numChannels > 0 ? voice->format.numChannels : 2; + u32 total_data_size = 0; + u32 total_samples = 0; + u32 num_channels = voice->format.numChannels > 0 ? voice->format.numChannels : 2; if (blocks->numBlocks > 0 && blocks->aBlock) { for (u32 i = 0; i < blocks->numBlocks; i++) { - totalDataSize += blocks->aBlock[i].dataSize; - totalSamples += blocks->aBlock[i].numSamples; + total_data_size += blocks->aBlock[i].dataSize; + total_samples += blocks->aBlock[i].numSamples; } voice->waveformBlocks.resize(blocks->numBlocks); @@ -727,18 +726,18 @@ s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, } } - const u8* basePtr = reinterpret_cast(blocks->data); - u32 dataOffset = + const u8* base_ptr = reinterpret_cast(blocks->data); + u32 data_offset = (blocks->numBlocks > 0 && blocks->aBlock) ? blocks->aBlock[0].dataOffset : 0; - const void* actualDataPtr = basePtr + dataOffset; + const void* actual_data_ptr = base_ptr + data_offset; - bool isFirstSetup = (voice->ringBufferCount == 0) && !voice->isStreaming; + bool is_first_setup = (voice->ringBufferCount == 0) && !voice->isStreaming; - if (isFirstSetup) { + if (is_first_setup) { voice->resetRing(); - voice->addToRing(blocks->data, actualDataPtr, totalDataSize, totalSamples); + voice->addToRing(blocks->data, actual_data_ptr, total_data_size, total_samples); voice->lastConsumedBuffer = nullptr; - voice->currentBufferPtr = actualDataPtr; + voice->currentBufferPtr = actual_data_ptr; voice->stateFlags &= ~0x80; voice->currentSamplePos = 0; voice->samplePosFloat = 0.0f; @@ -746,7 +745,7 @@ s32 PS4_SYSV_ABI sceNgs2VoiceControl(OrbisNgs2Handle voiceHandle, voice->isStreaming = false; } else { bool added = - voice->addToRing(blocks->data, actualDataPtr, totalDataSize, totalSamples); + voice->addToRing(blocks->data, actual_data_ptr, total_data_size, total_samples); if (added) { voice->isStreaming = true; @@ -925,24 +924,24 @@ s32 PS4_SYSV_ABI sceNgs2VoiceGetState(OrbisNgs2Handle voiceHandle, OrbisNgs2Voic return ORBIS_OK; } - auto* samplerState = reinterpret_cast(outState); - samplerState->voiceState.stateFlags = voice->stateFlags; + auto* sampler_state = reinterpret_cast(outState); + sampler_state->voiceState.stateFlags = voice->stateFlags; if (voice->isStreaming && voice->getReadyBufferCount() <= VoiceInternal::STARVATION_THRESHOLD) { - samplerState->userData = 1; + sampler_state->userData = 1; } else { - samplerState->userData = 0; + sampler_state->userData = 0; } - samplerState->envelopeHeight = 1.0f; - samplerState->peakHeight = 1.0f; - samplerState->reserved = 0; + sampler_state->envelopeHeight = 1.0f; + sampler_state->peakHeight = 1.0f; + sampler_state->reserved = 0; - samplerState->numDecodedSamples = voice->totalDecodedSamples; + sampler_state->numDecodedSamples = voice->totalDecodedSamples; - u32 bytesPerSample = 2 * (voice->format.numChannels > 0 ? voice->format.numChannels : 2); - samplerState->decodedDataSize = samplerState->numDecodedSamples * bytesPerSample; + u32 bytes_per_sample = 2 * (voice->format.numChannels > 0 ? voice->format.numChannels : 2); + sampler_state->decodedDataSize = sampler_state->numDecodedSamples * bytes_per_sample; - samplerState->waveformData = voice->currentBufferPtr; + sampler_state->waveformData = voice->currentBufferPtr; return ORBIS_OK; } @@ -1022,36 +1021,36 @@ s32 PS4_SYSV_ABI sceNgs2PanGetVolumeMatrix(OrbisNgs2PanWork* work, const OrbisNg } // matrixFormat: 1 = mono, 2 = stereo, etc. - u32 numOutputChannels = matrixFormat; - if (numOutputChannels == 0) - numOutputChannels = 2; - if (numOutputChannels > 8) - numOutputChannels = 8; + u32 num_output_channels = matrixFormat; + if (num_output_channels == 0) + num_output_channels = 2; + if (num_output_channels > 8) + num_output_channels = 8; // Initialize volume matrix to identity/center pan // For stereo output (format 2), we create a simple center pan for (u32 p = 0; p < numParams; p++) { - float* matrix = outVolumeMatrix + p * numOutputChannels; + float* matrix = outVolumeMatrix + p * num_output_channels; if (aParam && numParams > 0) { // Use the pan angle to compute left/right levels float angle = aParam[p].angle; // Simple stereo panning: angle 0 = center, -1 = left, +1 = right - float leftLevel = 0.5f * (1.0f - angle); - float rightLevel = 0.5f * (1.0f + angle); + float left_level = 0.5f * (1.0f - angle); + float right_level = 0.5f * (1.0f + angle); - if (numOutputChannels >= 2) { - matrix[0] = leftLevel; - matrix[1] = rightLevel; - for (u32 ch = 2; ch < numOutputChannels; ch++) { + if (num_output_channels >= 2) { + matrix[0] = left_level; + matrix[1] = right_level; + for (u32 ch = 2; ch < num_output_channels; ch++) { matrix[ch] = 0.0f; } } else { matrix[0] = 1.0f; } } else { - for (u32 ch = 0; ch < numOutputChannels; ch++) { - matrix[ch] = 1.0f / numOutputChannels; + for (u32 ch = 0; ch < num_output_channels; ch++) { + matrix[ch] = 1.0f / num_output_channels; } } } diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index b7d9560f7..29f3223d9 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -14,8 +14,8 @@ using namespace Libraries::Kernel; namespace Libraries::Ngs2 { -s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handleType) { - switch (handleType) { +s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handle_type) { + switch (handle_type) { case 1: LOG_ERROR(Lib_Ngs2, "Invalid system handle {}", handle); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; @@ -38,33 +38,33 @@ void* MemoryClear(void* buffer, size_t size) { return memset(buffer, 0, size); } -s32 StackBufferClose(StackBuffer* stackBuffer, size_t* outTotalSize) { - if (outTotalSize) { - *outTotalSize = stackBuffer->usedSize + stackBuffer->alignment; +s32 StackBufferClose(StackBuffer* stack_buffer, size_t* out_total_size) { + if (out_total_size) { + *out_total_size = stack_buffer->usedSize + stack_buffer->alignment; } return ORBIS_OK; } -s32 StackBufferOpen(StackBuffer* stackBuffer, void* bufferStart, size_t bufferSize, - void** outBuffer, u8 flags) { - stackBuffer->top = outBuffer; - stackBuffer->base = bufferStart; - stackBuffer->size = (size_t)bufferStart; - stackBuffer->currentOffset = (size_t)bufferStart; - stackBuffer->usedSize = 0; - stackBuffer->totalSize = bufferSize; - stackBuffer->alignment = 8; // this is a fixed value - stackBuffer->flags = flags; +s32 StackBufferOpen(StackBuffer* stack_buffer, void* buffer_start, size_t buffer_size, + void** out_buffer, u8 flags) { + stack_buffer->top = out_buffer; + stack_buffer->base = buffer_start; + stack_buffer->size = (size_t)buffer_start; + stack_buffer->currentOffset = (size_t)buffer_start; + stack_buffer->usedSize = 0; + stack_buffer->totalSize = buffer_size; + stack_buffer->alignment = 8; // this is a fixed value + stack_buffer->flags = flags; - if (outBuffer != NULL) { - *outBuffer = NULL; + if (out_buffer != NULL) { + *out_buffer = NULL; } return ORBIS_OK; } -s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outInfo) { - if (!systemHandle) { +s32 SystemCleanup(OrbisNgs2Handle system_handle, OrbisNgs2ContextBufferInfo* out_info) { + if (!system_handle) { return ORBIS_NGS2_ERROR_INVALID_HANDLE; } @@ -73,65 +73,66 @@ s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outI return ORBIS_OK; } -s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* option, - SystemInternal* outSystem) { - u32 maxGrainSamples = 512; - u32 numGrainSamples = 256; - u32 sampleRate = 48000; +s32 SystemSetupCore(StackBuffer* stack_buffer, const OrbisNgs2SystemOption* option, + SystemInternal* out_system) { + u32 max_grain_samples = 512; + u32 num_grain_samples = 256; + u32 sample_rate = 48000; if (option) { - sampleRate = option->sampleRate; - maxGrainSamples = option->maxGrainSamples; - numGrainSamples = option->numGrainSamples; + sample_rate = option->sampleRate; + max_grain_samples = option->maxGrainSamples; + num_grain_samples = option->numGrainSamples; } - if (maxGrainSamples < 64 || maxGrainSamples > 1024 || (maxGrainSamples & 63) != 0) { - LOG_ERROR(Lib_Ngs2, "Invalid system option (maxGrainSamples={},x64)", maxGrainSamples); + if (max_grain_samples < 64 || max_grain_samples > 1024 || (max_grain_samples & 63) != 0) { + LOG_ERROR(Lib_Ngs2, "Invalid system option (maxGrainSamples={},x64)", max_grain_samples); return ORBIS_NGS2_ERROR_INVALID_MAX_GRAIN_SAMPLES; } - if (numGrainSamples < 64 || numGrainSamples > 1024 || (numGrainSamples & 63) != 0) { - LOG_ERROR(Lib_Ngs2, "Invalid system option (numGrainSamples={},x64)", numGrainSamples); + if (num_grain_samples < 64 || num_grain_samples > 1024 || (num_grain_samples & 63) != 0) { + LOG_ERROR(Lib_Ngs2, "Invalid system option (numGrainSamples={},x64)", num_grain_samples); return ORBIS_NGS2_ERROR_INVALID_NUM_GRAIN_SAMPLES; } - if (sampleRate != 11025 && sampleRate != 12000 && sampleRate != 22050 && sampleRate != 24000 && - sampleRate != 44100 && sampleRate != 48000 && sampleRate != 88200 && sampleRate != 96000 && - sampleRate != 176400 && sampleRate != 192000) { - LOG_ERROR(Lib_Ngs2, "Invalid system option(sampleRate={}:44.1/48kHz series)", sampleRate); + if (sample_rate != 11025 && sample_rate != 12000 && sample_rate != 22050 && + sample_rate != 24000 && sample_rate != 44100 && sample_rate != 48000 && + sample_rate != 88200 && sample_rate != 96000 && sample_rate != 176400 && + sample_rate != 192000) { + LOG_ERROR(Lib_Ngs2, "Invalid system option(sampleRate={}:44.1/48kHz series)", sample_rate); return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE; } - if (outSystem) { + if (out_system) { // Initialize system - std::memset(outSystem, 0, sizeof(SystemInternal)); - outSystem->systemHandle = reinterpret_cast(outSystem); - outSystem->sampleRate = sampleRate; - outSystem->currentSampleRate = sampleRate; - outSystem->maxGrainSamples = static_cast(maxGrainSamples); - outSystem->minGrainSamples = 64; - outSystem->numGrainSamples = static_cast(numGrainSamples); - outSystem->currentNumGrainSamples = numGrainSamples; - outSystem->renderCount = 0; - outSystem->rackCount = 0; - outSystem->isActive = 1; - + std::memset(out_system, 0, sizeof(SystemInternal)); + out_system->systemHandle = reinterpret_cast(out_system); + out_system->sampleRate = sample_rate; + out_system->currentSampleRate = sample_rate; + out_system->maxGrainSamples = static_cast(max_grain_samples); + out_system->minGrainSamples = 64; + out_system->numGrainSamples = static_cast(num_grain_samples); + out_system->currentNumGrainSamples = num_grain_samples; + out_system->renderCount = 0; + out_system->rackCount = 0; + out_system->isActive = 1; + if (option && option->name[0] != '\0') { - std::strncpy(outSystem->name, option->name, ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1); - outSystem->name[ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1] = '\0'; + std::strncpy(out_system->name, option->name, ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1); + out_system->name[ORBIS_NGS2_SYSTEM_NAME_LENGTH - 1] = '\0'; } } return ORBIS_OK; } -s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* hostBufferInfo, - OrbisNgs2BufferFreeHandler hostFree, OrbisNgs2Handle* outHandle) { - u8 optionFlags = 0; - StackBuffer stackBuffer; - SystemInternal setupResult; - void* systemList = NULL; - size_t requiredBufferSize = 0; +s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* host_buffer_info, + OrbisNgs2BufferFreeHandler host_free, OrbisNgs2Handle* out_handle) { + u8 option_flags = 0; + StackBuffer stack_buffer; + SystemInternal setup_result; + void* system_list = NULL; + size_t required_buffer_size = 0; u32 result = ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; if (option) { @@ -139,71 +140,71 @@ s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* LOG_ERROR(Lib_Ngs2, "Invalid system option size ({})", option->size); return ORBIS_NGS2_ERROR_INVALID_OPTION_SIZE; } - optionFlags = option->flags >> 31; + option_flags = option->flags >> 31; } // Init - StackBufferOpen(&stackBuffer, NULL, 0, NULL, optionFlags); - result = SystemSetupCore(&stackBuffer, option, 0); + StackBufferOpen(&stack_buffer, NULL, 0, NULL, option_flags); + result = SystemSetupCore(&stack_buffer, option, 0); if (result < 0) { return result; } - StackBufferClose(&stackBuffer, &requiredBufferSize); + StackBufferClose(&stack_buffer, &required_buffer_size); - // outHandle unprovided - if (!outHandle) { - hostBufferInfo->hostBuffer = NULL; - hostBufferInfo->hostBufferSize = requiredBufferSize; - MemoryClear(&hostBufferInfo->reserved, sizeof(hostBufferInfo->reserved)); + // out_handle unprovided + if (!out_handle) { + host_buffer_info->hostBuffer = NULL; + host_buffer_info->hostBufferSize = required_buffer_size; + MemoryClear(&host_buffer_info->reserved, sizeof(host_buffer_info->reserved)); return ORBIS_OK; } - if (!hostBufferInfo->hostBuffer) { - LOG_ERROR(Lib_Ngs2, "Invalid system buffer address ({})", hostBufferInfo->hostBuffer); + if (!host_buffer_info->hostBuffer) { + LOG_ERROR(Lib_Ngs2, "Invalid system buffer address ({})", host_buffer_info->hostBuffer); return ORBIS_NGS2_ERROR_INVALID_BUFFER_ADDRESS; } - if (hostBufferInfo->hostBufferSize < requiredBufferSize) { + if (host_buffer_info->hostBufferSize < required_buffer_size) { LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", - hostBufferInfo->hostBufferSize, requiredBufferSize); + host_buffer_info->hostBufferSize, required_buffer_size); return ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; } // Setup - StackBufferOpen(&stackBuffer, hostBufferInfo->hostBuffer, hostBufferInfo->hostBufferSize, - &systemList, optionFlags); - + StackBufferOpen(&stack_buffer, host_buffer_info->hostBuffer, host_buffer_info->hostBufferSize, + &system_list, option_flags); + // Allocate SystemInternal from the buffer auto* system = new SystemInternal(); - result = SystemSetupCore(&stackBuffer, option, system); + result = SystemSetupCore(&stack_buffer, option, system); if (result < 0) { delete system; return result; } - StackBufferClose(&stackBuffer, &requiredBufferSize); + StackBufferClose(&stack_buffer, &required_buffer_size); - system->bufferInfo = *hostBufferInfo; - system->hostFree = hostFree; + system->bufferInfo = *host_buffer_info; + system->hostFree = host_free; system->systemHandle = reinterpret_cast(system); - if (hostBufferInfo->hostBufferSize >= requiredBufferSize) { - *outHandle = system->systemHandle; + if (host_buffer_info->hostBufferSize >= required_buffer_size) { + *out_handle = system->systemHandle; return ORBIS_OK; } delete system; - LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", hostBufferInfo->hostBufferSize, - requiredBufferSize); + LOG_ERROR(Lib_Ngs2, "Invalid system buffer size ({}<{}[byte])", + host_buffer_info->hostBufferSize, required_buffer_size); return ORBIS_NGS2_ERROR_INVALID_BUFFER_SIZE; } -u32 RackIdToIndex(u32 rackId) { - switch (rackId) { +u32 RackIdToIndex(u32 rack_id) { + switch (rack_id) { case 0x1000: return 0; // Sampler case 0x3000: return 1; // Mastering case 0x2000: return 2; // Submixer @@ -215,33 +216,33 @@ u32 RackIdToIndex(u32 rackId) { } } -s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* option, - const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle) { +s32 RackCreate(SystemInternal* system, u32 rack_id, const OrbisNgs2RackOption* option, + const OrbisNgs2ContextBufferInfo* buffer_info, OrbisNgs2Handle* out_handle) { if (!system) { LOG_ERROR(Lib_Ngs2, "RackCreate: Invalid system handle"); return ORBIS_NGS2_ERROR_INVALID_SYSTEM_HANDLE; } - - u32 rackIndex = RackIdToIndex(rackId); - if (rackIndex == 0xFF) { - LOG_ERROR(Lib_Ngs2, "Invalid rack ID: {:#x}", rackId); + + u32 rack_index = RackIdToIndex(rack_id); + if (rack_index == 0xFF) { + LOG_ERROR(Lib_Ngs2, "Invalid rack ID: {:#x}", rack_id); return ORBIS_NGS2_ERROR_INVALID_RACK_ID; } - + auto* rack = new RackInternal(); rack->ownerSystem = system; - rack->rackType = rackIndex; - rack->rackId = rackId; + rack->rackType = rack_index; + rack->rackId = rack_id; rack->handle.systemData = system; - + // Setup rack info with defaults or from option rack->info.rackHandle = reinterpret_cast(rack); rack->info.ownerSystemHandle = system->systemHandle; - rack->info.type = rackIndex; - rack->info.rackId = rackId; + rack->info.type = rack_index; + rack->info.rackId = rack_id; rack->info.minGrainSamples = 64; rack->info.stateFlags = 0; - + // Use option values if provided, otherwise use defaults if (option && option->size >= sizeof(OrbisNgs2RackOption)) { std::strncpy(rack->info.name, option->name, ORBIS_NGS2_RACK_NAME_LENGTH - 1); @@ -252,18 +253,18 @@ s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* op // Default values when option is NULL - based on libSceNgs2.c analysis rack->info.name[0] = '\0'; // Sampler rack (0x1000) defaults to 0x100 (256) voices, others default to 1 - if (rackId == 0x1000) { - rack->info.maxVoices = 256; // Sampler default + if (rack_id == 0x1000) { + rack->info.maxVoices = 256; // Sampler default } else { rack->info.maxVoices = 1; } rack->info.maxGrainSamples = 512; } - + // Allocate voices - u32 numVoices = rack->info.maxVoices; - rack->voices.reserve(numVoices); - for (u32 i = 0; i < numVoices; i++) { + u32 num_voices = rack->info.maxVoices; + rack->voices.reserve(num_voices); + for (u32 i = 0; i < num_voices; i++) { auto voice = std::make_unique(); voice->ownerRack = rack; voice->voiceIndex = i; @@ -271,22 +272,22 @@ s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* op voice->stateFlags = 0; // Not playing rack->voices.push_back(std::move(voice)); } - + system->racks.push_back(rack); system->rackCount++; - - if (outHandle) { - *outHandle = reinterpret_cast(rack); + + if (out_handle) { + *out_handle = reinterpret_cast(rack); } - + return ORBIS_OK; } -s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* outBufferInfo) { +s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* out_buffer_info) { if (!rack) { return ORBIS_NGS2_ERROR_INVALID_RACK_HANDLE; } - + SystemInternal* system = rack->ownerSystem; if (system) { auto it = std::find(system->racks.begin(), system->racks.end(), rack); @@ -295,7 +296,7 @@ s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* outBufferInfo) { system->rackCount--; } } - + delete rack; return ORBIS_OK; } diff --git a/src/core/libraries/ngs2/ngs2_impl.h b/src/core/libraries/ngs2/ngs2_impl.h index e684d5bc0..f466fb3e6 100644 --- a/src/core/libraries/ngs2/ngs2_impl.h +++ b/src/core/libraries/ngs2/ngs2_impl.h @@ -174,26 +174,26 @@ struct HandleInternal { u32 handleID; // 28 }; -s32 StackBufferClose(StackBuffer* stackBuffer, size_t* outTotalSize); -s32 StackBufferOpen(StackBuffer* stackBuffer, void* buffer, size_t bufferSize, void** outBuffer, +s32 StackBufferClose(StackBuffer* stack_buffer, size_t* out_total_size); +s32 StackBufferOpen(StackBuffer* stack_buffer, void* buffer, size_t buffer_size, void** out_buffer, u8 flags); -s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* option, - SystemInternal* outSystem); +s32 SystemSetupCore(StackBuffer* stack_buffer, const OrbisNgs2SystemOption* option, + SystemInternal* out_system); -s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handleType); +s32 HandleReportInvalid(OrbisNgs2Handle handle, u32 handle_type); void* MemoryClear(void* buffer, size_t size); -s32 SystemCleanup(OrbisNgs2Handle systemHandle, OrbisNgs2ContextBufferInfo* outInfo); -s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* hostBufferInfo, - OrbisNgs2BufferFreeHandler hostFree, OrbisNgs2Handle* outHandle); +s32 SystemCleanup(OrbisNgs2Handle system_handle, OrbisNgs2ContextBufferInfo* out_info); +s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* host_buffer_info, + OrbisNgs2BufferFreeHandler host_free, OrbisNgs2Handle* out_handle); // Forward declarations for internal types struct RackInternal; struct SystemInternal; struct OrbisNgs2RackOption; -u32 RackIdToIndex(u32 rackId); -s32 RackCreate(SystemInternal* system, u32 rackId, const OrbisNgs2RackOption* option, - const OrbisNgs2ContextBufferInfo* bufferInfo, OrbisNgs2Handle* outHandle); -s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* outBufferInfo); +u32 RackIdToIndex(u32 rack_id); +s32 RackCreate(SystemInternal* system, u32 rack_id, const OrbisNgs2RackOption* option, + const OrbisNgs2ContextBufferInfo* buffer_info, OrbisNgs2Handle* out_handle); +s32 RackDestroy(RackInternal* rack, OrbisNgs2ContextBufferInfo* out_buffer_info); } // namespace Libraries::Ngs2 From dd1451f725c0bcb1f8a312523070ede17aa232c9 Mon Sep 17 00:00:00 2001 From: Gursukh Date: Sat, 3 Jan 2026 23:18:19 +0000 Subject: [PATCH 3/3] Chore: clang format fix and GPL license add --- src/core/libraries/ngs2/README.md | 3 +- src/core/libraries/ngs2/ngs2_impl.cpp | 26 ++++-- src/core/libraries/ngs2/ngs2_impl.h | 6 +- src/core/libraries/ngs2/ngs2_internal.h | 110 ++++++++++++------------ 4 files changed, 78 insertions(+), 67 deletions(-) diff --git a/src/core/libraries/ngs2/README.md b/src/core/libraries/ngs2/README.md index 5a378ec23..0f84d8486 100644 --- a/src/core/libraries/ngs2/README.md +++ b/src/core/libraries/ngs2/README.md @@ -1,4 +1,5 @@ -# NGS2 HLE Implementation + ## Overview diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index 29f3223d9..aa8fe92ca 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -5,10 +5,10 @@ #include "ngs2_impl.h" #include "ngs2_internal.h" +#include #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/kernel/kernel.h" -#include using namespace Libraries::Kernel; @@ -205,14 +205,22 @@ s32 SystemSetup(const OrbisNgs2SystemOption* option, OrbisNgs2ContextBufferInfo* u32 RackIdToIndex(u32 rack_id) { switch (rack_id) { - case 0x1000: return 0; // Sampler - case 0x3000: return 1; // Mastering - case 0x2000: return 2; // Submixer - case 0x2001: return 3; // Submixer alt - case 0x4001: return 4; // Reverb - case 0x4002: return 5; // Equalizer - case 0x4003: return 6; // Custom - default: return 0xFF; + case 0x1000: + return 0; // Sampler + case 0x3000: + return 1; // Mastering + case 0x2000: + return 2; // Submixer + case 0x2001: + return 3; // Submixer alt + case 0x4001: + return 4; // Reverb + case 0x4002: + return 5; // Equalizer + case 0x4003: + return 6; // Custom + default: + return 0xFF; } } diff --git a/src/core/libraries/ngs2/ngs2_impl.h b/src/core/libraries/ngs2/ngs2_impl.h index f466fb3e6..c668c33c2 100644 --- a/src/core/libraries/ngs2/ngs2_impl.h +++ b/src/core/libraries/ngs2/ngs2_impl.h @@ -3,10 +3,10 @@ #pragma once -#include "common/types.h" -#include "core/libraries/kernel/threads/pthread.h" #include #include +#include "common/types.h" +#include "core/libraries/kernel/threads/pthread.h" namespace Libraries::Ngs2 { @@ -161,7 +161,7 @@ struct SystemInternal { u32 rackCount; // 336 float lastRenderRatio; // 340 float cpuLoad; // 344 - + // Rack management std::vector racks; }; diff --git a/src/core/libraries/ngs2/ngs2_internal.h b/src/core/libraries/ngs2/ngs2_internal.h index 586319fcf..d4df86bea 100644 --- a/src/core/libraries/ngs2/ngs2_internal.h +++ b/src/core/libraries/ngs2/ngs2_internal.h @@ -3,11 +3,11 @@ #pragma once -#include "ngs2.h" -#include "ngs2_impl.h" #include #include #include +#include "ngs2.h" +#include "ngs2_impl.h" namespace Libraries::Ngs2 { @@ -17,29 +17,31 @@ struct VoiceInternal; // Waveform block for streaming support struct WaveformBlockInternal { - u32 dataOffset; // Offset into the base data pointer - u32 dataSize; // Size of this block in bytes - u32 numRepeats; // Number of times to repeat this block (0 = once) - u32 numSkipSamples; // Samples to skip at start - u32 numSamples; // Total samples in this block - u32 currentRepeat; // Current repeat iteration - - WaveformBlockInternal() : dataOffset(0), dataSize(0), numRepeats(0), - numSkipSamples(0), numSamples(0), currentRepeat(0) {} + u32 dataOffset; // Offset into the base data pointer + u32 dataSize; // Size of this block in bytes + u32 numRepeats; // Number of times to repeat this block (0 = once) + u32 numSkipSamples; // Samples to skip at start + u32 numSamples; // Total samples in this block + u32 currentRepeat; // Current repeat iteration + + WaveformBlockInternal() + : dataOffset(0), dataSize(0), numRepeats(0), numSkipSamples(0), numSamples(0), + currentRepeat(0) {} }; // Ring buffer slot for streaming struct RingBufferSlot { - const void* basePtr; // Original base pointer provided by game - const void* data; // Actual data pointer (with offset applied) + const void* basePtr; // Original base pointer provided by game + const void* data; // Actual data pointer (with offset applied) u32 dataSize; u32 numSamples; - bool valid; // Whether this slot contains valid data - bool consumed; // Whether this slot has been fully consumed - - RingBufferSlot() : basePtr(nullptr), data(nullptr), dataSize(0), numSamples(0), - valid(false), consumed(false) {} - + bool valid; // Whether this slot contains valid data + bool consumed; // Whether this slot has been fully consumed + + RingBufferSlot() + : basePtr(nullptr), data(nullptr), dataSize(0), numSamples(0), valid(false), + consumed(false) {} + void set(const void* base, const void* d, u32 ds, u32 ns) { basePtr = base; data = d; @@ -48,11 +50,11 @@ struct RingBufferSlot { valid = true; consumed = false; } - + void markConsumed() { consumed = true; } - + void reset() { basePtr = nullptr; data = nullptr; @@ -70,43 +72,42 @@ struct VoiceInternal { u32 stateFlags; std::vector ports; std::vector matrices; - + // Sampler-specific data OrbisNgs2WaveformFormat format; float pitchRatio; - float portVolume; // Volume level for the voice (0.0 to 1.0+) + float portVolume; // Volume level for the voice (0.0 to 1.0+) bool isSetup; - + // Playback position (in samples, not bytes) - u32 currentSamplePos; // Integer sample position - float samplePosFloat; // Floating point position for resampling - + u32 currentSamplePos; // Integer sample position + float samplePosFloat; // Floating point position for resampling + // Block-based streaming support std::vector waveformBlocks; - u32 currentBlockIndex; // Which block we're reading from + u32 currentBlockIndex; // Which block we're reading from u32 flags; bool isStreaming; - + // Ring buffer for streaming audio // The game provides buffers in a circular fashion (A->B->C->A->B->C...) // We track which slots have data and which have been consumed // Game typically refills with 3 buffers at once, so ring must accommodate that - static constexpr u32 MAX_RING_SLOTS = 3; // Support game's 3-buffer refill pattern - static constexpr u32 STARVATION_THRESHOLD = 0; // Signal when down to 1 buffer (game adds 3) + static constexpr u32 MAX_RING_SLOTS = 3; // Support game's 3-buffer refill pattern + static constexpr u32 STARVATION_THRESHOLD = 0; // Signal when down to 1 buffer (game adds 3) RingBufferSlot ringBuffer[MAX_RING_SLOTS]; - u32 ringWriteIndex; // Next slot to write to (from game) - u32 ringReadIndex; // Current slot being read (by renderer) - u32 ringBufferCount; // Number of valid buffers in ring - const void* lastConsumedBuffer = nullptr; // Last buffer we finished reading (for game to check) - u64 totalDecodedSamples; // Total samples decoded so far - const void* currentBufferPtr = nullptr; // Add this - - VoiceInternal() : ownerRack(nullptr), voiceIndex(0), stateFlags(0), - pitchRatio(1.0f), portVolume(1.0f), isSetup(false), - currentSamplePos(0), samplePosFloat(0.0f), - currentBlockIndex(0), flags(0), isStreaming(false), - ringWriteIndex(0), ringReadIndex(0), ringBufferCount(0), - lastConsumedBuffer(nullptr) { + u32 ringWriteIndex; // Next slot to write to (from game) + u32 ringReadIndex; // Current slot being read (by renderer) + u32 ringBufferCount; // Number of valid buffers in ring + const void* lastConsumedBuffer = nullptr; // Last buffer we finished reading (for game to check) + u64 totalDecodedSamples; // Total samples decoded so far + const void* currentBufferPtr = nullptr; // Add this + + VoiceInternal() + : ownerRack(nullptr), voiceIndex(0), stateFlags(0), pitchRatio(1.0f), portVolume(1.0f), + isSetup(false), currentSamplePos(0), samplePosFloat(0.0f), currentBlockIndex(0), flags(0), + isStreaming(false), ringWriteIndex(0), ringReadIndex(0), ringBufferCount(0), + lastConsumedBuffer(nullptr) { handle.selfPtr = &handle; handle.systemData = nullptr; handle.refCount = 1; @@ -117,7 +118,7 @@ struct VoiceInternal { ringBuffer[i].reset(); } } - + // Get the current buffer being read RingBufferSlot* getCurrentSlot() { // We use the consumed flag to determine if the current read head is valid data @@ -126,20 +127,21 @@ struct VoiceInternal { } return nullptr; } - + // Advance to the next buffer in the ring void advanceReadIndex() { - if (!ringBuffer[ringReadIndex].valid) return; - + if (!ringBuffer[ringReadIndex].valid) + return; + ringBuffer[ringReadIndex].consumed = true; lastConsumedBuffer = ringBuffer[ringReadIndex].basePtr; ringReadIndex = (ringReadIndex + 1) % MAX_RING_SLOTS; - + if (ringBufferCount > 0) { ringBufferCount--; } } - + // Add a buffer to the ring bool addToRing(const void* basePtr, const void* data, u32 dataSize, u32 numSamples) { if (ringBufferCount >= MAX_RING_SLOTS) { @@ -154,10 +156,10 @@ struct VoiceInternal { slot->set(basePtr, data, dataSize, numSamples); ringWriteIndex = (ringWriteIndex + 1) % MAX_RING_SLOTS; ringBufferCount++; - + return true; } - + // Reset the ring buffer state void resetRing() { for (u32 i = 0; i < MAX_RING_SLOTS; i++) { @@ -170,7 +172,7 @@ struct VoiceInternal { currentBufferPtr = nullptr; totalDecodedSamples = 0; } - + // Get number of buffers ready to read u32 getReadyBufferCount() const { return ringBufferCount; @@ -184,7 +186,7 @@ struct RackInternal { std::vector> voices; u32 rackType; u32 rackId; - + RackInternal() : ownerSystem(nullptr), rackType(0), rackId(0) { handle.selfPtr = &handle; handle.systemData = nullptr;