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:
parent
8381065e5b
commit
b6bdd2557c
@ -157,9 +157,11 @@ wish to use the ADSR envelope generator only to modulate lights.
|
|||||||
Configures the waveform generator to generate a square wave.
|
Configures the waveform generator to generate a square wave.
|
||||||
|
|
||||||
`sample` is initialised as `+amplitude`, and the half-period is
|
`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`.
|
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`
|
Each sample, `period_remain` is decremented. When `period_remain`
|
||||||
reaches zero:
|
reaches zero:
|
||||||
|
|
||||||
|
16
synth.h
16
synth.h
@ -41,22 +41,6 @@ extern const uint16_t __attribute__((weak)) synth_freq;
|
|||||||
#define synth_freq SYNTH_FREQ
|
#define synth_freq SYNTH_FREQ
|
||||||
#endif
|
#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
|
* Polyphonic synthesizer structure
|
||||||
*/
|
*/
|
||||||
|
41
waveform.c
41
waveform.c
@ -33,6 +33,14 @@
|
|||||||
/* Amplitude scaling */
|
/* Amplitude scaling */
|
||||||
#define VOICE_WF_AMP_SCALE (8)
|
#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) {
|
int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) {
|
||||||
switch(wf_gen->mode) {
|
switch(wf_gen->mode) {
|
||||||
case VOICE_MODE_DC:
|
case VOICE_MODE_DC:
|
||||||
@ -48,12 +56,12 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) {
|
|||||||
wf_gen->sample);
|
wf_gen->sample);
|
||||||
break;
|
break;
|
||||||
case VOICE_MODE_SQUARE:
|
case VOICE_MODE_SQUARE:
|
||||||
if (!wf_gen->period_remain) {
|
if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) {
|
||||||
/* Swap value */
|
/* Swap value */
|
||||||
wf_gen->sample = -wf_gen->sample;
|
wf_gen->sample = -wf_gen->sample;
|
||||||
wf_gen->period_remain = wf_gen->period;
|
wf_gen->period_remain += wf_gen->period;
|
||||||
} else {
|
} else {
|
||||||
wf_gen->period_remain--;
|
wf_gen->period_remain -= (1 << PERIOD_FP_SCALE);
|
||||||
}
|
}
|
||||||
_DPRINTF("wf=%p mode=SQUARE amp=%d rem=%d "
|
_DPRINTF("wf=%p mode=SQUARE amp=%d rem=%d "
|
||||||
"→ sample=%d\n",
|
"→ sample=%d\n",
|
||||||
@ -62,13 +70,13 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) {
|
|||||||
wf_gen->sample);
|
wf_gen->sample);
|
||||||
break;
|
break;
|
||||||
case VOICE_MODE_SAWTOOTH:
|
case VOICE_MODE_SAWTOOTH:
|
||||||
if (!wf_gen->period_remain) {
|
if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) {
|
||||||
/* Back to -amplitude */
|
/* Back to -amplitude */
|
||||||
wf_gen->sample = -wf_gen->amplitude;
|
wf_gen->sample = -wf_gen->amplitude;
|
||||||
wf_gen->period_remain = wf_gen->period;
|
wf_gen->period_remain += wf_gen->period;
|
||||||
} else {
|
} else {
|
||||||
wf_gen->sample += wf_gen->step;
|
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 "
|
_DPRINTF("wf=%p mode=SAWTOOTH amp=%d rem=%d step=%d "
|
||||||
"→ sample=%d\n",
|
"→ sample=%d\n",
|
||||||
@ -77,17 +85,17 @@ int8_t voice_wf_next(struct voice_wf_gen_t* const wf_gen) {
|
|||||||
wf_gen->sample);
|
wf_gen->sample);
|
||||||
break;
|
break;
|
||||||
case VOICE_MODE_TRIANGLE:
|
case VOICE_MODE_TRIANGLE:
|
||||||
if (!wf_gen->period_remain) {
|
if ((wf_gen->period_remain >> PERIOD_FP_SCALE) == 0) {
|
||||||
/* Switch direction */
|
/* Switch direction */
|
||||||
if (wf_gen->step > 0)
|
if (wf_gen->step > 0)
|
||||||
wf_gen->sample = wf_gen->amplitude;
|
wf_gen->sample = wf_gen->amplitude;
|
||||||
else
|
else
|
||||||
wf_gen->sample = -wf_gen->amplitude;
|
wf_gen->sample = -wf_gen->amplitude;
|
||||||
wf_gen->step = -wf_gen->step;
|
wf_gen->step = -wf_gen->step;
|
||||||
wf_gen->period_remain = wf_gen->period;
|
wf_gen->period_remain += wf_gen->period;
|
||||||
} else {
|
} else {
|
||||||
wf_gen->sample += wf_gen->step;
|
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 "
|
_DPRINTF("wf=%p mode=TRIANGLE amp=%d rem=%d step=%d "
|
||||||
"→ sample=%d\n",
|
"→ 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) */
|
/* Compute frequency period (sawtooth wave) */
|
||||||
static uint16_t voice_wf_calc_sawtooth_period(uint16_t freq) {
|
static uint16_t voice_wf_calc_sawtooth_period(uint16_t freq) {
|
||||||
#if SYNTH_FREQ_SCALE > 0
|
/* Use 16-bit 12.4 fixed point */
|
||||||
/*
|
return (((uint32_t)(synth_freq << PERIOD_FP_SCALE)) / freq);
|
||||||
* 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compute frequency period (square/triangle wave) */
|
/* 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 = voice_wf_calc_sawtooth_period(freq);
|
||||||
wf_gen->period_remain = wf_gen->period;
|
wf_gen->period_remain = wf_gen->period;
|
||||||
wf_gen->amplitude = -wf_gen->sample;
|
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 "
|
_DPRINTF("wf=%p INIT mode=SAWTOOTH amp=%d per=%d step=%d rem=%d "
|
||||||
"→ sample=%d\n",
|
"→ sample=%d\n",
|
||||||
wf_gen, wf_gen->amplitude, wf_gen->period,
|
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 = voice_wf_calc_square_period(freq);
|
||||||
wf_gen->period_remain = wf_gen->period;
|
wf_gen->period_remain = wf_gen->period;
|
||||||
wf_gen->amplitude = -wf_gen->sample;
|
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 "
|
_DPRINTF("wf=%p INIT mode=TRIANGLE amp=%d per=%d step=%d rem=%d "
|
||||||
"→ sample=%d\n",
|
"→ sample=%d\n",
|
||||||
wf_gen, wf_gen->amplitude, wf_gen->period,
|
wf_gen, wf_gen->amplitude, wf_gen->period,
|
||||||
|
@ -29,10 +29,10 @@ struct voice_wf_gen_t {
|
|||||||
int16_t sample;
|
int16_t sample;
|
||||||
/*! Amplitude sample in fixed point */
|
/*! Amplitude sample in fixed point */
|
||||||
int16_t amplitude;
|
int16_t amplitude;
|
||||||
/*! Samples to next waveform period */
|
/*! Samples to next waveform period (12.4 fixed point) */
|
||||||
uint16_t period_remain;
|
uint16_t period_remain;
|
||||||
/*!
|
/*!
|
||||||
* Period duration in samples.
|
* Period duration in samples (12.4 fixed point).
|
||||||
* (Half period for SQUARE and TRIANGLE)
|
* (Half period for SQUARE and TRIANGLE)
|
||||||
*/
|
*/
|
||||||
uint16_t period;
|
uint16_t period;
|
||||||
|
Loading…
Reference in New Issue
Block a user