Skip to content

Commit c94b118

Browse files
committed
Smooth filter transitions
This adds per-sample smoothing of filter cutoff targets to remove zipper noise during live tweaks. It also resets filter state to the requested cutoff when notes start so attacks stay consistent.
1 parent c6264b4 commit c94b118

File tree

3 files changed

+43
-5
lines changed

3 files changed

+43
-5
lines changed

include/picosynth.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ typedef struct {
9090

9191
/* Single-pole filter state */
9292
typedef struct {
93-
const q15_t *in; /* Input signal pointer */
94-
int32_t accum; /* Internal accumulator (Q31) */
95-
q15_t coeff; /* Cutoff: 0=DC, Q15_MAX=bypass */
93+
const q15_t *in; /* Input signal pointer */
94+
int32_t accum; /* Internal accumulator (Q31) */
95+
q15_t coeff; /* Smoothed cutoff: 0=DC, Q15_MAX=bypass */
96+
q15_t coeff_target; /* Target cutoff for smoothing */
9697
} picosynth_filter_t;
9798

9899
/* 3-input mixer state */
@@ -189,6 +190,9 @@ void picosynth_init_hp(picosynth_node_t *n,
189190
const q15_t *in,
190191
q15_t coeff);
191192

193+
/* Set filter cutoff with smoothing */
194+
void picosynth_filter_set_coeff(picosynth_node_t *n, q15_t coeff);
195+
192196
/* Initialize 3-input mixer node */
193197
void picosynth_init_mix(picosynth_node_t *n,
194198
const q15_t *gain,

src/picosynth.c

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ static void voice_note_on(picosynth_voice_t *v, uint8_t note)
4444
n->state = 0;
4545
n->out = 0;
4646
/* Reset filter accumulators to prevent DC offsets/pops */
47-
if (n->type == PICOSYNTH_NODE_LP || n->type == PICOSYNTH_NODE_HP)
47+
if (n->type == PICOSYNTH_NODE_LP || n->type == PICOSYNTH_NODE_HP) {
4848
n->flt.accum = 0;
49+
n->flt.coeff = n->flt.coeff_target;
50+
}
4951
/* Reset envelope block state to force immediate rate calculation */
5052
if (n->type == PICOSYNTH_NODE_ENV) {
5153
n->env.block_counter = 0;
@@ -325,6 +327,7 @@ void picosynth_init_lp(picosynth_node_t *n,
325327
n->type = PICOSYNTH_NODE_LP;
326328
n->flt.in = in;
327329
n->flt.coeff = coeff;
330+
n->flt.coeff_target = coeff;
328331
}
329332

330333
void picosynth_init_hp(picosynth_node_t *n,
@@ -337,6 +340,14 @@ void picosynth_init_hp(picosynth_node_t *n,
337340
n->type = PICOSYNTH_NODE_HP;
338341
n->flt.in = in;
339342
n->flt.coeff = coeff;
343+
n->flt.coeff_target = coeff;
344+
}
345+
346+
void picosynth_filter_set_coeff(picosynth_node_t *n, q15_t coeff)
347+
{
348+
if (!n || (n->type != PICOSYNTH_NODE_LP && n->type != PICOSYNTH_NODE_HP))
349+
return;
350+
n->flt.coeff_target = q15_sat(coeff);
340351
}
341352

342353
void picosynth_init_mix(picosynth_node_t *n,
@@ -504,6 +515,18 @@ q15_t picosynth_process(picosynth_t *s)
504515
}
505516
case PICOSYNTH_NODE_LP:
506517
case PICOSYNTH_NODE_HP: {
518+
/* Smooth cutoff changes to avoid zipper noise (~4ms time
519+
* constant: delta/256 per sample).
520+
*/
521+
int32_t coeff_delta =
522+
(int32_t) n->flt.coeff_target - n->flt.coeff;
523+
if (coeff_delta) {
524+
int32_t step = coeff_delta >> 8;
525+
if (step == 0)
526+
step = coeff_delta > 0 ? 1 : -1;
527+
n->flt.coeff = q15_sat((int32_t) n->flt.coeff + step);
528+
}
529+
507530
/* Single-pole filter accumulator update:
508531
* accum += (input - output)
509532
* where output is the filtered signal from the previous sample.

web/wasm.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,18 @@ void picosynth_wasm_set_filter_coeff(uint8_t voice, q15_t coeff)
182182
picosynth_params.v0_filter_coeff = coeff;
183183
else
184184
picosynth_params.v1_filter_coeff = coeff;
185-
reinit_voices();
185+
186+
if (!picosynth_instance)
187+
return;
188+
189+
picosynth_voice_t *v = picosynth_get_voice(picosynth_instance, voice);
190+
if (!v)
191+
return;
192+
193+
picosynth_node_t *flt = picosynth_voice_get_node(v, 0);
194+
if (flt &&
195+
(flt->type == PICOSYNTH_NODE_LP || flt->type == PICOSYNTH_NODE_HP))
196+
picosynth_filter_set_coeff(flt, coeff);
186197
}
187198

188199
EMSCRIPTEN_KEEPALIVE

0 commit comments

Comments
 (0)