From ded7db1904a4175178687d01db9f22c42f6ede7b Mon Sep 17 00:00:00 2001 From: Fawn Date: Mon, 23 Mar 2026 16:47:36 -0600 Subject: [PATCH 1/2] Add normalizePhase() for normalizing phase - Replaces std::fmod(x, 2 * pi) with normalizePhase(x) - Replaces absFraction(x) with normalizePhase<1>(x), which more clearly communicates the intent of the function call - Does the same for calls to fraction(x) where the input and output are obviously intended to be an oscillator or LFO phase --- include/BandLimitedWave.h | 2 +- include/Oscillator.h | 14 ++++---- include/QuadratureLfo.h | 5 +-- include/lmms_math.h | 22 ++++++------ plugins/Delay/Lfo.h | 4 ++- .../FrequencyShifterEffect.cpp | 4 +-- plugins/Monstro/Monstro.cpp | 34 +++++++++--------- plugins/Monstro/Monstro.h | 2 +- plugins/Watsyn/Watsyn.cpp | 19 ++++------ plugins/Watsyn/Watsyn.h | 35 +++++++++---------- src/core/DrumSynth.cpp | 2 +- src/core/LfoController.cpp | 4 +-- src/core/Oscillator.cpp | 2 +- 13 files changed, 71 insertions(+), 78 deletions(-) diff --git a/include/BandLimitedWave.h b/include/BandLimitedWave.h index 0c38413ef53..6632fd80087 100644 --- a/include/BandLimitedWave.h +++ b/include/BandLimitedWave.h @@ -135,7 +135,7 @@ class LMMS_EXPORT BandLimitedWave while( t < MAXTBL && _wavelen >= TLENS[t+1] ) { t++; } int tlen = TLENS[t]; - const float ph = fraction( _ph ); + const float ph = normalizePhase<1>(_ph); const float lookupf = ph * static_cast( tlen ); int lookup = static_cast( lookupf ); const float ip = fraction( lookupf ); diff --git a/include/Oscillator.h b/include/Oscillator.h index f3d2b2d3629..5871eba8f4b 100644 --- a/include/Oscillator.h +++ b/include/Oscillator.h @@ -118,7 +118,7 @@ class LMMS_EXPORT Oscillator static inline sample_t triangleSample( const float _sample ) { - const float ph = absFraction( _sample ); + const float ph = normalizePhase<1>(_sample); if( ph <= 0.25f ) { return ph * 4.0f; @@ -132,17 +132,17 @@ class LMMS_EXPORT Oscillator static inline sample_t sawSample( const float _sample ) { - return -1.0f + absFraction( _sample ) * 2.0f; + return -1.0f + normalizePhase<1>(_sample) * 2.0f; } static inline sample_t squareSample( const float _sample ) { - return ( absFraction( _sample ) > 0.5f ) ? -1.0f : 1.0f; + return normalizePhase<1>(_sample) > 0.5f ? -1.0f : 1.0f; } static inline sample_t moogSawSample( const float _sample ) { - const float ph = absFraction( _sample ); + const float ph = normalizePhase<1>(_sample); if( ph < 0.5f ) { return -1.0f + ph * 4.0f; @@ -152,7 +152,7 @@ class LMMS_EXPORT Oscillator static inline sample_t expSample( const float _sample ) { - float ph = absFraction( _sample ); + float ph = normalizePhase<1>(_sample); if( ph > 0.5f ) { ph = 1.0f - ph; @@ -169,7 +169,7 @@ class LMMS_EXPORT Oscillator { if (buffer == nullptr || buffer->size() == 0) { return 0; } const auto frames = buffer->size(); - const auto frame = absFraction(sample) * frames; + const auto frame = normalizePhase<1>(sample) * frames; const auto f1 = static_cast(frame); return std::lerp(buffer->data()[f1][0], buffer->data()[(f1 + 1) % frames][0], fraction(frame)); @@ -185,7 +185,7 @@ class LMMS_EXPORT Oscillator inline wtSampleControl getWtSampleControl(const float sample) const { wtSampleControl control; - control.frame = absFraction(sample) * OscillatorConstants::WAVETABLE_LENGTH; + control.frame = normalizePhase<1>(sample) * OscillatorConstants::WAVETABLE_LENGTH; control.f1 = static_cast(control.frame); control.f2 = control.f1 < OscillatorConstants::WAVETABLE_LENGTH - 1 ? control.f1 + 1 : diff --git a/include/QuadratureLfo.h b/include/QuadratureLfo.h index 3a75ae1f236..19d9bad8c7b 100644 --- a/include/QuadratureLfo.h +++ b/include/QuadratureLfo.h @@ -28,6 +28,8 @@ #include #include +#include "lmms_math.h" + namespace lmms { @@ -80,8 +82,7 @@ class QuadratureLfo { *l = std::sin(m_phase); *r = std::sin(m_phase + m_offset); - m_phase += m_increment; - m_phase = std::fmod(m_phase, 2 * std::numbers::pi); + m_phase = normalizePhase(m_phase + m_increment); } private: diff --git a/include/lmms_math.h b/include/lmms_math.h index a0f43c3e6f9..b2132fe2dcd 100644 --- a/include/lmms_math.h +++ b/include/lmms_math.h @@ -67,19 +67,17 @@ inline auto fraction(std::floating_point auto x) noexcept // TODO C++23: Make constexpr since std::floor() will be constexpr -/*! - * @brief Returns the wrapped fractional part of a float, a value between 0.0f and 1.0f. - * - * absFraction( 2.3) => 0.3 - * absFraction(-2.3) => 0.7 - * - * Note that this not the same as the absolute value of the fraction (as the function name suggests). - * If the result is interpreted as a phase of an oscillator, it makes that negative phases are - * converted to positive phases. - */ -inline auto absFraction(std::floating_point auto x) noexcept +//! @brief Normalizes a phase, such that it is wrapped within [0, Period). +//! This safely converts negative phases into an equivalent positive phase. +//! @param x The phase to normalize. +//! @tparam Period The exclusive upper bound of the range in which to wrap the phase. +//! @returns A phase greater than or equal to 0 and less than Period. +template, std::floating_point T> +inline T normalizePhase(T x) noexcept { - return x - std::floor(x); + constexpr auto p = static_cast(Period); + constexpr auto r = static_cast(1.0 / Period); + return x - p * std::floor(x * r); } diff --git a/plugins/Delay/Lfo.h b/plugins/Delay/Lfo.h index f3fc22cf7a9..eedeaf49902 100644 --- a/plugins/Delay/Lfo.h +++ b/plugins/Delay/Lfo.h @@ -28,6 +28,8 @@ #include #include +#include "lmms_math.h" + namespace lmms { @@ -50,7 +52,7 @@ class Lfo m_frequency = frequency; m_increment = m_frequency * m_twoPiOverSr; - m_phase = std::fmod(m_phase, 2 * std::numbers::pi_v); + m_phase = normalizePhase(m_phase); } diff --git a/plugins/FrequencyShifter/FrequencyShifterEffect.cpp b/plugins/FrequencyShifter/FrequencyShifterEffect.cpp index 12d4b6163e2..573b98d68dd 100755 --- a/plugins/FrequencyShifter/FrequencyShifterEffect.cpp +++ b/plugins/FrequencyShifter/FrequencyShifterEffect.cpp @@ -110,10 +110,10 @@ Effect::ProcessStatus FrequencyShifterEffect::processImpl(SampleFrame* buf, cons const float glideCoeff = glide ? std::exp(-1.f / (glide * m_sampleRate)) : 0.f; // we only bother with wrapping phases once per buffer - m_lfoPhase = std::fmod(m_lfoPhase, twoPi); + m_lfoPhase = normalizePhase(m_lfoPhase); for (int ch = 0; ch < 2; ++ch) { - m_phase[ch] = std::fmod(m_phase[ch], twoPi); + m_phase[ch] = normalizePhase(m_phase[ch]); } for (size_t i = 0; i < frames; ++i) diff --git a/plugins/Monstro/Monstro.cpp b/plugins/Monstro/Monstro.cpp index c6fc25bb3b5..f49d2ebfb58 100644 --- a/plugins/Monstro/Monstro.cpp +++ b/plugins/Monstro/Monstro.cpp @@ -153,8 +153,8 @@ void MonstroSynth::renderOutput( f_cnt_t _frames, SampleFrame* _buf ) const float lfo2_po = m_parent->m_lfo2Phs.value() / 360.0f; // remove cruft from phase counters to prevent overflow, add phase offset - m_lfo_phase[0] = absFraction( m_lfo_phase[0] + lfo1_po ); - m_lfo_phase[1] = absFraction( m_lfo_phase[1] + lfo2_po ); + m_lfo_phase[0] = normalizePhase<1>(m_lfo_phase[0] + lfo1_po); + m_lfo_phase[1] = normalizePhase<1>(m_lfo_phase[1] + lfo2_po); // LFO rates and increment m_lfo_rate[0] = ( m_parent->m_lfo1Rate.value() * 0.001f * m_parent->m_samplerate ); @@ -385,8 +385,8 @@ void MonstroSynth::renderOutput( f_cnt_t _frames, SampleFrame* _buf ) } // pulse wave osc - sample_t O1L = ( absFraction( leftph ) < o1_pw ) ? 1.0f : -1.0f; - sample_t O1R = ( absFraction( rightph ) < o1_pw ) ? 1.0f : -1.0f; + sample_t O1L = (normalizePhase<1>(leftph ) < o1_pw) ? 1.0f : -1.0f; + sample_t O1R = (normalizePhase<1>(rightph) < o1_pw) ? 1.0f : -1.0f; // check for rise/fall, and sync if appropriate // sync on rise @@ -489,8 +489,8 @@ void MonstroSynth::renderOutput( f_cnt_t _frames, SampleFrame* _buf ) modulatephs( leftph, o2p ) modulatephs( rightph, o2p ) } - leftph = absFraction( leftph ); - rightph = absFraction( rightph ); + leftph = normalizePhase<1>(leftph ); + rightph = normalizePhase<1>(rightph); // phase delta pd_l = qAbs( leftph - m_ph2l_last ); @@ -569,8 +569,8 @@ void MonstroSynth::renderOutput( f_cnt_t _frames, SampleFrame* _buf ) leftph += O2L * 0.5f; rightph += O2R * 0.5f; } - leftph = absFraction( leftph ); - rightph = absFraction( rightph ); + leftph = normalizePhase<1>(leftph ); + rightph = normalizePhase<1>(rightph); // phase delta pd_l = qAbs( leftph - m_ph3l_last ); @@ -670,15 +670,15 @@ void MonstroSynth::renderOutput( f_cnt_t _frames, SampleFrame* _buf ) } // update phases - m_osc1l_phase = absFraction( o1l_p - o1lpo ); - m_osc1r_phase = absFraction( o1r_p - o1rpo ); - m_osc2l_phase = absFraction( o2l_p - o2lpo ); - m_osc2r_phase = absFraction( o2r_p - o2rpo ); - m_osc3l_phase = absFraction( o3l_p - o3lpo ); - m_osc3r_phase = absFraction( o3r_p - o3rpo ); - - m_lfo_phase[0] = absFraction( m_lfo_phase[0] - lfo1_po ); - m_lfo_phase[1] = absFraction( m_lfo_phase[1] - lfo2_po ); + m_osc1l_phase = normalizePhase<1>(o1l_p - o1lpo); + m_osc1r_phase = normalizePhase<1>(o1r_p - o1rpo); + m_osc2l_phase = normalizePhase<1>(o2l_p - o2lpo); + m_osc2r_phase = normalizePhase<1>(o2r_p - o2rpo); + m_osc3l_phase = normalizePhase<1>(o3l_p - o3lpo); + m_osc3r_phase = normalizePhase<1>(o3r_p - o3rpo); + + m_lfo_phase[0] = normalizePhase<1>(m_lfo_phase[0] - lfo1_po); + m_lfo_phase[1] = normalizePhase<1>(m_lfo_phase[1] - lfo2_po); } diff --git a/plugins/Monstro/Monstro.h b/plugins/Monstro/Monstro.h index a800522555e..b2a3f3b2730 100644 --- a/plugins/Monstro/Monstro.h +++ b/plugins/Monstro/Monstro.h @@ -200,7 +200,7 @@ class MonstroSynth break; case WAVE_SQRSOFT: { - const float ph = fraction( _ph ); + const float ph = normalizePhase<1>(_ph); if( ph < 0.1 ) return Oscillator::sinSample( ph * 5 + 0.75 ); else if( ph < 0.5 ) return 1.0f; else if( ph < 0.6 ) return Oscillator::sinSample( ph * 5 + 0.75 ); diff --git a/plugins/Watsyn/Watsyn.cpp b/plugins/Watsyn/Watsyn.cpp index 532d2093e18..44037286112 100644 --- a/plugins/Watsyn/Watsyn.cpp +++ b/plugins/Watsyn/Watsyn.cpp @@ -135,10 +135,8 @@ void WatsynObject::renderOutput( f_cnt_t _frames ) // if phase mod, add to phases if( m_amod == MOD_PM ) { - A1_lphase = std::fmod(A1_lphase + A2_L * PMOD_AMT, WAVELEN); - if( A1_lphase < 0 ) A1_lphase += WAVELEN; - A1_rphase = std::fmod(A1_rphase + A2_R * PMOD_AMT, WAVELEN); - if( A1_rphase < 0 ) A1_rphase += WAVELEN; + A1_lphase = normalizePhase(A1_lphase + A2_L * PMOD_AMT); + A1_rphase = normalizePhase(A1_rphase + A2_R * PMOD_AMT); } // A1 sample_t A1_L = m_parent->m_lvol[A1_OSC] * std::lerp( @@ -177,10 +175,8 @@ void WatsynObject::renderOutput( f_cnt_t _frames ) // if phase mod, add to phases if( m_bmod == MOD_PM ) { - B1_lphase = std::fmod(B1_lphase + B2_L * PMOD_AMT, WAVELEN); - if( B1_lphase < 0 ) B1_lphase += WAVELEN; - B1_rphase = std::fmod(B1_rphase + B2_R * PMOD_AMT, WAVELEN); - if( B1_rphase < 0 ) B1_rphase += WAVELEN; + B1_lphase = normalizePhase(B1_lphase + B2_L * PMOD_AMT); + B1_rphase = normalizePhase(B1_rphase + B2_R * PMOD_AMT); } // B1 sample_t B1_L = m_parent->m_lvol[B1_OSC] * std::lerp( @@ -236,10 +232,9 @@ void WatsynObject::renderOutput( f_cnt_t _frames ) // update phases for( int i = 0; i < NUM_OSCS; i++ ) { - m_lphase[i] += ( static_cast( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_lfreq[i] ) ) ); - m_lphase[i] = std::fmod(m_lphase[i], WAVELEN); - m_rphase[i] += ( static_cast( WAVELEN ) / ( m_samplerate / ( m_nph->frequency() * m_parent->m_rfreq[i] ) ) ); - m_rphase[i] = std::fmod(m_rphase[i], WAVELEN); + const auto phasePerSample = static_cast(WAVELEN) * m_nph->frequency() / m_samplerate; + m_lphase[i] = normalizePhase(m_lphase[i] + phasePerSample * m_parent->m_lfreq[i]); + m_rphase[i] = normalizePhase(m_rphase[i] + phasePerSample * m_parent->m_rfreq[i]); } } diff --git a/plugins/Watsyn/Watsyn.h b/plugins/Watsyn/Watsyn.h index b45bcebc4d5..8bb8b9354c0 100644 --- a/plugins/Watsyn/Watsyn.h +++ b/plugins/Watsyn/Watsyn.h @@ -42,25 +42,22 @@ namespace lmms #define B1ROW 72 #define B2ROW 95 - -const int GRAPHLEN = 220; // don't change - must be same as the size of the widget - -const int WAVERATIO = 32; // oversampling ratio - -const int WAVELEN = GRAPHLEN * WAVERATIO; -const int PMOD_AMT = WAVELEN / 2; - -const int MOD_MIX = 0; -const int MOD_AM = 1; -const int MOD_RM = 2; -const int MOD_PM = 3; -const int NUM_MODS = 4; - -const int A1_OSC = 0; -const int A2_OSC = 1; -const int B1_OSC = 2; -const int B2_OSC = 3; -const int NUM_OSCS = 4; +constexpr int GRAPHLEN = 220; // don't change - must be same as the size of the widget +constexpr int WAVERATIO = 32; //!< Oversampling ratio +constexpr int WAVELEN = GRAPHLEN * WAVERATIO; +constexpr int PMOD_AMT = WAVELEN / 2; + +constexpr int MOD_MIX = 0; +constexpr int MOD_AM = 1; +constexpr int MOD_RM = 2; +constexpr int MOD_PM = 3; +constexpr int NUM_MODS = 4; + +constexpr int A1_OSC = 0; +constexpr int A2_OSC = 1; +constexpr int B1_OSC = 2; +constexpr int B2_OSC = 3; +constexpr int NUM_OSCS = 4; class WatsynInstrument; diff --git a/src/core/DrumSynth.cpp b/src/core/DrumSynth.cpp index 6f8afc7d3f5..4bd824f9897 100644 --- a/src/core/DrumSynth.cpp +++ b/src/core/DrumSynth.cpp @@ -177,7 +177,7 @@ float DrumSynth::waveform(float ph, int form) // sine^2 if (form == 1) { return std::abs(2.f * std::sin(0.5f * ph)) - 1.f; } // sawtooth with range [0, 1], used to generate triangle, sawtooth, and square - auto saw01 = absFraction(ph / (2 * std::numbers::pi_v)); + auto saw01 = normalizePhase<1>(ph / (2 * std::numbers::pi_v)); // triangle if (form == 2) { return 1.f - 4.f * std::abs(saw01 - 0.5f); } // sawtooth diff --git a/src/core/LfoController.cpp b/src/core/LfoController.cpp index 097921e78d4..45d44556f23 100644 --- a/src/core/LfoController.cpp +++ b/src/core/LfoController.cpp @@ -113,7 +113,7 @@ void LfoController::updateValueBuffer() { case Oscillator::WaveShape::WhiteNoise: { - if (absFraction(phase) < absFraction(phasePrev)) + if (normalizePhase<1>(phase) < normalizePhase<1>(phasePrev)) { // Resample when phase period has completed m_heldSample = m_sampleFunction(phase); @@ -142,7 +142,7 @@ void LfoController::updateValueBuffer() amountPtr += amountInc; } - m_currentPhase = absFraction(phase - m_phaseOffset); + m_currentPhase = normalizePhase<1>(phase - m_phaseOffset); m_bufferLastUpdated = s_periods; } diff --git a/src/core/Oscillator.cpp b/src/core/Oscillator.cpp index 1ea97c4442f..c796b550143 100644 --- a/src/core/Oscillator.cpp +++ b/src/core/Oscillator.cpp @@ -547,7 +547,7 @@ inline void Oscillator::recalcPhase() m_phaseOffset = m_ext_phaseOffset; m_phase += m_phaseOffset; } - m_phase = absFraction( m_phase ); + m_phase = normalizePhase<1>(m_phase); } From e34c91f8238efe78526924571f1461f5b7d60ecc Mon Sep 17 00:00:00 2001 From: Fawn Date: Thu, 2 Apr 2026 10:46:19 -0600 Subject: [PATCH 2/2] Remove redundant includes --- include/QuadratureLfo.h | 3 --- plugins/Delay/Lfo.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/include/QuadratureLfo.h b/include/QuadratureLfo.h index 19d9bad8c7b..a75665340de 100644 --- a/include/QuadratureLfo.h +++ b/include/QuadratureLfo.h @@ -25,9 +25,6 @@ #ifndef LMMS_QUADRATURE_LFO_H #define LMMS_QUADRATURE_LFO_H -#include -#include - #include "lmms_math.h" namespace lmms diff --git a/plugins/Delay/Lfo.h b/plugins/Delay/Lfo.h index eedeaf49902..8294673f57b 100644 --- a/plugins/Delay/Lfo.h +++ b/plugins/Delay/Lfo.h @@ -25,9 +25,6 @@ #ifndef LFO_H #define LFO_H -#include -#include - #include "lmms_math.h" namespace lmms