diff --git a/README.md b/README.md index 5afdb5c..b6bdc1a 100644 --- a/README.md +++ b/README.md @@ -157,9 +157,11 @@ wish to use the ADSR envelope generator only to modulate lights. Configures the waveform generator to generate a square wave. `sample` is initialised as `+amplitude`, and the half-period is -computed as `period=SYNTH_FREQ/(2*freq)`. `period_remain` is +computed as `period=SYNTH_FREQ/(2*freq)`. `period_remain` is initialised to `period`. +Fixed-point `12.4` format (16 bit) is used to store the period counters, to allow tuned notes on low sampling rates too. + Each sample, `period_remain` is decremented. When `period_remain` reaches zero: diff --git a/synth.h b/synth.h index 11420e2..c3c37d2 100644 --- a/synth.h +++ b/synth.h @@ -41,22 +41,6 @@ extern const uint16_t __attribute__((weak)) synth_freq; #define synth_freq SYNTH_FREQ #endif -#ifdef SYNTH_FREQ_SCALE -/*! - * Amount of scaling to apply to synthesizer output frequencies. This - * right-shifts frequencies, allowing for some bits to be used to indicate - * fractional components, trading off frequency range for precision. - * - * Precision is 1/(2^SYNTH_FREQ_SCALE) Hz. - * Range is 0-((65536/(2^SYNTH_FREQ_SCALE))-1). - * - * The default of 0 applies no scaling, allowing (integer) frequencies between - * 0 and 65.535 kHz. The value 1 multiplies everything by two, allowing - * frequencies between 0 and 32.767kHz with 500mHz precision. - */ -#define SYNTH_FREQ_SCALE (0) -#endif - /*! * Polyphonic synthesizer structure */ diff --git a/waveform.c b/waveform.c index 27887e3..57bf076 100644 --- a/waveform.c +++ b/waveform.c @@ -33,6 +33,14 @@ /* Amplitude scaling */ #define VOICE_WF_AMP_SCALE (8) +/*! + * Number of fractional bits for `period` and `period_remain`. + * This allows tuned notes even in lower sampling frequencies. + * The integer part (12 bits) is wide enough to render a 20Hz + * note on the higher 48kHz sampling frequency. + */ +#define PERIOD_FP_SCALE (4) + int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) { switch(wf_gen->mode) { case VOICE_MODE_DC: @@ -48,12 +56,12 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) { wf_gen->sample); break; case VOICE_MODE_SQUARE: - if (!wf_gen->period_remain) { + if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) { /* Swap value */ wf_gen->sample = -wf_gen->sample; - wf_gen->period_remain = wf_gen->period; + wf_gen->period_remain += wf_gen->period; } else { - wf_gen->period_remain--; + wf_gen->period_remain -= (1 << PERIOD_FP_SCALE); } _DPRINTF("wf=%p mode=SQUARE amp=%d rem=%d " "→ sample=%d\n", @@ -62,13 +70,13 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) { wf_gen->sample); break; case VOICE_MODE_SAWTOOTH: - if (!wf_gen->period_remain) { + if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) { /* Back to -amplitude */ wf_gen->sample = -wf_gen->amplitude; - wf_gen->period_remain = wf_gen->period; + wf_gen->period_remain += wf_gen->period; } else { wf_gen->sample += wf_gen->step; - wf_gen->period_remain--; + wf_gen->period_remain -= (1 << PERIOD_FP_SCALE); } _DPRINTF("wf=%p mode=SAWTOOTH amp=%d rem=%d step=%d " "→ sample=%d\n", @@ -77,17 +85,17 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) { wf_gen->sample); break; case VOICE_MODE_TRIANGLE: - if (!wf_gen->period_remain) { + if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) { /* Switch direction */ if (wf_gen->step > 0) wf_gen->sample = wf_gen->amplitude; else wf_gen->sample = -wf_gen->amplitude; wf_gen->step = -wf_gen->step; - wf_gen->period_remain = wf_gen->period; + wf_gen->period_remain += wf_gen->period; } else { wf_gen->sample += wf_gen->step; - wf_gen->period_remain--; + wf_gen->period_remain -= (1 << PERIOD_FP_SCALE); } _DPRINTF("wf=%p mode=TRIANGLE amp=%d rem=%d step=%d " "→ sample=%d\n", @@ -102,15 +110,8 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) { /* Compute frequency period (sawtooth wave) */ static uint16_t voice_wf_calc_sawtooth_period(uint16_t freq) { -#if SYNTH_FREQ_SCALE > 0 - /* - * Cast synth_freq to 32-bits in case we overflow in the right-shift. - * Yes, this is going to hurt! - */ - return (((uint32_t)(synth_freq << SYNTH_FREQ_SCALE)) / freq); -#else - return (synth_freq / freq); -#endif + /* Use 16-bit 12.4 fixed point */ + return (((uint32_t)(synth_freq << PERIOD_FP_SCALE)) / freq); } /* Compute frequency period (square/triangle wave) */ @@ -146,7 +147,7 @@ void voice_wf_set_sawtooth(struct voice_wf_gen_t* const wf_gen, wf_gen->period = voice_wf_calc_sawtooth_period(freq); wf_gen->period_remain = wf_gen->period; wf_gen->amplitude = -wf_gen->sample; - wf_gen->step = ((int32_t)(wf_gen->amplitude << 1)) / wf_gen->period; + wf_gen->step = (wf_gen->amplitude / (wf_gen->period >> PERIOD_FP_SCALE)) << 1; _DPRINTF("wf=%p INIT mode=SAWTOOTH amp=%d per=%d step=%d rem=%d " "→ sample=%d\n", wf_gen, wf_gen->amplitude, wf_gen->period, @@ -161,7 +162,7 @@ void voice_wf_set_triangle(struct voice_wf_gen_t* const wf_gen, wf_gen->period = voice_wf_calc_square_period(freq); wf_gen->period_remain = wf_gen->period; wf_gen->amplitude = -wf_gen->sample; - wf_gen->step = ((int32_t)(wf_gen->amplitude << 1)) / wf_gen->period; + wf_gen->step = (wf_gen->amplitude / (wf_gen->period >> PERIOD_FP_SCALE)) << 1; _DPRINTF("wf=%p INIT mode=TRIANGLE amp=%d per=%d step=%d rem=%d " "→ sample=%d\n", wf_gen, wf_gen->amplitude, wf_gen->period, diff --git a/waveform.h b/waveform.h index 177ab9d..5c6757b 100644 --- a/waveform.h +++ b/waveform.h @@ -29,10 +29,10 @@ struct voice_wf_gen_t { int16_t sample; /*! Amplitude sample in fixed point */ int16_t amplitude; - /*! Samples to next waveform period */ + /*! Samples to next waveform period (12.4 fixed point) */ uint16_t period_remain; /*! - * Period duration in samples. + * Period duration in samples (12.4 fixed point). * (Half period for SQUARE and TRIANGLE) */ uint16_t period;