/* * Snd_flt.cpp * ----------- * Purpose: Calculation of resonant filter coefficients. * Notes : Extended filter range was introduced in MPT 1.12 and went up to 8652 Hz. * MPT 1.16 upped this to the current 10670 Hz. * We have no way of telling whether a file was made with MPT 1.12 or 1.16 though. * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Sndfile.h" #include "Tables.h" #include "../common/misc_util.h" #include "mpt/base/numbers.hpp" OPENMPT_NAMESPACE_BEGIN // AWE32: cutoff = reg[0-255] * 31.25 + 100 -> [100Hz-8060Hz] // EMU10K1 docs: cutoff = reg[0-127]*62+100 uint8 CSoundFile::FrequencyToCutOff(double frequency) const { // IT Cutoff is computed as cutoff = 110 * 2 ^ (0.25 + x/y), where x is the cutoff and y defines the filter range. // Reversed, this gives us x = (log2(cutoff / 110) - 0.25) * y. // <==========> Rewrite as x = (log2(cutoff) - log2(110) - 0.25) * y. // <==========> Rewrite as x = (ln(cutoff) - ln(110) - 0.25*ln(2)) * y/ln(2). // <4.8737671609324025> double cutoff = (std::log(frequency) - 4.8737671609324025) * (m_SongFlags[SONG_EXFILTERRANGE] ? (20.0 / mpt::numbers::ln2) : (24.0 / mpt::numbers::ln2)); Limit(cutoff, 0.0, 127.0); return mpt::saturate_round(cutoff); } uint32 CSoundFile::CutOffToFrequency(uint32 nCutOff, int envModifier) const { MPT_ASSERT(nCutOff < 128); float computedCutoff = static_cast(nCutOff * (envModifier + 256)); // 0...127*512 float Fc; if(GetType() != MOD_TYPE_IMF) { Fc = 110.0f * std::pow(2.0f, 0.25f + computedCutoff / (m_SongFlags[SONG_EXFILTERRANGE] ? 20.0f * 512.0f : 24.0f * 512.0f)); } else { // EMU8000: Documentation says the cutoff is in quarter semitones, with 0x00 being 125 Hz and 0xFF being 8 kHz // The first half of the sentence contradicts the second, though. Fc = 125.0f * std::pow(2.0f, computedCutoff * 6.0f / (127.0f * 512.0f)); } int freq = mpt::saturate_round(Fc); Limit(freq, 120, 20000); if(freq * 2 > (int)m_MixerSettings.gdwMixingFreq) freq = m_MixerSettings.gdwMixingFreq / 2; return static_cast(freq); } // Simple 2-poles resonant filter. Returns computed cutoff in range [0, 254] or -1 if filter is not applied. int CSoundFile::SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier) const { int cutoff = static_cast(chn.nCutOff) + chn.nCutSwing; int resonance = static_cast(chn.nResonance & 0x7F) + chn.nResSwing; Limit(cutoff, 0, 127); Limit(resonance, 0, 127); if(!m_playBehaviour[kMPTOldSwingBehaviour]) { chn.nCutOff = (uint8)cutoff; chn.nCutSwing = 0; chn.nResonance = (uint8)resonance; chn.nResSwing = 0; } // envModifier is in [-256, 256], so cutoff is in [0, 127 * 2] after this calculation. const int computedCutoff = cutoff * (envModifier + 256) / 256; // Filtering is only ever done in IT if either cutoff is not full or if resonance is set. if(m_playBehaviour[kITFilterBehaviour] && resonance == 0 && computedCutoff >= 254) { if(chn.rowCommand.IsNote() && !chn.rowCommand.IsPortamento() && !chn.nMasterChn && chn.triggerNote) { // Z7F next to a note disables the filter, however in other cases this should not happen. // Test cases: filter-reset.it, filter-reset-carry.it, filter-reset-envelope.it, filter-nna.it, FilterResetPatDelay.it chn.dwFlags.reset(CHN_FILTER); } return -1; } chn.dwFlags.set(CHN_FILTER); // 2 * damping factor const float dmpfac = std::pow(10.0f, -resonance * ((24.0f / 128.0f) / 20.0f)); const float fc = CutOffToFrequency(cutoff, envModifier) * (2.0f * mpt::numbers::pi_v); float d, e; if(m_playBehaviour[kITFilterBehaviour] && !m_SongFlags[SONG_EXFILTERRANGE]) { const float r = m_MixerSettings.gdwMixingFreq / fc; d = dmpfac * r + dmpfac - 1.0f; e = r * r; } else { const float r = fc / m_MixerSettings.gdwMixingFreq; d = (1.0f - 2.0f * dmpfac) * r; LimitMax(d, 2.0f); d = (2.0f * dmpfac - d) / r; e = 1.0f / (r * r); } float fg = 1.0f / (1.0f + d + e); float fb0 = (d + e + e) / (1 + d + e); float fb1 = -e / (1.0f + d + e); #if defined(MPT_INTMIXER) #define MPT_FILTER_CONVERT(x) mpt::saturate_round((x) * (1 << MIXING_FILTER_PRECISION)) #else #define MPT_FILTER_CONVERT(x) (x) #endif switch(chn.nFilterMode) { case FilterMode::HighPass: chn.nFilter_A0 = MPT_FILTER_CONVERT(1.0f - fg); chn.nFilter_B0 = MPT_FILTER_CONVERT(fb0); chn.nFilter_B1 = MPT_FILTER_CONVERT(fb1); #ifdef MPT_INTMIXER chn.nFilter_HP = -1; #else chn.nFilter_HP = 1.0f; #endif // MPT_INTMIXER break; default: chn.nFilter_A0 = MPT_FILTER_CONVERT(fg); chn.nFilter_B0 = MPT_FILTER_CONVERT(fb0); chn.nFilter_B1 = MPT_FILTER_CONVERT(fb1); #ifdef MPT_INTMIXER if(chn.nFilter_A0 == 0) chn.nFilter_A0 = 1; // Prevent silence at low filter cutoff and very high sampling rate chn.nFilter_HP = 0; #else chn.nFilter_HP = 0; #endif // MPT_INTMIXER break; } #undef MPT_FILTER_CONVERT if (bReset) { chn.nFilter_Y[0][0] = chn.nFilter_Y[0][1] = 0; chn.nFilter_Y[1][0] = chn.nFilter_Y[1][1] = 0; } return computedCutoff; } OPENMPT_NAMESPACE_END