@@ -113,6 +113,8 @@ struct mixer_t {
113
113
SDL_AudioDeviceID sdldevice = 0 ;
114
114
115
115
MixerState state = MixerState::Uninitialized; // use MIXER_SetState() to change
116
+
117
+ std::array<Iir::Butterworth::HighPass<2 >, 2 > highpass_filter = {};
116
118
};
117
119
118
120
static struct mixer_t mixer = {};
@@ -853,7 +855,7 @@ void MixerChannel::AddStretched(uint16_t len, int16_t *data)
853
855
index += index_add;
854
856
mixpos &= MIXER_BUFMASK;
855
857
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 ;
857
859
mixer.work [mixpos][mapped_output_right] += sample * volmul.right ;
858
860
mixpos++;
859
861
}
@@ -1008,23 +1010,42 @@ static void MIXER_MixData(int needed)
1008
1010
for (auto &it : mixer.channels )
1009
1011
it.second ->Mix (needed);
1010
1012
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
+
1011
1026
if (CaptureState & (CAPTURE_WAVE | CAPTURE_VIDEO)) {
1012
1027
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
+
1015
1031
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
+
1022
1041
readpos = (readpos + 1 ) & MIXER_BUFMASK;
1023
1042
}
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));
1025
1046
}
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 ())
1028
1049
mixer.tick_add = calc_tickadd (mixer.sample_rate );
1029
1050
mixer.done = needed;
1030
1051
}
@@ -1082,7 +1103,7 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
1082
1103
auto sample = 0 ;
1083
1104
/* Enough room in the buffer ? */
1084
1105
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() );
1086
1107
if ((need - mixer.done ) > (need >> 7 )) // Max 1 percent stretch.
1087
1108
return ;
1088
1109
reduce = mixer.done ;
@@ -1103,15 +1124,13 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
1103
1124
left = (mixer.min_needed - left);
1104
1125
left = 1 + (2 * left) / mixer.min_needed ; // left=1,2,3
1105
1126
}
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);
1107
1128
reduce = need - left;
1108
1129
index_add = (reduce << INDEX_SHIFT_LOCAL) / need;
1109
1130
} else {
1110
1131
reduce = need;
1111
1132
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);
1115
1134
1116
1135
/* Mixer tick value being updated:
1117
1136
* 3 cases:
@@ -1134,7 +1153,7 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
1134
1153
}
1135
1154
} else {
1136
1155
/* 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());
1138
1157
if (mixer.done > MIXER_BUFSIZE)
1139
1158
index_add = MIXER_BUFSIZE - 2 * mixer.min_needed ;
1140
1159
else
@@ -1155,29 +1174,31 @@ static void SDLCALL MIXER_CallBack([[maybe_unused]] void *userdata, Uint8 *strea
1155
1174
mixer.pos = (mixer.pos + reduce) & MIXER_BUFMASK;
1156
1175
if (need != reduce) {
1157
1176
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);
1159
1179
index += index_add;
1160
- sample = static_cast <int >(mixer.work [i][0 ]);
1180
+
1181
+ sample = static_cast <int >(mixer.work [i][0 ]);
1161
1182
*output++ = MIXER_CLIP (sample);
1162
- sample = static_cast <int >(mixer.work [i][1 ]);
1183
+ sample = static_cast <int >(mixer.work [i][1 ]);
1163
1184
*output++ = MIXER_CLIP (sample);
1164
1185
}
1165
1186
/* Clean the used buffer */
1166
1187
while (reduce--) {
1167
1188
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 ;
1170
1191
pos++;
1171
1192
}
1172
1193
} else {
1173
1194
while (reduce--) {
1174
1195
pos &= MIXER_BUFMASK;
1175
- sample = static_cast <int >(mixer.work [pos][0 ]);
1196
+ sample = static_cast <int >(mixer.work [pos][0 ]);
1176
1197
*output++ = MIXER_CLIP (sample);
1177
- sample = static_cast <int >(mixer.work [pos][1 ]);
1198
+ sample = static_cast <int >(mixer.work [pos][1 ]);
1178
1199
*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 ;
1181
1202
pos++;
1182
1203
}
1183
1204
}
@@ -1582,6 +1603,34 @@ void MIXER_Init(Section *sec)
1582
1603
1583
1604
// Initialize the 8-bit to 16-bit lookup table
1584
1605
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
+ }
1585
1634
}
1586
1635
1587
1636
// Toggle the mixer on/off when a true-bool is passed.
0 commit comments