//======================================================================
// DSP.cpp
//======================================================================

#include "DSP.h"

#include <i2c_t3.h>
//#include <Wire.h>
//#include <SPI.h>
//#include <SD.h>
//#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioInputUSB            usbIn;          //xy=63,305
AudioInputI2S            lineIn;         //xy=71,197
AudioSynthWaveformSine   tone1;          //xy=111,369
AudioSynthWaveformSine   tone2;          //xy=111,410
AudioMixer4              rxAudio;        //xy=328,111
AudioMixer4              txAudio;        //xy=332,299
AudioAnalyzeRMS          txVoxLevel;     //xy=490,340
AudioFilterFIR           rxFilter;       //xy=493,103
AudioAmplifier           usbOutAmp;      //xy=658,99
AudioAmplifier           lineOutAmp;     //xy=659,162
AudioAmplifier           usbBypassAmp;   //xy=666,225
AudioAmplifier           txOutAmp;       //xy=713,301
AudioOutputI2S           lineOut;        //xy=876,294
AudioOutputUSB           usbOut;         //xy=878,255
AudioConnection          patchCord1(usbIn, 0, txAudio, 1);
AudioConnection          patchCord2(lineIn, 0, rxAudio, 0);
AudioConnection          patchCord3(lineIn, 1, txAudio, 0);
AudioConnection          patchCord4(tone1, 0, txAudio, 2);
AudioConnection          patchCord5(tone1, 0, rxAudio, 2);
AudioConnection          patchCord6(tone2, 0, txAudio, 3);
AudioConnection          patchCord7(tone2, 0, rxAudio, 3);
AudioConnection          patchCord8(rxAudio, rxFilter);
AudioConnection          patchCord9(rxAudio, usbBypassAmp);
AudioConnection          patchCord10(txAudio, txVoxLevel);
AudioConnection          patchCord11(txAudio, txOutAmp);
AudioConnection          patchCord12(rxFilter, usbOutAmp);
AudioConnection          patchCord13(rxFilter, lineOutAmp);
AudioConnection          patchCord14(usbOutAmp, 0, usbOut, 0);
AudioConnection          patchCord15(lineOutAmp, 0, lineOut, 0);
AudioConnection          patchCord16(usbBypassAmp, 0, usbOut, 1);
AudioConnection          patchCord17(txOutAmp, 0, lineOut, 1);
AudioControlSGTL5000     audioCtrl;      //xy=337,440
// GUItool: end automatically generated code

void UBitxDSP::begin() {

  // Basic audio setup
  AudioMemory(16);            // TODO: optimize this
  audioCtrl.enable();
  audioCtrl.volume(0.0);      // headphone volume...
  audioCtrl.muteHeadphone();  // ...not used by UBitxDSP

  setupRxAudio();
  setupTxAudio();

  // Default to RX.
  muteRxIn(); // redundant?
  muteTxIn(); // redundant?
  isTx = true;  // so that rx() call works
  rx();

  // Setup the VOX - TBD

  // Setup the RX Filter.
  setRxFilter(300.0, 3000.0);

  sinceLastUpdate = 0;
}

void UBitxDSP::update() {
  // Update the USB volume (level of TX USB output) periodically.
  if (sinceLastUpdate > DSP_MILLIS_PER_UPDATE) {
    float vol = usbIn.volume();
    if (vol != usbVol) {
      setTxInLevel(TX_USB, vol);
      usbVol = vol;
    }
    sinceLastUpdate = 0;
  }
}

void UBitxDSP::end() {
  bypassRxFilter();
}

/**********************************************************************
 * Transmit/Receive switching
 **********************************************************************/

/*!
 *  Return to receive (RX) mode from transmit (TX) mode.  
 *  First the transmit audio output is muted, to ensure that no more
 *  audio goes to the rig.  Then we check to see if the latched TX audio 
 *  source was different than the selected TX audio source; this happens 
 *  if the radio is currently set for a particular input (which 
 *  determines what is monitored by the VOX), but then is commanded to 
 *  transmit a different source (e.g. based on a CAT command).  The 
 *  actual transmit audio source is latched during transmit, but upon 
 *  returning to receive, we restore the selected transmit audio.
 */
void UBitxDSP::rx() {
  if (isTx) {
    muteTxOut();
    if (txSrcLatched != txSrc) {
      setTxAudioIn(txSrc);
    }
    if (txSrcLatched == MIC_IN) {
      audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
    }
    unmuteRxIn(RX_AUDIO);
    isTx = false;
  }
}

/*!
 *  Enter transmit (TX) mode from receive (RX) mode.  
 */
void UBitxDSP::tx(TxAudioIn src) {
  if (!isTx) {
    muteRxIn(RX_AUDIO);

    txSrcLatched = src;
    if (txSrcLatched != txSrc) {
      setTxAudioIn(txSrcLatched, true);
    }

    if (txSrcLatched == MIC_IN) {
      audioCtrl.inputSelect(AUDIO_INPUT_MIC);
      audioCtrl.micGain(micGain);
    }

    unmuteTxOut();
    isTx = true;
  }
}

/**********************************************************************
 * General audio setup -- called via begin()
 **********************************************************************/

void UBitxDSP::setupRxAudio() {
  for (int i = 0; i < NUM_RX_AUDIO_CH; i++) {
    if (i == RX_AUDIO)
      rxAudio.gain(i, 1.0);
    else
      rxAudio.gain(i, 0.0);
  }  

  // Rig (Line) Input (RX)
  audioCtrl.inputSelect(AUDIO_INPUT_LINEIN);
  audioCtrl.unmuteLineout();
  audioCtrl.lineInLevel(9, 5);  // RX, TX
  audioCtrl.lineOutLevel(29, 31); // RX, TX

  // Line Output (RX)
  setLineOutLevel(1.0);

  // USB Output (RX)
  setUSBOutLevel(1.0);
}

void UBitxDSP::setupTxAudio() {
  for (int i = 0; i < NUM_TX_AUDIO_CH; i++) {
    txAudio.gain(i, 0.0);
  }

  // Mic Input (TX)
  audioCtrl.micGain(0); // TODO: set value
  // Line Input (TX)

  // USB Input (TX)

  // Rig (Line) Output (TX)
  txOutAmp.gain(1.0);

  tone1.amplitude(1.0);   // TODO - just do this once.
  tone1.frequency(1500);  // TODO:  Make this dynamic based on CW (sidetone freq) versus data (1500 Hz)

  tone1.amplitude(1.0); // TODO - just do this once.
  tone1.amplitude(1.0); // TODO - just do this once.
  tone1.frequency(700);
  tone2.frequency(1900);
}

/**********************************************************************
 * Receive audio chain
 **********************************************************************/

void UBitxDSP::setRxInLevel(RxAudioCh ch, float level) {
  if (ch < NUM_RX_AUDIO_CH) {
    state.rxIn[ch].level = level;
    rxAudio.gain(ch, state.rxIn[ch].mute ? 0.0 : state.rxIn[ch].level);
  }
}

void UBitxDSP::muteRxIn() {
  for (int i = 0; i < NUM_RX_AUDIO_CH; i++) {
    state.rxIn[RxAudioCh(i)].mute = true;
    rxAudio.gain(i, 0.0);
  }
}

void UBitxDSP::muteRxIn(RxAudioCh ch) {
  if (ch < NUM_RX_AUDIO_CH) {
    if (!state.rxIn[ch].mute) {
      state.rxIn[ch].mute = true;
      rxAudio.gain(ch, 0.0);
    }
  }
}

void UBitxDSP::unmuteRxIn(RxAudioCh ch) {
  if (ch < NUM_RX_AUDIO_CH) {
    if (state.rxIn[ch].mute) {
      state.rxIn[ch].mute = false;
      rxAudio.gain(ch, state.rxIn[ch].level);
    }
  }
}

void UBitxDSP::setLineOutLevel(float level) {
  state.rxOut[LINE_OUT].level = level;
  lineOutAmp.gain(state.rxOut[LINE_OUT].mute ? 0.0 : state.rxOut[LINE_OUT].level);
}

void UBitxDSP::setUSBOutLevel(float level) {
  state.rxOut[USB_OUT].level = level;
  usbOutAmp.gain(state.rxOut[USB_OUT].mute ? 0.0 : state.rxOut[USB_OUT].level);
  usbBypassAmp.gain(state.rxOut[USB_OUT].mute ? 0.0 : state.rxOut[USB_OUT].level);
}

/**********************************************************************
 * Transmit audio chain
 **********************************************************************/

void UBitxDSP::setTxInLevel(TxAudioCh ch, float level) {
  if (ch < NUM_TX_AUDIO_CH) {
    state.txIn[ch].level = level;
    txAudio.gain(ch, state.txIn[ch].mute ? 0.0 : state.txIn[ch].level);
  }
}

void UBitxDSP::muteTxIn() {
  for (int i = 0; i < NUM_TX_AUDIO_CH; i++) {
    state.txIn[TxAudioCh(i)].mute = true;
    txAudio.gain(i, 0.0);
  }
}

void UBitxDSP::muteTxIn(TxAudioCh ch) {
  if (ch < NUM_TX_AUDIO_CH) {
    if (!state.txIn[ch].mute) {
      state.txIn[ch].mute = true;
      txAudio.gain(ch, 0.0);
    }
  }
}

void UBitxDSP::unmuteTxIn(TxAudioCh ch) {
  if (ch < NUM_TX_AUDIO_CH) {
    if (state.txIn[ch].mute) {
      state.txIn[ch].mute = false;
      rxAudio.gain(ch, state.txIn[ch].level);
    }
  }
}

void UBitxDSP::setTxOutLevel(float level) {
  state.txOut.level = level;
  txOutAmp.gain(state.txOut.mute ? 0.0 : state.txOut.level);
}

void UBitxDSP::muteTxOut() {
  if (!state.txOut.mute) {
    state.txOut.mute = true;
    txOutAmp.gain(0.0);
  }
}

void UBitxDSP::unmuteTxOut() {
  if (state.txOut.mute) {
    state.txOut.mute = false;
    txOutAmp.gain(state.txOut.level);
  }
}

void UBitxDSP::setLineInLevel(float level) {
  state.txIn[TX_LINE].level = level;
  txAudio.gain(TX_LINE, state.txIn[TX_LINE].mute ? 0.0 : state.txIn[TX_LINE].level);  
}

void UBitxDSP::setUSBInLevel(float level) {
  state.txIn[TX_USB].level = level;
  txAudio.gain(TX_USB, state.txIn[TX_USB].mute ? 0.0 : state.txIn[TX_USB].level);    
}

void UBitxDSP::setTxAudioIn(TxAudioIn src, bool isTemp) {
  if (!isTemp) {
    txSrc = src;
  }

  if (!isTx) {
    muteTxIn();     // Mute all channels, then unmute the desired ones.
    switch (src) {  // Don't switch inputs while transmitting.
      case MIC_IN:
      // Note that we can't actually use the VOX code on the mic input,
      // because we can't make the actual mic input active without
      // losing our receive audio.  So, mic input is never actually 
      // selected until it is time for it to transmit, which makes the
      // VOX moot.  The caller must make use of an external, analog VOX
      // circuit driving a GPIO pin, or something similar (or the PTT of
      // course) to begin actually using the mic input.  So this case 
      // just falls through to the line input.
      
      case LINE_IN:
      unmuteTxIn(TX_LINE);
      break;

      case USB_IN:
      unmuteTxIn(TX_USB);
      break;
  
      case TUNE_IN:
      tone1.amplitude(1.0);   // TODO - just do this once.
      tone1.frequency(1500);  // TODO:  Make this dynamic based on CW (sidetone freq) versus data (1500 Hz)
      unmuteTxIn(TX_TONE1);
      break;
  
      case TWO_TONE_IN:
      tone1.amplitude(1.0); // TODO - just do this once.
      tone1.amplitude(1.0); // TODO - just do this once.
      tone1.frequency(700);
      tone2.frequency(1900);
      unmuteTxIn(TX_TONE1);
      unmuteTxIn(TX_TONE2);
      break;

      default:
      // should never happen
      break;
    }
  }
}

/**********************************************************************
 * Receive audio filter (band pass)
 **********************************************************************/

const int minRxFilterLo = MIN_RX_FILTER_LO;
const int maxRxFilterHi = MAX_RX_FILTER_HI;
const int minRxFilterWidth = MIN_RX_FILTER_WIDTH;
const int maxRxFilterWidth = MAX_RX_FILTER_WIDTH;
const int minRxFilterCenter = MIN_RX_FILTER_CENTER;
const int maxRxFilterCenter = MAX_RX_FILTER_CENTER;

/*!
 *  @brief  Bypass the RX audio filter.
 */
void UBitxDSP::bypassRxFilter() {
  rxFilter.begin(FIR_PASSTHRU, NUM_COEFFICIENTS);
}

/*!
    @brief  Update the RX audio filter using the currently set low and
            high frequencies.  This is called by each of the public
            filter methods to update the filter with new frequencies.
*/
void UBitxDSP::updateRxFilter() {
  audioFilter(coefficients, NUM_COEFFICIENTS, ID_BANDPASS, W_HAMMING, double(state.rxFilterLo), double(state.rxFilterHi));
  rxFilter.begin(coefficients, NUM_COEFFICIENTS);
}

void UBitxDSP::setRxFilter(int lo, int hi) {
  if (hi < lo + minRxFilterWidth) {
    hi = lo + minRxFilterWidth;
  }
  if (hi > maxRxFilterHi) {
    hi = maxRxFilterHi;
  }
  if (lo > hi - minRxFilterWidth) {
    lo = hi - minRxFilterWidth;
  }
  if (lo < minRxFilterLo) {
    lo = minRxFilterLo;
  }
  state.rxFilterHi = hi;
  state.rxFilterLo = lo;
  updateRxFilter();
}

void UBitxDSP::setRxFilterLo(int lo) {
  if (lo > state.rxFilterHi - minRxFilterWidth) {
    lo = state.rxFilterHi - minRxFilterWidth;
  }
  if (lo < minRxFilterLo) {
    lo = minRxFilterLo;
  }
  state.rxFilterLo = lo;
  updateRxFilter();
}

void UBitxDSP::setRxFilterHi(int hi) {
  if (hi < state.rxFilterLo + minRxFilterWidth) {
    hi = state.rxFilterLo + minRxFilterWidth;
  }
  if (hi > maxRxFilterHi) {
    hi = maxRxFilterHi;
  }
  state.rxFilterHi = hi;
  updateRxFilter();
}

void UBitxDSP::setRxFilterWidth(int width) {
  if (width < minRxFilterWidth) {
    width = minRxFilterWidth;
  } else if (width > maxRxFilterWidth) {
    width = maxRxFilterWidth;
  }
  int center = (state.rxFilterHi + state.rxFilterLo) / 2;
  int lo = center - (width / 2);
  int hi = center + (width / 2);
  setRxFilter(lo, hi);
}

void UBitxDSP::setRxFilterCenter(int center) {
  if (center < minRxFilterCenter) {
    center = minRxFilterCenter;
  } else if (center > maxRxFilterCenter) {
    center = maxRxFilterCenter;
  }
  int width = state.rxFilterHi - state.rxFilterLo;
  int lo = center - (width / 2);
  int hi = center + (width / 2);
  setRxFilter(lo, hi);
}

/**********************************************************************
 * Transmit Voice-Operated-Switch (VOX)
 **********************************************************************/

float UBitxDSP::getVoxLevel() const {
  if (return txVoxLevel.available()) {
    prevVox = txVoxLevel.read();
  }
  return prevVox;
}

/**********************************************************************
 * Singleton - the DSP instance
 **********************************************************************/

// TODO:  Fix this. This won't work... this compilation unit won't be
// able to instantiate a class it doesn't know about.

#ifndef UBITXDSP_CLASS
#define UBITXDSP_CLASS UBitxDSP
#endif

UBITXDSP_CLASS theDSP;
UBitxDSP& DSP = theDSP;

/*
    NOTES

    Major functions:
      - tx() - start transmitting / pause receiving
      - rx() - stop transmitting / resume receiving
      - setTxSource() - set the TX audio source to MIC_IN, LINE_IN, or USB_IN
                      - also sets the relevant VOX source/parameters (as applicable)

    Receive audio chain:
      -
*/

//======================================================================
// EOF
//======================================================================
