/* FreqShifter_FD_OA.ino
 *
 * Demonstrate frequency shifting via frequency domain processin.
 *
 * Created: Chip Audette (OpenAudio) Aug 2019
 *
 * Approach: This processing is performed in the frequency domain.
 *    Frequencies can only be shifted by an integer number of bins,
 *    so small frequency shifts are not possible.  For example, for
 *    a sample rate of 44.1kHz, and when using N=256, one can only
 *    shift frequencies in multiples of 44.1/256 = 172.3 Hz.
 *
 *    This processing is performed in the frequency domain where
 *    we take the FFT, shift the bins upward or downward, take
 *    the IFFT, and listen to the results.  In effect, this is
 *    single sideband modulation, which will sound very unnatural
 *    (like robot voices!).  Maybe you'll like it, or maybe not.
 *    Probably not, unless you like weird.  ;)
 *
 *    You can shift frequencies upward or downward with this algorithm.
 *
 * Frequency Domain Processing:
 *    * Take samples in the time domain
 *    * Take FFT to convert to frequency domain
 *    * Manipulate the frequency bins to do the freqyebct shifting
 *    * Take IFFT to convert back to time domain
 *    * Send samples back to the audio interface
 *
 * Built for the Tympan library for Teensy 3.6-based hardware
 *
 * Convert to Open Audio Bob Larkin June 2020
 * Tested OK for Teensy 3.6 and 4.0.
 * For settings:
 *   sample rate (Hz) = 44117.00
 *   block size (samples) = 128
 *   N_FFT = 512
 * the following resources were used for Teensy 3.6
 *   CPU Cur/Peak: 50.02%/50.24%
 *   MEM Float32 Cur/Peak: 8/9
 *   MEM Int16 Cur/Peak:   3/5
 * For Teensy 4.0:
 *   CPU Cur/Peak: 6.53%/6.84%
 *   MEM Float32 Cur/Peak: 8/9
 *   MEM Int16 Cur/Peak:   3/4
 *
 * MIT License.  Use at your own risk.
 */

#include "Audio.h"
#include "AudioStream_F32.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "SerialManager_FreqShift_OA.h"

//set the sample rate and block size
const float sample_rate_Hz = 44117.f; ; // other frequencies in the table in AudioOutputI2S_F32 for T3.x only
const int audio_block_samples = 128;     //for freq domain processing, a power of 2: 16, 32, 64, 128
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);

//create audio library objects for handling the audio
AudioInputI2S                 i2sIn;   // This I16 input/output is T4.x compatible
AudioConvert_I16toF32         cnvrt1;  // Convert to float
AudioEffectFreqShiftFD_OA_F32 freqShift(audio_settings); // the frequency-domain processing block
AudioEffectGain_F32           gain1;   //Applies digital gain to audio data.
AudioConvert_F32toI16         cnvrt2;
AudioOutputI2S                i2sOut;
AudioControlSGTL5000          codec;
//Make all of the audio connections
AudioConnection       patchCord1(i2sIn,        0, cnvrt1, 0); // connect to Left codec, 16-bit
AudioConnection_F32   patchCord2(cnvrt1,       0, freqShift, 0);
AudioConnection_F32   patchCord3(freqShift, 0, gain1, 0); //connect to gain
AudioConnection_F32   patchCord4(gain1,        0, cnvrt2, 0); //connect to the left output
AudioConnection       patchCord6(cnvrt2,       0, i2sOut, 0);

//control display and serial interaction
bool enable_printCPUandMemory = false;
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; };
SerialManagerFreqShift_OA serialMgr;

//inputs and levels
float input_gain_dB = 15.0f; //gain on the microphone
float vol_knob_gain_dB = 0.0;      //will be overridden by volume knob
      
// ***************   SETUP   **********************************
void setup() {
  Serial.begin(1); delay(1000);
  Serial.println("freqShifter: starting setup()...");
  Serial.print("    : sample rate (Hz) = ");  Serial.println(audio_settings.sample_rate_Hz);
  Serial.print("    : block size (samples) = ");  Serial.println(audio_settings.audio_block_samples);

  // Audio connections require memory to work.  For more
  // detailed information, see the MemoryAndCpuUsage example
  AudioMemory(10);            // I16 type
  AudioMemory_F32(40, audio_settings);

  // Configure the FFT parameters algorithm
  int overlap_factor = 4;  //set to 2, 4 or 8...which yields 50%, 75%, or 87.5% overlap (8x)
  int N_FFT = audio_block_samples * overlap_factor;  
  Serial.print("    : N_FFT = "); Serial.println(N_FFT);
  freqShift.setup(audio_settings, N_FFT); //do after AudioMemory_F32();

  //configure the frequency shifting
  float shiftFreq_Hz = 750.0; //shift audio upward a bit
  float Hz_per_bin = audio_settings.sample_rate_Hz / ((float)N_FFT);
  int shift_bins = (int)(shiftFreq_Hz / Hz_per_bin + 0.5);  //round to nearest bin
  
  shiftFreq_Hz = shift_bins * Hz_per_bin;
  Serial.print("Setting shift to "); Serial.print(shiftFreq_Hz);
     Serial.print(" Hz, which is "); Serial.print(shift_bins); 
     Serial.println(" bins");
  freqShift.setShift_bins(shift_bins); //0 is no ffreq shifting.
 
  //Enable the Tympan to start the audio flowing!
  codec.enable();
  codec.adcHighPassFilterEnable();
  codec.inputSelect(AUDIO_INPUT_LINEIN);

  //finish the setup by printing the help menu to the serial connections
  serialMgr.printHelp();
}

// *************************   LOOP    ****************************
void loop() {

  //respond to Serial commands
  while (Serial.available()) serialMgr.respondToByte((char)Serial.read());   //USB Serial
 
  //check to see whether to print the CPU and Memory Usage
  if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec

} //end loop();

//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) {
  //static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
  static unsigned long lastUpdate_millis = 0;

  //has enough time passed to update everything?
  if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
  if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
    Serial.print("CPU Cur/Peak: ");
    Serial.print(audio_settings.processorUsage());
    Serial.print("%/");
    Serial.print(audio_settings.processorUsageMax());
    Serial.print("%,   ");
    Serial.print(" Dyn MEM Float32 Cur/Peak: ");
    Serial.print(AudioMemoryUsage_F32());
    Serial.print("/");
    Serial.print(AudioMemoryUsageMax_F32());
    Serial.print(" MEM Int16 Cur/Peak: ");
    Serial.print(AudioMemoryUsage());
    Serial.print("/");
    Serial.print(AudioMemoryUsageMax());
    Serial.println();

    lastUpdate_millis = curTime_millis; //we will use this value the next time around.
  }
}

void printGainSettings(void) {
  Serial.print("Gain (dB): ");
  Serial.print("Vol Knob = "); Serial.print(vol_knob_gain_dB,1);
  //Serial.print(", Input PGA = "); Serial.print(input_gain_dB,1);
  Serial.println();
}

void incrementKnobGain(float increment_dB) {
  setVolKnobGain_dB(vol_knob_gain_dB+increment_dB);
}

void setVolKnobGain_dB(float gain_dB) {
  vol_knob_gain_dB = gain_dB;
  gain1.setGain_dB(vol_knob_gain_dB);
  printGainSettings();
}

int incrementFreqShift(int incr_factor) {
  int cur_shift_bins = freqShift.getShift_bins();
  return freqShift.setShift_bins(cur_shift_bins + incr_factor);
}