// BandLimitedWaveform
class BandLimitedWaveform
{
public:
BandLimitedWaveform (void) ;
int16_t generate_sawtooth (uint32_t new_phase, int i) ;
int16_t generate_square (uint32_t new_phase, int i) ;
int16_t generate_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
void init_sawtooth (uint32_t freq_word) ;
void init_square (uint32_t freq_word) ;
void init_pulse (uint32_t freq_word, uint32_t pulse_width) ;
private:
int32_t lookup (int offset) ;
void insert_step (int offset, bool rising, int i) ;
int32_t process_step (int i) ;
int32_t process_active_steps (uint32_t new_phase) ;
int32_t process_active_steps_saw (uint32_t new_phase) ;
int32_t process_active_steps_pulse (uint32_t new_phase, uint32_t pulse_width) ;
void new_step_check_square (uint32_t new_phase, int i) ;
void new_step_check_pulse (uint32_t new_phase, uint32_t pulse_width, int i) ;
void new_step_check_saw (uint32_t new_phase, int i) ;
uint32_t phase_word ;
int32_t dc_offset ;
step_state states [32] ; // circular buffer of active steps
int newptr ; // buffer pointers into states, AND'd with PTRMASK to keep in buffer range.
int delptr ;
int32_t cyclic[16] ; // circular buffer of output samples
bool pulse_state ;
uint32_t sampled_width ; // pulse width is sampled once per waveform
};
#define SUPPORT_SHIFT 4
#define SUPPORT (1 << SUPPORT_SHIFT)
#define PTRMASK ((2 << SUPPORT_SHIFT) - 1)
#define SCALE 16
#define SCALE_MASK (SCALE-1)
#define N (SCALE * SUPPORT * 2)
#define GUARD_BITS 8
#define GUARD (1 << GUARD_BITS)
#define HALF_GUARD (1 << (GUARD_BITS-1))
#define DEG180 0x80000000u
#define PHASE_SCALE (0x100000000L / (2 * BASE_AMPLITUDE))
extern "C"
{
extern const int16_t step_table [258] ;
}
int32_t BandLimitedWaveform::lookup (int offset)
{
int off = offset >> GUARD_BITS ;
int frac = offset & (GUARD-1) ;
int32_t a, b ;
if (off < N/2) // handle odd symmetry by reflecting table
{
a = step_table [off+1] ;
b = step_table [off+2] ;
}
else
{
a = - step_table [N-off] ;
b = - step_table [N-off-1] ;
}
return BASE_AMPLITUDE + ((frac * b + (GUARD - frac) * a + HALF_GUARD) >> GUARD_BITS) ; // interpolated
}
// create a new step, apply its past waveform into the cyclic sample buffer
// and add a step_state object into active list so it can be added for the future samples
void BandLimitedWaveform::insert_step (int offset, bool rising, int i)
{
while (offset <= (N/2-SCALE)<<GUARD_BITS)
{
if (offset >= 0)
cyclic [i & 15] += rising ? lookup (offset) : -lookup (offset) ;
offset += SCALE<<GUARD_BITS ;
i ++ ;
}
states[newptr].offset = offset ;
states[newptr].positive = rising ;
newptr = (newptr+1) & PTRMASK ;
}
// generate value for current sample from one active step, checking for the
// dc_offset adjustment at the end of the table.
int32_t BandLimitedWaveform::process_step (int i)
{
int off = states[i].offset ;
bool positive = states[i].positive ;
int32_t entry = lookup (off) ;
off += SCALE<<GUARD_BITS ;
states[i].offset = off ; // update offset in table for next sample
if (off >= N<<GUARD_BITS) // at end of step table we alter dc_offset to extend the step into future
dc_offset += positive ? 2*BASE_AMPLITUDE : -2*BASE_AMPLITUDE ;
return positive ? entry : -entry ;
}
// process all active steps for current sample, basically generating the waveform portion
// due only to steps
// square waves use this directly.
int32_t BandLimitedWaveform::process_active_steps (uint32_t new_phase)
{
int32_t sample = dc_offset ;
int step_count = (newptr - delptr) & PTRMASK ;
if (step_count > 0) // for any steps in-flight we sum in table entry and update its state
{
int i = newptr ;
do
{
i = (i-1) & PTRMASK ;
sample += process_step (i) ;
} while (i != delptr) ;
if (states[delptr].offset >= N<<GUARD_BITS) // remove any finished entries from the buffer.
{
delptr = (delptr+1) & PTRMASK ;
// can be upto two steps per sample now for pulses
if (newptr != delptr && states[delptr].offset >= N<<GUARD_BITS)
delptr = (delptr+1) & PTRMASK ;
}
}
return sample ;
}
// for sawtooth need to add in the slope and compensate for all the steps being one way
int32_t BandLimitedWaveform::process_active_steps_saw (uint32_t new_phase)
{
int32_t sample = process_active_steps (new_phase) ;
sample += (int16_t) ((((uint64_t)phase_word * (2*BASE_AMPLITUDE)) >> 32) - BASE_AMPLITUDE) ; // generate the sloped part of the wave
if (new_phase < DEG180 && phase_word >= DEG180) // detect wrap around, correct dc offset
dc_offset += 2*BASE_AMPLITUDE ;
return sample ;
}
// for pulse need to adjust the baseline according to the pulse width to cancel the DC component.
int32_t BandLimitedWaveform::process_active_steps_pulse (uint32_t new_phase, uint32_t pulse_width)
{
int32_t sample = process_active_steps (new_phase) ;
return sample + BASE_AMPLITUDE/2 - pulse_width / (0x80000000u / BASE_AMPLITUDE) ; // correct DC offset for duty cycle
}
// Check for new steps using the phase update for the current sample for a square wave
void BandLimitedWaveform::new_step_check_square (uint32_t new_phase, int i)
{
if (new_phase >= DEG180 && phase_word < DEG180) // detect falling step
{
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
if (pulse_state) // guard against two falling steps in a row (if pulse width changing for instance)
{
insert_step (- offset, false, i) ;
pulse_state = false ;
}
}
else if (new_phase < DEG180 && phase_word >= DEG180) // detect wrap around, rising step
{
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (- phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
if (!pulse_state) // guard against two rising steps in a row (if pulse width changing for instance)
{
insert_step (- offset, true, i) ;
pulse_state = true ;
}
}
}
// Checking for new steps for pulse waveform has to deal with changing frequency and pulse width and
// not letting a pulse glitch out of existence as these change across a single period of the waveform
// now we detect the rising edge just like for a square wave and use that to sample the pulse width
// parameter, which then has to be checked against the instantaneous frequency every sample.
void BandLimitedWaveform::new_step_check_pulse (uint32_t new_phase, uint32_t pulse_width, int i)
{
if (pulse_state && phase_word < sampled_width && (new_phase >= sampled_width || new_phase < phase_word)) // falling edge
{
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
insert_step (- offset, false, i) ;
pulse_state = false ;
}
if ((!pulse_state) && phase_word >= DEG180 && new_phase < DEG180) // detect wrap around, rising step
{
// sample the pulse width value so its not changing under our feet later in cycle due to modulation
sampled_width = pulse_width ;
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (- phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
insert_step (- offset, true, i) ;
pulse_state = true ;
if (pulse_state && new_phase >= sampled_width) // detect falling step directly after a rising edge
//if (new_phase - sampled_width < DEG180) // detect falling step directly after a rising edge
{
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (sampled_width - phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
insert_step (- offset, false, i) ;
pulse_state = false ;
}
}
}
// new steps for sawtooth are at 180 degree point, always falling.
void BandLimitedWaveform::new_step_check_saw (uint32_t new_phase, int i)
{
if (new_phase >= DEG180 && phase_word < DEG180) // detect falling step
{
int32_t offset = (int32_t) ((uint64_t) (SCALE<<GUARD_BITS) * (DEG180 - phase_word) / (new_phase - phase_word)) ;
if (offset == SCALE<<GUARD_BITS)
offset -- ;
insert_step (- offset, false, i) ;
}
}
// the generation function pushd new sample into cyclic buffer, having taken out the oldest entry
// to return. The output is thus 16 samples behind, which allows the non-casual step function to
// work in real time.
int16_t BandLimitedWaveform::generate_sawtooth (uint32_t new_phase, int i)
{
new_step_check_saw (new_phase, i) ;
int32_t val = process_active_steps_saw (new_phase) ;
int16_t sample = (int16_t) cyclic [i&15] ;
cyclic [i&15] = val ;
phase_word = new_phase ;
return sample ;
}
int16_t BandLimitedWaveform::generate_square (uint32_t new_phase, int i)
{
new_step_check_square (new_phase, i) ;
int32_t val = process_active_steps (new_phase) ;
int16_t sample = (int16_t) cyclic [i&15] ;
cyclic [i&15] = val ;
phase_word = new_phase ;
return sample ;
}
int16_t BandLimitedWaveform::generate_pulse (uint32_t new_phase, uint32_t pulse_width, int i)
{
new_step_check_pulse (new_phase, pulse_width, i) ;
int32_t val = process_active_steps_pulse (new_phase, pulse_width) ;
int32_t sample = cyclic [i&15] ;
cyclic [i&15] = val ;
phase_word = new_phase ;
return (int16_t) ((sample >> 1) - (sample >> 5)) ; // scale down to avoid overflow on narrow pulses, where the DC shift is big
}
void BandLimitedWaveform::init_sawtooth (uint32_t freq_word)
{
phase_word = 0 ;
newptr = 0 ;
delptr = 0 ;
for (int i = 0 ; i < 2*SUPPORT ; i++)
phase_word -= freq_word ;
dc_offset = phase_word < DEG180 ? BASE_AMPLITUDE : -BASE_AMPLITUDE ;
for (int i = 0 ; i < 2*SUPPORT ; i++)
{
uint32_t new_phase = phase_word + freq_word ;
new_step_check_saw (new_phase, i) ;
cyclic [i & 15] = (int16_t) process_active_steps_saw (new_phase) ;
phase_word = new_phase ;
}
}
void BandLimitedWaveform::init_square (uint32_t freq_word)
{
init_pulse (freq_word, DEG180) ;
}
void BandLimitedWaveform::init_pulse (uint32_t freq_word, uint32_t pulse_width)
{
phase_word = 0 ;
sampled_width = pulse_width ;
newptr = 0 ;
delptr = 0 ;
for (int i = 0 ; i < 2*SUPPORT ; i++)
phase_word -= freq_word ;
if (phase_word < pulse_width)
{
dc_offset = BASE_AMPLITUDE ;
pulse_state = true ;
}
else
{
dc_offset = -BASE_AMPLITUDE ;
pulse_state = false ;
}
for (int i = 0 ; i < 2*SUPPORT ; i++)
{
uint32_t new_phase = phase_word + freq_word ;
new_step_check_pulse (new_phase, pulse_width, i) ;
cyclic [i & 15] = (int16_t) process_active_steps_pulse (new_phase, pulse_width) ;
phase_word = new_phase ;
}
}
BandLimitedWaveform::BandLimitedWaveform()
{
newptr = 0 ;
delptr = 0 ;
dc_offset = BASE_AMPLITUDE ;
phase_word = 0 ;
}