aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/soundlib/Snd_flt.cpp
blob: e5ea5b6e1c98f3b953c3287da46817d4834bd072 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/*
 * 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