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

Revision:
0:6fa19738f62e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GSDPlayer.cpp	Mon Sep 08 16:07:05 2014 +0000
@@ -0,0 +1,746 @@
+// --------------------------------------------------------
+//  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
\ No newline at end of file