33#include <stdlib.h>
44#include <string.h>
55
6+ #include "dsp-math.h"
67#include "picosynth.h"
7- #include "sinewave.h"
88
99/* Envelope state: bit 31 = decay mode, bits 0-30 = value */
1010#define ENVELOPE_STATE_MODE_BIT 0x80000000u
@@ -48,6 +48,14 @@ static void voice_note_on(picosynth_voice_t *v, uint8_t note)
4848 n -> flt .accum = 0 ;
4949 n -> flt .coeff = n -> flt .coeff_target ;
5050 }
51+ /* Reset SVF states */
52+ if (n -> type == PICOSYNTH_NODE_SVF_LP ||
53+ n -> type == PICOSYNTH_NODE_SVF_HP ||
54+ n -> type == PICOSYNTH_NODE_SVF_BP ) {
55+ n -> svf .lp = 0 ;
56+ n -> svf .bp = 0 ;
57+ n -> svf .f = n -> svf .f_target ;
58+ }
5159 /* Reset envelope block state to force immediate rate calculation */
5260 if (n -> type == PICOSYNTH_NODE_ENV ) {
5361 n -> env .block_counter = 0 ;
@@ -86,7 +94,7 @@ static int ptr_to_node_idx(picosynth_voice_t *v, const q15_t *ptr)
8694}
8795
8896/* Recursively mark node and all its dependencies as used.
89- * NOTE : mask is 8-bit, so only nodes 0-7 can be tracked. For voices with >8
97+ * Note : mask is 8-bit, so only nodes 0-7 can be tracked. For voices with >8
9098 * nodes, the mask optimization is disabled (mask stays 0, all nodes processed).
9199 */
92100static void mark_node_used (picosynth_voice_t * v , int idx )
@@ -129,6 +137,13 @@ static void mark_node_used(picosynth_voice_t *v, int idx)
129137 if (dep >= 0 )
130138 mark_node_used (v , dep );
131139 break ;
140+ case PICOSYNTH_NODE_SVF_LP :
141+ case PICOSYNTH_NODE_SVF_HP :
142+ case PICOSYNTH_NODE_SVF_BP :
143+ dep = ptr_to_node_idx (v , n -> svf .in );
144+ if (dep >= 0 )
145+ mark_node_used (v , dep );
146+ break ;
132147 case PICOSYNTH_NODE_MIX :
133148 for (int j = 0 ; j < 3 ; j ++ ) {
134149 dep = ptr_to_node_idx (v , n -> mix .in [j ]);
@@ -443,6 +458,106 @@ void picosynth_filter_set_coeff(picosynth_node_t *n, q15_t coeff)
443458 n -> flt .coeff_target = q15_sat (coeff );
444459}
445460
461+ q15_t picosynth_svf_freq (uint16_t fc_hz )
462+ {
463+ /* f = 2 * sin(pi * fc / fs)
464+ * For stability, fc should be < fs/4 (Nyquist/2)
465+ * Map fc_hz to table index: idx = fc_hz * 64 / SAMPLE_RATE
466+ */
467+ if (fc_hz == 0 )
468+ return 0 ;
469+
470+ /* Clamp to safe range (fs/4 max for stability) */
471+ uint32_t max_fc = SAMPLE_RATE / 4 ;
472+ if (fc_hz > max_fc )
473+ fc_hz = (uint16_t ) max_fc ;
474+
475+ /* Calculate table index with interpolation
476+ * idx = fc * 64 / fs, but we use fc * 64 * 256 / fs for fractional part
477+ */
478+ uint32_t scaled = ((uint32_t ) fc_hz * 64 * 256 ) / SAMPLE_RATE ;
479+ uint8_t idx = (uint8_t ) (scaled >> 8 );
480+ uint8_t frac = (uint8_t ) (scaled & 0xFF );
481+
482+ if (idx >= 32 ) {
483+ idx = 32 ;
484+ frac = 0 ;
485+ }
486+
487+ /* Linear interpolation between table entries */
488+ int32_t s0 = svf_sin_table [idx ];
489+ int32_t s1 = svf_sin_table [idx + 1 ];
490+ int32_t sin_val = s0 + (((s1 - s0 ) * frac ) >> 8 );
491+
492+ /* f = 2 * sin(...), but we limit to avoid instability */
493+ int32_t f = sin_val * 2 ;
494+ if (f > Q15_MAX )
495+ f = Q15_MAX ;
496+
497+ return (q15_t ) f ;
498+ }
499+
500+ void picosynth_init_svf_lp (picosynth_node_t * n ,
501+ const q15_t * gain ,
502+ const q15_t * in ,
503+ q15_t f_coeff ,
504+ q15_t q )
505+ {
506+ memset (n , 0 , sizeof (picosynth_node_t ));
507+ n -> gain = gain ;
508+ n -> type = PICOSYNTH_NODE_SVF_LP ;
509+ n -> svf .in = in ;
510+ n -> svf .f = f_coeff ;
511+ n -> svf .f_target = f_coeff ;
512+ n -> svf .q = q ;
513+ n -> svf .lp = 0 ;
514+ n -> svf .bp = 0 ;
515+ }
516+
517+ void picosynth_init_svf_hp (picosynth_node_t * n ,
518+ const q15_t * gain ,
519+ const q15_t * in ,
520+ q15_t f_coeff ,
521+ q15_t q )
522+ {
523+ memset (n , 0 , sizeof (picosynth_node_t ));
524+ n -> gain = gain ;
525+ n -> type = PICOSYNTH_NODE_SVF_HP ;
526+ n -> svf .in = in ;
527+ n -> svf .f = f_coeff ;
528+ n -> svf .f_target = f_coeff ;
529+ n -> svf .q = q ;
530+ n -> svf .lp = 0 ;
531+ n -> svf .bp = 0 ;
532+ }
533+
534+ void picosynth_init_svf_bp (picosynth_node_t * n ,
535+ const q15_t * gain ,
536+ const q15_t * in ,
537+ q15_t f_coeff ,
538+ q15_t q )
539+ {
540+ memset (n , 0 , sizeof (picosynth_node_t ));
541+ n -> gain = gain ;
542+ n -> type = PICOSYNTH_NODE_SVF_BP ;
543+ n -> svf .in = in ;
544+ n -> svf .f = f_coeff ;
545+ n -> svf .f_target = f_coeff ;
546+ n -> svf .q = q ;
547+ n -> svf .lp = 0 ;
548+ n -> svf .bp = 0 ;
549+ }
550+
551+ void picosynth_svf_set_freq (picosynth_node_t * n , q15_t f_coeff )
552+ {
553+ if (!n )
554+ return ;
555+ if (n -> type != PICOSYNTH_NODE_SVF_LP && n -> type != PICOSYNTH_NODE_SVF_HP &&
556+ n -> type != PICOSYNTH_NODE_SVF_BP )
557+ return ;
558+ n -> svf .f_target = f_coeff ;
559+ }
560+
446561void picosynth_init_mix (picosynth_node_t * n ,
447562 const q15_t * gain ,
448563 const q15_t * in1 ,
@@ -530,6 +645,30 @@ q15_t picosynth_process(picosynth_t *s)
530645 tmp [i ] = 0 ;
531646 }
532647 break ;
648+ case PICOSYNTH_NODE_SVF_LP :
649+ /* SVF low-pass output: scaled down from internal precision */
650+ tmp [i ] = n -> svf .lp >> 8 ;
651+ break ;
652+ case PICOSYNTH_NODE_SVF_HP : {
653+ /* SVF high-pass: hp = in - lp - q*bp
654+ * Computed at internal <<8 scale for consistency with state,
655+ * then scaled down. Uses int64_t to prevent overflow.
656+ */
657+ int64_t in_val = n -> svf .in ? ((int32_t ) * n -> svf .in ) << 8 : 0 ;
658+ int64_t q_bp = ((int64_t ) n -> svf .bp * n -> svf .q ) >> 15 ;
659+ int64_t hp = in_val - n -> svf .lp - q_bp ;
660+ /* Clamp to int32_t range before scaling */
661+ if (hp > 0x7FFFFFFF )
662+ hp = 0x7FFFFFFF ;
663+ else if (hp < (-0x7FFFFFFF - 1 ))
664+ hp = (-0x7FFFFFFF - 1 );
665+ tmp [i ] = (int32_t ) (hp >> 8 );
666+ break ;
667+ }
668+ case PICOSYNTH_NODE_SVF_BP :
669+ /* SVF band-pass output */
670+ tmp [i ] = n -> svf .bp >> 8 ;
671+ break ;
533672 case PICOSYNTH_NODE_MIX : {
534673 int32_t sum = 0 ;
535674 for (int j = 0 ; j < 3 ; j ++ )
@@ -623,8 +762,8 @@ q15_t picosynth_process(picosynth_t *s)
623762 }
624763 case PICOSYNTH_NODE_LP :
625764 case PICOSYNTH_NODE_HP : {
626- /* Smooth cutoff changes to avoid zipper noise (~4ms time
627- * constant: delta/ 256 per sample ).
765+ /* Smooth cutoff changes to avoid zipper noise.
766+ * Time constant: ~ 256 samples (~23ms @ 11kHz, ~6ms @ 44kHz ).
628767 */
629768 int32_t coeff_delta =
630769 (int32_t ) n -> flt .coeff_target - n -> flt .coeff ;
@@ -650,6 +789,60 @@ q15_t picosynth_process(picosynth_t *s)
650789 n -> flt .accum = (int32_t ) acc ;
651790 break ;
652791 }
792+ case PICOSYNTH_NODE_SVF_LP :
793+ case PICOSYNTH_NODE_SVF_HP :
794+ case PICOSYNTH_NODE_SVF_BP : {
795+ /* Smooth frequency changes to avoid zipper noise */
796+ int32_t f_delta = (int32_t ) n -> svf .f_target - n -> svf .f ;
797+ if (f_delta ) {
798+ int32_t step = f_delta >> 8 ;
799+ if (step == 0 )
800+ step = f_delta > 0 ? 1 : -1 ;
801+ n -> svf .f = q15_sat ((int32_t ) n -> svf .f + step );
802+ }
803+
804+ /* State Variable Filter update:
805+ * hp = in - lp - q*bp
806+ * lp_new = lp + f*bp
807+ * bp_new = bp + f*hp
808+ *
809+ * States stored with <<8 scaling for precision.
810+ */
811+ int32_t in_val = n -> svf .in ? ((int32_t ) * n -> svf .in ) << 8 : 0 ;
812+ int32_t lp = n -> svf .lp ;
813+ int32_t bp = n -> svf .bp ;
814+
815+ /* Compute high-pass: hp = in - lp - q*bp
816+ * Uses int64_t to prevent overflow with large <<8 scaled
817+ * values.
818+ */
819+ int64_t q_bp = ((int64_t ) bp * n -> svf .q ) >> 15 ;
820+ int64_t hp64 = (int64_t ) in_val - lp - q_bp ;
821+ if (hp64 > 0x7FFFFFFF )
822+ hp64 = 0x7FFFFFFF ;
823+ else if (hp64 < (-0x7FFFFFFF - 1 ))
824+ hp64 = (-0x7FFFFFFF - 1 );
825+ int32_t hp = (int32_t ) hp64 ;
826+
827+ /* Update low-pass: lp += f*bp */
828+ int32_t f_bp = (int32_t ) (((int64_t ) bp * n -> svf .f ) >> 15 );
829+ int64_t lp_new = (int64_t ) lp + f_bp ;
830+ if (lp_new > 0x7FFFFFFF )
831+ lp_new = 0x7FFFFFFF ;
832+ else if (lp_new < (-0x7FFFFFFF - 1 ))
833+ lp_new = (-0x7FFFFFFF - 1 );
834+ n -> svf .lp = (int32_t ) lp_new ;
835+
836+ /* Update band-pass: bp += f*hp */
837+ int32_t f_hp = (int32_t ) (((int64_t ) hp * n -> svf .f ) >> 15 );
838+ int64_t bp_new = (int64_t ) bp + f_hp ;
839+ if (bp_new > 0x7FFFFFFF )
840+ bp_new = 0x7FFFFFFF ;
841+ else if (bp_new < (-0x7FFFFFFF - 1 ))
842+ bp_new = (-0x7FFFFFFF - 1 );
843+ n -> svf .bp = (int32_t ) bp_new ;
844+ break ;
845+ }
653846 default :
654847 break ;
655848 }
0 commit comments