Music Player for ARCH-PRO

Dependencies:   GT20L16J1Y_font TinyJpgDec mbed

SeeedStudio Arch Pro + aitendo TFT-LCD w/Touch panel => .wav File Player
LPC1768 + aitendo TFT-LCD w/Touch panel => .wav File Player
http://goji2100.com/
/media/uploads/Goji/017s.png

GSDPlayer.cpp

Committer:
Goji
Date:
2014-09-08
Revision:
0:6fa19738f62e

File content as of revision 0:6fa19738f62e:

// --------------------------------------------------------
//  GSDPlayer (c) CopYright 2013-2014 Goji.   goji2100.com
// --------------------------------------------------------

#include "mbed.h"
#include "mainconf.h"

#include "SDFileSystem.h"
#include "TinyJpgDec.h"

#include "mGTFT.h"
#include "mGTP.h"
#include "GT20L16J1Y_font.h"


/* SDFileSystem.cpp change
 -----------------------------------------------------------------------
144:    _spi.frequency(16000000);
210:    _spi.frequency(24000000); // Set to 1MHz for data transfer
*/


//#define Level_Indicator 4000  // for Level Indicator
//#define Test                  // for Simple Player

#define FILESYS "sd"
#if defined(_ARCH_PRO)
  SDFileSystem sd(P0_9,  P0_8,  P0_7,  P0_6,  FILESYS); // Arch Pro SPI1SD
  #define BUFF_SIZE (512 * 4)               // WAV file buffer size
  #define LLow    1
  #define LHigh   0
  PwmOut  Line_R(P2_0), Line_L(P2_1);       // PWM_1, PWM_2
  BusOut  TFTBus(D8, D9, D2, D3, D4, D5, D6, D7);
#else
#endif

Serial pc(USBTX, USBRX);
DigitalOut statLED1(LED1), statLED2(LED2), statLED3(LED3), statLED4(LED4);

#define PWM_CLKMHZ  24  // 24MHz
Ticker Tick_Timer;

volatile uint16_t ps_stat = 0;      // Player status
uint16_t tp_stat = 0, tp_statN = 0;
#define PS_PLAY     BIT1(15)
#define PS_PAUSE    BIT1(14)
#define PS_FFR      BIT1(13)
#define PS_FFF      BIT1(12)
#define PS_SEEK     BIT1(11)
#define PS_FR       BIT1(10)
#define PS_FF       BIT1( 9)
#define PS_VDOWN    BIT1( 8)
#define PS_VUP      BIT1( 7)
#define PS_EOR      BIT1( 6)   // End of Root
#define PS_EOD      BIT1( 5)   // End of DIR
#define PS_EOF      BIT1( 4)   // Eod of File

#define SWBuff()    { if (buffNX==0) { buffNW=0,buffNX=1; } else { buffNW=1,buffNX=0; } buffLN[buffNX]=buffGP=0; }
volatile int32_t buffNW, buffNX, buffLN[2], buffGP;
unsigned char Wave_buff[2][BUFF_SIZE];
#define _WS16(n,v)  *(short*         )&Wave_buff[n][v]
#define _WU16(n,v)  *(unsigned short*)&Wave_buff[n][v]
#define _WU32(n,v)  *(unsigned long* )&Wave_buff[n][v]
uint8_t  Wave_CHs, Wave_Bits;
uint32_t Wave_BPS, Wave_Size, Wave_Rlen, Wave_Play, Wave_FPos;
uint16_t Wave_Prid;

volatile uint16_t Pwm_Ratio, Line_Vols = 16, Line_Vol = 160;

#if defined(Level_Indicator)
volatile uint32_t Pwm_cnt = 0;
volatile uint16_t Pwm_aveL = 0, Pwm_aveR = 0;
#endif

void PwmLR_Out16(int16_t lv, int16_t rv)
{
    int16_t lw = ((lv + 32768) / Pwm_Ratio);
    int16_t rw = ((rv + 32768) / Pwm_Ratio);
//  if (lw >= LPC_PWM1->MR0) lw = LPC_PWM1->MR0;
//  if (rw >= LPC_PWM1->MR0) rw = LPC_PWM1->MR0;
  #if defined(_ARCH_PRO)
    LPC_PWM1->MR1 = lw;
    LPC_PWM1->MR2 = rw;
    if (Line_Vols)
        LPC_PWM1->LER |= ((1 << 1) | (1 << 2));
  #else
    LPC_PWM1->MR6 = lw;
    LPC_PWM1->MR5 = rw;
    if (Line_Vols)
        LPC_PWM1->LER |= ((1 << 6) | (1 << 5));
  #endif
  #if defined(Level_Indicator)
    Pwm_cnt++;
    Pwm_aveL = (Pwm_aveL + ((uint16_t)(lv) + 32768) >> 8) / 2;
    Pwm_aveR = (Pwm_aveR + ((uint16_t)(rv) + 32768) >> 8) / 2;
  #endif
}


void ISR_Tick(void)
{
    if (!(ps_stat & PS_PAUSE) && (buffGP < buffLN[buffNW])) {
        statLED2 = LHigh;
        PwmLR_Out16(_WS16(buffNW, buffGP), _WS16(buffNW, buffGP + 2));
        buffGP += 4;
        if ((buffGP >= buffLN[buffNW]) && (buffNX != -1)) SWBuff();
        statLED2 = LLow;
    }
}


extern TFT_INFO TFT_info;
int32_t Last_Err;

GT20L16J1Y_FONT font(p11, p12, p13, P0_16);
void draw_kanji(int x1, int y1, uint16_t c1, uint16_t c2)
{
    for (int x = 0; x < 32; x++)
        for (int y = 0, m = 0x01; y < 8; y++, m <<= 1)
            TFT_setPixel(x1 + x%16,  y1 + y + (8 * (x >> 4)), ((font.bitmap[x] & m) ? c1 : c2));
}


uint16_t bmpXsize, bmpYsize;

void BMP24Bits(int16_t x1, int16_t y1, char *fn)
{
    uint8_t rbuff[64];

    FILE *fp = fopen(fn, "rb");
    if (!fp) {
        pc.printf("open error\n");
        return;
    }

    fread(rbuff, 1, (14 + 40), fp);
    bmpXsize = *(uint16_t*)&rbuff[18];
    bmpYsize = *(uint16_t*)&rbuff[22];

    if ((bmpXsize > TFT_MAX_X) ||
        (bmpYsize > TFT_MAX_Y) ||
        (rbuff[28] != 24)) {
        pc.printf("format error\n");
        return;
    }

    int dummy = (bmpXsize * 3) % 4;
    for (int z = 0, y = y1; z < bmpYsize; z++, y--) {
        TFT_setXY(x1, y);
        for (int i = 0; i < bmpXsize; i++) {
            fread(rbuff, 1, 3, fp);
            TFT_wr_data((uint16_t)(
                ((rbuff[2] & 0xF8) << 8) |      // R
                ((rbuff[1] & 0xFC) << 3) |      // G
                ((rbuff[0]       ) >> 3) ));    // B
        }
        if (dummy) fread(rbuff, 1, dummy, fp);
    }
    fclose(fp);
}


FILE *jfp;
UINT jpeg_input(JDEC *jd, BYTE *buff, UINT ndata)
{
    if (buff) {
        int n = fread(buff, 1, ndata, jfp);
        return(n == -1 ? 0 : n);
    }
    return(fseek(jfp, ndata, SEEK_CUR) == -1 ? 0 : ndata);
}

int16_t Cover_tlx, Cover_tly;

UINT jpeg_output(JDEC *jd, void *bitmap, JRECT *rect)
{
    WORD *src = (WORD *)bitmap;

    int x0 = rect->left;
    int x1 = rect->right;
    int y0 = rect->top;
    int y1 = rect->bottom;
    int w = x1 - x0 + 1;

    if (x0 >= TFT_MAX_X || y0 >= TFT_MAX_Y) return 1;
    if (x1 >= TFT_MAX_X) x1 = TFT_MAX_X - 1;
    if (y1 >= TFT_MAX_Y) y1 = TFT_MAX_Y - 1;

    for (int y = y0; y <= y1; y++) {
        TFT_setXY(Cover_tlx + x0, Cover_tly + y);
        WORD *p = src + w * (y - y0);
        for (int x = x0; x <= x1; x++)
            TFT_wr_data(*p++);
    }
    return 1;
}


#define MM_TLX      0
#define MM_TLY      (TFT_MAX_Y - bmpYsize)
#define MM_YH       bmpYsize

#define MM_FR       80
#define MM_PLAY     (MM_FR   + 50)
#define MM_FF       (MM_PLAY + 54)
#define MM_FFE      (MM_FF   + 50)
#define MM_VUPD     (TFT_MAX_X - 58)

#define MM_VUP      (TFT_MAX_Y - bmpYsize)
#define MM_VDWN     (TFT_MAX_Y - (bmpYsize / 2))

uint16_t tp_menu()
{
    int16_t wposX, wposY;

    if (TPC_getXY(&wposX, &wposY) == 2) {
        if (wposY < MM_TLY) return(PS_SEEK);
        if (wposX < MM_FR ) return(0);
        if      (wposX < MM_PLAY) return(PS_FFR);
        else if (wposX < MM_FF  ) return(PS_PAUSE);
        else if (wposX < MM_FFE ) return(PS_FFF);
        else if (wposY < MM_VDWN) return(PS_VUP);
        else if (wposY > MM_VDWN) return(PS_VDOWN);
    }
    return(0);
}

//#define SEEK_Dump() 
#define SEEK_Dump() pc.printf("%d - (%8d - %d = %8d) = %8d(%8d)\n", Wave_Size,Wave_FPos,Wave_Play,Wave_FPos-Wave_Play,Wave_Rlen,Wave_Rlen-(Wave_Size-(Wave_FPos-Wave_Play)))

uint16_t event_chk()
{
    static uint16_t stepS  = 0, timep, barp, bcp;
    static int16_t  tp_cnt = 0;

    int16_t px, py;
    uint16_t statN = 0, tw, barl, bc;
    char times[8];

    while (1) {
        switch (stepS) {
            case  0:
            case 50:
                tw = (Wave_FPos - Wave_Play) / (Wave_BPS * 4);   // (Wave_FPos * 8) / (Wave_BPS * 16 * 4)
                if (tw != timep) {
                    timep = tw;
                    sprintf(times, "%02d:%02d", (timep / 60), (timep % 60));
                    TFT_drawText( 28, (TFT_MAX_Y - 1) - 6 - (bmpYsize / 2), (uint8_t*)times, WHITE, BLACK);
                    barp = 0;
                }
                stepS += 10;
                break;

            case 10:
            case 60:
                barl = (((Wave_FPos - Wave_Play) / 1000) * TFT_MAX_X) / (Wave_Size / 1000);
                bc = (ps_stat & PS_PAUSE) ? YELLOW : BLUE;
                if ((stepS >= 50) || (barp != barl) || (bcp != bc)) {
                    TFT_fillRectangle(  0, (TFT_MAX_Y - 1) - bmpYsize - 2, barl, (TFT_MAX_Y - 1) - bmpYsize, bc, bc);
                    if (barl < (TFT_MAX_X - 1))
                        TFT_fillRectangle(barl, (TFT_MAX_Y - 1) - bmpYsize - 2, (TFT_MAX_X - 1), (TFT_MAX_Y - 1) - bmpYsize, BLACK, BLACK);
                    barp = barl;
                    bcp = bc;
                }
                if (stepS > 50) stepS += 10;
                else stepS = 20;
                break;

            case 70:
                if (TPC_getXY(&px, &py) == 2) {
                    if (px < ((320 * 1) / 10)) px = ((320 * 1) / 10);
                    if (px > ((320 * 9) / 10)) px = ((320 * 9) / 10);
                    if (px < ((320 * 1) / 7)) {
                        if (Wave_FPos < (Wave_BPS * 2 * 2 * 2)) {
                            SEEK_Dump();
                            statN = PS_FFR;
                            tp_cnt = 0;
                            stepS = 0;
                            break;
                        }
                        Wave_FPos = Wave_Play;
                        Wave_Rlen = Wave_Size;
                    } else if (px >= ((320 * 9) / 10)) {
                        Wave_FPos = Wave_Play + Wave_Size;
                        Wave_Rlen = 0;
                    } else {
                        uint64_t pr = (px - ((320 * 1) / 10)) * 1000 / (((320 * 8) / 10));
                        Wave_FPos = ((Wave_Size * pr) / 1000);
                        if (Wave_FPos > (Wave_Play + Wave_Size)) Wave_FPos = Wave_Size + Wave_Play;
                        if (Wave_FPos < Wave_Play) Wave_FPos = Wave_Play;
                        Wave_Rlen = Wave_Size - (Wave_FPos - Wave_Play);
                    }
                } else {
                    tp_cnt++;
                    if (tp_cnt > 100) {
                        if (Wave_FPos < Wave_Size + Wave_Play) {
                            Wave_FPos &= (uint64_t)-BUFF_SIZE;
                            if (Wave_FPos < Wave_Play) Wave_FPos = Wave_Play;
                            Wave_Rlen = Wave_Size - (Wave_FPos - Wave_Play);
                        }
                        SEEK_Dump();
                        statN = PS_SEEK;
                        tp_cnt = 0;
                        stepS = 0;
                        break;
                    }
                }
                stepS = 50;
                break;

            case 20:
                if (tp_stat != 0) {
                    stepS = 22;
                    break;
                }
                tp_stat = tp_menu();
                tp_cnt = 0;
                if (tp_stat & PS_SEEK) {
                    ps_stat |= PS_PAUSE;
                    stepS = 50;
                    SEEK_Dump();
                    break;
                }
                if (tp_stat == 0) stepS = 0;
                else stepS = 22;
                break;

            case 22:
                tp_statN = tp_menu();
                if (tp_statN == 0) {
                    tp_cnt = 0;
                    tp_stat = 0;
                    stepS = 0;
                    break;
                }
                if (tp_stat == tp_statN) {
                    if (tp_cnt != -1) tp_cnt++;
                    stepS = 24;
                    break;
                }
                tp_cnt = 0;
                tp_stat = tp_statN;
                stepS = 22;
                break;

            case 24:
                if (tp_cnt > 1) {
                    if (tp_stat & PS_VUP) {
                        if (tp_cnt == 3) statN = tp_stat;
                        if (tp_cnt >= 5) tp_cnt = 0;
                    } else if (tp_stat & PS_VDOWN) {
                        if (tp_cnt == 2) statN = tp_stat;
                        if (tp_cnt >= 3) tp_cnt = 0;
                    } else {
                        statN = tp_stat;
                        tp_cnt = -1;
                    }
                }
                stepS = 0;
                break;

            default:
                stepS = 0;
                break;
        }
        if (stepS < 50) break;
    }
    return(statN);
}


void poll()
{
    uint16_t statN;
    char stext[8];

    if ((statN = event_chk()) == 0) return;
    if (statN & PS_PAUSE) { ps_stat ^= PS_PAUSE; return; }
    if (statN & (PS_FFR | PS_FFF | PS_SEEK)) { ps_stat |= statN; return; }
    if (statN & (PS_VUP | PS_VDOWN)) {
        if (statN & PS_VUP) {
            if (Line_Vols < 32) { Line_Vol -= (Line_Vols >= 16 ? 10 : 40); Line_Vols++; }
        } else {
            if (Line_Vols >  0) { Line_Vol += (Line_Vols >  16 ? 10 : 40); Line_Vols--; }
        }
        sprintf(stext, "%2d", Line_Vols);
        TFT_drawText((TFT_MAX_X - 76), (TFT_MAX_Y - 1) - 6 - (bmpYsize / 2), (uint8_t*)stext, WHITE, BLACK);
        Pwm_Ratio = ((65536 / PWM_CLKMHZ) / Wave_Prid) + Line_Vol;
    }
    return;
}


DIR  *dp;
FILE *fp;
struct dirent *p;

int Open_Wave(char *path)
{
    fp = fopen(path, "rb");
    if (fp == NULL)  return (Last_Err = -3);    // can't open

    buffLN[0] = fread(Wave_buff[0], 1, BUFF_SIZE, fp);
    if (buffLN[0] < 46) return (Last_Err = -4); // invalid file

    // "RIFF", (long)File size, "WAVE"
    if ((_WU32(0, 0) != 0x46464952) ||
        (_WU32(0, 8) != 0x45564157))
        return (Last_Err = -5);  // invalid file

    buffGP = 12;
    while (buffGP < BUFF_SIZE) {
        unsigned long tagn = _WU32(0, buffGP);
        unsigned long tagl = _WU32(0, buffGP + 4);
        buffGP += 8;

        if (tagn == 0x20746D66) {   // "fmt "
            Wave_CHs  = _WU16(0, buffGP + 2);
            Wave_BPS  = _WU32(0, buffGP + 4);
            Wave_Bits = _WU32(0, buffGP +14);
            Wave_Prid = 2000000 / Wave_BPS;
            if (Wave_Prid & 1) Wave_Prid++;
            Wave_Prid >>= 1;
        }

        // "fact" 0x74636166)
        // "LIST" 0x74636166)
        if (tagn != 0x61746164) {   // !"data"
            buffGP += tagl;
            continue;
        }

        if ((Wave_BPS == 24000) ||
            (Wave_BPS == 32000) ||
            (Wave_BPS == 44100) ||
            (Wave_BPS == 48000)) {
            Wave_Size = tagl;
            return 0;
        }
        break;
    }
    return (Last_Err = -6);  // invalid file
}


int music_nw, music_nx;

int Play_Wave()
{
    int32_t getln;

    if (((buffNX != -1) && (buffLN[buffNX] == 0)) ||(ps_stat & (PS_SEEK | PS_FFR))) {
        statLED1 = LHigh;
        if (ps_stat & (PS_SEEK | PS_FFR)) {
            if (fseek(fp, Wave_FPos, SEEK_SET) == -1) {
                ps_stat |= PS_EOF;
                buffNX = -1;
                return (Last_Err = -7); // read error
            }
            Wave_Rlen = Wave_Size - (Wave_FPos - Wave_Play);
            ps_stat &= ~PS_FFR;
        }

        if (Wave_FPos <= Wave_Play)
            getln = ((BUFF_SIZE - Wave_Play) > Wave_Rlen) ? Wave_Rlen : (BUFF_SIZE - Wave_Play);
        else
            getln = (Wave_Rlen > BUFF_SIZE) ? BUFF_SIZE : Wave_Rlen;
        if (getln && (buffNX != -1)) {
            if (fread(Wave_buff[buffNX], 1, getln, fp) != getln) {
                ps_stat |= PS_EOF;
                buffNX = -1;
                return (Last_Err = -7); // read error
            }
            buffLN[buffNX] = getln & -4;
            Wave_FPos += getln;
            if (Wave_Rlen > getln) Wave_Rlen -= getln; else Wave_Rlen = 0;

            if (ps_stat & PS_SEEK) {
                buffLN[buffNW] = buffLN[buffNX];
                SWBuff();
                ps_stat &= ~(PS_SEEK | PS_PAUSE);
            }
        }
        statLED1 = LLow;

        if (Wave_Rlen == 0) {
            buffNX = -1;
            ps_stat |= PS_EOF;
            wait_ms(50);
            return(0);
        }
        return getln;
    }

    if (ps_stat & PS_PAUSE) return(1);
    return 0;
}


#if defined(Test)
int main()
{
    if (Open_Wave("/" FILESYS "/music/01.wav")) error("open error\n");

    Pwm_Ratio = ((65535 / PWM_CLKMHZ) / Wave_Prid) + Line_Vol;
    buffNW = 0, buffNX = 1;
    buffLN[buffNX] = 0;

    Line_R.period_us(Wave_Prid);
    Line_L.period_us(Wave_Prid);
    Tick_Timer.attach_us(&ISR_Tick, Wave_Prid);

    pc.printf("start\n");
    while (1) {
        if (buffLN[buffNX] == 0) {
            if (fread(Wave_buff[buffNX], 1, BUFF_SIZE, fp) != BUFF_SIZE) break;
            if (Wave_Size >= BUFF_SIZE) Wave_Size -= BUFF_SIZE; else break;
            buffLN[buffNX] = BUFF_SIZE;
        }
    }
    Tick_Timer.detach();
    fclose(fp);
    pc.printf("stop\n");
}

#else

int main()
{
    char path[256], msc_file[256], stext[20];

    pc.baud(115200);
    TFT_init();
    TPC_Init();
    TFT_clearScreen(GRAY);

    while (1) {

        // Open Music Directory
        // ---------------------
        if (sd.disk_initialize()) return -1;

        BMP24Bits(0, (TFT_MAX_Y - 1), "/" FILESYS "/images/Play.bmp");
        dp = 0, music_nw = 1, music_nx = 1;

        while (1) {

            // Get Music file(.wav)
            // ---------------------
            {
                int i;

                if (music_nw >= music_nx) {
                    if (dp) closedir(dp);
                    dp = opendir("/" FILESYS "/music");
                    if (dp == NULL) {
                        pc.printf("\n" "/" FILESYS "/music/ open error.\n");
                        return 0;
                    }
                    music_nw = 0;
                }

                if ((p = readdir(dp)) == NULL) break;
                for (i = 0; p->d_name[i]; i++)
                    msc_file[i] = p->d_name[i];
                msc_file[i] = '\0';

                if (msc_file[i - 4] == '.') msc_file[i - 4] = '\0';
                if (strcmp(p->d_name + (i - 3), "wav") != 0) {
                    pc.printf("Skip - music/%s\n", p->d_name);
                    continue;
                }

                if (++music_nw < music_nx) continue;
                music_nx++;
            }

            // View Navigate(Play time, Sound volume)
            // ---------------------------------------
            {
                snprintf(path, sizeof path, "/" FILESYS "/music/%s", msc_file);
                pc.printf("Play - %s\n", path);

                TFT_fillRectangle(  0,   0, 319, 239 - bmpYsize, GRAY,  GRAY);
                TFT_drawText( 28, 239 - 6 - (bmpYsize / 2), (uint8_t*)"00:00", WHITE, BLACK);
                sprintf(stext, "%2d", Line_Vols);
                TFT_drawText(244, 239 - 6 - (bmpYsize / 2), (uint8_t*)stext, WHITE, BLACK);
                TFT_fillRectangle(  0, 239 - bmpYsize - 2, 319, 239 - bmpYsize, BLACK, BLACK);
            }

            // View Image
            // -----------
            {
                JDEC jdec;
                WORD work[4000/sizeof(WORD)];

                snprintf(path, sizeof path, "/" FILESYS "/music/%s" ".jpg", msc_file);
                pc.printf("jpg - %s\n", path);
                jfp = fopen(path, "rb");
                if (!jfp) jfp = fopen("/" FILESYS "/images/NoImage.jpg", "rb");
                if (jfp) {
                    JRESULT r = jd_prepare(&jdec, jpeg_input, work, sizeof(work), jfp);
                    if (r == JDR_OK) {
                        uint16_t ww = jdec.width & 0x3FF;   // struct jdec ???
                        uint16_t wh = jdec.width >> 16;     // ..
                        Cover_tlx = (ww < 170) ? ((176 - ww) / 2) : 4;
                        Cover_tly = (wh < 180) ? ((188 - wh) / 2) : 4;
                        r = jd_decomp(&jdec, jpeg_output, 0);
                      #if (0)
                        TFT_drawLine(Cover_tlx + 1, Cover_tly + wh, Cover_tlx + ww, Cover_tly + wh, WHITE);
                        TFT_drawLine(Cover_tlx + ww, Cover_tly + 1, Cover_tlx + ww, Cover_tly + wh, WHITE);
                      #endif
                    }
                    fclose(jfp);
                }
            }

            // View Song title
            // ----------------
            {
                int l = 0;
                char msc_Text[256];

                snprintf(path, sizeof path, "/" FILESYS "/music/%s" ".txt", msc_file);
                pc.printf("txt - %s\n", path);

                fp = fopen(path, "r");
                if (fp) {
                    l = fread(msc_Text, 1, 256, fp);
                    fclose(fp);
                }

                if (l > 4)
                     msc_Text[l] = '\0';
                else snprintf(msc_Text, sizeof(msc_Text), "||%s||", msc_file);
                pc.printf("%02d - %s\n\n", music_nw, msc_Text);

                int  ps, pl = 0;
                int  px = 176, py = Cover_tly + 2;
                uint8_t ws[2];
                ws[1] = '\0';

                for (ps = 0; ps < 256; ps++) {
                    if (msc_Text[ps] == '|') {
                        if (++pl > 2) break;
                        py += 20;
                        px = 176;
                        continue;
                    }
                    if (msc_Text[ps] & 0x80) {  // KANJI ?
                        font.read((msc_Text[ps] << 8)| msc_Text[ps + 1]);
                        draw_kanji(px, py, ((pl == 2) ? WHITE : DWHITE), GRAY);
                        px += 16;
                        ps++;
                    } else {
                        ws[0] = msc_Text[ps];
                        TFT_drawText(px, py + 2, ws, ((pl == 2) ? WHITE : DWHITE), GRAY);
                        px += 8;
                    }
                }
            }

            // Play Music
            // -----------
            {
                snprintf(path, sizeof path, "/" FILESYS "/music/%s" ".wav", msc_file);
                int rc = Open_Wave(path);
                if (rc) {
                    if (fp != NULL) fclose(fp);
                    pc.printf(" rc=%d, Last_Err=%d\n", rc, Last_Err);
                    Last_Err = 0;
                    continue;
                }

                uint16_t tw = Wave_Size / (Wave_BPS * 4);
                sprintf(stext, "%02d %2dks %02d:%02d", music_nw, (Wave_BPS / 1000), (tw / 60), (tw % 60));
                TFT_drawText(214, (222 - bmpYsize), (uint8_t*)stext, BLACK, GRAY);

                Wave_Play = buffGP;
                Wave_FPos = BUFF_SIZE;
                Wave_Rlen = Wave_Size - (Wave_FPos - Wave_Play);
                buffNW = 0, buffNX = 1;
                buffLN[buffNX] = 0;

                Last_Err = 0;
                ps_stat  = PS_PLAY;

                Pwm_Ratio = ((65535 / PWM_CLKMHZ) / Wave_Prid) + Line_Vol;
                Line_R.period_us(Wave_Prid);
                Line_L.period_us(Wave_Prid);
                Tick_Timer.attach_us(&ISR_Tick, Wave_Prid); // Play!
 
                while (!(ps_stat & PS_EOF) && (ps_stat & PS_PLAY) && (Last_Err == 0)) {

                  #if defined(Level_Indicator)
                    uint16_t yl0 = 0, yr0 = 0, yl, yr;
                    if (Pwm_cnt > Level_Indicator) {
                        yl = 238 - (((Pwm_aveL - 64) * (bmpYsize - 2)) / 200);
                        yr = 238 - (((Pwm_aveR - 64) * (bmpYsize - 2)) / 200);
                        Pwm_cnt = 0;
                        if (yl > 237) yl = 238;
                        if (yr > 237) yr = 238;
                        yl0 = (yl > yl0) ? yl : ((yl0 + yl) / 2);
                        yr0 = (yr > yr0) ? yr : ((yr0 + yl) / 2);
                        TFT_fillRectangle(MM_VUPD + 5, MM_VUP + 1, MM_VUPD +  7, yl0, BLACK, BLACK);
                        TFT_fillRectangle(MM_VUPD + 9, MM_VUP + 1, MM_VUPD + 11, yr0, BLACK, BLACK);
                        if (yl0 < 238) TFT_fillRectangle(MM_VUPD + 5, yl0, MM_VUPD +  7, 238, YELLOW, YELLOW);
                        if (yr0 < 238) TFT_fillRectangle(MM_VUPD + 9, yr0, MM_VUPD + 11, 238, YELLOW, YELLOW);
                    }
                  #endif

                    if (Play_Wave()) {

                        statLED4 = LHigh;
                        if (ps_stat & PS_PAUSE) wait_ms(5);
                        poll();
                        statLED4 = LLow;

                        if (ps_stat & PS_FFF) {
                            buffNX = -1;
                            break;
                        }

                        if (ps_stat & PS_FFR) {
                            if (Wave_FPos < (Wave_BPS * 2 * 2 * 2)) {
                                buffNX = -1;
                                if (music_nx > music_nw)
                                    music_nx = (music_nw > 1) ? music_nw - 1 : 1;
                                break;
                            } else
                                Wave_FPos = Wave_Play;
                        }
                    }
                }

                Tick_Timer.detach();
                fclose(fp);
                pc.printf("Close stat=%04X, %d - %d\n", ps_stat, Wave_Rlen, Last_Err);
                ps_stat  = 0;
                Last_Err = 0;
            }
        }
        closedir(dp);
    }
}
#endif