/*
 * RadioFMDetector_F32.cpp
 *
 * 25 April 2022
 * Bob Larkin, in support of the library:
 * Chip Audette, OpenAudio, Apr 2017
 *     -------------------
 *
 * Copyright (c) 2022 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.
 *
 * See RadioFMDetector_F32.h for usage details
*/
#include "RadioFMDetector_F32.h"

// 513 values of the sine wave in a float array:
#include "sinTable512_f32.h"

//  ====  UPDATE  ====
void RadioFMDetector_F32::update(void) {
  audio_block_f32_t  *blockIn, *blockOut=NULL, *blockZero;

  static float saveIn = 0.0f;  // for squelch
  static float saveOut = 0.0f;
  uint16_t i, index_sine;
  float32_t deltaPhase, a, b, dtemp1, dtemp2;
  float32_t  v_i[128];  // max size
  float32_t  v_q[128];
  mathDSP_F32 mathDSP1;    // Math support functions

#if TEST_TIME_FM
  if (iitt++ >1000000) iitt = -10;
  uint32_t t1, t2;
  t1 = tElapse;
#endif

  // Get input to FM Detector block
  blockIn = AudioStream_F32::receiveWritable_f32(0);
  if (!blockIn) {
     return;
     }


  if (fir_Out_Coeffs == NULL) {
    //if(errorPrintFM)  Serial.println("FMDET-ERR: No Out FIR Coefficients");
    AudioStream_F32::release(blockIn);
    return;
    }

  // Try to get a block for the FM output
  blockOut = AudioStream_F32::allocate_f32();
  if (!blockOut){      // Didn't have any
    //if(errorPrintFM)  Serial.println("FMDET-ERR: No Output Memory");
    AudioStream_F32::release(blockIn);
    return;
    }

  blockZero = AudioStream_F32::allocate_f32();
     for(int kk=0; kk<128; kk++)  blockZero->data[kk] = 0.0f;

  // Generate sine and cosine of center frequency and double-balance mix
  // these with the input signal to produce an intermediate result
  // saved as v_i[] and v_q[]
  for (i=0; i < block_size; i++) {
      phaseS += phaseIncrement;
      if (phaseS > 512.0f)
         phaseS -= 512.0f;
      index_sine = (uint16_t) phaseS;
      deltaPhase = phaseS -(float32_t) index_sine;
      /* Read two nearest values of input value from the sin table */
      a = sinTable512_f32[index_sine];
      b = sinTable512_f32[index_sine+1];
      // Linear interpolation and multiplying (DBMixer) with input
      v_i[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase);

      /* Repeat for cosine by adding 90 degrees phase  */
      index_sine = (index_sine + 128) & 0x01ff;
      /* Read two nearest values of input value from the sin table */
      a = sinTable512_f32[index_sine];
      b = sinTable512_f32[index_sine+1];
      /* deltaPhase will be the same as used for sin  */
      v_q[i] = blockIn->data[i] * (a + 0.001953125*(b-a)*deltaPhase);
      }

   // Do I FIR and Q FIR. We can borrow blockIn and blockOut at this point
   //void arm_fir_f32( const arm_fir_instance_f32* S, float32_t* pSrc, float32_t* pDst, uint32_t blockSize)
   arm_fir_f32(&FMDet_I_inst, v_i, blockIn->data,  (uint32_t)blockIn->length);
   arm_fir_f32(&FMDet_Q_inst, v_q, blockOut->data, (uint32_t)blockOut->length);
   // Do ATAN2, differentiation and de-emphasis  in single loop
   for(i=0; i<block_size; i++) { //  y        x
       dtemp1 = mathDSP1.fastAtan2((float)blockOut->data[i], (float)blockIn->data[i]);
       // Apply differentiator by subtracting last value of atan2
       if(dtemp1>MF_PI_2  &&  diffLast<-MF_PI_2)       // Probably a wrap around
           dtemp2 = dtemp1 - diffLast - MF_TWOPI;
       else if(dtemp1<-MF_PI_2  &&  diffLast >MF_PI_2)  // Probably a reverse wrap around
           dtemp2 = dtemp1 - diffLast + MF_TWOPI;
       else
           dtemp2 = dtemp1 - diffLast;      // Differentiate
       diffLast = dtemp1;               // Ready for next time through loop
       // Data point is now dtemp2. Apply single pole de-emphasis LPF, in place
       dLast = Kdem * dtemp2 + OneMinusKdem * dLast;
       blockIn->data[i] = dLast;        // and save to an array
       }

    // Do output FIR filter.  Data now in blockIn.
    arm_fir_f32(&FMDet_Out_inst, blockIn->data, blockOut->data,  (uint32_t)blockIn->length);

    // Squelch picks the audio from before the output filter and does a 4-pole BiQuad BPF
    // blockIn->data still has the data we need
    arm_biquad_cascade_df1_f32(&iirSqIn_inst, blockIn->data,
             blockIn->data, blockIn->length);

    // Update the Squelch full-wave envelope detector and single pole LPF
    float sumNoise = 0.0f;
    for(i=0; i<block_size; i++)
       sumNoise += fabsf(blockIn->data[i]);  // Ave of rectified noise
    squelchLevel = alpha*(sumNoise + saveIn) + gamma*saveOut;  // 1 pole
    saveIn = sumNoise;
    saveOut = squelchLevel;
    // Add hysteresis here  <<<<

    // Squelch gate sends audio to output 1 (right) if squelch is open
    if(squelchLevel < squelchThreshold)
        AudioStream_F32::transmit(blockOut, 1);
    else
        AudioStream_F32::transmit(blockZero, 1);

    // The un-squelch controlled audio for tone decoding, etc., goes to output 0
    AudioStream_F32::transmit(blockOut, 0);

    AudioStream_F32::release(blockIn);        // Give back the blocks
    AudioStream_F32::release(blockOut);      AudioStream_F32::release(blockZero);
#if TEST_TIME_FM
  t2 = tElapse;
  if(iitt++ < 0) {Serial.print("At end of FM Det ");  Serial.println (t2 - t1); }
  t1 = tElapse;
#endif
}