Skip to content

Commit 24a0875

Browse files
committed
Add master high-pass filter to the mixer
1 parent 9467fbc commit 24a0875

File tree

2 files changed

+76
-28
lines changed

2 files changed

+76
-28
lines changed

include/mixer.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
#include <set>
3232

3333
#include "envelope.h"
34-
#include "mixer.h"
3534

3635
#include "../src/libs/iir1/Iir.h"
3736

src/hardware/mixer.cpp

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ struct mixer_t {
113113
SDL_AudioDeviceID sdldevice = 0;
114114

115115
MixerState state = MixerState::Uninitialized; // use MIXER_SetState() to change
116+
117+
std::array<Iir::Butterworth::HighPass<2>, 2> highpass_filter = {};
116118
};
117119

118120
static struct mixer_t mixer = {};
@@ -853,7 +855,7 @@ void MixerChannel::AddStretched(uint16_t len, int16_t *data)
853855
index += index_add;
854856
mixpos &= MIXER_BUFMASK;
855857
const auto sample = prev_frame.left + ((diff * diff_mul) >> FREQ_SHIFT);
856-
mixer.work[mixpos][mapped_output_left] += sample * volmul.left;
858+
mixer.work[mixpos][mapped_output_left] += sample * volmul.left;
857859
mixer.work[mixpos][mapped_output_right] += sample * volmul.right;
858860
mixpos++;
859861
}
@@ -1008,23 +1010,42 @@ static void MIXER_MixData(int needed)
10081010
for (auto &it : mixer.channels)
10091011
it.second->Mix(needed);
10101012

1013+
// Master high-pass filter
1014+
const auto added = check_cast<work_index_t>(
1015+
std::min(needed - mixer.done, 1024));
1016+
auto pos = check_cast<work_index_t>((mixer.pos + mixer.done) & MIXER_BUFMASK);
1017+
1018+
for (work_index_t i = 0; i < added; ++i) {
1019+
for (auto ch = 0; ch < 2; ++ch) {
1020+
mixer.work[pos][ch] = mixer.highpass_filter[ch].filter(
1021+
mixer.work[pos][ch]);
1022+
}
1023+
pos = (pos + 1) & MIXER_BUFMASK;
1024+
}
1025+
10111026
if (CaptureState & (CAPTURE_WAVE | CAPTURE_VIDEO)) {
10121027
int16_t convert[1024][2];
1013-
const auto added = check_cast<work_index_t>(std::min(needed - mixer.done, 1024));
1014-
auto readpos = check_cast<work_index_t>((mixer.pos + mixer.done) & MIXER_BUFMASK);
1028+
auto readpos = check_cast<work_index_t>((mixer.pos + mixer.done) &
1029+
MIXER_BUFMASK);
1030+
10151031
for (work_index_t i = 0; i < added; i++) {
1016-
const auto sample_1 = mixer.work[readpos][0];
1017-
const auto sample_2 = mixer.work[readpos][1];
1018-
const auto s1 = static_cast<uint16_t>(MIXER_CLIP(static_cast<int>(sample_1)));
1019-
const auto s2 = static_cast<uint16_t>(MIXER_CLIP(static_cast<int>(sample_2)));
1020-
convert[i][0] = static_cast<int16_t>(host_to_le16(s1));
1021-
convert[i][1] = static_cast<int16_t>(host_to_le16(s2));
1032+
const auto left = static_cast<uint16_t>(MIXER_CLIP(
1033+
static_cast<int>(mixer.work[readpos][0])));
1034+
1035+
const auto right = static_cast<uint16_t>(MIXER_CLIP(
1036+
static_cast<int>(mixer.work[readpos][1])));
1037+
1038+
convert[i][0] = static_cast<int16_t>(host_to_le16(left));
1039+
convert[i][1] = static_cast<int16_t>(host_to_le16(right));
1040+
10221041
readpos = (readpos + 1) & MIXER_BUFMASK;
10231042
}
1024-
CAPTURE_AddWave(mixer.sample_rate, added, reinterpret_cast<int16_t*>(convert));
1043+
CAPTURE_AddWave(mixer.sample_rate,
1044+
added,
1045+
reinterpret_cast<int16_t *>(convert));
10251046
}
1026-
//Reset the the tick_add for constant speed
1027-
if( Mixer_irq_important() )
1047+
// Reset the the tick_add for constant speed
1048+
if (Mixer_irq_important())
10281049
mixer.tick_add = calc_tickadd(mixer.sample_rate);
10291050
mixer.done = needed;
10301051
}
@@ -1082,7 +1103,7 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
10821103
auto sample = 0;
10831104
/* Enough room in the buffer ? */
10841105
if (mixer.done < need) {
1085-
//LOG_WARNING("Full underrun need %d, have %d, min %d", need, mixer.done, mixer.min_needed);
1106+
// LOG_WARNING("Full underrun need %d, have %d, min %d", need, mixer.done.load(), mixer.min_needed.load());
10861107
if ((need - mixer.done) > (need >> 7)) // Max 1 percent stretch.
10871108
return;
10881109
reduce = mixer.done;
@@ -1103,15 +1124,13 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
11031124
left = (mixer.min_needed - left);
11041125
left = 1 + (2 * left) / mixer.min_needed; // left=1,2,3
11051126
}
1106-
//LOG_WARNING("needed underrun need %d, have %d, min %d, left %d", need, mixer.done, mixer.min_needed, left);
1127+
// LOG_WARNING("needed underrun need %d, have %d, min %d, left %d", need, mixer.done.load(), mixer.min_needed.load(), left);
11071128
reduce = need - left;
11081129
index_add = (reduce << INDEX_SHIFT_LOCAL) / need;
11091130
} else {
11101131
reduce = need;
11111132
index_add = (1 << INDEX_SHIFT_LOCAL);
1112-
// LOG_MSG("regular run need %d, have
1113-
//%d, min %d, left %d", need, mixer.done,
1114-
//mixer.min_needed, left);
1133+
// LOG_MSG("regular run need %d, have %d, min %d, left %d", need, mixer.done.load(), mixer.min_needed.load(), left);
11151134

11161135
/* Mixer tick value being updated:
11171136
* 3 cases:
@@ -1134,7 +1153,7 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
11341153
}
11351154
} else {
11361155
/* There is way too much data in the buffer */
1137-
LOG_WARNING("overflow run need %u, have %u, min %u", need, mixer.done.load(), mixer.min_needed.load());
1156+
// LOG_WARNING("overflow run need %u, have %u, min %u", need, mixer.done.load(), mixer.min_needed.load());
11381157
if (mixer.done > MIXER_BUFSIZE)
11391158
index_add = MIXER_BUFSIZE - 2 * mixer.min_needed;
11401159
else
@@ -1155,29 +1174,31 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
11551174
mixer.pos = (mixer.pos + reduce) & MIXER_BUFMASK;
11561175
if (need != reduce) {
11571176
while (need--) {
1158-
const auto i = check_cast<work_index_t>((pos + (index >> INDEX_SHIFT_LOCAL)) & MIXER_BUFMASK);
1177+
const auto i = check_cast<work_index_t>(
1178+
(pos + (index >> INDEX_SHIFT_LOCAL)) & MIXER_BUFMASK);
11591179
index += index_add;
1160-
sample = static_cast<int>(mixer.work[i][0]);
1180+
1181+
sample = static_cast<int>(mixer.work[i][0]);
11611182
*output++ = MIXER_CLIP(sample);
1162-
sample = static_cast<int>(mixer.work[i][1]);
1183+
sample = static_cast<int>(mixer.work[i][1]);
11631184
*output++ = MIXER_CLIP(sample);
11641185
}
11651186
/* Clean the used buffer */
11661187
while (reduce--) {
11671188
pos &= MIXER_BUFMASK;
1168-
mixer.work[pos][0] = 0;
1169-
mixer.work[pos][1] = 0;
1189+
mixer.work[pos][0] = 0.0f;
1190+
mixer.work[pos][1] = 0.0f;
11701191
pos++;
11711192
}
11721193
} else {
11731194
while (reduce--) {
11741195
pos &= MIXER_BUFMASK;
1175-
sample = static_cast<int>(mixer.work[pos][0]);
1196+
sample = static_cast<int>(mixer.work[pos][0]);
11761197
*output++ = MIXER_CLIP(sample);
1177-
sample = static_cast<int>(mixer.work[pos][1]);
1198+
sample = static_cast<int>(mixer.work[pos][1]);
11781199
*output++ = MIXER_CLIP(sample);
1179-
mixer.work[pos][0] = 0;
1180-
mixer.work[pos][1] = 0;
1200+
mixer.work[pos][0] = 0.0f;
1201+
mixer.work[pos][1] = 0.0f;
11811202
pos++;
11821203
}
11831204
}
@@ -1582,6 +1603,34 @@ void MIXER_Init(Section *sec)
15821603

15831604
// Initialize the 8-bit to 16-bit lookup table
15841605
fill_8to16_lut();
1606+
1607+
// Initialise master high-pass filter
1608+
// ----------------------------------
1609+
// The purpose of this filter is two-fold:
1610+
//
1611+
// - Remove any DC offset from the summed master output (any high-pass
1612+
// filter can achieve this, even a 6dB/oct HPF at 1Hz). Virtually all
1613+
// synth modules (CMS, OPL, etc.) can introduce DC offset; this usually
1614+
// isn't a problem on real hardware as most audio interfaces include
1615+
// a DC-blocking or high-pass filter in the analog output stages.
1616+
//
1617+
// - Get rid of (or more precisely, attenuate) unnecessary rumble below
1618+
// 20Hz that serves no musical purpose and only eats up headroom.
1619+
// Issues like this could have gone unnoticed in the 80s/90s due to
1620+
// much lower quality consumer audio equipment available, plus
1621+
// most sound cards had weak bass response (on some models the bass
1622+
// rolloff starts from as high as 100-120Hz), so the presence of
1623+
// unnecessary ultra low-frequency content never became an issue back
1624+
// then.
1625+
//
1626+
// Thanks to the float mixbuffer, it is sufficient to peform the high-pass
1627+
// filtering only once at the very end of the processing chain, instead of
1628+
// doing it on every single mixer channel.
1629+
//
1630+
constexpr auto highpass_cutoff_freq = 20.0;
1631+
for (auto &f : mixer.highpass_filter) {
1632+
f.setup(mixer.sample_rate, highpass_cutoff_freq);
1633+
}
15851634
}
15861635

15871636
// Toggle the mixer on/off when a true-bool is passed.

0 commit comments

Comments
 (0)