#include #include #include #include #include #include #include #include #include #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" static constexpr std::uint32_t TAP_RING_SAMPLES = 14400; static constexpr std::uint32_t EARLY_TAPS = 4; static constexpr std::uint32_t COMBS = 16; static constexpr std::uint32_t ALLPASSES = 4; static constexpr std::uint32_t LFE_CHANNEL = 3; static constexpr float TAP_TIMES[8][EARLY_TAPS] = { {0, 11, 23, 35}, {0, 14.75f, 26.5499992f, 38.0299988f}, {0, 6, 17, 29}, {0, 8, 20, 53}, {0, 11, 17, 30}, {0, 16, 30, 55}, {0, 20, 29, 40}, {0, 37, 47, 62}, }; static constexpr float TAP_GAINS[8][EARLY_TAPS] = { {0.7f, 0.4f, 0.134f, 0.1f}, {0.7f, 0.4f, 0.3f, 0.1f}, {0.7f, 0.45f, -0.6f, 0.2f}, {0.7f, 0.55f, -0.6f, 0.2f}, {0.7f, -0.45f, 0.6f, 0.3f}, {0.7f, 0.7f, 0.7f, 0.4f}, {0.7f, 0.7f, -0.4f, 0.3f}, {0.7f, 0.7f, -0.4f, 0.4f}, }; static constexpr float COMB_TIMES[COMBS] = { 22.0100002f, 23.25f, 24.75f, 26.6000004f, 28.25f, 29.6299992f, 31.0599995f, 32.4399986f, 33.6899986f, 34.9500008f, 36.2900009f, 37.6500015f, 38.9900017f, 40.3499985f, 41.6699982f, 43.0499992f, }; static constexpr float ALLPASS_TIMES[ALLPASSES] = {11.5799999f, 9.18999958f, 7.0999999f, 4.69000006f}; static constexpr std::int32_t ALLPASS_OFFSETS[8][ALLPASSES] = { {0, -32, -6, -11}, {-10, -5, 0, -34}, {-8, -3, -33, -5}, {-7, -9, -5, -8}, {-33, -8, -8, 0}, {-11, -21, -10, -7}, {-9, -10, -27, -3}, {-6, -11, -9, -24}, }; static constexpr std::uint16_t CHANNEL_COMBS[8] = {0xaaaa, 0x5555, 0xcccc, 0x3333, 0x0ff0, 0x9ab2, 0x3c3c, 0x399c}; static constexpr Ngs2ReverbI3DL2Param SETUP_PARAMS{1.0f, 0.0f, -10000, 0, 0, 1.0f, 0.5f, -10000, 0.02f, -10000, 0.04f, 100.0f, 100.0f, 5000.0f, {}}; struct Ngs2ReverbTaps { std::array delays{}; std::array gains{}; }; struct Ngs2Comb { std::vector buffer; std::uint32_t cursor = 0; float feedback = 0.0f; float lowpassIn = 0.0f; float lowpassBack = 0.0f; float direct = 0.0f; float filtered = 0.0f; float emphasis = 0.0f; float emphasisBack = 0.0f; float previous = 0.0f; float lowpass = 0.0f; }; struct Ngs2Allpass { std::vector buffer; std::uint32_t cursor = 0; }; struct Ngs2ReverbChannel { std::array allpasses; float y1 = 0.0f; float y2 = 0.0f; float wet = 0.0f; float dry = 0.0f; }; struct Ngs2ReverbState { Ngs2ReverbI3DL2Param params{}; bool reset = true; bool changed = true; std::uint32_t sampleRate = 0; std::vector ring; std::uint32_t ringCursor = 0; Ngs2ReverbTaps taps; float earlyGain = 0.0f; float lateGain = 0.0f; float density = NAN; std::array combs; std::array channels; bool roomFilter = false; float roomB0 = 0.0f; float roomA1 = 0.0f; float roomA2 = 0.0f; }; void Ngs2ReverbDeleter::operator()(Ngs2ReverbState* reverb) const { delete reverb; } static float Millibels(float millibels) { if (millibels < -10000.0f) return 0.0f; return static_cast(std::pow(10.0, static_cast(millibels * 0.0005f))); } static float Clamp(float value, float low, float high) { return value < low ? low : std::min(high, value); } static std::int32_t ClampMillibels(std::int32_t value, std::int32_t high) { return std::max(-10000, std::min(value, high)); } static std::uint32_t Samples(float milliseconds, float rate) { return static_cast(rate * milliseconds * 0.001f + 0.5f); } static void Ramp(float* samples, float from, float to, std::uint32_t grain, const float* source, bool accumulate) { const float step = (to - from) / static_cast(grain); float gain = from; for (std::uint32_t i = 0; i < grain; i++) { const float value = source[i] * (from == to ? to : gain); samples[i] = accumulate ? samples[i] + value : value; gain += step; } } static void RunTaps(const Ngs2ReverbState& reverb, const Ngs2ReverbTaps& taps, const float* input, std::uint32_t grain, float* early, float* late) { const auto size = static_cast(reverb.ring.size()); std::fill(early, early + grain, 0.0f); std::fill(late, late + grain, 0.0f); for (std::uint32_t tap = 0; tap <= EARLY_TAPS; tap++) { float* out = tap < EARLY_TAPS ? early : late; const float gain = taps.gains[tap]; const std::uint32_t delay = taps.delays[tap]; std::uint32_t read = (reverb.ringCursor + size - delay) % size; for (std::uint32_t i = 0; i < grain; i++) { if (i < delay) { out[i] += reverb.ring[read] * gain; read = read + 1 == size ? 0 : read + 1; } else { out[i] += input[i - delay] * gain; } } } } static Ngs2ReverbTaps TapsFor(const Ngs2ReverbI3DL2Param& params, std::uint32_t size, float rate) { Ngs2ReverbTaps taps; const std::uint32_t pattern = params.reflection_pattern < 8 ? params.reflection_pattern : params.reflection_pattern - 8; const auto early = std::min(static_cast(params.reflections_delay * rate * 1000.0f * 0.001f + 0.5f), size); const auto late = std::min(static_cast((params.reflections_delay + params.reverb_delay) * rate * 1000.0f * 0.001f + 0.5f), size); const auto last = static_cast(rate * TAP_TIMES[pattern][EARLY_TAPS - 1] * 0.001f + 0.5f); const std::uint32_t limit = params.reflection_pattern > 7 ? size : late; const float scale = early + last > limit ? static_cast(limit - early) / static_cast(last) : 1.0f; taps.delays[0] = early; taps.gains[0] = TAP_GAINS[pattern][0]; for (std::uint32_t tap = 1; tap < EARLY_TAPS; tap++) { taps.delays[tap] = static_cast(scale * TAP_TIMES[pattern][tap] * rate * 0.001f + 0.5f) + early; taps.gains[tap] = taps.delays[tap] == taps.delays[tap - 1] ? 0.0f : TAP_GAINS[pattern][tap]; } taps.delays[EARLY_TAPS] = std::min(early + static_cast(params.reverb_delay * rate * 1000.0f * 0.001f + 0.5f), size); taps.gains[EARLY_TAPS] = 1.0f; return taps; } static void SetupComb(Ngs2Comb& comb, std::uint32_t length, const Ngs2ReverbI3DL2Param& params, float rate, bool clear) { if (clear || comb.buffer.size() != length) { comb.buffer.assign(length, 0.0f); if (comb.cursor >= length) comb.cursor = 0; } comb.feedback = std::pow(10.0f, static_cast(length) / rate / params.decay_time * -3.0f); const float ratio = params.decay_hf_ratio; if (ratio > 1.0f) { const float high = std::pow(10.0f, (ratio - 1.0f) * std::log10(comb.feedback)); comb.lowpassIn = 0.0506209731f; comb.lowpassBack = 0.949379027f; comb.direct = 1.0f; comb.filtered = 1.0f - high; } else { float shaped = ratio * ratio; if (0.8f > ratio) { shaped = (0.8f - ratio) * -0.857142925f + 0.8f; shaped *= shaped; } const float back = std::min(0.99977994f, std::log10(comb.feedback) * 2.30258512f * 0.25f * (1.0f - 1.0f / shaped)); comb.lowpassIn = 1.0f - back; comb.lowpassBack = back; comb.direct = 0.0f; comb.filtered = 1.0f; } const float root = std::sqrt(ratio); const float tilt = (1.0f - root) / (root + 1.0f); comb.emphasis = tilt / (1.0f - tilt) + 1.0f; comb.emphasisBack = -tilt / (1.0f - tilt); } static void Reset(Ngs2ReverbState& reverb) { reverb.ring.assign(TAP_RING_SAMPLES, 0.0f); reverb.ringCursor = 0; reverb.taps = {}; reverb.earlyGain = 1.0f; reverb.lateGain = 1.0f; reverb.density = NAN; reverb.combs = {}; reverb.channels = {}; } static void ApplyParams(Ngs2ReverbState& reverb, float rate) { const auto& params = reverb.params; reverb.taps = TapsFor(params, static_cast(reverb.ring.size()), rate); const float room = std::min(1.0f, Millibels(static_cast(params.room))); reverb.earlyGain = Millibels(static_cast(ClampMillibels(params.reflections, 1000))) * room; reverb.lateGain = Millibels(static_cast(ClampMillibels(params.reverb, 2000) - 2400)) * room; const bool densityChanged = !(reverb.density == params.density); reverb.density = params.density; const float densityScale = params.density * -3.0f / 100.0f + 4.0f; for (std::uint32_t i = 0; i < COMBS; i++) { auto length = static_cast(rate * COMB_TIMES[i] / densityScale * 0.001f + 0.5f); length += ~length & 1; SetupComb(reverb.combs[i], length, params, rate, densityChanged); } for (std::uint32_t channel = 0; channel < 8; channel++) { for (std::uint32_t stage = 0; stage < ALLPASSES; stage++) { auto& allpass = reverb.channels[channel].allpasses[stage]; const auto signedLength = static_cast(Samples(ALLPASS_TIMES[stage], rate)) + ALLPASS_OFFSETS[channel][stage]; if (signedLength < 1) throw std::runtime_error("NGS2: reverb at a sample rate of " + std::to_string(reverb.sampleRate) + " Hz is not implemented"); const auto length = static_cast(signedLength); if (allpass.buffer.size() != length) allpass.buffer.assign(length, 0.0f); if (allpass.cursor >= length) allpass.cursor = 0; } } } static void UpdateRoomFilter(Ngs2ReverbState& reverb, float rate) { const auto& params = reverb.params; const float roomHf = Millibels(static_cast(ClampMillibels(params.room_hf, 0))); reverb.roomFilter = 1.0f > roomHf; if (!reverb.roomFilter) return; const float cosine = std::cos(params.hf_reference * 6.28318548f / rate); const float root = std::sqrt((roomHf + roomHf) * (1.0f - cosine) - roomHf * roomHf * (1.0f - cosine * cosine)); const float pole = ((1.0f - roomHf * cosine) - root) / (1.0f - roomHf); reverb.roomB0 = (1.0f - pole) * (1.0f - pole); reverb.roomA1 = pole + pole; reverb.roomA2 = pole * -pole; } static void RunComb(Ngs2Comb& comb, const float* input, float* output, std::uint32_t grain) { const auto length = static_cast(comb.buffer.size()); for (std::uint32_t i = 0; i < grain; i++) { const float emphasized = input[i] * comb.emphasis + comb.previous * comb.emphasisBack; comb.previous = input[i]; const float delayed = comb.buffer[comb.cursor]; comb.lowpass = delayed * comb.lowpassIn + comb.lowpass * comb.lowpassBack; const float back = (delayed * comb.direct + comb.lowpass * comb.filtered) * comb.feedback; comb.buffer[comb.cursor] = emphasized + back; output[i] = back; comb.cursor = comb.cursor + 1 == length ? 0 : comb.cursor + 1; } } static void RunAllpass(Ngs2Allpass& allpass, float gain, float* samples, std::uint32_t grain) { const auto length = static_cast(allpass.buffer.size()); for (std::uint32_t i = 0; i < grain; i++) { const float delayed = allpass.buffer[allpass.cursor]; const float stored = delayed * gain + samples[i]; allpass.buffer[allpass.cursor] = stored; samples[i] = delayed - gain * stored; allpass.cursor = allpass.cursor + 1 == length ? 0 : allpass.cursor + 1; } } static void MixDown(const float* input, std::uint32_t channels, std::uint32_t grain, float* mono) { const float* channel[8]; for (std::uint32_t c = 0; c < channels; c++) channel[c] = input + static_cast(c) * grain; if (channels == 1) { std::copy(channel[0], channel[0] + grain, mono); return; } const float gain = channels == 2 ? 0.707000017f : channels == 6 ? 0.316000015f : 0.26699999f; for (std::uint32_t i = 0; i < grain; i++) mono[i] = channel[0][i] * gain; for (std::uint32_t c = 1; c < channels; c++) { if (c == LFE_CHANNEL) continue; for (std::uint32_t i = 0; i < grain; i++) mono[i] += channel[c][i] * gain; } } bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate) { auto& reverb = *voice.reverb; const float rate = static_cast(sampleRate); const std::uint32_t channels = voice.channels; if (reverb.sampleRate != sampleRate) { reverb.sampleRate = sampleRate; reverb.reset = true; } const bool reset = reverb.reset; if (reset) { Reset(reverb); reverb.changed = true; } const std::vector input = voice.samples; std::vector mono(grain), early(grain), late(grain); MixDown(input.data(), channels, grain, mono.data()); if (reverb.changed) { std::vector oldEarly(grain), oldLate(grain); RunTaps(reverb, reverb.taps, mono.data(), grain, oldEarly.data(), oldLate.data()); ApplyParams(reverb, rate); RunTaps(reverb, reverb.taps, mono.data(), grain, early.data(), late.data()); Ramp(early.data(), 0.0f, 1.0f, grain, early.data(), false); Ramp(early.data(), 1.0f, 0.0f, grain, oldEarly.data(), true); Ramp(late.data(), 0.0f, 1.0f, grain, late.data(), false); Ramp(late.data(), 1.0f, 0.0f, grain, oldLate.data(), true); } else { RunTaps(reverb, reverb.taps, mono.data(), grain, early.data(), late.data()); } for (std::uint32_t i = 0; i < grain; i++) { reverb.ring[reverb.ringCursor] = mono[i]; reverb.ringCursor = reverb.ringCursor + 1 == reverb.ring.size() ? 0 : reverb.ringCursor + 1; } std::vector combs(static_cast(COMBS) * grain); for (std::uint32_t i = 0; i < COMBS; i++) RunComb(reverb.combs[i], late.data(), combs.data() + static_cast(i) * grain, grain); UpdateRoomFilter(reverb, rate); const auto& params = reverb.params; bool audible = false; for (std::uint32_t channel = 0; channel < channels; channel++) { auto& state = reverb.channels[channel]; float* out = voice.samples.data() + static_cast(channel) * grain; const float* in = input.data() + static_cast(channel) * grain; if (channel == LFE_CHANNEL) { std::copy(in, in + grain, out); } else { std::vector tail(grain, 0.0f); std::uint32_t selected = 0; for (std::uint32_t i = 0; i < COMBS; i++) { if ((CHANNEL_COMBS[channel] >> i & 1) == 0) continue; const float weight = (selected & 1) != 0 ? 1.0f : 1.0f / (static_cast(selected) * 0.5f * 20.0f + 40.0f) * (params.density + -100.0f) + 1.0f; const float* comb = combs.data() + static_cast(i) * grain; for (std::uint32_t n = 0; n < grain; n++) tail[n] += comb[n] * weight; selected++; } for (auto& allpass : state.allpasses) RunAllpass(allpass, params.diffusion * 0.005f, tail.data(), grain); for (std::uint32_t n = 0; n < grain; n++) out[n] = early[n] * reverb.earlyGain + tail[n] * reverb.lateGain; for (std::uint32_t n = 0; n < grain; n++) { const float y = reverb.roomFilter ? reverb.roomB0 * out[n] + reverb.roomA1 * state.y1 + reverb.roomA2 * state.y2 : out[n]; state.y2 = state.y1; state.y1 = y; out[n] = y; } } const float wet = channel == LFE_CHANNEL ? 0.0f : params.wet; Ramp(out, reset ? wet : state.wet, wet, grain, out, false); Ramp(out, reset ? params.dry : state.dry, params.dry, grain, in, true); state.wet = wet; state.dry = params.dry; for (std::uint32_t n = 0; n < grain && !audible; n++) audible = out[n] != 0.0f; } reverb.reset = false; reverb.changed = false; return audible; } void Ngs2SetReverbParams(Ngs2Voice& voice, const Ngs2ReverbI3DL2Param& params) { if (std::isnan(params.wet) || std::isnan(params.dry) || std::isnan(params.decay_time) || std::isnan(params.decay_hf_ratio) || std::isnan(params.reflections_delay) || std::isnan(params.reverb_delay) || std::isnan(params.diffusion) || std::isnan(params.density) || std::isnan(params.hf_reference)) { APS5_INVALID_ARG_EX; } if (!voice.reverb) voice.reverb.reset(new Ngs2ReverbState{}); auto& stored = voice.reverb->params; stored = params; stored.wet = Clamp(params.wet, 0.0f, 32.0f); stored.dry = Clamp(params.dry, 0.0f, 32.0f); stored.reflection_pattern = params.reflection_pattern < 16 ? params.reflection_pattern : 0; stored.decay_time = Clamp(params.decay_time, 0.1f, 20.0f); stored.decay_hf_ratio = Clamp(params.decay_hf_ratio, 0.00999999978f, 2.0f); stored.reflections_delay = Clamp(params.reflections_delay, 0.0f, 0.300000012f); stored.reverb_delay = Clamp(params.reverb_delay, 0.0f, 0.100000001f); stored.diffusion = Clamp(params.diffusion, 0.0f, 100.0f); stored.density = Clamp(params.density, 0.0f, 100.0f); stored.hf_reference = Clamp(params.hf_reference, 20.0f, 20000.0f); voice.reverb->changed = true; } void Ngs2SetupReverb(Ngs2Voice& voice) { voice.reverb.reset(new Ngs2ReverbState{}); Ngs2SetReverbParams(voice, SETUP_PARAMS); } void Ngs2ClearReverb(Ngs2Voice& voice) { if (voice.reverb) voice.reverb->reset = true; }