//*****************************************************************************
//
// audio_playback_with_sdcard_by_SPIDAC.c - Audio example using SPI-DAC.
//
// Copyright (c) 2012-2017 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_timer.h"
#include "inc/hw_types.h"
#include "driverlib/flash.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/rom_map.h"
#include "driverlib/ssi.h"
#include "driverlib/sysctl.h"
#include "driverlib/systick.h"
#include "driverlib/timer.h"
#include "driverlib/udma.h"
#include "grlib/grlib.h"
#include "grlib/widget.h"
#include "grlib/canvas.h"
#include "grlib/listbox.h"
#include "grlib/pushbutton.h"
#include "third_party/fatfs/src/ff.h"
#include "third_party/fatfs/src/diskio.h"
#include "utils/ustdlib.h"
#include "utils/wavfile.h"
#include "drivers/kentec320x240x16_ssd2119.h"
#include "drivers/frame.h"
#include "drivers/pinout.h"
#include "drivers/touch.h"

//*****************************************************************************
//
//! \addtogroup example_list
//! <h1>Audio playback using PWM method with external SD Card FAT file system
//! (audio_playback_with_sdcard_by_PWM)</h1>
//!
//! This example application demonstrates playing .wav files from an SD card
//! that is formatted with a FAT file system.  The application can browse the
//! file system on the SD card and displays all files that are found.  Files
//! can be selected to show their format and then played if the application
//! determines that they are a valid .wav file. Only PCM format (uncompressed)
//! files may be played.
//!
//! This example creates an audio signal to an external speaker on the
//! BOOSTXL-AUDIO BoosterPack by sending the audio data stored in .wav file to
//! a DAC on the BoosterPack through a SPI interface.  The DAC output is then
//! passed through a low pass filter to form the final audio signal that plays
//! through the speaker.
//!
//! For additional details about FatFs, see the following site:
//! http://elm-chan.org/fsw/ff/00index_e.html
//!
//
//*****************************************************************************

//*****************************************************************************
//
// These are the global .wav file states used by the application.
//
//*****************************************************************************
tWavFile g_sWavFile;
tWavHeader g_sWavHeader;

//*****************************************************************************
//
// The main audio buffer and it's pointers.
//
//*****************************************************************************
volatile uint8_t *g_pui8Read;
volatile uint8_t *g_pui8Write;
volatile uint32_t g_ui32ValidBytes;

//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, uint32_t ui32Line)
{
}
#endif

//*****************************************************************************
//
// Graphics context used to show text on the display.
//
//*****************************************************************************
tContext g_sContext;

//*****************************************************************************
//
// Global needed by the FAT driver to know the processor speed of the system.
//
//*****************************************************************************
uint32_t g_ui32SysClock;

//*****************************************************************************
//
// Global variable for PWM period.
//
//*****************************************************************************
uint32_t g_ui32Period;

//*****************************************************************************
//
// Defines for the basic screen area used by the application.
//
//*****************************************************************************
#define STATUS_HEIGHT           40
#define BG_MIN_X                7
#define BG_MAX_X                (320 - 8)
#define BG_MIN_Y                24
#define BG_MAX_Y                (240 - 8)
#define BUTTON_HEIGHT           (STATUS_HEIGHT - 8)

//*****************************************************************************
//
// Forward declarations for functions called by the widgets used in the user
// interface.
//
//*****************************************************************************
void OnListBoxChange(tWidget *psWidget, int16_t i16Selected);
static void PlayPause(tWidget *psWidget);
static void Stop(tWidget *psWidget);

//*****************************************************************************
//
// The following are data structures used by FatFs.
//
//*****************************************************************************
static FATFS g_sFatFs;
static DIR g_sDirObject;
static FILINFO g_sFileInfo;

//*****************************************************************************
//
// Define a pair of buffers that are used for holding path information.
// The buffer size must be large enough to hold the longest expected
// full path name, including the file name, and a trailing null character.
// The initial path is set to root "/".
//
//*****************************************************************************
#define PATH_BUF_SIZE   80
static char g_pcCwdBuf[PATH_BUF_SIZE] = "/";
static char g_pcTmpBuf[PATH_BUF_SIZE];

//*****************************************************************************
//
// The number of SysTick ticks per second.
//
//*****************************************************************************
#define TICKS_PER_SECOND 100
#define MS_PER_SYSTICK (1000 / TICKS_PER_SECOND)

//*****************************************************************************
//
// Audio buffering definitions, these are optimized to deal with the selected
// audio file quality.  For example, if the audio file has 20500Hz sampling
// frequency and 16-bit resolution with mono channel then AUDIO_TRANSFER_SIZE
// defines one frame of audio at 20500 mono 16 bit per ms and AUDIO_BUFFERS
// declares 64 frames(64ms) of audio buffering.
// TODO: User should update AUDIO_TRANSFER_SIZE to align with the audio file
// quality.
//
//*****************************************************************************
#define AUDIO_TRANSFER_SIZE     (41) // 20500 * 2 / 1000
#define AUDIO_BUFFERS           (64)
#define AUDIO_BUFFER_SIZE       (AUDIO_TRANSFER_SIZE * AUDIO_BUFFERS)
uint32_t g_ui32BufferSize = AUDIO_BUFFER_SIZE;

//*****************************************************************************
//
// The main audio buffer and it's pointers.
//
//*****************************************************************************
uint8_t g_pui8AudioBuffer[AUDIO_BUFFER_SIZE];
volatile uint8_t *g_pui8Read;
volatile uint8_t *g_pui8Write;
volatile uint32_t g_ui32ValidBytes;

//*****************************************************************************
//
// Holds global flags for the system.
//
//*****************************************************************************
uint32_t g_ui32Flags;

//*****************************************************************************
//
// Set initial macro values so that the application starts in a state where the
// last transfer has competed so a new one can be started.
//
//*****************************************************************************
#define FLAGS_TX_COMPLETE       1
#define FLAGS_BUFFER_COMPLETE       0

//*****************************************************************************
//
// The global playback state for the application.
//
//*****************************************************************************
volatile enum
{
    AUDIO_PLAYING,
    AUDIO_PAUSED,
    AUDIO_STOPPED,
}
g_ePlayState;

//*****************************************************************************
//
// Widget definitions
//
//*****************************************************************************

//*****************************************************************************
//
// Storage for the filename listbox widget string table.
//
//*****************************************************************************
#define NUM_LIST_STRINGS 48
const char *g_ppcDirListStrings[NUM_LIST_STRINGS];

//*****************************************************************************
//
// Storage for the names of the files in the current directory.  Filenames
// are stored in format "(D) filename.ext" for directories or "(F) filename.ext"
// for files.
//
//*****************************************************************************
#define MAX_FILENAME_STRING_LEN (4 + 8 + 1 + 3 + 1)
char g_pcFilenames[NUM_LIST_STRINGS][MAX_FILENAME_STRING_LEN];

//*****************************************************************************
//
// The canvas widgets for the wav file information.
//
//*****************************************************************************
extern tCanvasWidget g_sWaveInfoBackground;

char g_pcTime[16] = "";
Canvas(g_sWaveInfoTime, &g_sWaveInfoBackground, 0, 0,
       &g_sKentec320x240x16_SSD2119, BG_MAX_X - 166, BG_MIN_Y + 28, 158, 10,
       CANVAS_STYLE_FILL | CANVAS_STYLE_TEXT | CANVAS_STYLE_TEXT_RIGHT |
       CANVAS_STYLE_TEXT_OPAQUE, ClrBlack, ClrWhite, ClrWhite,
       g_psFontFixed6x8, g_pcTime, 0, 0);

char g_pcFormat[24] = "";
Canvas(g_sWaveInfoSample, &g_sWaveInfoBackground, &g_sWaveInfoTime, 0,
       &g_sKentec320x240x16_SSD2119, BG_MAX_X - 166, BG_MIN_Y + 18, 158, 10,
       CANVAS_STYLE_FILL | CANVAS_STYLE_TEXT | CANVAS_STYLE_TEXT_RIGHT |
       CANVAS_STYLE_TEXT_OPAQUE, ClrBlack, ClrWhite, ClrWhite,
       g_psFontFixed6x8, g_pcFormat, 0, 0);

Canvas(g_sWaveInfoFileName, &g_sWaveInfoBackground, &g_sWaveInfoSample, 0,
       &g_sKentec320x240x16_SSD2119, BG_MAX_X - 166, BG_MIN_Y + 8, 158, 10,
       CANVAS_STYLE_FILL | CANVAS_STYLE_TEXT | CANVAS_STYLE_TEXT_RIGHT |
       CANVAS_STYLE_TEXT_OPAQUE, ClrBlack, ClrWhite, ClrWhite,
       g_psFontFixed6x8, "", 0, 0);

//*****************************************************************************
//
// The canvas widget acting as the background for the wav file information.
//
//*****************************************************************************
Canvas(g_sWaveInfoBackground, WIDGET_ROOT, 0,
       &g_sWaveInfoFileName, &g_sKentec320x240x16_SSD2119,
       BG_MAX_X - 170, BG_MIN_Y + 4, 166, 80,
       CANVAS_STYLE_FILL, ClrBlack, ClrWhite, ClrWhite,
       g_psFontFixed6x8, 0, 0, 0);

extern tCanvasWidget g_sStatusPanel;

//
// Status text area.
//
Canvas(g_sStatusText, &g_sStatusPanel, 0, 0, &g_sKentec320x240x16_SSD2119,
       BG_MIN_X + 112, BG_MAX_Y - STATUS_HEIGHT + 4, 189, BUTTON_HEIGHT,
       CANVAS_STYLE_FILL | CANVAS_STYLE_TEXT | CANVAS_STYLE_TEXT_RIGHT |
       CANVAS_STYLE_TEXT_OPAQUE, ClrLightGrey, ClrDarkGray, ClrBlack,
       g_psFontCmss16, "         ", 0, 0);

//
// Stop button.
//
RectangularButton(g_sStop, &g_sStatusPanel, &g_sStatusText, 0,
       &g_sKentec320x240x16_SSD2119, BG_MIN_X + 58,
       BG_MAX_Y - STATUS_HEIGHT + 4, 50, BUTTON_HEIGHT,
       PB_STYLE_FILL | PB_STYLE_TEXT |
       PB_STYLE_RELEASE_NOTIFY, ClrLightGrey, ClrDarkGray, 0,
       ClrBlack, g_psFontCmss16, "Stop", 0, 0, 0 ,0 , Stop);

//
// Play/Pause button.
//
RectangularButton(g_sPlayPause, &g_sStatusPanel, &g_sStop, 0,
       &g_sKentec320x240x16_SSD2119, BG_MIN_X + 4,
       BG_MAX_Y - STATUS_HEIGHT + 4, 50, BUTTON_HEIGHT,
       PB_STYLE_FILL | PB_STYLE_TEXT |
       PB_STYLE_RELEASE_NOTIFY, ClrLightGrey, ClrDarkGray, 0,
       ClrBlack, g_psFontCmss16, "Play", 0, 0, 0 ,0 , PlayPause);

//
// Background of the status area behind the buttons.
//
Canvas(g_sStatusPanel, WIDGET_ROOT, 0, &g_sPlayPause,
       &g_sKentec320x240x16_SSD2119, BG_MIN_X, BG_MAX_Y - STATUS_HEIGHT,
       BG_MAX_X - BG_MIN_X, STATUS_HEIGHT,
       CANVAS_STYLE_FILL | CANVAS_STYLE_TEXT_RIGHT |
       CANVAS_STYLE_TEXT_TOP, ClrGray, ClrWhite, ClrBlack, 0,
       0, 0, 0);

//
// The file list box.
//
ListBox(g_sDirList, WIDGET_ROOT, 0, 0,
        &g_sKentec320x240x16_SSD2119,
        BG_MIN_X + 4, BG_MIN_Y + 4, 120,
        BG_MAX_Y - BG_MIN_Y - STATUS_HEIGHT - 8,
        LISTBOX_STYLE_OUTLINE, ClrBlack, ClrDarkBlue, ClrSilver, ClrWhite,
        ClrWhite, g_psFontCmss12, g_ppcDirListStrings,
        NUM_LIST_STRINGS, 0, OnListBoxChange);

//*****************************************************************************
//
// State information for keep track of time.
//
//*****************************************************************************
static uint32_t g_ui32BytesPlayed;
static uint32_t g_ui32NextUpdate;

//*****************************************************************************
//
// Globals used to track play back position.
//
//*****************************************************************************
static uint16_t g_ui16Minutes;
static uint16_t g_ui16Seconds;

//*****************************************************************************
//
// Static constant strings used by the application.
//
//*****************************************************************************
static const char g_pcPlay[] = "Play";
static const char g_pcPause[] = "Pause";
static const char g_pcDirError[] ="Directory Read Error ";

//*****************************************************************************
//
// This function is used to tell when to update the play back times for a file.
// It will only update the screen at 1 second intervals but can be called more
// often with no result.
//
//*****************************************************************************
static void
DisplayTime(uint32_t ui32ForceUpdate)
{
    uint32_t ui32Seconds;
    uint32_t ui32Minutes;

    //
    // Only display on the screen once per second.
    //
    if((g_ui32BytesPlayed >= g_ui32NextUpdate) || (ui32ForceUpdate != 0))
    {
        //
        // Set the next update time to one second later.
        //
        g_ui32NextUpdate = g_ui32BytesPlayed + g_sWavHeader.ui32AvgByteRate;

        //
        // Calculate the integer number of minutes and seconds.
        //
        ui32Seconds = g_ui32BytesPlayed / g_sWavHeader.ui32AvgByteRate;
        ui32Minutes = ui32Seconds / 60;
        ui32Seconds -= ui32Minutes * 60;

        //
        // Print the time string in the format mm.ss/mm.ss
        //
        usprintf(g_pcTime, "%2d:%02d/%d:%02d\0", ui32Minutes,ui32Seconds,
                 g_ui16Minutes, g_ui16Seconds);

        //
        // Display the updated time on the screen.
        //
        WidgetPaint((tWidget *)&g_sWaveInfoTime);
    }
}

//*****************************************************************************
//
// This function will handle stopping the play back of audio.  It will not do
// this immediately but will defer stopping audio at a later time.  This allows
// this function to be called from an interrupt handler.
//
//*****************************************************************************
static void
WaveStop(void)
{
    uint32_t ui32Idx;
    uint32_t *pui32Buffer;

    //
    // Stop playing audio.
    //
    g_ePlayState = AUDIO_STOPPED;

    //
    // Do unsigned long accesses to clear out the buffer.
    //
    pui32Buffer = (uint32_t *)g_pui8AudioBuffer;

    //
    // Zero out the buffer.
    //
    for(ui32Idx = 0; ui32Idx < (AUDIO_BUFFER_SIZE >> 2); ui32Idx++)
    {
        pui32Buffer[ui32Idx] = 0;
    }

    //
    // Reset the number of bytes played and for a time update on the screen.
    //
    g_ui32BytesPlayed = 0;
    g_ui32NextUpdate = 0;
    DisplayTime(1);

    //
    // Change the play/pause button to say play.
    //
    PushButtonTextSet(&g_sPlayPause, g_pcPlay);
    WidgetPaint((tWidget *)&g_sPlayPause);
    CanvasTextSet(&g_sStatusText,
                  "Press Play button to restart");
    WidgetPaint((tWidget *)&g_sStatusText);
}

//*****************************************************************************
//
// This function is used to change to a new directory in the file system.
// It takes a parameter that specifies the directory to make the currentw
// Path separators must use a forward slash "/".  The directory parameter
// can be one of the following:
// * root ("/")
// * a fully specified path ("/my/path/to/mydir")
// * a single directory name that is in the current directory ("mydir")
// * parent directory ("..")
//
// It does not understand relative paths, so dont try something like this:
// ("../my/new/path")
//
// Once the new directory is specified, it attempts to open the directory
// to make sure it exists.  If the new path is opened successfully, then
// the current working directory (cwd) is changed to the new path.
//
// In cases of error, the pui32Reason parameter will be written with one of
// the following values:
//
//  NAME_TOO_LONG_ERROR - combination of paths are too long for the buffer
//  OPENDIR_ERROR - there is some problem opening the new directory
//
//*****************************************************************************
static FRESULT
ChangeToDirectory(char *pcDirectory)
{
    uint32_t ui32Idx;
    FRESULT fresult;

    //
    // Copy the current working path into a temporary buffer so
    // it can be manipulated.
    //
    strcpy(g_pcTmpBuf, g_pcCwdBuf);

    //
    // If the first character is /, then this is a fully specified
    // path, and it should just be used as-is.
    //
    if(pcDirectory[0] == '/')
    {
        //
        // Make sure the new path is not bigger than the cwd buffer.
        //
        if(strlen(pcDirectory) + 1 > sizeof(g_pcCwdBuf))
        {
            return(FR_OK);
        }

        //
        // If the new path name (in argv[1])  is not too long, then
        // copy it into the temporary buffer so it can be checked.
        //
        else
        {
            strncpy(g_pcTmpBuf, pcDirectory, sizeof(g_pcTmpBuf));
        }
    }

    //
    // If the argument is .. then attempt to remove the lowest level
    // on the CWD.
    //
    else if(!strcmp(pcDirectory, ".."))
    {
        //
        // Get the index to the last character in the current path.
        //
        ui32Idx = strlen(g_pcTmpBuf) - 1;

        //
        // Back up from the end of the path name until a separator (/)
        // is found, or until we bump up to the start of the path.
        //
        while((g_pcTmpBuf[ui32Idx] != '/') && (ui32Idx > 1))
        {
            //
            // Back up one character.
            //
            ui32Idx--;
        }

        //
        // Now we are either at the lowest level separator in the
        // current path, or at the beginning of the string (root).
        // So set the new end of string here, effectively removing
        // that last part of the path.
        //
        g_pcTmpBuf[ui32Idx] = 0;
    }

    //
    // Otherwise this is just a normal path name from the current
    // directory, and it needs to be appended to the current path.
    //
    else
    {
        //
        // Test to make sure that when the new additional path is
        // added on to the current path, there is room in the buffer
        // for the full new path.  It needs to include a new separator,
        // and a trailing null character.
        //
        if(strlen(g_pcTmpBuf) + strlen(pcDirectory) + 2 > sizeof(g_pcCwdBuf))
        {
            return(FR_INVALID_OBJECT);
        }

        //
        // The new path is okay, so add the separator and then append
        // the new directory to the path.
        //
        else
        {
            //
            // If not already at the root level, then append a /
            //
            if(strcmp(g_pcTmpBuf, "/"))
            {
                strcat(g_pcTmpBuf, "/");
            }

            //
            // Append the new directory to the path.
            //
            strcat(g_pcTmpBuf, pcDirectory);
        }
    }

    //
    // At this point, a candidate new directory path is in g_pcTmpBuf.
    // Try to open it to make sure it is valid.
    //
    fresult = f_opendir(&g_sDirObject, g_pcTmpBuf);

    //
    // This was a valid path, so copy it into the CWD.
    //
    if(fresult == FR_OK)
    {
        strncpy(g_pcCwdBuf, g_pcTmpBuf, sizeof(g_pcCwdBuf));
    }

    //
    // Return success.
    //
    return(fresult);
}

//*****************************************************************************
//
// Fill the audio buffer with data from the open file.
//
//*****************************************************************************
static void
FillAudioBuffer(void)
{
    uint32_t ui32Count, ui32Size;
    uint8_t *pui8Read;

    //
    // If already full return.
    //
    if(g_ui32ValidBytes == g_ui32BufferSize)
    {
        return;
    }

    //
    // Snap shot the read pointer since it might change while we are filling
    // the buffer.  If the read pointer moved due to an interrupt we will
    // get the update on the next call to this function.
    //
    pui8Read = (uint8_t *)g_pui8Read;

    //
    // If write is ahead of read then fill to the end of the buffer if
    // possible.
    //
    if(pui8Read <= g_pui8Write)
    {
        //
        // Calculate the amount of space we have. Fill the buffer from the
        // current write pointer until the end of the buffer.
        //
        ui32Size = g_ui32BufferSize -
                   (uint32_t)(g_pui8Write - g_pui8AudioBuffer);
        ui32Count = WavRead(&g_sWavFile, (uint8_t *)g_pui8Write, ui32Size);

        g_pui8Write += ui32Count;
        IntMasterDisable();
        g_ui32ValidBytes += ui32Count;
        IntMasterEnable();

        //
        // If the write pointer is pointing to the last position in
        // the buffer then reset write pointer to beginning again.
        if(g_pui8Write == (g_pui8AudioBuffer + g_ui32BufferSize))
        {
            g_pui8Write = g_pui8AudioBuffer;
        }

        //
        // Also fill the buffer from the beginning of the buffer until
        // the current read pointer if the current read pointer is less
        // than the write pointer.
        //
        ui32Size = pui8Read - g_pui8AudioBuffer;

        if(ui32Size)
        {
            ui32Count = WavRead(&g_sWavFile, (uint8_t *)g_pui8Write, ui32Size);
            g_pui8Write += ui32Count;
            IntMasterDisable();
            g_ui32ValidBytes += ui32Count;
            IntMasterEnable();
        }
    }

    //
    // If read is now ahead of write then fill only the buffer between the two
    // pointers starting from the current write pointer to the current read pointer.
    //
    if(pui8Read > g_pui8Write)
    {
        //
        // Calculate the amount of space we have.
        //
        ui32Size = (uint32_t)(pui8Read - g_pui8Write);
        ui32Count = WavRead(&g_sWavFile, (uint8_t *)g_pui8Write, ui32Size);

        g_pui8Write += ui32Count;

        IntMasterDisable();
        g_ui32ValidBytes += ui32Count;
        IntMasterEnable();
    }

    //
    // Indicate the buffer fill operation is complete.
    //
    HWREGBITW(&g_ui32Flags, FLAGS_BUFFER_COMPLETE) = 1;
}

//*****************************************************************************
//
// This function setups Timer 3B for load and match timeout modes.
//
//*****************************************************************************
void
Timer3BSetup(void)
{
    //
    // Enable the peripherals used by the this application.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER3);

    //
    // Compute the timeout period based on the system clock.
    //
    g_ui32Period = g_ui32SysClock / g_sWavFile.sWavHeader.ui32SampleRate;

    //
    // Configure the timer to run in periodic timeout mode.
    //
    if((HWREG(TIMER3_BASE + TIMER_O_CTL) & TIMER_CTL_TBEN) == 0)
    {
        MAP_TimerConfigure(TIMER3_BASE, (TIMER_CFG_SPLIT_PAIR |
                                        TIMER_CFG_B_PERIODIC ));
    }

    //
    // Load the  timer with the period equal to the sample
    // rate to generate timeout interrupt.
    //
    MAP_TimerLoadSet(TIMER3_BASE, TIMER_B, g_ui32Period - 1);

    //
    // Update the timer values on timeouts and not immediately.
    //
    MAP_TimerUpdateMode(TIMER3_BASE, TIMER_B, TIMER_UP_LOAD_TIMEOUT |
                                              TIMER_UP_MATCH_TIMEOUT);
    //
    // Configure the timer to generate an interrupt at every time-out event.
    //
    MAP_TimerIntEnable(TIMER3_BASE, TIMER_TIMB_TIMEOUT);

    //
    // Enable the timer.  At this point, the timer generates an interrupt
    // after TIMER_B timeout.
    //
    MAP_TimerEnable(TIMER3_BASE, TIMER_B);
    MAP_IntEnable(INT_TIMER3B);

    //
    // Set Timer to highest priority. User's must decide
    // the best priority arrangement for his application.
    //
    IntPrioritySet(INT_TIMER3B, 0);
}

//*****************************************************************************
//
// This is the callback for the play/pause button.
//
//*****************************************************************************
static void
PlayPause(tWidget *psWidget)
{
    int16_t i16Select;

    //
    // If we are stopped, then start playing.
    //
    if(g_ePlayState == AUDIO_STOPPED)
    {
        //
        // Get the current selection from the list box.
        //
        i16Select = ListBoxSelectionGet(&g_sDirList);

        //
        // See if this is a valid .wav file that can be opened.
        //
        if(WavOpen(g_pcFilenames[i16Select], &g_sWavFile) == 0)
        {
            //
            // Initialize the read and write pointers.
            //
            g_pui8Read = g_pui8AudioBuffer;
            g_pui8Write = g_pui8AudioBuffer;
            g_ui32ValidBytes = 0;

            //
            // Fill the audio buffer from the file.
            //
            FillAudioBuffer();

            //
            // Setup the timer.
            //
            Timer3BSetup();

            //
            // Indicate that wave play back should start.
            //
            g_ePlayState = AUDIO_PLAYING;

            //
            // Change the button to indicate pause.
            //
            PushButtonTextSet(&g_sPlayPause, g_pcPause);
            WidgetPaint((tWidget *)&g_sPlayPause);
            CanvasTextSet(&g_sStatusText,
                          "Audio Playing... ");
            WidgetPaint((tWidget *)&g_sStatusText);
        }
        else
        {
            //
            // Play was pressed on an invalid file.
            //
            CanvasTextSet(&g_sStatusText, "Invalid wav format ");
            WidgetPaint((tWidget *)&g_sStatusText);

            return;
        }

        //
        // Initialize the read and write pointers.
        //
        g_pui8Read = g_pui8AudioBuffer;
        g_pui8Write = g_pui8AudioBuffer;
        g_ui32ValidBytes = 0;

    }
    else if(g_ePlayState == AUDIO_PLAYING)
    {
        //
        // Now switching to a paused state, so change the button to say play.
        //
        PushButtonTextSet(&g_sPlayPause, g_pcPlay);
        WidgetPaint((tWidget *)&g_sPlayPause);
        CanvasTextSet(&g_sStatusText,
                      "Press Play button to continue");
        WidgetPaint((tWidget *)&g_sStatusText);

        //
        // Disable timer interrupt.
        //
        MAP_IntDisable(INT_TIMER3B);

        g_ePlayState = AUDIO_PAUSED;
    }
    else if(g_ePlayState == AUDIO_PAUSED)
    {

        //
        // Now switching to a play state, so change the button to say paused.
        //
        PushButtonTextSet(&g_sPlayPause, g_pcPause);
        WidgetPaint((tWidget *)&g_sPlayPause);
        CanvasTextSet(&g_sStatusText,
                      "Audio Playing...");
        WidgetPaint((tWidget *)&g_sStatusText);
        
        //
        // Enable timer interrupt.
        //
        MAP_IntEnable(INT_TIMER3B);

        g_ePlayState = AUDIO_PLAYING;
    }
}

//*****************************************************************************
//
// Stopped pressed so stop the wav playback.
//
//*****************************************************************************
static void
Stop(tWidget *psWidget)
{
    //
    // Stop play back if we are playing.
    //
    if((g_ePlayState == AUDIO_PLAYING) || (g_ePlayState == AUDIO_PAUSED))
    {
        //
        // Disable timer interrupt
        //
        MAP_IntDisable(INT_TIMER3B);

        WaveStop();
    }
}

//*****************************************************************************
//
//! Handles the TIMER3B interrupt.
//!
//! This function responds to the TIMER3B interrupt, updating the duty cycle of
//! the output waveform in order to produce sound.  It is the application's
//! responsibility to ensure that this function is called in response to the
//! TIMER3B interrupt, typically by installing it in the vector table as the
//! handler for the TIMER3B interrupt.
//!
//! \return None.
//
//*****************************************************************************
void
SoundIntHandler(void)
{
    uint16_t ui16DutyCycle;
    uint16_t ui16NumChannel;
    uint16_t ui16Resolution;
    uint8_t ui8UpperByte;
    uint8_t ui8LowerByte;

    ui16NumChannel = g_sWavFile.sWavHeader.ui16NumChannels;
    ui16Resolution = g_sWavFile.sWavHeader.ui16BitsPerSample;

    //
    // Clear the timer interrupt.
    //
    MAP_TimerIntClear(TIMER3_BASE, TIMER_TIMB_TIMEOUT);

    //
    // If the read pointer is at the end of the buffer indicated by
    // FLAGS_TX_COMPLETE=1 and if buffer is still being filled indicated by
    // FLAGS_BUFFER_COMPLETE=0 then exit the ISR so we don't output any data
    // to the speaker.
    //
    if ((HWREGBITW(&g_ui32Flags, FLAGS_BUFFER_COMPLETE) == 0) &&
        (HWREGBITW(&g_ui32Flags, FLAGS_TX_COMPLETE) == 1))
    {
        return;
    }

    //
    // Check if we are reaching the end of the wave file.
    //
    if (g_ui32BytesPlayed < g_sWavFile.sWavHeader.ui32DataSize)
    {
        if (ui16Resolution == 16)
        {
            //
            // Read two bytes at a time if resolution is 16 bit.
            //
            ui8LowerByte = *g_pui8Read++;
            ui8UpperByte = *g_pui8Read++;

            //
            // 1. Retrieve the two bytes from the .wav file.
            // 2. Right-shift by 2 since the BoosterPack DAC is only 14 bits.
            // 3. Zero out the two MSB to force the DAC in normal mode.  Refer
            // to the DAC datasheet for more details.
            ui16DutyCycle = ((((ui8UpperByte << 8) + ui8LowerByte)
                              + 32768) >> 2) & 0x3FFF;

            // 1. Pull the SYNC signal low.
            // 2. Write the duty cycle value to DAC.
            // 3. Wait until the transmit shift register is complete.
            // 4. Release the SYNC signal.
            MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_1, 0);
            MAP_SSIDataPut(SSI2_BASE, ui16DutyCycle);
            while (SSIBusy(SSI2_BASE));
            MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_1, GPIO_PIN_1);

            //
            // Increment the number of bytes played. The boosterPack speaker is
            // only one channel.  If the wave file playback in stereo mode
            // (2 channels) then skip the second channel.
            //
            g_ui32BytesPlayed += ui16NumChannel * 2;
            
            //
            // Increment the read pointer by 2 to skip the 2nd channel.
            //
            g_pui8Read = (ui16NumChannel == 2) ? (g_pui8Read + 2) : g_pui8Read;
        }
        if (ui16Resolution == 8)
        {
            //
            // Read the next byte.
            //
            ui8LowerByte = *g_pui8Read++;
            
            //
            // 1. Retrieve the each audio byte from the wave file.
            // 2. Left-shift by 6 for scaling since the BoosterPack DAC is 14 bits.
            // 3. Zero out the two MSB to force the DAC in normal mode.  Refer
            // to the DAC datasheet for more details.
            //
            ui16DutyCycle = (ui8LowerByte << 6) & 0x3FFF;

            //
            // 1. Pull the SYNC signal low.
            // 2. Write the duty cycle value to DAC.
            // 3. Wait until the transmit shift register is complete.
            // 4. Release the SYNC signal.
            //
            MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_1, 0);
            MAP_SSIDataPut(SSI2_BASE, ui16DutyCycle);
            while (MAP_SSIBusy(SSI2_BASE));
            MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_1, GPIO_PIN_1);

            //
            // Increment the number of bytes played. The boosterPack speaker is
            // only one channel.  If the wave file playback in stereo mode
            // (2 channels) then skip the second channel.
            //
            g_ui32BytesPlayed += ui16NumChannel * 1;
            
            //
            // Increment the read pointer by 1 to skip the 2nd channel.
            //
            g_pui8Read = (ui16NumChannel == 2) ? (g_pui8Read + 1) : g_pui8Read;

        }
        //
        // Once we play half of the buffer size we will request the buffer to
        // be re-filled from the .wav file by the non-interrupt code in main();
        // For example, if audio rate is 220500Hz at 16 bit resolution mono
        // playback, the buffer size will 220500 * 2 / 1000 * 64 = 2624.  So
        // every 2624 / 2 = 1312 bytes of data we'll set the FLAGS_TX_COMPLETE
        // flag to have more data filled in the buffer.
        //
        if (((g_ui32BytesPlayed % (g_ui32BufferSize / 2 )) == 0))
        {
            HWREGBITW(&g_ui32Flags, FLAGS_TX_COMPLETE) = 1;
            HWREGBITW(&g_ui32Flags, FLAGS_BUFFER_COMPLETE) = 0;
            
            //
            // Wrap the read pointer if the read pointer is reaching the
            // the end of buffer.
            //
            if(g_pui8Read >= (g_pui8AudioBuffer + g_ui32BufferSize))
            {
                g_pui8Read = g_pui8AudioBuffer;
            }
        }
        //
        // For 16-bit resolution, we'll read 2 bytes at a time and deduct
        // g_ui32ValidBytes by 2 for mono speaker.  If stereo, then we deduct
        // by 4.  For 8-bit resolution, we read 1 byte at a time and we deduct
        // g_ui32ValidBytes by 1 for mono speaker.  If stereo, then we deduct
        // by 2.  g_ui32ValidBytes indicates the number of valid bytes still
        // left in the buffer.
        //
        if ((ui16Resolution == 16) && (g_ui32ValidBytes >= 2))
        {
            g_ui32ValidBytes -= ui16NumChannel * 2;
        }
        else if ((ui16Resolution == 8) && (g_ui32ValidBytes >= 1))
        {
            g_ui32ValidBytes -= ui16NumChannel * 1;
        }
        else
        {
            g_ui32ValidBytes = 0;
        }
    }
    else
    {
        //
        // Disable timer interrupt.
        //
        MAP_IntDisable(INT_TIMER3B);

        //
        // Close the file, it will be re-opened on play.
        //
        WaveStop();
    }
}

//*****************************************************************************
//
// This function is called to read the contents of the current directory on
// the SD card and fill the listbox containing the names of all files and
// directories.
//
//*****************************************************************************
static int
PopulateFileListBox(bool bRepaint)
{
    uint32_t ui32ItemCount;
    FRESULT iFResult;

    //
    // Empty the list box on the display.
    //
    ListBoxClear(&g_sDirList);

    //
    // Make sure the list box will be redrawn next time the message queue
    // is processed.
    //
    if(bRepaint)
    {
        WidgetPaint((tWidget *)&g_sDirList);
    }

    //
    // Open the current directory for access.
    //
    iFResult = f_opendir(&g_sDirObject, g_pcCwdBuf);

    //
    // Check for error and return if there is a problem.
    //
    if(iFResult != FR_OK)
    {
        //
        // Change the text to reflect the change.
        //
        CanvasTextSet(&g_sStatusText, g_pcDirError);
        WidgetPaint((tWidget *)&g_sStatusText);
        return(iFResult);
    }

    //
    // If not at the root then add the ".." entry.
    //
    if(g_pcCwdBuf[1] != 0)
    {
        g_pcFilenames[0][0] = '.';
        g_pcFilenames[0][1] = '.';
        g_pcFilenames[0][2] = 0;
        ListBoxTextAdd(&g_sDirList, g_pcFilenames[0]);
        ui32ItemCount = 1;
    }
    else
    {
        ui32ItemCount = 0;
    }

    //
    // Enter loop to enumerate through all directory entries.
    //
    for(;;)
    {
        //
        // Read an entry from the directory.
        //
        iFResult = f_readdir(&g_sDirObject, &g_sFileInfo);

        //
        // Check for error and return if there is a problem.
        //
        if(iFResult != FR_OK)
        {
            //
            // Change the text to reflect the change.
            //
            CanvasTextSet(&g_sStatusText, g_pcDirError);
            WidgetPaint((tWidget *)&g_sStatusText);
            return(iFResult);
        }

        //
        // If the file name is blank, then this is the end of the
        // listing.
        //
        if(!g_sFileInfo.fname[0])
        {
            break;
        }

        //
        // Add the information as a line in the listbox widget.
        //
        if(ui32ItemCount < NUM_LIST_STRINGS)
        {
            if(g_sFileInfo.fattrib & AM_DIR)
            {
                usnprintf(g_pcFilenames[ui32ItemCount],
                          MAX_FILENAME_STRING_LEN, "+ %s", g_sFileInfo.fname);
            }
            else
            {
                ustrncpy(g_pcFilenames[ui32ItemCount], g_sFileInfo.fname,
                         MAX_FILENAME_STRING_LEN);
            }
            ListBoxTextAdd(&g_sDirList, g_pcFilenames[ui32ItemCount]);
        }

        //
        // Move to the next entry in the item array we use to populate the
        // list box.
        //
        ui32ItemCount++;
    }

    //
    // Made it to here, return with no errors.
    //
    return(0);
}

//*****************************************************************************
//
// The listbox widget callback function.
//
// This function is called whenever someone changes the selected entry in the
// listbox containing the files and directories found in the current directory.
//
//*****************************************************************************
void OnListBoxChange(tWidget *psWidget, int16_t i16Selected)
{
    int16_t i16Sel;

    //
    // Get the current selection from the list box.
    //
    i16Sel = ListBoxSelectionGet(&g_sDirList);

    //
    // Is there any selection?
    //
    if(i16Sel == -1)
    {
        return;
    }
    else
    {
        //
        // Is the selection a directory name.
        //
        if(g_pcFilenames[i16Sel][0] == '+')
        {
            if((g_ePlayState != AUDIO_PLAYING) &&
               (g_ePlayState != AUDIO_PAUSED))
            {
                CanvasTextSet(&g_sWaveInfoFileName, "");
                CanvasTextSet(&g_sWaveInfoSample, "");
                g_pcTime[0] = 0;
            }

            if(ChangeToDirectory(&g_pcFilenames[i16Sel][2]) == FR_OK)
            {
                PopulateFileListBox(true);
            }
        }
        else if((g_pcFilenames[i16Sel][0] == '.') &&
                (g_pcFilenames[i16Sel][1] == '.'))
        {
            //
            // If this was the .. selection then move up a directory.
            //
            if(ChangeToDirectory("..") == FR_OK)
            {
                PopulateFileListBox(true);
            }

            if((g_ePlayState != AUDIO_PLAYING) &&
               (g_ePlayState != AUDIO_PAUSED))
            {
                CanvasTextSet(&g_sWaveInfoFileName, "");
                CanvasTextSet(&g_sWaveInfoSample, "");
                g_pcTime[0] = 0;
            }
        }
        else
        {
            //
            // This was a normal file selections so see if it is a wav file.
            //
            CanvasTextSet(&g_sWaveInfoFileName, g_pcFilenames[i16Sel]);

            if((g_ePlayState == AUDIO_PLAYING) ||
               (g_ePlayState == AUDIO_PAUSED))
            {
                //
                // Don't load a new file while playing.
                //
                return;
            }

            if(WavOpen(g_pcFilenames[i16Sel], &g_sWavFile) == 0)
            {
                //
                // Read the .wav file format.
                //
                WavGetFormat(&g_sWavFile, &g_sWavHeader);

                //
                // Print the formatted string so that it can be
                // displayed.
                //
                usprintf(g_pcFormat, "%d Hz %d bit ",
                         g_sWavHeader.ui32SampleRate,
                         g_sWavHeader.ui16BitsPerSample);

                //
                // Concatenate the number of channels.
                //
                if(g_sWavHeader.ui16NumChannels == 1)
                {
                    strcat(g_pcFormat, "Mono");
                }
                else
                {
                    strcat(g_pcFormat, "Stereo");
                }

                CanvasTextSet(&g_sWaveInfoSample, g_pcFormat);

                //
                // Calculate the minutes and seconds in the file.
                //
                g_ui16Seconds = g_sWavHeader.ui32DataSize /
                              g_sWavHeader.ui32AvgByteRate;
                g_ui16Minutes = g_ui16Seconds / 60;
                g_ui16Seconds -= g_ui16Minutes * 60;

                //
                // Close the file, it will be re-opened on play.
                //
                WavClose(&g_sWavFile);

                //
                // Update the file time information.
                //
                DisplayTime(1);

                //
                // Close the file, it will be re-opened on play.
                //
                WavClose(&g_sWavFile);
            }
            else
            {
                CanvasTextSet(&g_sWaveInfoSample, "");
                g_pcTime[0] = 0;
                WidgetPaint((tWidget *)&g_sWaveInfoTime);
            }
        }

        //
        // Update the file name and wav file information
        //
        WidgetPaint((tWidget *)&g_sWaveInfoFileName);
        WidgetPaint((tWidget *)&g_sWaveInfoSample);
        WidgetPaint((tWidget *)&g_sWaveInfoTime);
    }
}

//*****************************************************************************
//
// This is the handler for this SysTick interrupt.  FatFs requires a
// timer tick every 10ms for internal timing purposes.
//
//*****************************************************************************
void
SysTickHandler(void)
{
    //
    // Call the FatFs tick timer.
    //
    disk_timerproc();
}

//*****************************************************************************
//
// Initializes the file system module.
//
// \param None.
//
// This function initializes the third party FAT implementation.
//
// \return Returns \e true on success or \e false on failure.
//
//*****************************************************************************
static bool
FileInit(void)
{
    //
    // Mount the file system, using logical disk 0.
    //
    if(f_mount(0, &g_sFatFs) != FR_OK)
    {
        return(false);
    }
    return(true);
}

//*****************************************************************************
//
// This function configure the pins to drive the boosterPack speaker.
// PN0 is used to enable the audio amplifier.
//
//*****************************************************************************
void
AudioPinConfig(void)
{
    //
    // GPIO ports need to be enabled so those pins can be used.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION);

    //
    // PN0 is used for speaker amp enable.
    //
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_0);
    
    //
    // Enable the speaker amp.
    //
    MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0, 0);
}

//*****************************************************************************
//
// This function configure the SSI2 to send audio data to the SPI-DAC on the.
// BoosterPack which will then then the audio signal to the speaker.
//
//*****************************************************************************
void DacSpiInit()
{
    //
    // The SSI2 peripheral must be enabled for use.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);

    //
    // For this example SSI2 is used with PortG[7] and PortG[5].
    // GPIO ports need to be enabled so those pins can be used.
    //
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOG);
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION);

    //
    // Configure the pin muxing for SSI2 functions.
    //
    MAP_GPIOPinConfigure(GPIO_PG7_SSI2CLK);
    MAP_GPIOPinConfigure(GPIO_PG5_SSI2XDAT0);

    //
    // GPION1 is used as the SYNC pin.
    //
    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_1);

    //
    // Configure the GPIO settings for the SSI pins.  This function also gives
    // control of these pins to the SSI hardware.  Consult the data sheet to
    // see which functions are allocated per pin.
    // The pins are assigned as follows:
    //
    //      SSI2
    //      PG7 - SSI2TX
    //      PG5 - SSI2CLK
    //
    MAP_GPIOPinTypeSSI(GPIO_PORTG_BASE, GPIO_PIN_7 | GPIO_PIN_5 );

    //
    // Configure and enable the SSI2 port for SPI master mode.  Use SSIs,
    // system clock supply, idle clock level low and active low clock in legacy
    // Freescale SPI mode, master mode, 60MHz SSI frequency, and 16-bit data.
    // For SPI mode, you can set the polarity of the SSI clock when the SSI
    // unit is idle.  You can also configure what clock edge you want to
    // capture data on.  Please reference the datasheet for more information on
    // the different SPI modes.
    //
    // Per DAC datasheet, it samples data at the falling edge, so
    // SSI_FRF_MOTO_MODE_1 is used.
    //
    MAP_SSIConfigSetExpClk(SSI2_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1,
                           SSI_MODE_MASTER, 60000000, 16);


    //
    // Enable the SSI2 modules.
    //
    MAP_SSIEnable(SSI2_BASE);
}
//*****************************************************************************
//
// The program main function.  It performs initialization, then handles wav
// file playback.
//
//*****************************************************************************
int
main(void)
{
    uint32_t ui32Retcode;

    //
    // Set the system clock to run at 120MHz from the PLL.
    //
    g_ui32SysClock = MAP_SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                            SYSCTL_OSC_MAIN | SYSCTL_USE_PLL |
                                            SYSCTL_CFG_VCO_240), 120000000);

    //
    // Configure the device pins.
    //
    PinoutSet();

    //
    // Enable and initialize SSI2 module to interface with the DAC
    //
    DacSpiInit();

    //
    // Configure pins used to drive the speaker.
    //
    AudioPinConfig();

    //
    // Set LCD to lower priority than Timer.  User's must decide
    // the best priority arrangement for his application.
    //
    IntPrioritySet(INT_LCD0, 0xE0);

    //
    // Enable Interrupts
    //
    MAP_IntMasterEnable();

    //
    // Initialize the display driver.
    //
    Kentec320x240x16_SSD2119Init(g_ui32SysClock);

    //
    // Initialize the graphics context.
    //
    GrContextInit(&g_sContext, &g_sKentec320x240x16_SSD2119);

    //
    // Draw the application frame.
    //
    FrameDraw(&g_sContext, "SPI-DAC Audio Playback Demo");

    //
    // Initialize the touch screen driver.
    //
    TouchScreenInit(g_ui32SysClock);

    //
    // Set the touch screen event handler.
    //
    TouchScreenCallbackSet(WidgetPointerMessage);

    WidgetAdd(WIDGET_ROOT, (tWidget *)&g_sDirList);
    WidgetAdd(WIDGET_ROOT, (tWidget *)&g_sStatusPanel);
    WidgetAdd(WIDGET_ROOT, (tWidget *)&g_sWaveInfoBackground);

    //
    // Issue the initial paint request to the widgets then immediately call
    // the widget manager to process the paint message.  This ensures that the
    // display is drawn as quickly as possible and saves the delay we would
    // otherwise experience if we processed the paint message after mounting
    // and reading the SD card.
    //
    WidgetPaint(WIDGET_ROOT);

    //
    // Determine whether or not an SD Card is installed.  If not, print a
    // warning and have the user install one and restart.
    //
    ui32Retcode = disk_initialize(0);

    if(ui32Retcode != RES_OK)
    {
        CanvasTextSet(&g_sStatusText,  "File system error! ");
        WidgetPaint((tWidget *)&g_sStatusText);

        //
        // Process any messages in the widget message queue.
        //
        WidgetMessageQueueProcess();
        
        while(1);
    }
    else
    {
        //
        // Mount the file system, using logical disk 0.
        //
        f_mount(0, &g_sFatFs);

        if(!FileInit())
        {
            CanvasTextSet(&g_sStatusText, "File system error! ");
            WidgetPaint((tWidget *)&g_sStatusText);
            
            //
            // Process any messages in the widget message queue.
            //
            WidgetMessageQueueProcess();

            while(1);
        }
        else
        {
            CanvasTextSet(&g_sStatusText, "SD card initialized. ");
            WidgetPaint((tWidget *)&g_sStatusText);
        }
    }

    //
    // Not playing anything right now.
    //
    g_ui32Flags = 0;
    g_ePlayState = AUDIO_STOPPED;

    PopulateFileListBox(true);

    //
    // Reset the CWD to the root directory.
    //
    g_pcCwdBuf[0] = '/';
    g_pcCwdBuf[1] = 0;

    //
    // Initiate a directory change to the root.  This will
    // populate a menu structure representing the root directory.
    //
    if(ChangeToDirectory("/") == FR_OK)
    {
        //
        // Request a repaint so the file menu will be shown
        //
        WidgetPaint(WIDGET_ROOT);
    }
    else
    {
        CanvasTextSet(&g_sStatusText,
                      "Error accessing root directory ");
        WidgetPaint((tWidget *)&g_sStatusText);
        while(1);
    }

    //
    // Enter an (almost) infinite loop for reading and processing commands from
    // the user.
    //
    while(1)
    {
        //
        // Handle the case when the wave file is playing.
        //
        if(g_ePlayState == AUDIO_PLAYING)
        {
            //
            // Handle the transmit complete event.
            //
            if(HWREGBITW(&g_ui32Flags, FLAGS_TX_COMPLETE))
            {
                //
                // Clear the transmit complete flag.
                //
                HWREGBITW(&g_ui32Flags, FLAGS_TX_COMPLETE) = 0;

                //
                // If there is space available then fill the buffer.
                //
                FillAudioBuffer();

                //
                // If we run out of valid bytes then stop the playback.
                //
                if(g_ui32ValidBytes == 0)
                {
                    //
                    // No more data or error so stop playing.
                    //
                    WaveStop();
                }
            }
            
            DisplayTime(0);
        }
        
        //
        // Process any messages in the widget message queue.
        //
        WidgetMessageQueueProcess();
    }
}

