PicoCalc/Code/MP3Player/main_task.cpp

674 lines
19 KiB
C++

#include <cstdio>
#include <cstring>
#include "main_task.h"
#include "logo50.h"
#include "icon_enter.h"
#include "icon_esc.h"
#include "icon_nav.h"
#include "icon_p.h"
main_task::main_task()
: task("Main", MAIN_STACKSIZE),
_cs(SD_CS_PIN),
_spi(SD_SPI0, SD_MISO_PIN, SD_MOSI_PIN, SD_SCLK_PIN, _cs),
_sd(_spi),
_next(NEXT_BUTTON),
_part(PART_BUTTON),
_ps(PICO_PS),
_fs(_sd),
_lcd_rst(LCD_RST_PIN),
_lcd_dc(LCD_DC_PIN),
_lcd_cs(LCD_CS_PIN),
_lcd_spi(LCD_SPI1, LCD_MISO_PIN, LCD_MOSI_PIN, LCD_SCK_PIN, _lcd_cs),
_lcd(_lcd_spi,_lcd_rst,_lcd_dc,ili9488_drv::PICO320),
_gui(_lcd),
_sel_index(0),
_page_index(0),
_last_page_index(0),
_last_sel_index(0),
update_sel(0),
update_required(1),
playing(0),
last_play_pos(0),
play_pos(0),
num_files(0),
pause_flag(0),
play_flag(0){
_next.gpioMode(GPIO::INPUT | GPIO::PULLUP);
_part.gpioMode(GPIO::INPUT | GPIO::PULLUP);
_ps.gpioMode(GPIO::OUTPUT | GPIO::INIT_HIGH);
_gui.FontSelect(&FONT_8X14);
_gui.SetForecolor(C_GAINSBORO);
for(auto & menu_item : menu_items){
menu_item.fsize = 0;
menu_item.fname = nullptr;
menu_item.x = 0;
menu_item.y = 0;
}
}
void main_task::draw_string(int x,int y,const char*str,uint8_t fnt = 1){
if(fnt == 0) {
_gui.FontSelect(&FONT_6X8);
}
if(fnt == 1) {
_gui.FontSelect(&FONT_8X14);
}
if(fnt == 2) {
_gui.FontSelect(&FONT_12X20);
}
_gui.SetForecolor(C_GAINSBORO);
_gui.SetBackcolor(C_BLACK);
_gui.PutString(x,y,str);
}
void main_task::draw_highlight_string(int x,int y,const char*str){
_gui.FontSelect(&FONT_8X14);
_gui.SetForecolor(C_BLACK);
_gui.SetBackcolor(C_GAINSBORO);
_gui.PutString(x,y,str);
}
void main_task::draw_char(int x,int y ,char c) {
_gui.FontSelect(&FONT_8X14);
_gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true);
}
void main_task::draw_big_char(int x,int y ,char c) {
_gui.FontSelect(&FONT_12X20);
_gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true);
}
void main_task::draw_bar(int x1, int x2,UG_COLOR c) {
uint8_t offsetx = 10;
_gui.FillFrame(x1+offsetx,280,x2+offsetx,285,c);
}
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 280
#define FONT_WIDTH 12
#define FONT_HEIGHT 20
#define MAX_CHARS_PER_LINE (SCREEN_WIDTH / FONT_WIDTH)
#define MAX_LINES 2
void main_task::draw_wrap_text(const char *text) {
int line_count = 0;
const char *ptr = text;
char lines[MAX_LINES][MAX_CHARS_PER_LINE + 1] = {{0}};
while (*ptr && line_count < MAX_LINES) {
int i = 0;
while (*ptr && i < MAX_CHARS_PER_LINE) {
lines[line_count][i++] = *ptr++;
}
lines[line_count][i] = '\0';
line_count++;
}
int text_height = line_count * FONT_HEIGHT;
int y_offset = (SCREEN_HEIGHT - text_height) / 2;
for (int line = 0; line < line_count; line++) {
int char_count = strlen(lines[line]);
int x_offset = (SCREEN_WIDTH - char_count * FONT_WIDTH) / 2;
for (int j = 0; j < char_count; j++) {
draw_big_char(x_offset + j * FONT_WIDTH, y_offset + line * FONT_HEIGHT, lines[line][j]);
}
}
}
int main_task::enum_files() {
char tmp[16];
_gui.FontSelect(&FONT_8X14);
_gui.ConsoleSetForecolor(C_GAINSBORO);
_gui.ConsoleSetBackcolor(C_BLACK);
_gui.ConsolePutString("Init FatFS");
FatFs::FRESULT res = _fs.mount(0);
if(res == FatFs::FR_OK){
_gui.ConsolePutString("[OK]");
}else{
sprintf(tmp,"[NG] code=0x%x",res);
_gui.ConsolePutString(tmp);
return -1;
}
_gui.ConsolePutString("\n");
_gui.ConsolePutString("File List");
num_files = 0;
res = _fs.findfirst(&_dir, &_finfo, "", "*.mp3");
// Loop over all MP3 files
while (res == FatFs::FR_OK && _finfo.fname[0] && strlen(_finfo.fname) > 4 ) {
printf("%s\n",_finfo.fname);
menu_items[num_files].fname = (char*)malloc(strlen(_finfo.fname) + 1);
strcpy(menu_items[num_files].fname, _finfo.fname);
menu_items[num_files].fsize = _finfo.fsize;
res = _fs.findnext(&_dir, &_finfo);
num_files++;
if(num_files>=MAX_FILES){
break;
}
}
if (res != FatFs::FR_OK) {
sprintf(tmp, "[NG] code=0x%x", (int)res);
_gui.ConsolePutString( tmp);
return -1;
}
_fs.closedir(&_dir);
_gui.ConsolePutString("[OK]");
return num_files;
}
//In ffconf.h,
// set
// #define FF_MAX_LFN 33
// #define FF_LFN_BUF 33
/*
void truncate_string(const char *input, char *output, size_t max_length) {
size_t input_len = strlen(input);
if (input_len <= max_length) {
strcpy(output, input);
} else {
strncpy(output, input, max_length - 3);
output[max_length - 3] = '\0';
strcat(output, "...");
}
}
*/
#define HEADER_HEIGHT 20
int main_task::select_mp3() {
char tmp[34];
uint16_t start_x = 11;
uint16_t start_y= HEADER_HEIGHT+3;
/* header
* ------------------------------------------
* 3px
* [item]
* 3px
* [item]
* 3px
* [item]
* 3px
*/
for (int i = _page_index; i < _page_index+ITEMS_PER_PAGE;i++) {
if(i >= num_files) break;
//truncate_string(file_list[_page_index+i],tmp,33);
menu_items[i].x = start_x;
menu_items[i].y = start_y;
draw_string(menu_items[i].x, menu_items[i].y, menu_items[i].fname);
start_y+=23;
}
return 0;
}
void main_task::draw_cursor() {
char buf[12];
//sprintf(buf, "%02d", _sel_index + 1);
//draw_string(0,_lcd.getSizeY()-20,buf);
printf("%d %d %d\n",_last_sel_index,_sel_index,_page_index);
uint16_t x,y;
if(update_sel){
x = menu_items[_last_sel_index % ITEMS_PER_PAGE].x;
y = menu_items[_last_sel_index % ITEMS_PER_PAGE].y;
_gui.FillFrame(10, y - 2, _lcd.getSizeX() - 10, y + 15, C_BLACK);
draw_string(x, y, menu_items[_last_sel_index].fname);
}
x = menu_items[_sel_index % ITEMS_PER_PAGE].x;
y = menu_items[_sel_index % ITEMS_PER_PAGE].y;
_gui.FillFrame(10,y-2,_lcd.getSizeX()-10,y+15,C_GAINSBORO);
//draw_string(0, (_sel_index % ITEMS_PER_PAGE) * 20+20, "=>");
draw_highlight_string(11, y, menu_items[_sel_index].fname);
}
void main_task::clear_screen() {
_lcd.clearScreen(C_BLACK);
}
void main_task::clear_menu() {
//clear menu section area, not whole screen
_lcd.fillArea(0,20,_lcd.getSizeX()-1,_lcd.getSizeY()-20,C_BLACK);
}
void main_task::boot_menu() {
_page_index = (_sel_index / ITEMS_PER_PAGE) * ITEMS_PER_PAGE;
if(_page_index!= _last_page_index){
clear_menu();
_last_page_index = _page_index;
update_required = 1;
update_sel = 0;
}
if(update_required) {
clear_menu();
draw_header("Playlist");
select_mp3();
draw_cursor();
draw_footer(1);
update_required = 0;
last_play_pos = 0;
play_pos = 0;
}
if(update_sel){
draw_cursor();
update_sel = 0;
}
}
void main_task::menu_up() {
_last_sel_index = _sel_index;
if (_sel_index == 0) {
_sel_index = num_files - 1;
} else {
_sel_index--;
}
update_sel = 1;
}
void main_task::menu_down() {
_last_sel_index = _sel_index;
if (_sel_index == num_files - 1) {
_sel_index = 0;
} else {
_sel_index++;
}
update_sel = 1;
}
void main_task::menu_start(sd_reader_task&sd_reader,mp3_decoder_task &decoder,pcm_pwm_rp2040_drv &pcm_drv) {
FatFs::FRESULT res = FatFs::FR_OK;
printf("start play: %s\n",menu_items[_sel_index].fname);
res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ);
assert(res == FatFs::FR_OK);
sd_reader.start(&_fs, &_file);
decoder.reset();
decoder.start();
update_required = 1;
playing =1;
last_play_pos = 0;
play_pos = 0;
pause_flag = 0;
}
//play through the list start from sel_index;
void main_task::menu_start_all(sd_reader_task &sd_reader, mp3_decoder_task &decoder, pcm_pwm_rp2040_drv &pcm_drv) {
FatFs::FRESULT res = FatFs::FR_OK;
printf("start play all: %d\n",_sel_index);
res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ);
assert(res == FatFs::FR_OK);
sd_reader.start(&_fs, &_file);
decoder.reset();
decoder.start();
update_required = 1;
playing =1;
last_play_pos = 0;
play_pos = 0;
pause_flag = 0;
play_flag = 1;
}
void main_task::draw_logo(){
uint16_t start_x,start_y;
start_x = (_lcd.getSizeX() - logo_width)/2;
start_y = (_lcd.getSizeY() - logo_height)/2;
unsigned char *data = (unsigned char *)header_data;
for (unsigned int y = 0; y < logo_height; ++y) {
for (unsigned int x = 0; x < logo_width; ++x) {
unsigned char pixel[3];
HEADER_PIXEL(data, pixel);
uint8_t r = pixel[0];
uint8_t g = pixel[1];
uint8_t b = pixel[2];
uint32_t color = (r << 16) | (g << 8) | b; // RGB888
_lcd.drawPixel(start_x+x,start_y+y, color);
}
}
}
//header Height 20
void main_task::draw_header(char*title) {
_gui.FontSelect(&FONT_8X14);
_gui.SetForecolor(C_GAINSBORO);
_gui.SetBackcolor(C_BLACK);
_gui.PutString(11,(20-14)/2," ");
_gui.PutString(11,(20-14)/2,title);
_lcd.drawHLine(10,19,_lcd.getSizeX()-10,C_LIGHT_GRAY);
}
void main_task::draw_footer_esc() {
uint16_t start_x,start_y;
start_x = 261;
start_y = _lcd.getSizeY() - esc_height-5;
unsigned char *data = (unsigned char *)esc_header_data;
for (unsigned int y = 0; y < esc_height; ++y) {
for (unsigned int x = 0; x < esc_width; ++x) {
unsigned char pixel[3];
ESC_PIXEL(data, pixel);
uint8_t r = pixel[0];
uint8_t g = pixel[1];
uint8_t b = pixel[2];
uint32_t color = (r << 16) | (g << 8) | b; // RGB888
_lcd.drawPixel(start_x+x,start_y+y, color);
}
}
draw_string(285,start_y+3,"Stop",0);
}
void main_task::draw_footer_enter() {
uint16_t start_x,start_y;
start_x = 260;
start_y = _lcd.getSizeY() - enter_height-5;
unsigned char *data = (unsigned char *)enter_header_data;
for (unsigned int y = 0; y < enter_height; ++y) {
for (unsigned int x = 0; x < enter_width; ++x) {
unsigned char pixel[3];
ENTER_PIXEL(data, pixel);
uint8_t r = pixel[0];
uint8_t g = pixel[1];
uint8_t b = pixel[2];
uint32_t color = (r << 16) | (g << 8) | b; // RGB888
_lcd.drawPixel(start_x+x,start_y+y, color);
}
}
draw_string(285,start_y+3,"Play",0);
}
void main_task::draw_footer_nav() {
uint16_t start_x,start_y;
start_x = 10;
start_y = _lcd.getSizeY() - nav_height-6;
unsigned char *data = (unsigned char *)nav_header_data;
for (unsigned int y = 0; y < nav_height; ++y) {
for (unsigned int x = 0; x < nav_width; ++x) {
unsigned char pixel[3];
NAV_PIXEL(data, pixel);
uint8_t r = pixel[0];
uint8_t g = pixel[1];
uint8_t b = pixel[2];
uint32_t color = (r << 16) | (g << 8) | b; // RGB888
_lcd.drawPixel(start_x+x,start_y+y, color);
}
}
draw_string(23,start_y+3,"Nav.",0);
}
void main_task::draw_footer_pause() {
uint16_t start_x,start_y;
start_x = 210;
start_y = _lcd.getSizeY() - icon_p_height-5;
unsigned char *data = (unsigned char *)icon_p_header_data;
for (unsigned int y = 0; y < icon_p_height; ++y) {
for (unsigned int x = 0; x < icon_p_width; ++x) {
unsigned char pixel[3];
ICON_P_PIXEL(data, pixel);
uint8_t r = pixel[0];
uint8_t g = pixel[1];
uint8_t b = pixel[2];
uint32_t color = (r << 16) | (g << 8) | b; // RGB888
_lcd.drawPixel(start_x+x,start_y+y, color);
}
}
draw_string(225,start_y+3,"Pause",0);
}
void main_task::draw_footer_pagenumber() {
// page1/4
char tmp[12];
int total_page = (num_files / ITEMS_PER_PAGE)+1;
int cur_page = (_sel_index / ITEMS_PER_PAGE)+1;
if(total_page > 1) {
sprintf(tmp, "%d/%d", cur_page, total_page);
draw_string(150, _lcd.getSizeY() - nav_height-2, tmp, 0);
}
}
void main_task::draw_footer(uint8_t stat) {
//stat 1 == menu_list ==> nav icon, Enter icon
//stat 2 == playing music ==> only Esc icon
_gui.FillFrame(0,_lcd.getSizeY()-20,_lcd.getSizeX()-1,_lcd.getSizeY()-1,C_BLACK);
if(stat == 1) {
draw_footer_nav();
draw_footer_enter();
draw_footer_pagenumber();
}
if(stat == 2) {
draw_footer_pause();
draw_footer_esc();
}
_lcd.drawHLine(10,_lcd.getSizeY()-20,_lcd.getSizeX()-10,C_LIGHT_GRAY);
}
void main_task::draw_playing() {
char * song_name = menu_items[_sel_index].fname;
draw_wrap_text(song_name);
}
void main_task::run() {
char tmp[34];
i2c_kbd _kbd;
pcm_pwm_rp2040_drv pcm_drv(AUDIO_LEFT, AUDIO_RIGHT);
sd_reader_task sd_reader;
mp3_decoder_task decoder(pcm_drv, sd_reader);
uint8_t keycheck = 0;
uint8_t keyread = 0;
uint8_t key_stat = 0;//press,release, or hold
int kbd_ret = 0;
int c;
static int ctrlheld = 0;
///const char spinner[] = {'/', '-', '|', '\\'};
uint8_t spin_i = 0;
uint8_t spin_timer = 0;
int play_pos_diff = 0;
_lcd.clearScreen(C_BLACK);
draw_logo();
task::sleep_ms(1000);
clear_screen();
num_files = enum_files();
if (num_files <= 0) {
printf("num_files <=0\n");
return;
}
clear_screen();
draw_header("Playlist");
while (true){
if(playing){
if (sd_reader.isAlive() && decoder.isAlive()) {
//printf("playing now\n");
if(update_required) {
clear_menu();
//truncate_string(fname_list[_sel_index],tmp,33);
if(play_flag == 1) {
draw_header("Now Playing All");
}else{
draw_header("Now Playing");
}
draw_playing();
draw_footer(2);
update_required = 0;
}
if(spin_timer >= 100) {
//(bitrate*8/)filesize
//draw bar use last_play_pos
//draw_bar(0,last_play_pos,C_BLACK);
play_pos = decoder.get_position(menu_items[_sel_index].fsize,_lcd.getSizeY()-20);
///printf("play_pos %d\n",play_pos);
//draw bar use play_pos
play_pos_diff = play_pos - last_play_pos;
if(play_pos_diff < 0) {// VBR
draw_bar(play_pos,last_play_pos,C_BLACK);
}else{ //>= 0
//draw_bar(last_play_pos,play_pos,C_BLACK);
draw_bar(play_pos,play_pos+play_pos_diff,C_GAINSBORO);
}
uint32_t seconds = decoder.get_total_seconds();
int minutes = seconds / 60;
int remaining_seconds = seconds % 60;
// "MM:SS"
snprintf(tmp, 6, "%02d:%02d", minutes, remaining_seconds);
draw_string(10,288,tmp,0);
///draw_char(20, 300, spinner[spin_i % 4]);
spin_i++;
spin_timer = 0;
last_play_pos = play_pos;
}
spin_timer++;
}else{
printf("not playing...auto exit\n");
_fs.close(&_file);
playing =0;
update_required =1;
if(play_flag == 1) {
menu_down();
menu_start_all(sd_reader,decoder,pcm_drv);
}
}
}else{
boot_menu();
}
if(keycheck == 0) {
if(keyread == 0) {
kbd_ret = _kbd.write_i2c_kbd();
keyread = 1;
}else{
kbd_ret = _kbd.read_i2c_kbd();
keyread = 0;
}
keycheck = 16;
}
if(kbd_ret){
if (kbd_ret == 0xA503)ctrlheld = 0;
else if (kbd_ret == 0xA502) {
ctrlheld = 1;
} else if ((kbd_ret & 0xff) == 1) {//pressed
key_stat = 1;
} else if ((kbd_ret & 0xff) == 3) {
key_stat = 3;
}
c = kbd_ret >> 8;
int realc = -1;
switch (c) {
case 0xA1:
case 0xA2:
case 0xA3:
case 0xA4:
case 0xA5:
realc = -1;//skip shift alt ctrl keys
break;
default:
realc = c;
break;
}
c = realc;
if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1;
switch (c) {
case 0xb5://UP
if(key_stat == 1 && !playing) {
menu_up();
}
break;
case 0xb6://DOWN
if(key_stat == 1 && !playing) {
menu_down();
}
break;
case 0xb4://LEFT
break;
case 0xb7://RIGHT
break;
case 0x0A://ENTER
if(key_stat == 1 && !playing) {
menu_start(sd_reader, decoder, pcm_drv);
}
break;
case 'a':
if(key_stat == 1 && !playing) {
menu_start_all(sd_reader,decoder,pcm_drv);
}
break;
case 0xB1://ESC
if(key_stat == 1 && playing) {
playing = 0;
if(decoder.getState()==task::SUSPENDED){//maybe is suspended
decoder.resume();
}
sd_reader.force_eof();
printf("stop playing\n");
pause_flag = 0;
update_required = 1;
play_flag = 0;
}
break;
case 'p':
if(key_stat == 1 && playing) {
if(pause_flag == 0) {
pause_flag = 1;
decoder.suspend();
draw_header("Paused");
}else{
pause_flag = 0;
decoder.resume();
draw_header("Now Playing");
}
printf("pause playing mp3\n");
}
break;
default:
break;
}
kbd_ret = 0;
}
task::sleep_ms(1);
if(keycheck) keycheck--;
}
}