/*********************************************************************************************************                                               
*                                          ACH Test Suite
*                             COPYRIGHT(c) 2019 Amped RF Technology (ART)
*                                        All Rights Reserved
*
* File    : test_wave_player.c
* Version : V1.0.0
* Brief   : ACH Test Suite
**********************************************************************************************************/

#include "ach118x.h"
#include <stdio.h>
#include <math.h>
#include "GKI_spi.h"
#include "GKI_uart.h"
#include "GKI_queue.h"
#include "md25d80.h"
#include "stdlib.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"
#ifdef ENABLE_WavePlayer

// IRQ level priorities (lower numbers are a higher priority)
#define TIM_IRQ_PRIORITY    (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1)

#define VOLUME_MIN      00
#define VOLUME_MAX      80
#define VOLUME_DEFAULT  00

//---------------------------------------
// SPI and MHz settings
//---------------------------------------
#define FLASH_SPI       SPI2  // Usually set to SPI2
#define CPU_MHZ         200
//---------------------------------------
#define SPI_DIV         10
int volume = VOLUME_DEFAULT;


#define FEED_PERIOD_MS      450
#define IRQ_I2S_BUF_SIZE    512
#define FILE_BUF_SIZE       2048
#define CR                  13
#define LF                  10

// Pointer to file address in flash
u32 WAVE_FILE_ADDR;

//------------------------------------------------
// Convert 8-bit sample to 16-bit sample
//------------------------------------------------
#define ConvertTo16(x)  ((int16_t)(x - 0x80) << 8)


//------------------------------------------
// Globals
//------------------------------------------
int PlayCountdown=0;
tQUEUE FileBufferQueue={0};
uint32_t MusicDataBase   = 0;
uint32_t MusicDataSize   = 0;
uint32_t MusicBlockPnt   = 0;
uint32_t MusicBlockAlign = 0;
uint32_t MusicBlockSize  = 0;
uint32_t ErrorFileQueueEmpty   = 0;
uint32_t ErrorFileQueueOverrun = 0;
TimerHandle_t FeedTimerHandle=NULL;
void vLoadMusicTask(void *);
QueueHandle_t xQueueFeedTimeout=NULL;

FileEntry_t wavefile={0};

//-------------------------------------------------------------------------------
//     Structures to hold Wave file attributes
//-------------------------------------------------------------------------------
typedef struct {
    char        ID[4];              // "RIFF" - Resource Interchange File Format
    uint32_t    Size;               // 36 + SubChunk2Size
    char        Format[4];          // "WAVE"
} WAV_CHUNK_t;

typedef struct {
    char        ID[4];              // "RIFF" - Resource Interchange File Format
    uint32_t    Size;               // 36 + SubChunk2Size
} WAV_SUBCHUNK_t;

typedef struct {
    uint16_t    AudioFormat;        // PCM = 1
    uint16_t    NumChannels;        // Mono = 1, Stereo = 2, etc.
    uint32_t    SampleRate;         // 8000, 44100, etc.
    uint32_t    ByteRate;           // SampleRate * NumChannels * BitsPerSample/8
    uint16_t    BlockAlign;         // NumChannels * BitsPerSample/8
    uint16_t    BitsPerSample;      // 8 bits = 8, 16 bits = 16, etc.
} WAV_FMT_t;

static WAV_FMT_t        WavFmt;
static WAV_SUBCHUNK_t   WavData;
static uint32_t         WavDataStart;


//----------------------
//  Define sample data
//----------------------
typedef struct {
    uint8_t lft;
    uint8_t rgt;
} b8_t;
typedef struct {
    int16_t lft;
    int16_t rgt;
} b16_t;
typedef union {
    b8_t    b8;
    b16_t   b16;
} SAMPLE_t;


typedef struct {
    uint32_t NumSamples;    // Number of sample blocks
    uint32_t PeriodMS;      // Interval in MS
} FILE_FEED_t;
static FILE_FEED_t FileFeed={0};


//-----------------------------------------
//   Private function prototypes
//-----------------------------------------
static void PlayWaveFile(void);
static int  LoadMusic(void);
static void vTaskMenu(void *pvParam);
static void FileFeedStop(void);
static int  ReadFileFromFlash(uint32_t NumBlocks);
static int  VerifyFlash(void);
static int  FileFeedStart(void);


//------------------------------------------
// Globals for holding directory entry list
//------------------------------------------



//-----------------------------------------------------
// Show Flash IDs
//-----------------------------------------------------
void RevealFlashIDs(void)
{
    u8 ElectSignature;
    u8 REMS[2];
    u8 RDID[3];

    MD25D80_ReadDeviceID(FLASH_SPI, &ElectSignature);
    MD25D80_ReadManufactureID(FLASH_SPI, REMS);
    MD25D80_ReadIdentification(FLASH_SPI, RDID);

    //printf("Signature: 0x%02X\n", ElectSignature);
    printf("Flash IDs: %02X %02X %02X %02X %02X\n",
           ElectSignature, REMS[1], RDID[0], RDID[1], RDID[2]);
}


//-----------------------------------------------------
// Check for character ready
//-----------------------------------------------------
static u8 CharIsReady(void) {
	return GKI_UartRxDataReady(UART0);
}


//-----------------------------------------------------
// Get char
//-----------------------------------------------------
int GetChar(void)
{
    int ch;
    do {
        vTaskDelay(30);
        ch = CharIsReady() ? getchar() : 0;
        if (ch==LF)
            ch = 0;
    } while (ch==0);
    return ch;
}


//-----------------------------------------------------
// Set volume
//-----------------------------------------------------
void setvolume(char ch)
{
    if (ch=='+' && volume<VOLUME_MAX) {
        printf("Volume set to %d\n", ++volume);
        codec_set_headset_volume(volume<<2);
    }
    else if (ch=='-' && volume>VOLUME_MIN) {
        printf("Volume set to %d\n", --volume);
        codec_set_headset_volume(volume<<2);
    }
}


//------------------------------------------------------------------------
// Main menu
//------------------------------------------------------------------------
void vTaskMenu(void *pvParam)
{
    int ch;
    int FileListIndex[64];
    int FileListNum = 0;

    //--------------------------------
    //  Set and show speed settings
    //--------------------------------
    printf("WavePlayer - ");
    printf(" %d MHz\n", SystemCoreClock/1000000);
    uint32_t baudr, Hz, MHz, kHz;

    GKI_SpiDivPolPhaInit(FLASH_SPI, SPI_DIV, 0, 0);

    baudr = FLASH_SPI->BAUDR;
    Hz = SystemCoreClock / 2 / baudr;
    MHz = Hz / 1000000;
    kHz = (Hz / 1000) % 1000;

    // Reset flash
    MD25D80_PowerDown(FLASH_SPI);
    MD25D80_ReleasePowerDown(FLASH_SPI);
    SPI_Cleanup(FLASH_SPI);

    // Check for flash existence
    if (VerifyFlash() < 0) {
        putstring(" *** Flash not detected ***\n");
    }
    else {
        void InitDir(void);
        InitDir();
        for (int n=0; n<NumFiles; n++) {
            if (Directory[n].EntryType==ENTRY_VALID) {
                if (strstr(Directory[n].FileName, ".wav")) {
                    FileListIndex[FileListNum++] = n;
                    wavefile = Directory[n];
                }
            }
        }
        if (wavefile.EntryType!=ENTRY_VALID) {
            printf("No wave file found **\n");
            while (1);
        }
    }
    
    //-----------------------------------------
    // Main loop
    //-----------------------------------------
    while (1) {
        int sel;
        sel = 0;
        printf("\nVolume is %d. Press +/- to change volume. (%d max)\n\n", volume, VOLUME_MAX);
        printf("Select WAV File:\n");
        for (int i=0; i<FileListNum; i++) {
            printf("%2d. %s\n", i+1, Directory[FileListIndex[i]].FileName);
        }
        
        // Adjust volume and loop here until a file selection is made
        while (sel<1 || sel>FileListNum) {
            ch = GetChar();
            sel = ch - '0';
            setvolume(ch);
        }
        printf("\n");
        wavefile = Directory[ FileListIndex[sel-1] ];
        WAVE_FILE_ADDR = wavefile.FileStartSector * SECTOR_SIZE;
        printf("Wave file: %s  %d bytes.\n", wavefile.FileName, wavefile.FileSize);
        
        // Play wave file loaded at address WAVE_FILE_ADDR
        PlayWaveFile();
        
        // loop here until any key is pressed or music ends
        printf("Press any key to stop\n");
        while (1) {
            vTaskDelay(30);
            if (GKI_UartRxDataReady(UART0)) {
                ch = GetChar();
                if (ch=='+' || ch=='-')
                    setvolume(ch);
                else
                    break;
            }
            if (PlayCountdown==0) {
                break;
            }
        }
        printf("\n");
        
        // turn off I2S interrupts
        AUDIO_I2S->Ch0.IMR |= I2S_IMR_TXFEM;
        FileFeedStop();
    }
}


//==============================================================
// ReadFromFlash() - Read blocks of music data from flash
//  pnt - block pointer to music data in flash
//  buf - destination memory
//  num - number of blocks of data to read
//==============================================================
void ReadFromFlash(uint32_t pntBlock, uint8_t *buf, uint32_t numBlocks)
{
    u32 FlashDataStart = MusicDataBase + (pntBlock * MusicBlockAlign);
    u32 FlashDataSize  = numBlocks * MusicBlockAlign;
    
    MD25D80_ReadData(FLASH_SPI, FlashDataStart, buf, FlashDataSize);
}



//==============================================================================
// Read Music data from flash and put into file buffer
//   NumBlocks: in units of data aligned blocks
//==============================================================================
int ReadFileFromFlash(uint32_t NumBlocks)
{
    uint32_t nblocks, FreeSpace, Total=0;
    uint32_t SampleBufMax;
    #define TEMPBUFMAX 5
    uint8_t TempBuf[TEMPBUFMAX*4];
        
    // Check for done
    if (PlayCountdown==0)
        return 0;

    // Determine max number of sample blocks to read at a time
    SampleBufMax = (SPI_FIFO_DEPTH-4) / MusicBlockAlign;

    // Limit NumBlocks to free space of FileBufferQueue
    FreeSpace = GKI_QFreeSpace(&FileBufferQueue);
    NumBlocks = MIN(NumBlocks, FreeSpace);

    while (NumBlocks) {
        nblocks = MIN(NumBlocks, SampleBufMax);
        nblocks = MIN(nblocks, MusicBlockSize-MusicBlockPnt);

        ReadFromFlash(MusicBlockPnt, TempBuf, nblocks);
        MusicBlockPnt += nblocks;
        GKI_QPutBuf(&FileBufferQueue, TempBuf, nblocks);
        
        Total += nblocks;
        NumBlocks -= nblocks;
        if (MusicBlockPnt >= MusicBlockSize) {
            printf("Found music end\n");
            FileFeedStop();
            return Total;
        }
    }

    return Total;
}


//==============================================================================
// File feed routine - read blocks of data from flash to the file buffer
//  Read FileFeed.NumSamples number of blocks from flash
//==============================================================================
void PeriodicReadFromFlash(void)
{
    uint32_t n, Result;

    if (PlayCountdown==0) {
        FileFeedStop();
    }
    else {        
        Result = ReadFileFromFlash(FileFeed.NumSamples);
        if (Result < FileFeed.NumSamples) 
        {
            // EOF detected
            FileFeedStop();
        }
    }
}



//==============================================================================
// stop file feed
// Stop Timer interrupts to file feed
//==============================================================================
void FileFeedStop(void)
{
    PlayCountdown = 0;
    NVIC_DisableIRQ(TIM_IRQn);
}


//==============================================================================
// Load wave file headers
//  MusicDataBase   - pointer to flash address of the start of music data
//  MusicDataSize   - length of data in blocks
//  MusicBlockPnt   - pointer to music data blocks
//  MusicBlockSize  - number of music data blocks
//==============================================================================
int LoadWavHeaders(void)
{
    int len;
    WAV_CHUNK_t WavChunk;
    WAV_SUBCHUNK_t SubChunk;
    int found_fmt=false;
    int found_data=false;
    int done;
    uint32_t pWavFile;
    uint32_t WavFileSize;

    pWavFile = 0;
    len = sizeof(WAV_CHUNK_t);
    MD25D80_ReadData(FLASH_SPI, WAVE_FILE_ADDR+pWavFile, (uint8_t *)&WavChunk, len);
    pWavFile += len;
    if (strncmp("RIFF", WavChunk.ID, 4) != 0) {
        printf("Not a RIFF file\n");
        return -1;
    }
    if (strncmp("WAVE", WavChunk.Format, 4) != 0) {
        printf("Not in WAVE format\n");
        return -1;
    }

    WavFileSize = WavChunk.Size + 8;

    do {
        len = sizeof(WAV_SUBCHUNK_t);
        MD25D80_ReadData(FLASH_SPI, WAVE_FILE_ADDR+pWavFile, (uint8_t *)&SubChunk, len);
        pWavFile += len;
        if (strncmp("fmt ", SubChunk.ID, 4) == 0) {
            if (SubChunk.Size < sizeof(WAV_FMT_t)) {
                printf("Invalid format\n");
                return -1;
            }
            found_fmt = true;
            MD25D80_ReadData(FLASH_SPI, WAVE_FILE_ADDR+pWavFile, (uint8_t *)&WavFmt, sizeof(WAV_FMT_t));
            MusicBlockAlign = WavFmt.BlockAlign;
        }
        if (strncmp("data", SubChunk.ID, 4) == 0) {
            found_data = true;
            MusicDataBase   = WAVE_FILE_ADDR+pWavFile;
            MusicDataSize   = SubChunk.Size;
            WavData = SubChunk;
        }
        pWavFile += SubChunk.Size;
        done = found_data && found_fmt;
    } while (pWavFile < WavFileSize && !done);

    MusicBlockPnt   = 0;
    MusicBlockSize  = MusicDataSize / MusicBlockAlign;

    return (found_fmt && found_data) ? 0 : -1;
}


//==============================================================================
// Start initial loading of music from flash to file buffer
//==============================================================================
static int LoadMusic(void)
{
    uint32_t pWavFile, WavFileSize;
    uint32_t preFillSecs;

    // Load wave file headers
    //  MusicDataBase   - pointer to flash address of the start of music data
    //  MusicDataSize   - length of data in bytes
    //  MusicBlockPnt   - pointer to music data blocks
    //  MusicBlockSize  - number of music data blocks
    if (LoadWavHeaders() < 0) {
        printf("Cannot load wave file\n");
        return -1;
    }

    preFillSecs = 2;

    // Allocate for one second of samples
    GKI_QInit(&FileBufferQueue, NULL, WavFmt.BlockAlign, preFillSecs * WavFmt.SampleRate);
    // Half fill the buffer queue
    ReadFileFromFlash(preFillSecs * WavFmt.SampleRate / 2);

    // Determine amount of samples to feed per period
    FileFeed.PeriodMS   = FEED_PERIOD_MS;
    FileFeed.NumSamples = FileFeed.PeriodMS * WavFmt.SampleRate / 1000;

    // Setup file feed interrupt every FEED_PERIOD_MS
    xTaskCreate(vLoadMusicTask, "LoadMisic", 500, NULL, 4, NULL);

    return 0;
}


//==============================================================================
// Setup file feed interrupt every FileFeed.PeriodMS
//==============================================================================
void vLoadMusicTask(void *param)
{
    //FileFeedStart();
    static uint32_t apb1, OneSec, OneMS, LoadValue;
    uint8_t MsgBuf[1];

    OneSec = apb1 = SystemCoreClock >> 2;
    OneMS = OneSec / 1000;
    LoadValue = OneMS * FileFeed.PeriodMS;

    TIM0->ControlReg = 0;
    TIM0->CurrentValue = 0;
    TIM0->LoadCount = LoadValue;
    TIM0->ControlReg = TIM_CTRL_LOAD_COUNT | TIM_CTRL_ENABLE;

    // Turn on timer interrupt. This sends a signal every
    // FileFeed.PeriodMS to loop below.
    SYSCFG->SYS_LEV_INTR_MASK_REG |= TIM_IRQ_MASK;
    NVIC_SetPriority(TIM_IRQn, TIM_IRQ_PRIORITY);
    NVIC_EnableIRQ(TIM_IRQn);

    // Loop here every FileFeed.PeriodMS signal
    xQueueReceive(xQueueFeedTimeout, MsgBuf, 0);
    while (PlayCountdown) { 
        xQueueReceive(xQueueFeedTimeout, MsgBuf, portMAX_DELAY);
        if (PlayCountdown)
            PeriodicReadFromFlash();
    }
    TIM0->ControlReg = 0;
    NVIC_DisableIRQ(TIM_IRQn);

    vTaskDelete(NULL);
}


//==============================================================================
//     wave player
//==============================================================================
static void PlayWaveFile(void)
{
    // Reset if currently playing music
    if (PlayCountdown != 0) {
        TIM0->ControlReg = 0;
        NVIC_DisableIRQ(TIM_IRQn);
        AUDIO_I2S->Ch0.IMR |= I2S_IMR_TXFEM;    // halt interrupts
    }

    PlayCountdown = 1;
    if (LoadMusic()<0)
        return;

    //==============================================================
    // Show WAV statistics
    //==============================================================
    printf("=====================================\n");
    printf("AudioFormat   %s\n", WavFmt.AudioFormat==1 ? "PCM":"???");
    printf("NumChannels   %d\n", WavFmt.NumChannels);
    printf("SampleRate    %d\n", WavFmt.SampleRate);
    printf("BitsPerSample %d\n", WavFmt.BitsPerSample);
    printf("BlockAlign    %d\n", WavFmt.BlockAlign);
    uint32_t playtimeSec  = (WavData.Size / WavFmt.BlockAlign) / WavFmt.SampleRate;
    uint32_t playtimeFrac = (WavData.Size / WavFmt.BlockAlign) % WavFmt.SampleRate;
    printf("PlayTime      %d.%d sec\n", playtimeSec, 10*playtimeFrac/WavFmt.SampleRate);
    printf("DataSize      %d bytes\n", WavData.Size);
    printf("=====================================\n");

    //==============================================================
    // Set I2S with Sample Rate
    //==============================================================
    if (util_ConfigAudioI2S_SampleRate(WavFmt.SampleRate) < 0) {
        printf("Invalid sample rate\n");
        return;
    }

    //==============================================================
    // Set Codec
    //==============================================================
    codec_init();
    if (codec_config_samplerate(WavFmt.SampleRate) < 0) {
        printf("Cannot set CODEC\n");
        return;
    }
    
    //==============================================================
    // Set Volume
    //==============================================================
    codec_set_master_volume(PLAYBACK_MASTER_VOLUME_MAX);
    codec_set_headset_volume(volume<<2);
    
    
    //==============================================================
    // Turn on I2S1 interrupt (internal I2S)
    //==============================================================
    REG32(SYSCFG, SYSCFG_SYS_LEV_INTR_MASK_REG) |= I2S1_IRQ_MASK;
    NVIC_EnableIRQ(I2S1_IRQn); // Enable I2S1 interrupt
    //==============================================================
    I2S1->CER = 0;
    I2S1->CCR = I2S_CCR_WSS_32 | I2S_CCR_SCLKG_NONE;

    //==============================================================
    // Set Transmit Ch Empty Trigger level
    // FIFO size is 16 samples
    // This will trigger when X number of samples remain
    I2S1->Ch0.TFCR  = 3;            

    //==============================================================
    // I2S1 config
    //==============================================================
    I2S1->IER  = I2S_IER_IEN;       // DW_apb_i2s Enable
    I2S1->ITER = I2S_ITER_TXEN;     // I2S Transmit Block Enable
    I2S1->CER  = I2S_CER_CLKEN;     // Clock generation enable

    //==============================================================
    // Interrupt on tx empty
    //==============================================================
    I2S1->Ch0.IMR &= ~I2S_IMR_TXFEM;
}



//==============================================================================
// I2S Interrupt Service Routine
//    I2S1 is the internal I2S for the integrated CODEC.
//    ADC and DAC data to/from the CODEC will utilize this
//    I2S port.  Defined in header ach118x_i2s.h
//    FIFO size of this I2S is 16 samples, each channel
//==============================================================================
void I2S1_IRQHandler(void)
{
    int i, k, n, c, count;
    SAMPLE_t *sample;
    int16_t samp16;
    uint8_t IrqBuf[32*sizeof(SAMPLE_t)];

    //--------------------------
    // Get FIFO trigger level
    //--------------------------
    i = I2S1->Ch0.TFCR;     // Samples already in FIFO
    n = 16 - i;             // Number of samples to fill FIFO

    //--------------------------
    // Load up IRQ buffer
    //  Determine if no more data
    //--------------------------
    c = GKI_QGetBuf(&FileBufferQueue, IrqBuf, n);
    if (c==0) {
        ErrorFileQueueEmpty++;
        AUDIO_I2S->Ch0.IMR |= I2S_IMR_TXFEM;    // halt interrupts
        return;
    }

    //--------------------------------------------
    //   Load samples from FileBufferQueue into I2S FIFO
    //--------------------------------------------
    for (i=0; i<c; i++) {
        k = i * WavFmt.BlockAlign;
        sample = (void *)&IrqBuf[k];
        // for demo purposes, this is not efficiently implemented below        
        if (WavFmt.BitsPerSample==8) { // writes a 8 bit sample to I2S Fifo
            I2S1->Ch0.LFT.THR = samp16 = ConvertTo16(sample->b8.lft);
            if (WavFmt.NumChannels==2)
                samp16 = ConvertTo16(sample->b8.rgt);
            I2S1->Ch0.RGT.THR = samp16;
        }
        else { // writes a 16 bit sample to I2S Fifo
            I2S1->Ch0.LFT.THR = sample->b16.lft;
            I2S1->Ch0.RGT.THR = 
                (WavFmt.NumChannels==1 ? sample->b16.lft : sample->b16.rgt);
        }
    }
}


//==============================================================================
// TIM Interrupt Service Routine
//  Send signal - time to load in more music data from flash into file buffer
//==============================================================================
void TIM0_IRQHandler(void)
{
    static uint8_t MsgBuf[1]={0};
    if (TIM0->IntStatus) {
        xQueueSendFromISR(xQueueFeedTimeout, MsgBuf, NULL);
        TIM0->EOI;
    }
}


//==============================================================================
//==============================================================================
// Begin application for wave player
//==============================================================================
//==============================================================================
void test_application(void)
{
    // Set clock mhz
    util_SetMainFreqMHz(CPU_MHZ);

    // UART Config
    UART_InitTypeDef uartStruct;
    uartStruct.UART_BaudRate = 115200;
    uartStruct.UART_HardwareFlowControl = FLOWCON_DISABLE;
    uartStruct.UART_Parity = NONE_PARITY;
    uartStruct.UART_StopBits = UART_STOP_1;
    uartStruct.UART_WordLength = UART_LEN_8;
    UART_Init((UART_Typedef *)UART0_REG_BASE, &uartStruct);

    // Init CODEC
    codec_init();

    xQueueFeedTimeout = xQueueCreate(1,1);
    xTaskCreate(vTaskMenu, "Menu", 500, NULL, 5, NULL);

    vTaskStartScheduler();
    printf("RTOS Failed\n");
}


//==============================================================================
// Check for flash. Return -1 on error, 0 on success
//==============================================================================
int VerifyFlash(void)
{
    u8 ReadBuf[4]={0};
    memset(ReadBuf, 0xaa, sizeof(ReadBuf));
    MD25D80_ReadIdentification(FLASH_SPI, ReadBuf);

    for (int i=0; i<3; i++) {
        if (ReadBuf[0]==0 || ReadBuf[0]==0xFF)
            return -1;
    }
    return 0;
}



#endif // ENABLE_WavePlayer
/************************ (C) COPYRIGHT ART *****END OF FILE****/
