1
0
mirror of https://github.com/sjlongland/atinysynth.git synced 2025-09-13 10:03:15 +10:00

- Fixed fractional frequency

This commit is contained in:
Luciano Martorella 2021-05-08 15:12:32 +02:00
parent 8381065e5b
commit b6bdd2557c
4 changed files with 26 additions and 39 deletions

View File

@ -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:

16
synth.h
View File

@ -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
*/

View File

@ -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,

View File

@ -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;