/*************************************************************************
 * @file		mp3dec_main.c
 * @brief		main test program for the MP3 player project
 * @version		1.1
 * @date		23. Feb. 2012
 * @author		NXP MCU SW Application Team
 *
 * Description:
 * This solution takes full advantage of LPC1700's high performance and rich
 * peripherals to makes it possible to playback MP3 files.
 * USB Host stack and file system are also integrated to support access files
 * in SDC/MMC card and USB flash disk
 *
 * Source:
 *   - Helix MP3 Decoder: by RealNetworks, https://datatype.helixcommunity.org/Mp3dec
 *       It has been ported to thumb-2 by NXP Semiconductors.
 *   - FatFs: by ChaN, http://elm-chan.org/fsw/ff/00index_e.html
 *       It has been ported to LPC1700 to access SDC/SDHC/MMC/USB
 *   - USBHosteLite: by NXP Semiconductors and OnChip Technologies, http://ics.nxp.com/support/software/usb.host.msc/
 *   - main program: by NXP Semiconductors
 *
 *************************************************************************
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * products. This software is supplied "AS IS" without any warranties.
 * NXP Semiconductors assumes no responsibility or liability for the
 * use of the software, conveys no license or title under any patent,
 * copyright, or mask work right to the product. NXP Semiconductors
 * reserves the right to make changes in the software without
 * notification. NXP Semiconductors also make no representation or
 * warranty that such application will be suitable for the specified
 * use without further testing or modification.
 ************************************************************************/

#include <string.h>
#include "LPC17xx.h"
#include "mp3dec.h"
#include "coder.h"  
#include "UDA1380.h"
#include "lcd.h"
#include "ff.h"
#include "ffconf.h"
#include "usbhost_inc.h"
#include "Storage.h"

/* set to 1 if CCLK is below 74MHz, otherwise set to 0 */
#define CCLK_BELOW_74MHZ 0 

/*----------------------------------------------------------------------------
  DEBUG configuration
 *----------------------------------------------------------------------------*/
/* Enable print debug messages via UART */ 
#define ENABLE_DEBUG_UART 1
#if ENABLE_DEBUG_UART
#define DEBUG_INIT  UART_Init
#define DEBUG_MSG   printf
#else
#define DEBUG_INIT(x)      do {} while(0)
#define DEBUG_MSG(...)     do {} while(0)
#endif

/*----------------------------------------------------------------------------
  RAM allocation
 *----------------------------------------------------------------------------*/
/* SRAM allocation: 
 *
 * IRAM: 0x10000000 - 0x10007FFF [32 kBytes] for data, bss & stack.
 * AHB0: 0x2007C000 - 0x2007DFFF [ 8 kBytes] for READBUF
 * AHB0: 0x2007E000 - 0x2007EFFF [ 4 kBytes] for USBHost
 * AHB1: 0x20080000 - 0x20083FFF [16 kBytes] for AUDIOBUF
*/

/* USBHostLite has a low file read speed (around 9ms/8kB), it might be
 * necessary to reduce USB buffer size to 4kB to ensure smooth playback.
 * However, for the SD card the file read speed is fast enough, so the
 * buffer can be 8kB for sure.
 */
#define  READBUF_BASEADDR	0x2007C000
#define  READBUF_SIZE_USB  (1024*8)
#define  READBUF_SIZE_SD  (1024*8)

#define  AUDIOBUF_BASEADDR	0x20080000
#define  AUDIOBUF_SIZE		(0x1200)	/* 4608 byte (4.5KB) */

/* Allocate 2 or 3 audio buffer to store PCM after decoding */
#define  MAX_AUDIOBUF_NUM	3
#if (MAX_AUDIOBUF_NUM < 2) || (MAX_AUDIOBUF_NUM > 3)
    #error Invalid MAX_AUDIOBUF_NUM setting.
#endif

/*----------------------------------------------------------------------------
  MP3
 *----------------------------------------------------------------------------*/
enum {AUDIOBUF0=0, AUDIOBUF1=1, AUDIOBUF2};
typedef struct
{
	uint32_t BaseAddr;  /* base address for the audio buf */
	int32_t Size;		/* size of the audio buf */
	uint8_t Empty;		/* buf status, 1-> empty */
} AUDIOBUF;

typedef struct MP3List_t
{
    uint8_t drive;					/* drive */
    char title[13];   /* mp3 title */
} MP3List_t;

/*----------------------------------------------------------------------------
  FAT
 *----------------------------------------------------------------------------*/
FILINFO Finfo;
FATFS Fatfs[_VOLUMES];		/* File system object for each logical drive */
FIL File1;			        /* File objects */
DIR Dir;					/* Directory object */
uint8_t Drives;				/* Drives mask */

/*----------------------------------------------------------------------------
  I2S and DMA
 *----------------------------------------------------------------------------*/
/* Wait for DMA is completed. */
#define WAIFORDMADONE   while(I2S_DMA_Done == 0) 
/* Wait for I2S TX buffer is empty. */
#define WAIFORI2SDONE	while (((LPC_I2S->I2SSTATE>>16) & 0xF) != 0)

/* GPDMA for I2S */
#define I2S_DMA_CHAN	7
#if I2S_DMA_CHAN<0 || I2S_DMA_CHAN>8
	#error Invalid DMA channel number!
#endif
#define DMA_I2S_TX_FIFO	0x400A8008

#define RENAME(x,y)  RENAME2(x,y)
#define RENAME2(x,y)	x##y 

LPC_GPDMACH_TypeDef *LPC_GPDMACH = RENAME(LPC_GPDMACH, I2S_DMA_CHAN);

volatile uint8_t I2S_DMA_Done = 0;
volatile uint8_t DMA_Buf_Empty = 0;

/*----------------------------------------------------------------------------
  MP3 variables
 *----------------------------------------------------------------------------*/

/* Play from audio buf 0, then move to the next buf. */
volatile uint8_t PlayingBufIdx = AUDIOBUF0;
volatile uint8_t DecodeBufIdx = AUDIOBUF0;

HMP3Decoder hMP3Decoder;

MP3FrameInfo mp3FrameInfo; 
uint8_t *readBuf, *readPtr;
int bytesLeft, outOfData, eofReached;
uint32_t startTime, endTime, totalDecTime, nFrames;
uint32_t totalReadFileTime, nReadFileBlkCnt;

uint32_t pcmframe_size;
float audioSecs;

volatile static uint32_t CurrentMP3, PreviousMP3;
volatile static uint32_t mp3_cnt;
static char *mp3_title;   /* Point to the current MP3 title */

MP3List_t MP3List[255];
volatile static unsigned char Cursor;

AUDIOBUF audiobuf[MAX_AUDIOBUF_NUM] = {
	{AUDIOBUF_BASEADDR, -1, 1},
	{AUDIOBUF_BASEADDR+AUDIOBUF_SIZE, -1, 1},
#if MAX_AUDIOBUF_NUM==3
	{AUDIOBUF_BASEADDR+AUDIOBUF_SIZE*2, -1, 1},
#endif
};

/*----------------------------------------------------------------------------
  others
 *----------------------------------------------------------------------------*/
#define LED_2           (1UL<<24)          /* P1.24 */
#define LED_4           (1UL<<28)          /* P1.28 */
#define LED_5           (1UL<<29)          /* P1.29 */

#define LED_FRAME_OUT   LED_2
#define LED_DEC_SLOWER  LED_4
#define LED_DEC_FASTER  LED_5

static volatile uint32_t Timer;  /* Performance timer (1kHz increment) */

uint8_t frame_out_flag = 0;
uint8_t decode_slower_flag = 0;
uint8_t decode_faster_flag = 0;

/*----------------------------------------------------------------------------
  local functions 
 *----------------------------------------------------------------------------*/
void    I2S_Init(void);
void    I2S_Mute (uint8_t on);
uint8_t I2S_ConfigBitrate( uint8_t channel, uint8_t wordwidth, uint32_t samplerate);
void    I2S_SearchPara(uint32_t bitrate, uint8_t *x, uint8_t *y, uint8_t *z);
void    I2S_Start(void);
void    I2S_Stop(void);
void    LED_Init (void);
void    LED_On (uint32_t led);
void    LED_Off (uint32_t led);
void    DMA_Init(void);
void    DMA_SendData(uint32_t *buf, uint32_t size);
void    ADC_Start (void);
void    ADC_Init (void);
uint8_t MP3_DecodeFrame (void);
void    MP3_AddAudioBuf (uint32_t len);
void    MP3_DisplayFormat(void);
uint8_t Mem_Init (void);
void    Mem_DeInit (void);
uint32_t Mem_SearchMp3(uint8_t CountOnly);
uint8_t MP3_Playback (void);
uint8_t Mem_OpenFile (char *fname);
uint8_t Mem_CloseFile (void);
uint8_t static IsMP3File (const char * fname);
uint32_t static LPC17xx_GetFpclk(uint8_t clksel);

uint8_t CursorDown(void);
uint8_t CursorUp(void);
uint8_t CursorSet(uint8_t Position);
void CursorEnter(void);
void CursorUpdate(void);

/*----------------------------------------------------------------------------
  Functions 
 *----------------------------------------------------------------------------*/
/* SysTick Interrupt Handler (1ms)    */
void SysTick_Handler (void)
{
    static uint32_t div10, div500;
    volatile unsigned char key;
    static uint32_t Number = 0;
    static uint8_t Counter = 0;
    static uint8_t Buttons;
    
    Timer++;
    
    if (++div10 >= 10)
    {
        div10 = 0;
        disk_timerproc(); 				  /* Disk timer function (100Hz) */
    }
    
    if (++div500 >= 500)                  /* Set Clock1s to 1 every 1 second    */
    {
        div500 = 0;    
        ADC_Start ();    
    }

    if(LPC_UART0->LSR & 1<<0)
    {
    	/* new byte in UART */
    	key = UART_GetChar();
    	if(key == '\r' || key == '\n')
    	{
    		/* Enter has been pressed */
    		if (Counter > 0)
    		{
    			/* Number contains some number */
    			if(Number > mp3_cnt || Number == 0)
    				DEBUG_MSG("\r\nError: Invalid number (%d)\r\n", Number);		/* Entered number is too high */
    			else
    			{
    				CursorSet(Number-1);											/* Play song at Number */
    				CursorEnter();
    			}
    		}
    		Counter = 0;
    		Number = 0;
    	}
    	else if(key >= 0x30 && key <= 0x39)
    	{
    		/* User hit button 0-9 */
    		UART_PutChar(key);
    		Number = Number*10 + key-0x30;
    		Counter++;
    	}
    }

    /* Button handler */
    if(!(LPC_GPIO0->FIOPIN & (1<<10)) && (Buttons & (1<<2)))						/* if button pressed now, and not pressed previous iteration */
    	CursorUp();
    if(!(LPC_GPIO0->FIOPIN & (1<<5)) && (Buttons & (1<<1)))							/* if button pressed now, and not pressed previous iteration */
        	CursorDown();
    if(!(LPC_GPIO0->FIOPIN & (1<<6)) && (Buttons & (1<<0)))							/* if button pressed now, and not pressed previous iteration */
        	CursorEnter();

    Buttons = ((LPC_GPIO0->FIOPIN >> 10) & 0x01) << 2 |
    		  ((LPC_GPIO0->FIOPIN >>  5) & 0x01) << 1 |
    		  ((LPC_GPIO0->FIOPIN >>  6) & 0x01) << 0;
}

/*----------------------------------------------------------------------------
  MAIN function 
 *----------------------------------------------------------------------------*/
/**
  * @brief  main program.
  *
  * @param  None
  * @retval None
  *
  */
int main ()
{
    uint32_t retv;
    char string[17];

#if !CCLK_BELOW_74MHZ
    I2S_Init();
    I2S_Start();
    UDA1380_Init(); 
#endif

	SystemInit();
    SysTick_Config(SystemFrequency/1000-1);  /* Generate interrupt each 1 ms */
	DEBUG_INIT(115200); /* UART_PCLK=CCLK/2 */
    ADC_Init();
    LED_Init();  
    /* Initialize the ST7637 LCD Controller/driver for use */
    LCDdriver_initialisation();

    /* Should start I2S first then config UDA1380 */
    I2S_Init();
    I2S_Start();
    UDA1380_Init();
    UDA1380_MasterVolCtrl(0, 0);
    DMA_Init(); /* Init DMA after init I2S */

    DEBUG_MSG("\n\rMP3 player demo on NXP LPC1768.\n\r\n\r");
    sprintf(string, "CCLK=%dMHz\n\r\n\r", SystemFrequency/1000000);
    DEBUG_MSG(string);

    LCD_PrintString(0*8, 0*15, "MP3 player demo", COLOR_WHITE, COLOR_BLACK);
    LCD_PrintString(0*8, 1*15, "on NXP LPC1768.", COLOR_WHITE, COLOR_BLACK);
    LCD_PrintString(0*8, 2*15, string, COLOR_WHITE, COLOR_BLACK);

    /* Init memory card (USB, SDC/MMC */
    Drives = Mem_Init();
    if (Drives == 0)
    {
        DEBUG_MSG ("Memory: Init failed.\n\r");
        LCD_PrintString(0*8, 7*15, "Mem: Init Fail", COLOR_RED, COLOR_BLACK);
        goto loop;    
    }

    LCD_ClearScreen(COLOR_BLACK);
    /* Search MP3, and store in mp3_list[][] */
    mp3_cnt = Mem_SearchMp3(1);
    if (mp3_cnt == 0)
    {
        DEBUG_MSG ("Found no MP3 files.\n\r");
        LCD_PrintString(0*8, 0*15, "No MP3's found", COLOR_RED, COLOR_BLACK);
        goto loop;
    }

    /* Display all searched MP3 */
    DEBUG_MSG("\n\r%d MP3 files:\n\r", mp3_cnt);
    Mem_SearchMp3(0);
    sprintf(string, "%d MP3 files:", mp3_cnt);
    LCD_PrintString(0*8, 0*15, string, COLOR_WHITE, COLOR_BLACK);

    /* Init MP3 decoder */
	if ( (hMP3Decoder = MP3InitDecoder()) == NULL )
    {
        DEBUG_MSG("MP3 Decoder Init failed!\r\n");
        goto loop;
    }

    /* Play all mp3 */
    CursorSet(0);
    CursorEnter();
    do 
    {
        mp3_title =  MP3List[CurrentMP3].title;
        sprintf(string, "%d:%s", MP3List[CurrentMP3].drive, MP3List[CurrentMP3].title);
        DEBUG_MSG ("\n\r%d: %s ", CurrentMP3+1, mp3_title);
        
        retv = Mem_OpenFile (string);
        if (retv == 0x01) /* Failed to open file */
        {
            DEBUG_MSG("is not found.\n\r");
            CursorSet(CurrentMP3 + 1);
            CursorEnter();
            continue;
        }

        DEBUG_MSG("is playing ...\n\r"); 

        /* Play */
        retv = MP3_Playback();

        /* Close file */
        /* If file is already closed, the next song to play is already set, so nothing should be done.
           But, if the file is not yet closed, the next song should be selected. */
        if(Mem_CloseFile() != 9)
        {
        	/* File not closed yet, play next song */
    		CursorSet(CurrentMP3 + 1);
    		CursorEnter();
        }

        if (retv)
        {
            DEBUG_MSG("failed with %d\n\r", retv);
            continue; /* Playback next mp3 */    
        }
            
    } while (1);

loop: 
    I2S_Stop();
    Mem_DeInit();

    while (1);
}

/*----------------------------------------------------------------------------
  LED function 
 *----------------------------------------------------------------------------*/
/**
  * @brief  Initializes GPIO for LED.
  *
  * @param  None
  * @retval None
  *
  */
__inline void LED_Init (void) 
{
  LPC_GPIO1->FIODIR |= 0xB0000000; /* Only use 3 LEDs on PORT1  */
}

/**
  * @brief  Turn on LED.
  *
  * @param  led: Specifies the led number, should be one of the following value:
  *              LED_0, LED_1, LED_2
  * @retval None
  *
  */
__inline void LED_On (uint32_t led) 
{
    LPC_GPIO1->FIOSET = led;
}
/**
  * @brief  Turn off LED.
  *
  * @param  led: Specifies the led number, should be one of the following value:
  *              LED_0, LED_1, LED_2
  * @retval None
  *
  */
__inline void LED_Off (uint32_t led) 
{
    LPC_GPIO1->FIOCLR = led;
}

/*----------------------------------------------------------------------------
  ADC functions 
 *----------------------------------------------------------------------------*/

/**
  * @brief  Initializes ADC.
  *
  * @param  None
  * @retval None
  *
  */
void ADC_Init (void) 
{
    LPC_PINCON->PINSEL1 &= ~(3<<14);      /* P0.23 is GPIO */
    LPC_PINCON->PINSEL1 |=  (1<<14);      /* P0.23 is AD0.0 */
    
    LPC_SC->PCONP       |=  (1<<12);      /* Enable power to ADC block */
    
    LPC_ADC->ADCR        =  (1<<0) |     /* select AD0.0 pin */
                          (4<< 8) |     /* ADC clock is 25MHz/5 */
                          (1<<21);      /* enable ADC */ 
    
    LPC_ADC->ADINTEN     =  (1<< 8);      /* global enable interrupt */
    
    NVIC_EnableIRQ(ADC_IRQn);             /* enable ADC Interrupt */
}

/**
  * @brief  Start one time ADC conversion.
  *
  * @param  None
  * @retval None
  *
  */
__inline void ADC_Start (void)
{
    LPC_ADC->ADCR |=  (1<<24);            /* Start A/D Conversion */    
}

/**
  * @brief  ADC ISR to Adjust the volume according to the ADC value every 500ms.
  *
  * @param  None
  * @retval None
  *
  */
void ADC_IRQHandler(void) 
{
    static uint8_t vol_last = 0;
    uint8_t vol;
    uint16_t AD_value;    

    /* Read ADC result */
    AD_value = (LPC_ADC->ADGDR>>4) & 0xFFF;

    /* convert 12 bit to 8 bit result */
    vol = (AD_value >> 4);   
  
    if (vol != vol_last)
    {
        /* Adjust the volume*/
        UDA1380_MasterVolCtrl (vol, vol);
    }

    vol_last = vol;
}

/*----------------------------------------------------------------------------
  I2S functions 
 *----------------------------------------------------------------------------*/

/**
  * @brief  Initialize I2S.
  *
  * @param  None
  * @retval None.
  *
  *
  */
void I2S_Init (void)
{
    /* Enable I2S in the PCONP register. I2S is disabled on reset. */
    LPC_SC->PCONP |= (1 << 27);
	
    /* Connect the I2S TX sigals to port pins(P0.7-P0.9) */
    LPC_PINCON->PINSEL0 &= ~0x000FC000;
    LPC_PINCON->PINSEL0 |= 0x00054000;

    /* Configure I2S with default setting */
    I2S_ConfigBitrate(2, 16, 44100);

    /* 16bit data, stereo, stop & reset enabled, master, ws_halfperiod=17 */
	LPC_I2S->I2SDAO = 0x419;  
}

/**
  * @brief  Start the I2S TX channel.
  *
  * @param  None
  * @retval None
  *
  */
__inline void I2S_Start()
{
	LPC_I2S->I2SDAO &= ~((1<<3) | (1<<4) | (1<<15));	
}

/**
  * @brief  Stop and reset I2S TX channel.
  *
  * @param  None
  * @retval None
  *
  */
__inline void I2S_Stop()
{

	LPC_I2S->I2SDAO |= (1<<3) | (1<<4);	
}

/**
  * @brief  Mute or un-mute the I2S TX channel.
  *
  * @param  on: 1-mute, 0-un-mute
  * @retval None
  *
  */
void I2S_Mute (uint8_t on)
{
    if (on == 0)
        LPC_I2S->I2SDAO &= ~(1<<15);
    else
        LPC_I2S->I2SDAO |= (1<<15); /* MUTE */
}

/**
  * @brief  Calculate appropriate x/y/z values in order to generate real bit rate 
  *         which is closer to the target value.
  *
  * @param  bitrate: target bit rate (in Hz)
  * @param  x, y, z: pointer to the buffer to store the calculated parameters
  * @retval None
  *
  */
void I2S_SearchPara(uint32_t bitrate, uint8_t *x, uint8_t *y, uint8_t *z)
{
	uint8_t x1, y1, z1, x_div, y_div, z_div, clksel;
	float delta, delta_tmp, delta_min;
	uint32_t pclk;
	
	/* Bit 22~23 is for I2S clock divider. */
	clksel = (LPC_SC->PCLKSEL1 >> 22) & 0x03;
	pclk = 	LPC17xx_GetFpclk(clksel);
	delta_min = 0xFFFF;
	delta = (float)pclk / (bitrate*2);

	for (z1=64;z1>0;z1--)
		for (y1=254;y1>0;y1--)
		{
			x1 = ((double)(y1*z1)/delta + 0.5);
			if (2*x1 >= y1)	 /* make sure x/y < 1/2 to decrease jitter */
				continue;
			
			delta_tmp = (float)y1*z1/x1 - delta;
			if (delta_tmp < 0)
				delta_tmp = -delta_tmp;

        /* Fast search: find parameters which only meet given precision */
		#if 0 
			if (delta_tmp < delta*0.001f)
			{
				if ((y1 % x1) == 0)
				{
					*x = x1; *y = y1; *z = z1;
					DEBUG_MSG("%d, %d, %d\r\n", x1, y1, z1);
					return;					
				}

			}
        #else
         	/* Slow search for the best x/y/z parameters which genetated the most accurate bitrate */
			if (delta_tmp < delta_min)
			{
				delta_min = delta_tmp;
				x_div = x1;	y_div = y1;	z_div = z1;
			}
        #endif
		}

    /* Store the found parameters. */
	*x = x_div; *y = y_div; *z = z_div;

#if 0
    /* Display */
    DEBUG_MSG("bitrate: %d -> %d\r\n", bitrate, (uint32_t)(pclk*x_div)/(2*y_div*z_div));
	DEBUG_MSG("x, y, z=:%d, %d, %d\r\n", x_div, y_div, z_div);
#endif
}

/**
  * @brief  Configure I2S bit rate with specified parameters.
  *
  * @param  channel: Specifies the channel number (1, 2)
  * @param  wordwidth: Specifies data width (8, 16, 32)
  * @param  samplerate: Specifies the sample rate (in Hz)
  * @retval 0-OK, others-failure code
  *
  */
uint8_t I2S_ConfigBitrate (uint8_t channel, uint8_t wordwidth, uint32_t samplerate)
{
    uint32_t ws_half, bitrate;
    uint8_t x, y, z;
    
    /* check parameter */
    if ((channel !=1 && channel != 2) |
        (wordwidth != 8 && wordwidth != 16) | /* 8/16/32 */
        (samplerate > 96000)) /* supported bitrate: 16-96kHz (for lpc1700) */
    return (0x1); /* unsupported format */
    
    I2S_Stop();
    
    /* bit rate config */
    if (wordwidth == 8) ws_half = 16;	/* for 8 bit, set ws_halfperiod to 16 CLK */
    else                ws_half = 17;	/* for 16 bit, set ws_halfperiod to 17 CLK	*/        
    bitrate = ws_half*2* samplerate;
    I2S_SearchPara(bitrate, &x, &y, &z);
    
    LPC_I2S->I2STXRATE = (y & 0xFF) | 	 /* Y */
                        ((x & 0xFF) << 8);	 /* X */
    LPC_I2S->I2STXBITRATE = z-1;   /* Z */
    
    return (0x0);    
}


/*----------------------------------------------------------------------------
  GPDMA functions 
 *----------------------------------------------------------------------------*/

/**
  * @brief  Initializes GPDMA.
  *
  * @param  None
  * @retval None
  *
  */
void DMA_Init(void)
{
	/* Enable GPDMA clock */
	LPC_SC->PCONP |= (1 << 29);	
	/* init DMA	channel x */
    LPC_GPDMA -> DMACIntTCClear = (1 << I2S_DMA_CHAN);
    LPC_GPDMA -> DMACIntErrClr = (1 << I2S_DMA_CHAN);
	 
	LPC_GPDMACH->DMACCDestAddr = DMA_I2S_TX_FIFO;
	LPC_GPDMACH -> DMACCLLI = 0;
	LPC_GPDMACH->DMACCControl = (0x04 << 12) | (0x04 << 15)
	  	| (0x02 << 18) | (0x02 << 21) 
		| (1 << 26) | 0x80000000;  
	LPC_GPDMACH->DMACCConfig = 0x18000 | (0x00 << 1) | (0x05 << 6) | (0x01 << 11);

    /* Enable DMA channels, little endian */
    LPC_GPDMA->DMACConfig = 0x01;	
    while ( !(LPC_GPDMA->DMACConfig & 0x01) );

	/* Enalbe I2S DMA TX */
	LPC_I2S->I2SDMA1 = (0x01<<1) | (0x04<<16);

  	NVIC_EnableIRQ(DMA_IRQn);
	NVIC_SetPriority (DMA_IRQn, 1);
}

/**
  * @brief  Start GPDMA to transer data from memory to peripheral.
  *
  * @param  buf: Pointer to the buffer
  * @param  size: data length in byte
  * @retval None
  *
  */
void DMA_SendData(uint32_t *buf, uint32_t size)
{
	LPC_GPDMACH->DMACCSrcAddr = (uint32_t)buf;
	LPC_GPDMACH->DMACCControl |= (size/4) & 0x0FFF; /* burst size=4 bytes */
	LPC_GPDMACH->DMACCConfig |= 0x01; /* Start DMA to send data */	
}

/**
  * @brief  DMA ISR.
  *
  * @param  None
  * @retval None
  *
  */
void DMA_IRQHandler(void) 
{
    uint32_t regVal;
    
    regVal = LPC_GPDMA->DMACIntErrStat;
    if ( regVal )
    {
        LPC_GPDMA->DMACIntErrClr |= regVal;
    }

    regVal = LPC_GPDMA->DMACIntTCStat;
    if ( regVal )
    {
        LPC_GPDMA->DMACIntTCClear |= regVal;

	    if (regVal & (1<<I2S_DMA_CHAN))
	    {
		    I2S_DMA_Done = 1;

            frame_out_flag = !frame_out_flag;
            if (frame_out_flag) LED_On (LED_FRAME_OUT);
            else                LED_Off (LED_FRAME_OUT);

            /* Data in audiobuf[PlayingBufIdx] has been sent out */
    		audiobuf[PlayingBufIdx].Empty = 1;
    		audiobuf[PlayingBufIdx].Size = -1;

		    /* Send the data in next audio buffer */
		    PlayingBufIdx++;
		    if (PlayingBufIdx == MAX_AUDIOBUF_NUM)
			    PlayingBufIdx = 0;
    	#if 1
    		if (audiobuf[PlayingBufIdx].Empty == 1)
    		{
                /* If empty==1, it means read file+decoder is slower than playback
                (it will cause noise) or playback is over (it is ok). */
    			DEBUG_MSG("\n\r**Play buf(%d) is empty!\n\r", PlayingBufIdx);
    			decode_slower_flag ^= 0x1;
    			if (decode_slower_flag) LED_On (LED_DEC_SLOWER);
    			else                    LED_Off (LED_DEC_SLOWER);  

                DMA_Buf_Empty = 1;
                return;
            }
    	#endif

		    DMA_SendData((uint32_t *)audiobuf[PlayingBufIdx].BaseAddr, audiobuf[PlayingBufIdx].Size);
		    I2S_DMA_Done = 0;	
	    }
    }
}

/*----------------------------------------------------------------------------
  USB functions 
 *----------------------------------------------------------------------------*/
/**
  * @brief  Initializes the USBHost .
  *
  * @param  None
  * @retval 0:OK, others: failure code
  *
  */
int USBHost_Init(void)
{
    int32_t  rc;
	uint32_t  numBlks, blkSize;
	uint8_t  inquiryResult[INQUIRY_LENGTH];

    Host_Init();               /* Initialize the lpc17xx host controller */
    rc = Host_EnumDev();       /* Enumerate the device connected  */
	if (rc != OK)
		return 1;
	rc = MS_Init( &blkSize, &numBlks, inquiryResult );
	if ((rc != OK) || (blkSize != 512))
		return 2;
	
	return OK;		                                          
}

/*----------------------------------------------------------------------------
  Memory functions
 *----------------------------------------------------------------------------*/
/**
  * @brief  Initializes memory.
  *
  * @param  None.
  * @retval Mask of mounted drives
  *
  */
uint8_t Mem_Init (void)
{
    uint8_t retv = 0x0;

#ifdef SUPPORT_USB
	/* Wait until there is USB flash disk inserted. */
    LCD_PrintString(0*8, 4*15, "Connect USB MSD", COLOR_WHITE, COLOR_BLACK);
	if (USBHost_Init() == OK)
	{
		DEBUG_MSG("USBHost: Init OK.\n\r");
		LCD_PrintString(0*8, 5*15, "USB: Init OK", COLOR_LIME, COLOR_BLACK);
		if (f_mount(0, &Fatfs[0]) == FR_OK) {
			DEBUG_MSG("USBFS: Mount OK.\n\r");
			LCD_PrintString(0*8, 6*15, "USBFS: Mount OK", COLOR_LIME, COLOR_BLACK);
			retv |= 1;
		} else {
			DEBUG_MSG("USBFS: Mount failed.\n\r");
			LCD_PrintString(0*8, 6*15, "USBFS: mnt fail", COLOR_RED, COLOR_BLACK);
	    }
	}
	else
	{
		DEBUG_MSG("USBHost: Init failed.\n\r");
		LCD_PrintString(0*8, 5*15, "USB: Init fail", COLOR_RED, COLOR_BLACK);
		return retv;
	}
#endif

#ifdef SUPPORT_SD
	if (f_mount(1, &Fatfs[1]) == FR_OK) {
		DEBUG_MSG("SDFS: Mount OK.\n\r");
		LCD_PrintString(0*8, 6*15, "SDFS: Mount OK", COLOR_LIME, COLOR_BLACK);
		retv |= 2;
	} else {
		DEBUG_MSG("SDFS: Mount failed.\n\r");
		LCD_PrintString(0*8, 6*15, "SDFS: mnt fail", COLOR_RED, COLOR_BLACK);
    }
#endif

    return (retv);
}

/**
  * @brief  De-Initializes memory
  *
  * @param  None.
  * @retval None.
  *
  */
void Mem_DeInit(void)
{
	if (f_mount(0, NULL) == FR_OK) {
		DEBUG_MSG("SDFS: Unmount OK.\n\r");
	} else {
		DEBUG_MSG("SDFS: Unmount failed.\n\r");
	}
}

/**
  * @brief  Open file.
  *
  * @param  fname: file name (with '\0' terminated).
  * @retval 0: success, 1: fail.
  *
  */
uint8_t Mem_OpenFile (char *fname)
{
    uint8_t res;
    uint8_t file_open_fail = 1;
    
    /* Open file */
	res = f_open(&File1, fname, FA_READ);
	file_open_fail = ((res == FR_OK)? 0:1);
    return file_open_fail;
}

/**
  * @brief  Close file.
  *
  * @param  None.
  * @retval None.
  *
  */
uint8_t Mem_CloseFile (void)
{
	return f_close(&File1);
}

/**
  * @brief  Searchs MP3 and place in MP3List[][].
  *
  * @param  CountOnly: When set, it will not list the LFN
  *         in debug terminal when an MP3 file has been found
  * @retval Number of MP3 files found in memory.
  *
  */
uint32_t Mem_SearchMp3 (uint8_t CountOnly)
{
    uint8_t res;
    uint32_t mp3_idx = 0;
    char Lfname[256];
    char string[3];
    uint8_t DrivesToDo = Drives;
    uint8_t CurrentDrive = 0;

    while(DrivesToDo)
    {
    	if(DrivesToDo & (1<<CurrentDrive))
    	{
			/* Open root directory */
			sprintf(string, "%d:", CurrentDrive);
			if (f_opendir(&Dir, string) != FR_OK)
			{
				DEBUG_MSG("%d: Open root dir failed.\n\r", CurrentDrive);
				break;
			}
#if _USE_LFN
			Finfo.lfname = Lfname;
			Finfo.lfsize = sizeof(Lfname);
#endif

			/* Search max 255 MP3 files */
			while(mp3_idx < 255)
			{
				res = f_readdir(&Dir, &Finfo);
				if ((res != FR_OK) || !Finfo.fname[0]) break;
				if ((Finfo.fattrib & AM_DIR) == 0) /* None directory */
				{
					/* Set _USE_LFN to 1 */
					/* Check if it is MP3 files (*.mp3 or *.MP3) */
					if (IsMP3File(Finfo.fname))
					{
						if(!CountOnly)
						{
							MP3List[mp3_idx].drive = CurrentDrive;
							strncpy(MP3List[mp3_idx].title, Finfo.fname, 13);
#if _USE_LFN
							DEBUG_MSG ("% 2d: %s\t\t%s\r\n", mp3_idx+1, Finfo.fname, Lfname);
#else
							DEBUG_MSG ("% 2d: %s\r\n", mp3_idx+1, Finfo.fname);
#endif
						}
						mp3_idx++;
					}
				}
			}
    	}
		DrivesToDo &= ~(1<<CurrentDrive);
		CurrentDrive++;;
    }
    return mp3_idx;    
}

/*----------------------------------------------------------------------------
  MP3 functions 
 *----------------------------------------------------------------------------*/

/**
  * @brief  Add an PCM frame to audio buf after decoding.
  *
  * @param  len: Specifies the frame size in bytes.
  * @retval None.
  *
  */
void MP3_AddAudioBuf (uint32_t len)
{
    /* Mark the status to not-empty which means it is available to playback. */
    audiobuf[DecodeBufIdx].Empty = 0;
    audiobuf[DecodeBufIdx].Size = len;
    
    /* Point to the next buffer */
    DecodeBufIdx ++;
    if (DecodeBufIdx == MAX_AUDIOBUF_NUM)
        DecodeBufIdx = 0;
}


/* Read MP3 data from file and fill in the read buffer. */
/**
  * @brief  Read data from MP3 file and fill in the Read Buffer.
  *
  * @param  readBuf: Pointer to the start of the Read Buffer
  * @param  readPtr: Pointer to the start of the left bytes in Read Buffer
  * @param  bytesLeft: Specifies the left bytes number in Read Buffer
  * @param  bytesAlign: Specifies the pad number to make sure it is aligned to 4 bytes
  * @retval Actual number of bytes read from file.
  *
  */
int MP3_FillReadBuffer(uint8_t *readBuf, uint8_t *readPtr, uint32_t bytesLeft, uint32_t bytesAlign)
{
	uint32_t nRead;

    /* Move the left bytes from the end to the front */
	memmove(readBuf+bytesAlign, readPtr, bytesLeft);

#ifdef SUPPORT_SD
	if(MP3List[CurrentMP3].drive == 1)
	{
        f_read(&File1, (void *)(readBuf+bytesLeft+bytesAlign), READBUF_SIZE_SD-bytesLeft-bytesAlign, &nRead);
		/* zero-pad to avoid finding false sync word after last frame (from old data in readBuf) */
		if (nRead < READBUF_SIZE_SD - bytesLeft - bytesAlign)
			memset(readBuf+bytesAlign+bytesLeft+nRead, 0, READBUF_SIZE_SD-bytesAlign-bytesLeft-nRead);
	}
#endif
#ifdef SUPPORT_USB
	if(MP3List[CurrentMP3].drive == 0)
	{
		f_read(&File1, (void *)(readBuf+bytesLeft+bytesAlign), READBUF_SIZE_USB-bytesLeft-bytesAlign, &nRead);
		/* zero-pad to avoid finding false sync word after last frame (from old data in readBuf) */
		if (nRead < READBUF_SIZE_USB - bytesLeft - bytesAlign)
			memset(readBuf+bytesAlign+bytesLeft+nRead, 0, READBUF_SIZE_USB-bytesAlign-bytesLeft-nRead);
	}
#endif

	return nRead;
}

/**
  * @brief  Decode a frame.
  *
  * @param  None.
  * @retval 0: success, 1: terminated.
  *
  */
uint8_t MP3_DecodeFrame ()
{
    uint8_t word_align, frame_decoded;    
    int nRead, offset, err;

    frame_decoded = 0;
    word_align = 0;
    nRead = 0;
    offset = 0;

	do
	{
		/* somewhat arbitrary trigger to refill buffer - should always be enough for a full frame */
		if (bytesLeft < 2*MAINBUF_SIZE && !eofReached) 
        {
            /* Align to 4 bytes */
    		word_align = (4-(bytesLeft&3)) & 3;

            Timer = 0;
            /* Fill read buffer */
    	    nRead = MP3_FillReadBuffer(readBuf, readPtr, bytesLeft, word_align);
    		totalReadFileTime += Timer;
    		nReadFileBlkCnt++;
    
    		bytesLeft += nRead;
    		readPtr = readBuf + word_align;
    		if (nRead == 0)
    		{
    			eofReached = 1;	/* end of file */
    			outOfData = 1;
    		}
		}

		/* find start of next MP3 frame - assume EOF if no sync found */
		offset = MP3FindSyncWord(readPtr, bytesLeft);
		if (offset < 0) {
			readPtr = readBuf;
			bytesLeft = 0;
			continue;
		}
		readPtr += offset;
		bytesLeft -= offset;

		//simple check for valid header
		if(((*(readPtr+1) & 24) == 8) || ((*(readPtr+1) & 6) != 2) || ((*(readPtr+2) & 240) == 240) || ((*(readPtr+2) & 12) == 12) || ((*(readPtr+3) & 3) == 2))
		{
			readPtr += 1;		//header not valid, try next one
			bytesLeft -= 1;
			continue;
		}

        Timer = 0;
		err = MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, (short *)audiobuf[DecodeBufIdx].BaseAddr, 0);
		totalDecTime += Timer;
		nFrames++;
		if(err == -6)
		{
			readPtr += 1;
			bytesLeft -= 1;
			continue;
		}

		if (err) {
			/* error occurred */
			switch (err) {
			case ERR_MP3_INDATA_UNDERFLOW:
				outOfData = 1;
				break;
			case ERR_MP3_MAINDATA_UNDERFLOW:
				/* do nothing - next call to decode will provide more mainData */
				break;
			case ERR_MP3_FREE_BITRATE_SYNC:
			default:
				outOfData = 1;
				break;
			}
		} else {
			/* no error */
			MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);				 
			frame_decoded = 1; 
		}

	} while (!frame_decoded && !outOfData);

    if (outOfData == 1) return 0x1; /* Decoder terminated */
    else                return 0x0;   /* Decoder success. */

}

/**
  * @brief  Playback MP3 file.
  *
  * @param  None
  * @retval 0: success, none-zero: failure code
  *         1: failed to decode the first frame
  *         2: Unsupported MP3 format
  *
  */
uint8_t MP3_Playback (void)
{
    /* Init variables */
	bytesLeft = 0;
	outOfData = 0;
	eofReached = 0;
	readPtr = readBuf = (uint8_t *)(READBUF_BASEADDR);
	totalDecTime = 0;
	nFrames = 0;
	nReadFileBlkCnt = 0;
	totalReadFileTime = 0;
	pcmframe_size = 0;

    /* Decode and playback from audio buf 0 */
    DecodeBufIdx = PlayingBufIdx = AUDIOBUF0;    
    audiobuf[AUDIOBUF0].Empty = audiobuf[AUDIOBUF1].Empty = 1;                 

    /* Decode the first frame to get the frame format */
    if (MP3_DecodeFrame() == 0)
    {
        pcmframe_size = (mp3FrameInfo.bitsPerSample / 8) * mp3FrameInfo.outputSamps;            
        MP3_AddAudioBuf (pcmframe_size);
    }
    else
    {
        DEBUG_MSG("\n\rFailed to decode the 1st frame, try to increase READBUF_SIZE.\n\r");
        return 0x1;  /* fail to decode the first frame */
    }

    /* Display the frame format */
    MP3_DisplayFormat();

    /* configure appropriate bit rate for each mp3, otherwise, 
    the mp3 will be too fast or slow with default setting */    
	if (I2S_ConfigBitrate(mp3FrameInfo.nChans, mp3FrameInfo.bitsPerSample, mp3FrameInfo.samprate))
	{
		DEBUG_MSG("Unsupported MP3 format!\n\r");
        return 0x2; /* Unsupported MP3 format */
	}

    /* Start I2S+DMA to send the first decoded frame */
    I2S_Start();
    DMA_SendData((uint32_t *)audiobuf[AUDIOBUF0].BaseAddr, audiobuf[AUDIOBUF0].Size);
    I2S_DMA_Done = 0;
    DMA_Buf_Empty = 0;
              
    /* Then deocder all the left frames */
    do 
    { 
        /* wait for playback is complete to release buf */
        while (audiobuf[DecodeBufIdx].Empty == 0)
        {
            /* Decoder is faster than play, all buf is full */
			decode_faster_flag ^= 0x1;
			if (decode_faster_flag)  LED_On (LED_DEC_FASTER);
			else                     LED_Off (LED_DEC_FASTER);           
        }

        /* Decoder one frame */
        if (MP3_DecodeFrame() == 0)    
        {
			pcmframe_size = (mp3FrameInfo.bitsPerSample / 8) * mp3FrameInfo.outputSamps;
	   		MP3_AddAudioBuf (pcmframe_size);                                             
        }
    } while ((outOfData == 0) && (DMA_Buf_Empty ==0));
    	
	/* Playback complete */
    WAIFORDMADONE;
	WAIFORI2SDONE;
	I2S_Stop();
       
    if (outOfData == 1 || (DMA_Buf_Empty==1))
    {   

    #if 1
    	MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);
    	audioSecs = ((float)nFrames * mp3FrameInfo.outputSamps) / ( (float)mp3FrameInfo.samprate * mp3FrameInfo.nChans);
    	DEBUG_MSG("Statistics: \n\r");
    	DEBUG_MSG("    Decode: %d frames, %d ms (%d ms/frame)\n\r", nFrames, totalDecTime, totalDecTime/nFrames);
    	DEBUG_MSG("    Audio duration(min:sec): %02d:%02d\n\r",  (uint32_t)audioSecs/60, (uint32_t)audioSecs%60);
#ifdef SUPPORT_USB
    	if(MP3List[File1.fs? CurrentMP3 : PreviousMP3].drive == 0)
    		DEBUG_MSG("    File read speed: %d ms/%dkB\n\r",  totalReadFileTime/nReadFileBlkCnt, READBUF_SIZE_USB>>10);
#endif
#ifdef SUPPORT_SD
    	if(MP3List[File1.fs? CurrentMP3 : PreviousMP3].drive == 1)
     	DEBUG_MSG("    File read speed: %d ms/%dkB\n\r",  totalReadFileTime/nReadFileBlkCnt, READBUF_SIZE_SD>>10);
#endif
    #endif 
    }

    return 0;
}

/**
  * @brief  Displays the format information in structure mp3FrameInfo.
  *
  * @param  None
  * @retval None 
  *
  */
void MP3_DisplayFormat(void)
{
	DEBUG_MSG("\n\rmp3FrameInfo:\n\r");
	DEBUG_MSG("    bitrate: %d bps\r\n", mp3FrameInfo.bitrate);
	DEBUG_MSG("    nChans: %d \r\n", mp3FrameInfo.nChans);
	DEBUG_MSG("    samprate: %d \r\n", mp3FrameInfo.samprate);
	DEBUG_MSG("    bitsPerSample: %d \r\n", mp3FrameInfo.bitsPerSample);
	DEBUG_MSG("    outputSamples: %d \r\n", mp3FrameInfo.outputSamps);
	DEBUG_MSG("    layer: %d \r\n", mp3FrameInfo.layer);
	DEBUG_MSG("    version: %d \r\n", mp3FrameInfo.version);    
}

/*----------------------------------------------------------------------------
  Other functions 
 *----------------------------------------------------------------------------*/

/*---------------------------------------------------------*/
/* User Provided RTC Function for FatFs module             */
/*---------------------------------------------------------*/
/* This is a real time clock service to be called from     */
/* FatFs module. Any valid time must be returned even if   */
/* the system does not support an RTC.                     */
/* This function is not required in read-only cfg.         */
unsigned long get_fattime ()
{
    return 0;
}

/* Check the file name, if MP3 file, return 0, otherwise return 1 */
static uint8_t IsMP3File (const char * fname)
{
    uint32_t len = strlen(fname);;

    if ( (fname[len-1]!= '3') ||
         ((fname[len-2]!= 'P') && (fname[len-2]!= 'p')) ||
         ((fname[len-3]!= 'M') && (fname[len-3]!= 'm')) ||
         (fname[len-4]!= '.'))
    {
        return 0;
    }

    return 1;  
}

/* Get PCLK */
static uint32_t LPC17xx_GetFpclk(uint8_t clksel)
{
	uint32_t pclk = 0;
	switch ( clksel )
	{
	  case 0x00:
	  default:
		pclk = SystemFrequency/4;
		break;
	  case 0x01:
		pclk = SystemFrequency;
		break; 
	  case 0x02:
		pclk = SystemFrequency/2;
		break; 
	  case 0x03:
		pclk = SystemFrequency/8;
		break;
	}

	return pclk;
}

/*----------------------------------------------------------------------------
  Menu functions
 *----------------------------------------------------------------------------*/

/**
  * @brief  Moves the cursor one position down. If bottom position has
  * been reached, it will move the cursor to the top position.
  *
  * @param  None.
  * @retval Updated cursor position.
  *
  */
uint8_t CursorDown(void)
{
	if(Cursor < (mp3_cnt - 1))
		Cursor++;
	else
		Cursor = 0;

	CursorUpdate();
	return Cursor;
}

/**
  * @brief  Moves the cursor one position up. If top position has
  * been reached, it will move the cursor to the bottom position.
  *
  * @param  None.
  * @retval Updated cursor position.
  *
  */
uint8_t CursorUp(void)
{
	if(Cursor > 0)
		Cursor--;
	else
		Cursor = mp3_cnt - 1;

	CursorUpdate();
	return Cursor;
}

/**
  * @brief  Moves the cursor to a specified location.
  *
  * @param  Position: Location to set the cursor to.
  * @retval Updated cursor position.
  *
  */
uint8_t CursorSet(uint8_t Position)
{
	if(Position < mp3_cnt)
		Cursor = Position;
	else
		Cursor = 0;

	CursorUpdate();
	return Cursor;
}

/**
  * @brief  Plays file selected by cursor
  *
  * @param  None.
  * @retval None.
  *
  */
void CursorEnter(void)
{
	Mem_CloseFile();
	PreviousMP3 = CurrentMP3;
	CurrentMP3 = Cursor;

	CursorUpdate();
}

/**
  * @brief  Updates the cursor and the content of the screen.
  *
  * @param  None.
  * @retval None.
  *
  */
void CursorUpdate(void)
{
	uint8_t Count;
	uint8_t Offset;
	uint8_t i;
	char string[17];
	int Color;

	if(mp3_cnt > 7)
	{
		Count = 7;
		if(Cursor >= 3)
		{
			 if(Cursor <= mp3_cnt-4)
				 Offset = Cursor - 3;
			 else
				 Offset = mp3_cnt - 7;
		}
		else
			Offset = 0;
	}
	else
	{
		Count = mp3_cnt;
		Offset = 0;
	}

	for(i=0;i<Count;i++)
	{
		if((Offset + i) == CurrentMP3)
			Color = COLOR_LIME;
		else
			Color = COLOR_WHITE;

		sprintf(string, "%3d %-16s",Offset+i+1, MP3List[Offset+i].title);
		LCD_PrintString(0*8, (i+1)*15, string, Color, (i == (Cursor-Offset))? COLOR_RED : COLOR_BLACK);
	}
}

