diff --git a/synth_sine_f32.cpp b/synth_sine_f32.cpp index 88736a2..816a4f3 100644 --- a/synth_sine_f32.cpp +++ b/synth_sine_f32.cpp @@ -7,52 +7,36 @@ * Purpose: Create sine wave of given amplitude and frequency * * License: MIT License. Use at your own risk. - * + * + * Revised per synth_sine_f32.h. 7 Feb 2022 Bob. */ - #include "synth_sine_f32.h" #include "utility/dspinst.h" +// 513 values of the sine wave in a float array: +#include "sinTable512_f32.h" -// data_waveforms.c -extern "C" { - extern const int16_t AudioWaveformSine[257]; -} - -void AudioSynthWaveformSine_F32::update(void) { - audio_block_f32_t *block; - uint32_t i, ph, inc, index, scale; - int32_t val1, val2; - static uint32_t block_length = 0; +void AudioSynthWaveformSine_F32::update(void) { + audio_block_f32_t *blockS; + uint16_t index, i; + float32_t a, b; - if (enabled) { - if (magnitude) { - block = allocate_f32(); - if (block) { - block_length = (uint32_t)block->length; - ph = phase_accumulator; - inc = phase_increment; - for (i=0; i < block_length; i++) { - index = ph >> 24; - val1 = AudioWaveformSine[index]; - val2 = AudioWaveformSine[index+1]; - scale = (ph >> 8) & 0xFFFF; - val2 *= scale; - val1 *= 0x10000 - scale; -#if (defined(KINETISK) || defined(__IMXRT1062__) ) - block->data[i] = (float) multiply_32x32_rshift32(val1 + val2, magnitude); -#elif defined(KINETISL) - block->data[i] = (float) ((((val1 + val2) >> 16) * magnitude) >> 16); -#endif - ph += inc; - block->data[i] = block->data[i] / 32767.0f; // scale to float - } - phase_accumulator = ph; + blockS = AudioStream_F32::allocate_f32(); // Output blocks + if (!blockS) return; - AudioStream_F32::transmit(block); - AudioStream_F32::release(block); - return; - } - }// end if (magnitude) - phase_accumulator += phase_increment * block_length; // continue sine wave while magnitude==0 - } // end if (enabled) + for (i=0; i < blockS->length; i++) { + phaseS += phaseIncrement; + if (phaseS > 512.0f) phaseS -= 512.0f; + index = (uint16_t) phaseS; + float32_t deltaPhase = phaseS - (float32_t)index; + /* Read two nearest values of input value from the sin table */ + a = sinTable512_f32[index]; + b = sinTable512_f32[index+1]; + blockS->data[i] = magnitude*(a+(b-a)*deltaPhase); // Linear interpolation + } + // For higher frequencies, an optional bandpass filter the output + // This does a pass through for lower frequencies + if(doPureSpectrum) + arm_biquad_cascade_df1_f32(&bq_inst, blockS->data, blockS->data, 128); + AudioStream_F32::transmit(blockS); + AudioStream_F32::release (blockS); } diff --git a/synth_sine_f32.h b/synth_sine_f32.h index a11025d..b6b7d34 100644 --- a/synth_sine_f32.h +++ b/synth_sine_f32.h @@ -1,63 +1,136 @@ -/* - * AdioSynthWaveformSine_F32 - * +/* + * AudioSynthWaveformSine_F32 + * * Created: Chip Audette (OpenAudio) Feb 2017 * Modeled on: AudioSynthWaveformSine from Teensy Audio Library - * + * * Purpose: Create sine wave of given amplitude and frequency * - * License: MIT License. Use at your own risk. + * License: MIT License. Use at your own risk. + * + */ +/* Revised 7 Feb 2022 to use a larger 512 point table and direct floating + * point. The level of harmonics depends on the exact frequency, but seems + * to be around -110 dB below the sine wave output. This is more than + * adequate for most applications. For some testing, a pure sine wave, + * limited only by the 24 bit mantissa, is useful. For this, the function + * pureSpectrum(true) will run two stages of biquad filtering putting the + * harmonics below -135 dBc. This filter tracks the frequency() entry, and + * is available above a few hundred Hz, depending on the sample rate. --Bob * + * Update time is about 9 microsends for 128 update() with T4.x. This goes + * up to 16 microseconds if "pureSpectrum" is used. */ -#ifndef synth_sine_f32_h_ -#define synth_sine_f32_h_ +#ifndef synth_sine2_f32_h_ +#define synth_sine2_f32_h_ #include "Arduino.h" #include "AudioStream_F32.h" #include "arm_math.h" - class AudioSynthWaveformSine_F32 : public AudioStream_F32 { //GUI: inputs:0, outputs:1 //this line used for automatic generation of GUI node //GUI: shortName:sine //this line used for automatic generation of GUI node public: - AudioSynthWaveformSine_F32() : AudioStream_F32(0, NULL), magnitude(16384) { } //uses default AUDIO_SAMPLE_RATE from AudioStream.h - AudioSynthWaveformSine_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL), magnitude(16384) { - setSampleRate_Hz(settings.sample_rate_Hz); - } - void frequency(float freq) { - if (freq < 0.0) freq = 0.0; - else if (freq > sample_rate_Hz/2.f) freq = sample_rate_Hz/2.f; - phase_increment = freq * (4294967296.0 / sample_rate_Hz); - } - void phase(float angle) { - if (angle < 0.0f) angle = 0.0f; - else if (angle > 360.0f) { - angle = angle - 360.0f; - if (angle >= 360.0f) return; - } - phase_accumulator = angle * (4294967296.0f / 360.0f); - } - void amplitude(float n) { - if (n < 0) n = 0; - else if (n > 1.0f) n = 1.0f; - magnitude = n * 65536.0f; - } - void setSampleRate_Hz(const float &fs_Hz) { - phase_increment *= sample_rate_Hz / fs_Hz; //change the phase increment for the new frequency - sample_rate_Hz = fs_Hz; - } - void begin(void) { enabled = true; } - void end(void) { enabled = false; } - virtual void update(void); - + + AudioSynthWaveformSine_F32() : AudioStream_F32(0, NULL), magnitude(0.5f) { + initSine(); + } //uses default AUDIO_SAMPLE_RATE from AudioStream.h + + AudioSynthWaveformSine_F32(const AudioSettings_F32 &settings) : + AudioStream_F32(0, NULL), magnitude(0.5f) { + setSampleRate_Hz(settings.sample_rate_Hz); + initSine(); + } + + void initSine(void) { + for(int ii=0; ii<10; ii++) // Coeff for BiQuad BPF + coeff32[ii] = 0.0; + coeff32[0] = 1.0; // b0 = 1 for pass through + coeff32[5] = 1.0; + // {numStages, pState, pCoeffs}; + arm_biquad_cascade_df1_init_f32( &bq_inst, 2, state32, coeff32 ); + } + + void frequency(float32_t _freq) { // Frequency in Hz + freq = _freq; + if (freq < 0.0f) + freq = 0.0f; + if (freq > sample_rate_Hz/2.0f) + freq = sample_rate_Hz/2.0f; + phaseIncrement = 512.0f * freq / sample_rate_Hz; + + // Find coeff for 2 stages of BPF to remove harmoncs + // Always compute these in case pureSpectrum is enabled later. + if(freq > 0.003f*sample_rate_Hz) + { + float32_t q = 20.0f; + float32_t w0 = freq * (2.0f * 3.141592654f / sample_rate_Hz); + float32_t alpha = sin(w0) / (q * 2.0); + float32_t scale = 1.0f / (1.0f + alpha); + /* b0 */ coeff32[0] = alpha * scale; + /* b1 */ coeff32[1] = 0; + /* b2 */ coeff32[2] = (-alpha) * scale; + /* a1 */ coeff32[3] = -(-2.0 * cos(w0)) * scale; + /* a2 */ coeff32[4] = -(1.0 - alpha) * scale; + /* b0 */ coeff32[5] = coeff32[0]; + /* b1 */ coeff32[6] = coeff32[1]; + /* b2 */ coeff32[7] = coeff32[2]; + /* a1 */ coeff32[8] = coeff32[3]; + /* a2 */ coeff32[9] = coeff32[4]; + arm_biquad_cascade_df1_init_f32( &bq_inst, 2, coeff32, state32 ); + } + else + { + for(int ii=0; ii<10; ii++) // Coeff for BiQuad BPF + coeff32[ii] = 0.0; + coeff32[0] = 1.0; // b0 = 1 for pass through + coeff32[5] = 1.0; + arm_biquad_cascade_df1_init_f32( &bq_inst, 2, coeff32, state32 ); + enabled = false; + } + } + + /* Externally, phase comes in the range (.0, 360.0). + * Internally, the full circle is represented as (0.0, 512.0). This is + * convenient for finding the entry to the sine table. + * Corrected 1-day at phase_r() 24 Feb 22 + */ + void phase(float32_t _angle) { + angle = 1.42222222f*_angle; // Change (0,360) to (0, 512) + while (angle < 0.0f) angle += 512.0f; + while (angle > 512.0f) angle -= 512.0; + } + + // The amplitude, a, is the peak, as in zero-to-peak. This produces outputs + // ranging from -a to +a. + void amplitude(float32_t a) { + if (a < 0.0f) a = 0.0f; + magnitude = a; + } + + void setSampleRate_Hz(const float &fs_Hz) { + phaseIncrement *= sample_rate_Hz / fs_Hz; //change the phase increment for the new frequency + sample_rate_Hz = fs_Hz; + } + void begin(void) { enabled = true; } + void end(void) { enabled = false; } + void pureSpectrum(bool _setPure) { doPureSpectrum = _setPure; } + virtual void update(void); + private: - uint32_t phase_accumulator = 0; - uint32_t phase_increment = 0; - int32_t magnitude = 0; - float sample_rate_Hz = AUDIO_SAMPLE_RATE; - volatile uint8_t enabled = 1; + float32_t freq = 1000.0f; + float32_t angle = 0.0f; // Phase angle + float32_t phaseS = 0.0f; + float32_t phaseIncrement = 0.0f; + float32_t magnitude = 0.0f; + float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE; + bool doPureSpectrum = false; // Adds bandpass filter (not normally needed) + bool enabled = true; + float32_t coeff32[10]; // 2 biquad stages for filtering output + float32_t state32[8]; + arm_biquad_casd_df1_inst_f32 bq_inst; // ARM DSP Math library filter instance. }; #endif