/*****************************************************************************\

  File Name: SpeexDemo.c

  Description: This is the main file for a Speex encode/decode demo.  It setts
               up the audio device to capture and then playback a captured 
               audio stream.  The audio stream is received thru the audio 
               codec's input jack, then it is encoded, then decoded and then 
               played out using the audio codec's front channels.

               For more details on hardware and other important notes please
               refer to the SpeexEcho-BFxxx-README.txt for BFxxx processor
               specific EZ Kit hardware.

               Supported Processor: ADSP-BF561
               Supported CODECs: ADI AD1836.
    
\*****************************************************************************/
#define VER_MAJOR 1
#define VER_MINOR 00

//-----------------------------------------------------------------------------
//  I N C L U D E S
//-----------------------------------------------------------------------------
#include "AudioStreaming.h"
#include "AudioDeviceIF.h"
#include "EZKitHWConfig.h"
#include <fract.h>
#include <cycles.h>
#include "speex_callbacks.h"
#include "speex_stereo.h"

//-----------------------------------------------------------------------------
//  D E F I N E S
//-----------------------------------------------------------------------------
#ifdef _DEBUG_
#define FLG_DEBUG true
#else //_DEBUG_
#define FLG_DEBUG false
#endif

#define NUM_CHANNELS      2   //Stereo
#define SPEEX_FRAME_SIZE  320 //For wideband (16 kHz) streams
#define AUDIO_NUM_SAMPLES SPEEX_FRAME_SIZE //Size of the buffer into which the 
                                           //data is decoded

#define SPEEX_DISABLED    (int)0
#define SPEEX_ENABLED     (int)1
#define ACTIVATE_STEREO // Speex can encode and decode stereo frames
//#define ALWAYS_PASS_THROUGH // for debugging

//-----------------------------------------------------------------------------
//  G L O B A L S 
//-----------------------------------------------------------------------------
AUDIOSTATE   g_AudSt; //Audio stream state structure
AUDDEVPROPS  g_DP;    //Device properties
volatile int g_EOS;   //End of stream semaphore used to end playback

//Data buffer to store filter/decoder output
//Double "ping/pong" buffer for the audio transfers over the SPORT
section ("L1_data_a") volatile ARTYP_16BITS TxBuffer[AUDIO_NUM_SAMPLES*NUM_CHANNELS*2]; 
section ("L1_data_b") volatile ARTYP_16BITS RxBuffer[AUDIO_NUM_SAMPLES*NUM_CHANNELS*2];

// Device Driver variables
ADI_DCB_HANDLE         g_DCBHandle;  //Handle to the callback service

// Speex variables
PVOID            g_pvEnc, g_pvDec;
char             g_cBits[200];
int              g_nBits;
SpeexBits        g_Bits;
SpeexCallback    g_Callback;
SpeexStereoState g_StereoState=SPEEX_STEREO_STATE_INIT;
volatile int     g_iSpeexEnabled=SPEEX_ENABLED; //State of Speex processing

fract16 in_buffer_calc[SPEEX_FRAME_SIZE];  //temporary buffer (used for debugging)
fract16 out_buffer_calc[SPEEX_FRAME_SIZE]; //temporary buffer (used for debugging)

cycle_stats_t    g_CycleStats;

//-----------------------------------------------------------------------------
//  F U N C T I O N    P R O T O T Y P E S
//-----------------------------------------------------------------------------
int    main(void);
static ADI_INT_HANDLER(ExceptionHandler);
static ADI_INT_HANDLER(HWErrorHandler);
int    InitEZKitBoard(void);
int    CallBackFunction(PVOID, PVOID, PVOID);
int    InitializeSpeex(void);
int    TerminateSpeex(void);
void   SpeexToggleCallback(PVOID, u32, PVOID);

//-----------------------------------------------------------------------------
// main() - Initializes the audio pseudo-driver to play encode and decode 
//          speech frames in the audio callback using Speex
//-----------------------------------------------------------------------------
int main(void)
{
  int i, bytesRead, Result;
  u32 ResponseCount;
  u32 cclk, sclk, vco;  

  printf("\n\n\nSpeex Talkthrough Demo (VDSP++4.50 %sV%d.%02d)\n\n", 
         ((true == FLG_DEBUG) ? "Debug " : ""), VER_MAJOR, VER_MINOR);
         
  Result = InitEZKitBoard(); //Initialize the EZKit board
  ezErrorCheck(Result); //Flash on all LEDs and loop forever 

  InitializeSpeex();
  printf("Encoding and decoding an audio stream in real-time\n\n");
  
  //Intialize the audio stream properties structure
  g_AudSt.bDevInit       = false; //Flag that the device was not intialized
  g_AudSt.pDevProp       = &g_DP; //Setup the pointer to the device prop
  g_AudSt.SampleFormat   = AUD_SAMPLFORMAT_FIXED16;
  g_AudSt.SampleRate     = 16000;
  g_AudSt.Direction      = ADI_DEV_DIRECTION_UNDEFINED;
  g_AudSt.NumInChannels  = NUM_CHANNELS;
  g_AudSt.NumOutChannels = NUM_CHANNELS;
  g_AudSt.pCBFunc        = CallBackFunction; //Init the callback pointer
  g_AudSt.NumFrames      = AUDIO_NUM_SAMPLES;
  
  // Initialize and start the sound device and stream
  Result = AudDev_Init(&g_AudSt, (PVOID)RxBuffer, (PVOID)TxBuffer, 
                       adi_dev_ManagerHandle, adi_dma_ManagerHandle, g_DCBHandle);
  ezErrorCheck(Result);
  g_AudSt.bDevInit = true; //Flag that the device was intialized successfully

    
  // Open an audio stream    
  Result = AudDev_Open();
  ezErrorCheck(Result);    
  
  ezTurnOnLED(LED_INIT_SUCCESS);  //If we made it this far then all init succeeded!
  printf("LED %d - successful system init\n", LED_INIT_SUCCESS);
  printf("LED %d - toggles inside the idle loop\n", LED_IDLE); 
  printf("        Note: small NUM_FRAMES causes fast duty cycle!\n"); 
  printf("LED %d - toggles during audio callbacks\n", LED_CALLBACK);
  printf("        Note: fast duty cycle makes LEDs appear dim\n");
  printf("LED %d - inidcates whether SPEEX is enabled\n\n", LED_CODEC_ENABLED);
  printf("Press button %d to stop the audio\n", BUTTON_QUIT);    
  FLUSHPRINTF("Press button %d to en/disable SPEEX processing\n\n", 
              BUTTON_CODEC_TOGGLE);
  
  CYCLES_INIT(g_CycleStats);
  AudDev_Start();
  while(0 == g_EOS) // the audio callback will set eos upon end of stream    
  { 
    ezToggleLED(LED_IDLE); // toggle a LED in the foreground        
    if (1 == ezIsButtonPushed(BUTTON_QUIT)) { g_EOS = 1; } //QUIT btn pressed!
  }
  AudDev_Stop();    
  AudDev_Close();
       
  //Cleanup Speex
  TerminateSpeex();
#ifdef _DEBUG_  
  printf("Printing number of cycles spent en/decoding in the Speex callback\n");
#endif  
  CYCLES_PRINT(g_CycleStats);
  FLUSHPRINTF("\n\nTerminating Speex Talkthrough Demo.\n");    
}

//-----------------------------------------------------------------------------
// ExceptionHandler & HWErrorHandler - We should never get an exception or 
//                                     hardware error, but just in case we'll 
//                                     catch them and simply turn on all the 
//                                     LEDS should one ever occur.
//-----------------------------------------------------------------------------
static ADI_INT_HANDLER(ExceptionHandler)
  { return(ADI_INT_RESULT_PROCESSED); }
static ADI_INT_HANDLER(HWErrorHandler)
  { return(ADI_INT_RESULT_PROCESSED); }

//-----------------------------------------------------------------------------
// InitEZKitBoard() - Function sets up and initializes the EZKit board hardware
//                    as specfied by DSP and needed services.
//
//                    Recommended Initialization Order
//                    1. Interrupt Manager
//                       Hook EXCEPTION and HARDWARE ERROR interrupts
//                    2. External Bus Interface Unit (EBIU)
//                    3. Power Management
//                    4. Port Control (Braemar class only)
//                    5. Deferred Callback Service
//                    6. DMA Manager
//                    7. Programmable Flag Service
//                    8. Timer Service
//-----------------------------------------------------------------------------
int InitEZKitBoard(void)
{
  int iRet = AUD_SUCCESS, i;
  
  	/* enable and configure async memory */
  	ezInit(1); 

  
  	/* initialize the ezKit power,EBIU,DMA.... */
	adi_ssl_Init();	

  
	/* speed up clock frequency */
#if defined(__ADSP_EDINBURGH__)			
	/* only configure 533 as 561 cannot operate a max speed */
	adi_pwr_SetFreq( 0, 0, ADI_PWR_DF_NONE );
#endif     

  //Initialize Buttons & LEDs....
  printf("Initializing LED flags and buttons\n");
  for (i=EZ_FIRST_BUTTON; i<EZ_NUM_BUTTONS; i++) { ezInitButton(i); }
  for (i=EZ_FIRST_LED; i<EZ_NUM_LEDS; i++) { ezInitLED(i); }
  ezTurnOffAllLEDs(); //Init EZ-KIT utilities
  
  if (AUD_SUCCESS == iRet) 
    { iRet = adi_flag_Open(ezButtonToFlag[BUTTON_CODEC_TOGGLE]); }
  if (AUD_SUCCESS == iRet) 
  { 
	  iRet = adi_flag_SetDirection(ezButtonToFlag[BUTTON_CODEC_TOGGLE], 
	                               ADI_FLAG_DIRECTION_INPUT);
  }
  if (AUD_SUCCESS == iRet) 
  {
	  iRet = adi_flag_InstallCallback(ezButtonToFlag[BUTTON_CODEC_TOGGLE], 
	                                  FLAG_PERIPHERAL_ID, 
	                                  ADI_FLAG_TRIGGER_RISING_EDGE, TRUE,
	                                  (void*)EZ_FIRST_BUTTON, NULL, 
	                                  SpeexToggleCallback);
  }
  if ((AUD_SUCCESS == iRet) && (SPEEX_ENABLED ==  g_iSpeexEnabled))
    { ezTurnOnLED(LED_CODEC_ENABLED); }
  
  return iRet;
}

//-----------------------------------------------------------------------------
// CallBackFunction() - Gets called by the audio pseudo-driver when a new audio
//                      block is ready to be played.
//-----------------------------------------------------------------------------
int CallBackFunction(PVOID inputBuffer, PVOID outputBuffer, PVOID userData)
{
  ezTurnOnLED(LED_CALLBACK);
  CYCLES_START(g_CycleStats);
    
  int                    i;
  volatile ARTYP_16BITS *in =(ARTYP_16BITS *)inputBuffer;
  volatile ARTYP_16BITS *out=(ARTYP_16BITS *)outputBuffer;        
  short                 *in_left = in_buffer_calc;   //for stereo encoding
  short                 *out_left = out_buffer_calc; //for stereo encoding

  if (SPEEX_DISABLED == g_iSpeexEnabled) //Activated by a pushbutton
  { 
    memcpy((void *)out, (void *)in, 
           SPEEX_FRAME_SIZE*NUM_CHANNELS*sizeof(ARTYP_16BITS));
  }
  else //Speex is enabled so do a lot more work
  {
#ifndef ACTIVATE_STEREO   
    // Demux the stereo  stream            
    for(i = 0; i < SPEEX_FRAME_SIZE; i++) 
    {
      *in_left++ = *in++; // channel 1
      in++; // channel 2     
    }        
    in_left = in_buffer_calc;
    speex_bits_reset(&g_Bits);
      
    // Encode the frame
    speex_encode_int(g_pvEnc, (spx_int16_t *)in_left, &g_Bits);
    nbBits = speex_bits_write(&g_Bits, cbits, 200);
    speex_bits_rewind(&g_Bits);
  
    // Decode the frame
    speex_decode_int(g_pvDec, &g_Bits, (spx_int16_t *)out_left);
         
#ifdef ALWAYS_PASS_THROUGH
    memcpy(out_buffer_calc, in_buffer_calc, 
           SPEEX_FRAME_SIZE*sizeof(SND_FIXED_16_TYPE));
#endif //ALWAYS_PASS_THROUGH
    // Mux the Speex output onto the stereo audio stream
    for(i = 0; i < SPEEX_FRAME_SIZE; i++) 
    {
      *out++ = *out_left; // channel 1
      *out++ = *out_left++; // channel 2     
    }
#else //ACTIVATE_STEREO
    speex_bits_reset(&g_Bits);

    // Encode the frame
    speex_encode_stereo_int((spx_int16_t *)in, SPEEX_FRAME_SIZE, &g_Bits);
    speex_encode_int(g_pvEnc, (spx_int16_t *)in, &g_Bits);
    g_nBits = speex_bits_write(&g_Bits, g_cBits, 200);
    speex_bits_rewind(&g_Bits);
  
    // Decode the frame
    speex_decode_int(g_pvDec, &g_Bits, (spx_int16_t *)out);
    speex_decode_stereo_int((spx_int16_t *)out, SPEEX_FRAME_SIZE, &g_StereoState);  
         
#ifdef ALWAYS_PASS_THROUGH
    memcpy(out, in, SPEEX_FRAME_SIZE*NUM_CHANNELS*sizeof(SND_FIXED_16_TYPE));
#endif //ALWAYS_PASS_THROUGH
#endif //ACTIVATE_STEREO
}

  if (1 == ezIsButtonPushed(BUTTON_QUIT)) { g_EOS = 1; } //Quit Btn pressed
  CYCLES_STOP(g_CycleStats);
  ezTurnOffLED(LED_CALLBACK);
  return 0;
}

//-----------------------------------------------------------------------------
// InitializeSpeex() -
//-----------------------------------------------------------------------------
int InitializeSpeex(void)
{
  int tmp;

  // Initialize the Speex encoder and decoder
  g_pvEnc = speex_encoder_init(&speex_wb_mode);
  g_pvDec = speex_decoder_init(&speex_wb_mode);
    
  // Initialize the Speex encoder and decoder
  g_Callback.callback_id = SPEEX_INBAND_CHAR;
  g_Callback.func = speex_std_char_handler;
  g_Callback.data = stderr;
  speex_decoder_ctl(g_pvDec, SPEEX_SET_HANDLER, &g_Callback);

  g_Callback.callback_id = SPEEX_INBAND_MODE_REQUEST;
  g_Callback.func = speex_std_mode_request_handler;
  g_Callback.data = stderr;
  speex_decoder_ctl(g_pvDec, SPEEX_SET_HANDLER, &g_Callback);
   
  g_Callback.callback_id = SPEEX_INBAND_MODE_REQUEST;
  g_Callback.func = speex_std_mode_request_handler;
  speex_decoder_ctl(g_pvDec, SPEEX_SET_HANDLER, &g_Callback);

#ifdef ACTIVATE_STEREO    
  g_Callback.callback_id = SPEEX_INBAND_STEREO;
  g_Callback.func = speex_std_stereo_request_handler;
  g_Callback.data = &g_StereoState;
  speex_decoder_ctl(g_pvDec, SPEEX_SET_HANDLER, &g_Callback);
#endif
          
  tmp=1;
  speex_decoder_ctl(g_pvDec, SPEEX_SET_ENH, &tmp);
  tmp=1;
  speex_encoder_ctl(g_pvEnc, SPEEX_SET_VBR, &tmp);
  tmp=3;
  speex_encoder_ctl(g_pvEnc, SPEEX_SET_QUALITY, &tmp);
  tmp=3;   
  speex_encoder_ctl(g_pvEnc, SPEEX_SET_COMPLEXITY, &tmp);
  speex_bits_init(&g_Bits);    
  return 0;
}

//-----------------------------------------------------------------------------
// TerminateSpeex() -
//-----------------------------------------------------------------------------
int TerminateSpeex(void)
{
  // Dispose of the Speex objects
  speex_encoder_destroy(g_pvEnc);
  speex_decoder_destroy(g_pvDec);
  speex_bits_destroy(&g_Bits);    
  return 0;
}

//-----------------------------------------------------------------------------
// SpeexToggleCallback() - This callback function is used to toggle on/off the
//                         SPEEX loopback for a simple loopback
//-----------------------------------------------------------------------------
void SpeexToggleCallback(PVOID AppHandle, u32 Event, PVOID pArg) 
{
  //The semaphore should really be protected against interrupts
  if (g_iSpeexEnabled == SPEEX_DISABLED) 
    { g_iSpeexEnabled = SPEEX_ENABLED; ezTurnOnLED(LED_CODEC_ENABLED); }
  else if (g_iSpeexEnabled == SPEEX_ENABLED) 
    { g_iSpeexEnabled = SPEEX_DISABLED; ezTurnOffLED(LED_CODEC_ENABLED); }
}
