// NativeMixer.cpp — xem NativeMixer.h cho cong thuc mirror. #include "NativeMixer.h" namespace sonicforge { namespace { // Mirror _clamp(v, lo, hi, default) cua mastering_engine.py: NaN -> default. float clampDb (float v, float lo, float hi, float def) { if (!(v == v)) // NaN return def; if (v < lo) return lo; if (v > hi) return hi; return v; } } // namespace TrackGainPan TrackGainPan::fromDbPan (float gainDb, float pan) { TrackGainPan t; t.gainLin = std::pow (10.0f, gainDb / 20.0f); float p = pan; if (p < -1.0f) p = -1.0f; else if (p > 1.0f) p = 1.0f; if (p == 0.0f) { t.panL = 1.0f; t.panR = 1.0f; } else { const float theta = ((p + 1.0f) / 2.0f) * (3.14159265358979323846f / 2.0f); t.panL = std::cos (theta); t.panR = std::sin (theta); } return t; } void NativeMixer::setLimiter (bool active, float thresholdDb) { m_limActive = active; if (!active) { m_limK = 1.0f; m_limTk = 1.0f; return; } const float t = clampDb (thresholdDb, -24.0f, 0.0f, -1.0f); const float tLin = std::pow (10.0f, t / 20.0f); m_limK = 1.0f / (tLin > 0.02f ? tLin : 0.02f); m_limTk = std::tanh (m_limK); } void NativeMixer::processInPlace (float* outL, float* outR, int32 frames) const { if (!outL || !outR || frames <= 0) return; const float gl = m_track.gainLin * m_track.panL; const float gr = m_track.gainLin * m_track.panR; for (int32 i = 0; i < frames; ++i) { float l = outL[i] * gl; float r = outR[i] * gr; if (m_limActive) { l = std::tanh (l * m_limK) / m_limTk; r = std::tanh (r * m_limK) / m_limTk; // Hard clip [-1,1] — mirror render_project sau apply_mastering // (chi khi mastering on; limiter off => khong clip). if (l > 1.0f) l = 1.0f; else if (l < -1.0f) l = -1.0f; if (r > 1.0f) r = 1.0f; else if (r < -1.0f) r = -1.0f; } outL[i] = l; outR[i] = r; } } } // namespace sonicforge