/*****************************************************************************\

  File Name: VorbisDemo.c
  
  Description: This is the main file for a decoder/player of audio streams.
               We demonstrate how to plauy any supported stream/clip (from)
               a file.  The list of supported filter en/decoders is shown
               below.  We also demonstrate how to send/receveive PCM data 
               to/from the supported codecs.  This list of supported codecs
               is shown below.
               
               Supported filters:
               - Ogg Vorbis:  Using the Tremor low memory implementation.
               
               For more details on hardware and other important notes please
               refer to the VorbisPlay-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 "AudioFilterIF.h"
#include "EZKitHWConfig.h"

//-----------------------------------------------------------------------------
//  D E F I N E S
//-----------------------------------------------------------------------------
#ifdef _DEBUG_
#define FLG_DEBUG true
#else //_DEBUG_
#define FLG_DEBUG false
#endif

//-----------------------------------------------------------------------------
//  G L O B A L S 
//-----------------------------------------------------------------------------
AUDIOSTATE     g_AudSt; //Audio stream state structure
AUDSTREAMPROP  g_SP;    //Stream properties
AUDDEVPROPS    g_DP;    //Device properties
SYSHANDLES     g_Hndls; //System handles
FILTERFUNCS    g_FFns;  //Filter function pointers
volatile int   g_EOS;   //End of stream semaphore used to end playback

//Data buffer to store filter/decoder output
section ("L1_data_a") short pcmout[NUM_FRAMES * NUM_CHANNELS]; 

//The following buffers is passed to snd_Open
//Double "ping-pong" buffer for the audio transfers over the SPORT
section ("voldata") volatile ARTYP_16BITS TxBuffer[SZ_FRAMEBFR];

//Device Driver variables
ADI_DCB_HANDLE         g_DCBHandle;  //Handle to the callback service

//-----------------------------------------------------------------------------
//  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);
ENCDRID GetStreamID(FILE *);
bool    OpenStream(PAUDIOSTATE, char *);
void    CloseStream(PAUDIOSTATE);
bool    OpenSoundDevice(PAUDIOSTATE);
void    CloseSoundDevice(PAUDIOSTATE);

//-----------------------------------------------------------------------------
// main() - 
//-----------------------------------------------------------------------------
int main(void)
{
  int i, bytesRead, Result;

  printf("\n\n\nAudio Play 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 
  CLR_AUDIOSTATE((&g_AudSt)); //clear all state structure values
  g_AudSt.pStrProp = &g_SP; //Setup the pointer to the stream prop
  CLR_STRMPROP(g_AudSt.pStrProp); //Clear the stream properties structure
  g_AudSt.pDevProp = &g_DP; //Setup the pointer to the device prop
  CLR_DEVPROP(g_AudSt.pDevProp); //Clear the device properties structure
  g_AudSt.pHndl = &g_Hndls; //Setup the pointer to systems handles
  CLR_SYSHANLDLES(g_AudSt.pHndl); //Clear the handle pointers
  g_AudSt.pCBFunc = CallBackFunction; //Init the callback pointer

  //Open the stream file and initialize the stream information as specified by
  //the file contents
  if (false == (g_AudSt.bStrInit=OpenStream(&g_AudSt, OGG_FILENAME)))
  { 
    ERRPRINTF("opening the file - exiting!\n");
    ezTurnOnAllLEDs(); exit(0);
  }

  //Open and initialize the desired sound device hardware.
  printf("Opening and initializing the sound device...");
  if (false == OpenSoundDevice(&g_AudSt))
  {
    ERRPRINTF("opening the sound device - exiting!\n");
    ezTurnOnAllLEDs(); exit(0);
  }
  printf(" succeeded\n\n");

  //If we have reached this far it means that all out stream and audio device
  //related initialization was successful so ificate it and proceed to play
  //audio stream
  ezTurnOnLED(LED_INIT_SUCCESS);  //If we got this far, init was successful
  printf("LED%d - successful system init\n", (LED_INIT_SUCCESS+BASE_LED));
  printf("LED%d - toggles inside the idle loop\n", (LED_IDLE+BASE_LED));
  printf("        Note: small NUM_FRAMES causes fast duty cycle!\n"); 
  printf("LED%d - toggles during audio callbacks\n", (LED_CALLBACK+BASE_LED));
  printf("        Note: fast duty cycle makes LEDs appear dim\n\n");
  FLUSHPRINTF("Press button SW%d to stop the audio\n\n",
              (BUTTON_QUIT+BASE_BUTTON));

  printf("Starting the audio stream play.\n");
  AudDev_Start(); //Start sound system...
  FLUSHPRINTF("Playing...\n");
  g_EOS = 0; //Init flag/semaphore value
  while(0 == g_EOS)
  {
    //Note: g_EOS is set inside audio callback upon end of stream    
    ezToggleLED(LED_IDLE); //toggle IDLE LED in the foreground as the func
                           //Callback decodes a Vorbis stream
    if (ezIsButtonPushed(BUTTON_QUIT) == 1) //BUTTON_QUIT pressed
      { FLUSHPRINTF("QUIT button pressed!\n"); g_EOS = 1; }
  }
  FLUSHPRINTF("finished with audio clip.\n");

  //Perfrom cleanup and resource freeing...  
  CloseSoundDevice(&g_AudSt); //Halt, cleanup and free sound device
  CloseStream(&g_AudSt); //Cleanup and release OV stream resource
  FLUSHPRINTF("Terminating Stream Player/Decoder 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;
  
    /* initialize the ezKit power,EBIU,DMA.... */
	adi_ssl_Init();	

	/* enable and configure async memory */
   	ezInit(1);
 
 	/* 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     

   	
  if (AUD_SUCCESS == iRet) 
    { iRet = adi_int_CECHook(3, ExceptionHandler, NULL, FALSE); }
    
  if (AUD_SUCCESS == iRet) { adi_int_CECHook(5, HWErrorHandler, NULL, FALSE); }


  //Initialize Buttons & LEDs....
  printf("Initializing LED flags and buttons\n");
  //ezInit(1); //initis cores for power management
  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
  
  return iRet;
}

//-----------------------------------------------------------------------------
// CallBackFunction() - Function gets called by the audio driver (indirectly)
//                      when a new audio block is ready to be played.
//
// Note: - In here one must know whether this call was for input or output.  At
//         present we assume output data is needed since we're only playing out
//         data.  Ultimately one must change it so that it also encodes any 
//         captured audio data.
//-----------------------------------------------------------------------------
int CallBackFunction(PVOID inputBuffer, PVOID outputBuffer, PVOID userData) 
{
  int iRet=0;
  int bytesFilled=0;
  int bytesRead;
  int numOutBytes=NUM_FRAMES*NUM_CHANNELS*sizeof(u16);

  volatile PARTYP_16BITS out = (PARTYP_16BITS)outputBuffer;

  ezTurnOffLED(LED_IDLE); //turn off IDLE LED since we're not idle
  if (1 == g_EOS) { return 1; } //Nothing to do so return
  ezTurnOnLED(LED_CALLBACK); //Indicate callback state

  //Fill up the decoded audio buffer provide we're not done either by signaling
  //end of stream or by flaging return condition.
  while ((bytesFilled < numOutBytes) && (0 == g_EOS) && (0 == iRet))
  {
    bytesRead = g_FFns.pReadData((char *)out+bytesFilled, 
                                 numOutBytes-bytesFilled); //Get more data
                        
    //Check to see if we have any data to playout?
    if (bytesRead <= 0) { g_EOS = 1; iRet = 1; } //Signal end of stream!
    else { bytesFilled += bytesRead; } //Add bytes read to the count
  }   

  //Check if PB1 was pressed indicating sstop of play desired by user
  if (ezIsButtonPushed(BUTTON_QUIT) == 1) { g_EOS = 1; }
  ezTurnOffLED(LED_CALLBACK);
  return iRet;
}

//-----------------------------------------------------------------------------
// GetStreamID() - Function returns the stream ID as read from the clip.
//
//          A D I    B L A C K F I N    D E V E L O P E R    N O T E
// Note:  Add code that calls the filter (en/decoder) specific function that
//        is used to determine and check the filter format ID.
//-----------------------------------------------------------------------------
ENCDRID GetStreamID(FILE *fp)
{
  ENCDRID fid=ENCDR_UNKNOWN; //Assume unknow decoder!
  if (ENCDR_UNKNOWN == fid) {fid = GetFilterID_OggVorbis(fp); }
  return fid;
}

//-----------------------------------------------------------------------------
// OpenStream() - Function open the passed stream file and initializes the 
//                stream properties structure as specified by the encoder
//                format.
//
//                NOTE: NUM_BYTES_TO_DECODE are defined at the beginning of 
//                      this file.
//-----------------------------------------------------------------------------
bool OpenStream(PAUDIOSTATE pAS, char *szFN)
{
  FILE           *pFIn=NULL;
  AUDRETVALUE    audRet=AUD_SUCCESS;
  PAUDSTREAMPROP pSP=NULL;
  
  //Check and make sure that none of our pointers are NULL...
  if (NULL == pAS)
    { ERRPRINTF("invalid state properties struct pointer!\n"); return false; }
  if (NULL == (pSP = pAS->pStrProp)) //Initialize prop pointers
    { ERRPRINTF("invalid stream properties struct pointer!\n"); return false; }
  
  //Open the file stream file to read from it.  At some point we will have to 
  //read from a file system, but for now we will read a portion of the file
  //into the BlackFin memory.
  if (!strcmp("", szFN)) //Make sure the file name is NOT null.
    { ERRPRINTF("no filename was specified!\n"); return false; }
  FLUSHPRINTF("Opening file \"%s\"\n", szFN);
  if (NULL == (pFIn=fopen(szFN, "rb")))
    { ERRPRINTF("opening \"%s\"!\n", szFN); return false; }

  //Get the filter format ID and then proceed to initialize as needed...
  pSP->EID = GetStreamID(pFIn); //Get filter ID.
  switch (pSP->EID)
  {
    //        A D I    B L A C K F I N    D E V E L O P E R    N O T E
    //Note: Add a CASE for every filter that gets added.  Along with the added 
    //      case, a developer must have implemented a function that returns a 
    //      TRUE/FALSE indication of where it is of type matching the case 
    //      below.
    case ENCDR_OGGVORBIS: 
      //Init the OV filter and get it's supported entry functions.
      audRet = FilterInit_OggVorbis(&g_FFns);
      if (AUD_SUCCESS == audRet) { audRet = g_FFns.pOpen(pSP, pFIn); }
      if (AUD_SUCCESS != audRet)
        { ERRPRINTF("failed to init Ogg Vorbis filter!\n"); return false; }
      break; 
        
    case ENCDR_PCM: case ENCDR_WAV: case ENCDR_MP3: case ENCDR_OGGSPEEX:
      ERRPRINTF("encoder support has not been added!\n"); return false;
      
    default: ERRPRINTF("unknown audio encoder!\n"); return false;
  }
  
  //Initialize stream direction (IN/OUT/BOTH)...
  pSP->Direction = AUD_DIRECTION_UNDEFINED;
  if (pSP->InChCnt > 0)  { pSP->Direction |= AUD_DIRECTION_INBOUND; }
  if (pSP->OutChCnt > 0) { pSP->Direction |= AUD_DIRECTION_OUTBOUND; }
  if (AUD_DIRECTION_UNDEFINED == pSP->Direction)
    { ERRPRINTF("no known stream direction specified!\n"); return false; }

  //Initialize the stream data bit size and byte count...
  switch(pSP->Format) 
  {
    case AUD_SAMPLFORMAT_FIXED16: 
      pSP->DatBitSz = 16; pSP->DatBytCnt = sizeof(ARTYP_16BITS); break;
    case AUD_SAMPLFORMAT_FIXED20: 
      pSP->DatBitSz = 20; pSP->DatBytCnt = sizeof(ARTYP_20BITS); break;
    case AUD_SAMPLFORMAT_FIXED24: 
      pSP->DatBitSz = 24; pSP->DatBytCnt = sizeof(ARTYP_24BITS); break;
    default: 
      ERRPRINTF("unknown stream data bit size or byte count!\n"); 
      return false;
  }  
  return true;
}

//-----------------------------------------------------------------------------
// CloseStream() - Cleans up and closes any stream related resources.
//-----------------------------------------------------------------------------
void CloseStream(PAUDIOSTATE pAS)
{
  PAUDSTREAMPROP pSP=NULL;
 
  FLUSHPRINTF("Closing audio stream & freeing up resources...\n");
  
  //Make sure we have no NULL pointers
  if (NULL == pAS) { return; }
  if (NULL == (pSP = pAS->pStrProp)) { return; }
    
  g_FFns.pClose(pSP); //Close the filter as specified by the struct entry
  
  //Do other resource cleaup and release...
  if (NULL != pSP->pRndrBfr) { free(pSP->pRndrBfr); }
  if (NULL != pSP->pCptrBfr) { free(pSP->pCptrBfr); }
  CLR_STRMPROP(pSP); //Clear the stream properties structure
}

//-----------------------------------------------------------------------------
// OpenSoundDevice() - Opens and initializes the sound device.
//-----------------------------------------------------------------------------
bool OpenSoundDevice(PAUDIOSTATE pAS)
{
  //Initialize and start the sound device hardware
  pAS->bDevInit = IS_SUCCESS(AudDev_Init(pAS, (PBYTE)0, (PBYTE)TxBuffer,
                                         adi_dev_ManagerHandle, 
                                         adi_dma_ManagerHandle, g_DCBHandle));
  return ((true == pAS->bDevInit) ? IS_SUCCESS(AudDev_Open()) : false);
}

//-----------------------------------------------------------------------------
// CloseSoundDevice() - Used to cleanup and close the sound device,
//-----------------------------------------------------------------------------
void CloseSoundDevice(PAUDIOSTATE pAS)
{
  FLUSHPRINTF("Halting and closing the sound device...\n");    
  if (NULL == pAS) { return; } //Make sure we have no NULL pointers
  if (NULL == pAS->pDevProp) { return; }
  AudDev_Stop(); AudDev_Close(); //Perform device termination functions
  CLR_DEVPROP(pAS->pDevProp); //Clear device properties structure
}
