Cog/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_flt.cpp
Christopher Snowhill 731e52c440 Build libOpenMPT from source once again
Bundle libOpenMPT as a dynamic framework, which should be safe once
again, now that there is only one version to bundle. Also, now it is
using the versions of libvorbisfile and libmpg123 that are bundled with
the player, instead of compiling minimp3 and stbvorbis.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-30 22:57:30 -07:00

164 lines
5.1 KiB
C++

/*
* 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<uint8>(cutoff);
}
uint32 CSoundFile::CutOffToFrequency(uint32 nCutOff, int envModifier) const
{
MPT_ASSERT(nCutOff < 128);
float computedCutoff = static_cast<float>(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<int>(Fc);
Limit(freq, 120, 20000);
if(freq * 2 > (int)m_MixerSettings.gdwMixingFreq) freq = m_MixerSettings.gdwMixingFreq / 2;
return static_cast<uint32>(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<int>(chn.nCutOff) + chn.nCutSwing;
int resonance = static_cast<int>(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>);
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<mixsample_t>((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