#include #include #include #include #include #include #include #include #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" static constexpr double PHASE_ONE = 4294967296.0; static constexpr std::uint64_t PHASE_FRACTION = 0xffffffff; static const std::uint8_t* FrameAt(const Ngs2Voice& voice, const Ngs2Block& block, std::uint32_t frame) { const std::size_t frameBytes = voice.channels * sizeof(std::int16_t); if (block.streaming) { for (const auto& piece : block.pieces) { if (frame >= piece.firstFrame && frame - piece.firstFrame < piece.frames) return piece.data + (frame - piece.firstFrame) * frameBytes; } return nullptr; } return block.data + (static_cast(block.info.num_skip_samples) + frame) * frameBytes; } static void ReadPcm(const Ngs2Voice& voice, const std::uint8_t* frame, float* out) { if (frame == nullptr) { for (std::uint32_t channel = 0; channel < voice.channels; channel++) out[channel] = 0.0f; return; } for (std::uint32_t channel = 0; channel < voice.channels; channel++) { std::int16_t sample; std::memcpy(&sample, frame + channel * sizeof(sample), sizeof(sample)); out[channel] = sample / 32768.0f; } } static void ReadFrames(Ngs2Voice& voice, float* current, float* next) { auto& block = voice.blocks.front(); if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) { std::memcpy(current, Ngs2Atrac9Frame(voice, block, block.cursor), voice.channels * sizeof(float)); if (block.cursor + 1 < block.info.num_samples) std::memcpy(next, Ngs2Atrac9Frame(voice, block, block.cursor + 1), voice.channels * sizeof(float)); else std::memcpy(next, current, voice.channels * sizeof(float)); return; } ReadPcm(voice, FrameAt(voice, block, block.cursor), current); if (block.streaming && block.cursor + 1 >= block.availableFrames) std::memcpy(next, current, voice.channels * sizeof(float)); else if (block.cursor + 1 < block.info.num_samples) ReadPcm(voice, FrameAt(voice, block, block.cursor + 1), next); else if (block.info.num_repeats != 0) ReadPcm(voice, FrameAt(voice, block, 0), next); else if (voice.blocks.size() > 1) ReadPcm(voice, FrameAt(voice, voice.blocks[1], 0), next); else std::memcpy(next, current, voice.channels * sizeof(float)); } static void FinishBlock(Ngs2Voice& voice) { auto& block = voice.blocks.front(); const bool repeat = block.info.num_repeats != 0; if (repeat) { if (block.info.num_repeats != UINT32_MAX) block.info.num_repeats--; block.numRepeated++; block.cursor = 0; if (voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9) { block.dataCursor = 0; Ngs2RestartAtrac9(voice); } } const Ngs2VoiceCallbackInfo info{ voice.callbackData, reinterpret_cast(&voice), repeat ? SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_REPEAT : SCE_NGS2_VOICE_CALLBACK_FLAG_BLOCK_END, 0, block.info.user_data, block.data, static_cast(block.info.data_size), block.numRepeated, 0, 0, }; if (!repeat) { voice.waveformEnd = voice.waveformType == SCE_NGS2_WAVEFORM_TYPE_ATRAC9 ? block.data + block.dataCursor : block.streaming ? Ngs2StreamEnd(voice, block) : FrameAt(voice, block, block.info.num_samples); voice.blocks.pop_front(); } if (voice.callback != nullptr && (voice.callbackFlags & info.flag) != 0) voice.callback(&info); } static void Advance(Ngs2Voice& voice, std::uint64_t frames) { while (frames != 0 && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty()) { auto& block = voice.blocks.front(); const std::uint64_t limit = block.streaming ? std::min(block.info.num_samples, block.availableFrames) : block.info.num_samples; if (block.cursor >= limit && block.cursor != block.info.num_samples && !(block.streaming && !voice.acceptsBlocks)) break; const auto step = std::min(frames, limit - block.cursor); block.cursor += static_cast(step); voice.decodedSamples += step; if (voice.waveformType != SCE_NGS2_WAVEFORM_TYPE_ATRAC9) voice.decodedBytes += step * voice.channels * sizeof(std::int16_t); frames -= step; if (block.streaming) { while (block.pieces.size() > 1 && block.cursor >= block.pieces.front().firstFrame + block.pieces.front().frames) block.pieces.erase(block.pieces.begin()); } if (block.cursor == block.info.num_samples || (block.streaming && !voice.acceptsBlocks && block.cursor == block.availableFrames)) FinishBlock(voice); } } static void ConsumeSamples(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) { const auto step = static_cast(std::llround(voice.sampleRate * static_cast(voice.pitch) / systemRate * PHASE_ONE)); std::uint32_t written = 0; float current[NGS2_MAX_CHANNELS]; float next[NGS2_MAX_CHANNELS]; for (; written < grain && voice.state == Ngs2PlayState::Playing && !voice.blocks.empty(); written++) { ReadFrames(voice, current, next); const auto fraction = static_cast(static_cast(voice.phase) / PHASE_ONE); for (std::uint32_t channel = 0; channel < voice.channels; channel++) { voice.samples[channel * grain + written] = std::lerp(current[channel], next[channel], fraction); } voice.phase += step; Advance(voice, voice.phase >> 32); voice.phase &= PHASE_FRACTION; } voice.hasSamples = written != 0; } static bool Bypasses(const Ngs2Filter& filter, std::uint32_t channel) { return channel < 64 && (filter.bypassMask & (std::uint64_t{1} << channel)) != 0; } static bool HasHistory(const Ngs2Filter& filter) { if (!filter.enabled) return false; for (std::uint32_t channel = 0; channel < filter.history.size(); channel++) { const auto& h = filter.history[channel]; if (!Bypasses(filter, channel) && (h.x1 != 0.0 || h.x2 != 0.0 || h.y1 != 0.0 || h.y2 != 0.0)) return true; } return false; } static void ApplyFilter(Ngs2Voice& voice, Ngs2Filter& filter, std::uint32_t grain) { for (std::uint32_t channel = 0; channel < voice.channels; channel++) { if (Bypasses(filter, channel)) continue; auto& h = filter.history[channel]; for (std::uint32_t i = 0; i < grain; i++) { auto& sample = voice.samples[channel * grain + i]; const double x = sample; double y = filter.b0 * x + filter.b1 * h.x1 + filter.b2 * h.x2 - filter.a1 * h.y1 - filter.a2 * h.y2; if (std::abs(y) < 1e-20) y = 0.0; h = {x, h.x1, y, h.y1}; sample = static_cast(y); } } } static void RenderSampler(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t systemRate) { ConsumeSamples(voice, grain, systemRate); for (auto& filter : voice.filters) { if (!filter.enabled || (!voice.hasSamples && !HasHistory(filter))) continue; ApplyFilter(voice, filter, grain); voice.hasSamples = true; } if (voice.state == Ngs2PlayState::Playing && voice.blocks.empty() && !voice.acceptsBlocks && std::none_of(voice.filters.begin(), voice.filters.end(), HasHistory)) { voice.state = Ngs2PlayState::Empty; } } static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate); static void MixPort(Ngs2Voice& voice, const Ngs2Voice& source, const Ngs2Port& port, std::uint32_t grain) { const auto* matrix = port.matrix < 0 ? nullptr : &source.matrices[port.matrix]; const std::size_t outputs = matrix == nullptr ? voice.channels : std::min(voice.channels, matrix->size() / source.channels); for (std::size_t dst = 0; dst < outputs; dst++) { for (std::uint32_t src = 0; src < source.channels; src++) { const float level = port.volume * (matrix == nullptr ? (src == dst ? 1.0f : 0.0f) : (*matrix)[dst * source.channels + src]); if (level == 0.0f) continue; for (std::uint32_t i = 0; i < grain; i++) voice.samples[dst * grain + i] += source.samples[src * grain + i] * level; } } } static void MixInputs(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate) { for (auto* source : voices) { for (const auto& port : source->ports) { if (port.dest != &voice || port.volume == 0.0f) continue; RenderVoice(*source, voices, grain, systemRate); if (!source->hasSamples) continue; MixPort(voice, *source, port, grain); voice.hasSamples = true; } } } static void RenderVoice(Ngs2Voice& voice, const std::vector& voices, std::uint32_t grain, std::uint32_t systemRate) { if (voice.rendered) return; if (voice.rendering) throw std::invalid_argument("NGS2: the voice patches form a cycle"); voice.rendering = true; voice.samples.assign(static_cast(grain) * voice.channels, 0.0f); voice.hasSamples = false; if (voice.state == Ngs2PlayState::Playing && voice.channels != 0) { if (voice.rack->rackId == SCE_NGS2_RACK_ID_SAMPLER) RenderSampler(voice, grain, systemRate); else MixInputs(voice, voices, grain, systemRate); if (voice.hasSamples) Ngs2ProcessUserFx(voice, grain, systemRate); } voice.rendering = false; voice.rendered = true; } static std::size_t SampleBytes(std::uint32_t waveformType) { if (waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_I16L) return sizeof(std::int16_t); if (waveformType == SCE_NGS2_WAVEFORM_TYPE_PCM_F32L) return sizeof(float); throw std::runtime_error("NGS2: render waveform type " + Ngs2Hex(waveformType) + " is not implemented"); } static bool IsStereoIntoSurround(const Ngs2Voice& voice, const Ngs2RenderBufferInfo& output) { return voice.channels == 2 && (output.num_channels == 6 || output.num_channels == 8); } static std::vector& OutputMix(std::vector>& mixes, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo, const Ngs2Voice& voice, std::uint32_t grain) { if (voice.outputId >= numBufferInfo) throw std::invalid_argument("NGS2: mastering output " + std::to_string(voice.outputId) + " has no render buffer"); const auto& output = bufferInfo[voice.outputId]; const auto sampleBytes = SampleBytes(output.waveform_type); if (output.num_channels != voice.channels && !IsStereoIntoSurround(voice, output)) throw std::runtime_error("NGS2: mixing " + std::to_string(voice.channels) + " mastering channels into " + std::to_string(output.num_channels) + " is not implemented"); if (output.buffer == nullptr || output.buffer_size < static_cast(grain) * output.num_channels * sampleBytes) { throw std::invalid_argument("NGS2: render buffer " + std::to_string(voice.outputId) + " is missing or too small"); } auto& mix = mixes[voice.outputId]; if (mix.empty()) mix.resize(static_cast(grain) * output.num_channels); return mix; } static void WriteOutput(const Ngs2RenderBufferInfo& output, const std::vector& mix) { if (output.waveform_type == SCE_NGS2_WAVEFORM_TYPE_PCM_F32L) { std::memcpy(output.buffer, mix.data(), mix.size() * sizeof(float)); return; } auto* pcm = static_cast(output.buffer); for (std::size_t i = 0; i < mix.size(); i++) { pcm[i] = static_cast(std::min(std::lrint(std::clamp(mix[i], -1.0f, 1.0f) * 32768.0f), 32767L)); } } void Ngs2RenderSystem(Ngs2System& system, const Ngs2RenderBufferInfo* bufferInfo, std::uint32_t numBufferInfo) { const auto grain = system.option.num_grain_samples; std::vector voices; for (auto* rack : system.racks) { for (auto& voice : rack->voices) { voice.rendered = false; voices.push_back(&voice); } } std::vector> mixes(numBufferInfo); for (auto* voice : voices) { RenderVoice(*voice, voices, grain, system.option.sample_rate); if (voice->rack->rackId != SCE_NGS2_RACK_ID_MASTERING || !voice->hasSamples) continue; auto& mix = OutputMix(mixes, bufferInfo, numBufferInfo, *voice, grain); const std::uint32_t outputChannels = bufferInfo[voice->outputId].num_channels; for (std::uint32_t channel = 0; channel < voice->channels; channel++) { const bool lfe = channel == 3 && (voice->channels == 6 || voice->channels == 8); const float level = lfe ? voice->lfeLevel : voice->fbwLevel; for (std::uint32_t i = 0; i < grain; i++) mix[i * outputChannels + channel] += voice->samples[channel * grain + i] * level; } } for (std::uint32_t i = 0; i < numBufferInfo; i++) { if (bufferInfo[i].buffer != nullptr) std::memset(bufferInfo[i].buffer, 0, bufferInfo[i].buffer_size); if (!mixes[i].empty()) WriteOutput(bufferInfo[i], mixes[i]); } for (auto* voice : voices) { if (voice->state == Ngs2PlayState::Stopped) voice->state = Ngs2PlayState::Empty; voice->stateFlags = static_cast(voice->state); } system.renderCount++; }