/*     synth_sin_cos_f32.h
 *   AudioSynthSineCosine_F32
 *
 * Status: Checked for function and accuracy.  19 April 2020
 * 10 March 2021  Corrected Interpolation equations.  Bob L
 * 23 Feb 2022 Added "pure spectrum" filtering. RSL
 * This adds the function pureSpectrum(bool _setPure);   // Default: false
 *
 * Created: Bob Larkin 15 April 2020
 *
 * Based on Chip Audette's OpenAudio sine(), that was
 * Modeled on: AudioSynthWaveformSine from Teensy Audio Library
 *
 * Purpose: Create sine and cosine wave of given amplitude, frequency
 * and phase.  Outputs are audio_block_f32_t blocks of float32_t.
 * Routines are from the arm CMSIS library and use a 512 point lookup
 * table with linear interpolation to achieve float accuracy limits.
 *
 * This provides for setting the phase of the sine, setting the difference
 * in phase between the sine and cosine and setting the
 * (-amplitude, amplitude) range.  If these are at the default values,
 * called doSimple, the caluclation is faster.
 * For doSimple either true or false, the frequency can be changed
 * at will using the frequency() method.
 *
 * Defaults:
 * Frequency:  1000.0 Hz
 * Phase of Sine:  0.0 radians (0.0 deg)
 * Phase of Cosine:  pi/2 radians (90.0 deg) ahead of Sine
 * Amplitude:  -1.0 to 1.0
 *
 * Time: T3.6 update() block of 128 with doSimple is 36 microseconds
 *       Same using flexible doSimple=false is       49 microseconds
 *       T4.0 update() block of 128 with doSimple is 16 microseconds
 *       Same using flexible doSimple=false is       24 microseconds
 *
 * Copyright (c) 2020 Bob Larkin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef synth_sin_cos_f32_h_
#define synth_sin_cos_f32_h_

#include "AudioStream_F32.h"
#include "arm_math.h"

#ifndef M_PI
#define M_PI   3.14159265358979323846
#endif

#ifndef M_PI_2
#define M_PI_2  1.57079632679489661923
#endif

#ifndef M_TWOPI
#define M_TWOPI  (M_PI * 2.0)
#endif

#define MF2_PI 6.2831853f

class AudioSynthSineCosine_F32 : public AudioStream_F32  {
//GUI: inputs:0, outputs:2 //this line used for automatic generation of GUI node
//GUI: shortName:SineCosine  //this line used for automatic generation of GUI node
public:
    AudioSynthSineCosine_F32(void) : AudioStream_F32(0, NULL) { } //uses default AUDIO_SAMPLE_RATE from AudioStream.h

    AudioSynthSineCosine_F32(const AudioSettings_F32 &settings) : AudioStream_F32(0, NULL) {
	    setSampleRate_Hz(settings.sample_rate_Hz);
	    setBlockLength(settings.audio_block_samples);
    }

    void frequency(float32_t fr) {    // Frequency in Hz
		freq = fr;
        if (freq < 0.0f) freq = 0.0f;
        else 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_instS, 2, coeff32, state32S );
           arm_biquad_cascade_df1_init_f32( &bq_instC, 2, coeff32, state32C );
           }
        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_instS, 2, coeff32, state32S );
           arm_biquad_cascade_df1_init_f32( &bq_instC, 2, coeff32, state32C );
        }
    }

    /* Externally, phase comes in the range (0,2*M_PI) keeping with C math functions
     * Internally,  the full circle is represented as (0.0, 512.0).  This is
     * convenient for finding the entry to the sine table.
     */
    void phase_r(float32_t a) {
        while (a < 0.0f) a += MF2_PI;
        while (a > MF2_PI) a -= MF2_PI;
        phaseS = 512.0f * a / MF2_PI;
        doSimple = false;
        return;
    }

    // phaseS_C_r  is the number of radians that the cosine output leads the
    // sine output.  The default is M_PI_2 = pi/2 = 1.57079633 radians,
    // corresponding to 90.00 degrees cosine leading sine.
    void phaseS_C_r(float32_t a) {
        while (a < 0.0f) a += MF2_PI;
        while (a > MF2_PI) a -= MF2_PI;
        // Internally a full circle is 512.00 of phase
        phaseS_C = 512.0f * a / MF2_PI;
        doSimple = false;
        return;
    }

    // The amplitude, a, is the peak, as in zero-to-peak.  This produces outputs
    // ranging from -a to +a.  Both outputs are the same amplitude.
    void amplitude(float32_t a) {
        amplitude_pk = a;
        doSimple = false;
        return;
    }

     // Speed up calculations by setting phaseS_C=90deg, amplitude=1
     // Note, s=true will override any setting of phaseS_C_r or amplitude.
     void simple(bool s) {
        doSimple = s;
        if(doSimple) {
			phaseS_C = 128.0f;
			amplitude_pk = 1.0f;
	    }
        return;
    }

    void setSampleRate_Hz(float32_t fs_Hz) {
        // Check freq range
        if (freq > sample_rate_Hz/2.0f) freq = sample_rate_Hz/2.f;
        // update phase increment for new frequency
        phaseIncrement = 512.0f * freq / fs_Hz;
    }

    void setBlockLength(uint16_t bl) {
      if(bl > 128)  bl = 128;
      block_length = bl;
      }

    void pureSpectrum(bool _setPure) { doPureSpectrum = _setPure; }

    virtual void update(void);

private:
    float32_t freq = 1000.0f;
    float32_t phaseS = 0.0f;
    float32_t phaseS_C = 128.00;
    float32_t amplitude_pk = 1.0f;
    float32_t sample_rate_Hz = AUDIO_SAMPLE_RATE;
    float32_t phaseIncrement = 512.0f * freq /sample_rate_Hz;
    uint16_t  block_length = 128;
    // if only freq() is used, the complexities of phase, phaseS_C,
    // and amplitude are not used, speeding up the sin and cos:
    bool      doSimple = true;
    bool doPureSpectrum = false;   // Adds bandpass filter (not normally needed)
    float32_t coeff32[10];       // 2 biquad stages for filtering output shared S&C
    float32_t state32S[8];
    arm_biquad_casd_df1_inst_f32 bq_instS; // ARM DSP Math library filter instance.
    float32_t state32C[8];
    arm_biquad_casd_df1_inst_f32 bq_instC; // ARM DSP Math library filter instance.
};
#endif