Skip to content

Commit 92ddec2

Browse files
committed
Add DC blocker post-mixer to remove DC offset
This implements a one-pole high-pass filter (alpha=0.995, ~8.7 Hz cutoff) placed after the main mixer and before the soft clipper. - Uses int64_t arithmetic to prevent overflow on large signals - Clamps state to INT32_MIN/MAX for stability - Truncates feedback (not rounds) to ensure full DC decay to zero It removes DC bias introduced by waveshaping and asymmetric waveforms with approximately 4 cycles/sample overhead.
1 parent 94b7235 commit 92ddec2

File tree

1 file changed

+28
-1
lines changed

1 file changed

+28
-1
lines changed

src/picosynth.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
#define ENVELOPE_STATE_MODE_BIT 0x80000000u
1111
#define ENVELOPE_STATE_VALUE_MASK 0x7FFFFFFF
1212

13+
/* DC blocker coefficient: alpha ≈ 0.995 in Q15 = 32604
14+
* Removes DC offset from waveshaping with ~4 cycles/sample overhead.
15+
*/
16+
#define DC_BLOCK_ALPHA 32604
17+
1318
/* Opaque type definitions */
1419
struct picosynth_voice {
1520
uint8_t note; /* Current MIDI note */
@@ -25,6 +30,8 @@ struct picosynth {
2530
picosynth_voice_t *voices;
2631
uint8_t num_voices;
2732
uint16_t voice_enable_mask; /* Bit N = voice N active */
33+
/* DC blocker state (placed after main mixer, before soft clipper) */
34+
int32_t dc_x_prev, dc_y_prev; /* Previous input, output */
2835
};
2936

3037
/* LFSR seed for noise generator.
@@ -868,7 +875,27 @@ q15_t picosynth_process(picosynth_t *s)
868875
q15_t gain = Q15_MAX / s->num_voices;
869876
out = (int32_t) (((int64_t) out * gain) >> 15);
870877
}
871-
return soft_clip(out);
878+
879+
/* DC blocker: y[n] = x[n] - x[n-1] + alpha * y[n-1]
880+
* Removes DC offset introduced by waveshaping and asymmetric waveforms.
881+
* Uses int64_t to prevent overflow, clamps state. No rounding on feedback
882+
* (truncation ensures full decay to zero, rounding leaves residual DC).
883+
*/
884+
int64_t delta = (int64_t) out - (int64_t) s->dc_x_prev;
885+
int64_t fb = ((int64_t) DC_BLOCK_ALPHA * (int64_t) s->dc_y_prev) >> 15;
886+
int64_t acc = delta + fb;
887+
888+
/* Clamp to int32 to keep state sane for the next sample */
889+
if (acc > INT32_MAX)
890+
acc = INT32_MAX;
891+
else if (acc < INT32_MIN)
892+
acc = INT32_MIN;
893+
894+
int32_t dc_out = (int32_t) acc;
895+
s->dc_x_prev = out;
896+
s->dc_y_prev = dc_out;
897+
898+
return soft_clip(dc_out);
872899
}
873900

874901
q15_t picosynth_wave_saw(q15_t phase)

0 commit comments

Comments
 (0)