Introduction: Design Music Player UI With LVGL

This instructables show how to use LVGL and SquareLine Studio design a Music Player UI.

Supplies

Step 1: What Is LVGL?

LVGL is a good graphics library for making fancy GUI easily. And SquareLine Studio help you reduces much coding required.

You can find more details at my previous instructables:

https://www.instructables.com/Design-a-Fancy-GUI-for-Your-Project/

https://www.instructables.com/Design-Watch-Face-With-LVGL/

Step 2: Sample Source Files

All the image sources, SquareLine Studio projects and source code can be found at Github:

https://github.com/moononournation/LVGL_Music_Player.git


Step 3: UI Design

Current dev device have various shape of display, round, square, rectangle. It is better determine using which shape first.

Then the next step is determine what the UI look like.

Step 4: Classic Music Player

If you do not have any UI design idea yet, imitate a classic music player may be a good start.

Winamp is a classic Windows desktop music player appeared from last century, then a similar UI variant with Chinese characters support called TTPlayer. This 2 applications are my (and also many people's) childhood memory, so I will use those UI as a design template.

Winamp is designed can change "skin" easily, and the skin image resources are in BMP format packed in a zip file. You can find a huge Winamp skin collection at skins.webamp.org, so it is easy to access your favor Winamp skin as a design template.

You can find how to start the UI design from the Winamp skin at bilibili:

https://www.bilibili.com/video/BV1ia4y137KM/

Step 5: Function Wishlist

Music player have various functions, but I'm more interested in the features found in Winamp or TTPlayer. Here are my function wishlist:

  • Play MP3 from SD card
  • List MP3 files from SD card
  • Display Unicode characters
  • Basic playing operation (play, pause, stop, previous and next)
  • Volume Control
  • Show MP3 ID3 information
  • Show MP3 cover image
  • Show MP3 Lyrics
  • Synchronize display Lyrics while play
  • Show audio spectrum analyzer

Step 6: Play MP3 From SD Card

At the beginning, we need an audio library that can read MP3 file from SD card and play it out. This time I am using ESP32-audioI2S. It support the ESP32 family, read audio files from various source and play the output to I2S module. You can find more details at Github:

https://github.com/schreibfaul1/ESP32-audioI2S.git

Step 7: Basic Playing Operation

ESP32-audioI2S provided all basic playing API, we just need create all the corresponding button widgets for each operation. However, the original Winamp button design is too tiny for operating on the touchscreen with finger. So I enlarge the buttons a little bit and also extend the touch area a little bit with transparent background.

For each button, assign the button widget to a corresponding function.

Use play button as an example:

lv_obj_add_event_cb(ui_ButtonPlay, playSong, LV_EVENT_CLICKED, NULL);

And then in the playSong function, call the ESP32-audioI2S API:

void playSong(lv_event_t *e)
{
if (isPlaying)
{
audio.pauseResume();
}
else
{
play_selected_song();
}
}

Step 8: List MP3 Files From SD Card

Before telling ESP32-audioI2S play the MP3 file, we need find and list out the MP3 files from the SD Card first. Here are the code extract of read_song_list() illustrate how to concat the song list string seperated by line feed character(\n):

File root = SD_MMC.open("/");
File file = root.openNextFile();
while (file)
{
 if (file.isDirectory())
  {
   Serial.printf("DIR: %s\n", file.name());
  }
  else
  {
   const char *filename = file.name();

   int8_t len = strlen(filename);
   const char *MP3_EXT = ".mp3";
   if ((filename[0] != '.') && (strcmp(MP3_EXT, &filename[len - 4]) == 0))
   {
    // Serial.printf("Song file: %s, size: %d\n", filename, file.size());
    if (song_count > 0)
    {
     stringSongList += '\n';
    }
    stringSongList += filename;
    song_count++;
   }
  }
  file = root.openNextFile();
}

Then assign the concatenated song list string to the LVGL roller component:

lv_roller_set_options(ui_RollerPlayList, stringSongList.c_str(), LV_ROLLER_MODE_INFINITE);

Step 9: Display Unicode Characters

LVGL support display Unicode characters but it requires a Unicode font file. My song list are mainly in Chinese characters, I selected 3 fonts for displaying it:

https://github.com/ACh-K/Cubic-11.git

https://fonts.google.com/noto/specimen/Noto+Sans+HK/glyphs

https://fonts.google.com/noto/specimen/Noto+Serif+HK/glyphs

Then use SquareLine Studio font tools to create the C source files.

Step 10: Volume Control

Assign the volume slider widget to a value changed event function:

lv_obj_add_event_cb(ui_ScaleVolume, volumeChanged, LV_EVENT_VALUE_CHANGED, NULL);

And then in the event function call the ESP32-audioI2S API:

void volumeChanged(lv_event_t *e)
{
int16_t volume = lv_slider_get_value(ui_ScaleVolume);
audio.setVolume(volume);
}

The time progress UI also a slider widget. But it is too near the buttons and volume control UI so I disabled the touch input to avoid the unexpected operation.

Step 11: Show MP3 ID3 Information

ESP32-audioI2S exposed an audio_id3data() callback function. Function called for each ID3 tag found in MP3 file.

In callback function, simply concatenate all data to a string:

if (playingStr.length() > 0)
{
playingStr += " ";
}
playingStr += info;

Then assign to a label for display:

lv_label_set_text(ui_LabelPlaying, playingStr.c_str());

Step 12: Show MP3 Cover Image

ESP32-audioI2S exposed an audio_id3image() callback function. Function called if found cover image in MP3 ID3 tag. The image can be any image format, currently on support decode and display non-progressive JPEG image file.

In callback function, copy the binary data:

file.seek(pos);
file.read(coverImgFile, len);

Seek the JPEG header:

size_t idx = 11;
while ((idx < len) && ((coverImgFile[idx++] != 0xFF) || (coverImgFile[idx] != 0xD8)))
;
--idx;

Then decode with JPEGDEC:

jpegdec.openRAM(coverImgFile + idx, len - idx, jpegDrawCallback);

Step 13: Show MP3 Lyrics

ESP32-audioI2S exposed an audio_id3lyrics() callback function. Function called if found synced lyrics, un-synced lyrics or text data tag in MP3 file.

In callback function, copy the binary data:

file.seek(pos);
file.read((uint8_t *)lyricsText, len);

Decode binary to UTF8 text:

audio.unicode2utf8(lyricsText, len);

If the text have sync time tag, store the time index to syncTimeLyricsSec[] and syncTimeLyricsLineIdx[] array.

Then set the lyrics text to the roller widget:

lv_roller_set_options(ui_RollerLyrics, lyricsText, LV_ROLLER_MODE_NORMAL);

Step 14: Synchronize Display Lyrics While Play

If synced lyrics tag found, roll the lyrics widget while playing:

for (int i = 0; i < syncTimeLyricsCount; ++i)
{
if (syncTimeLyricsSec[i] == currentTime)
{
lv_roller_set_selected(ui_RollerLyrics, syncTimeLyricsLineIdx[i], LV_ANIM_ON);
break;
}
}

Step 15: Show Audio Spectrum Analyzer

ESP32-audioI2S also exposed an audio_process_i2s() callback function for processing audio output. We can utilize this function collect the audio data to visualize the audio spectrum.

In callback function, collect the audio data:

raw_data[raw_data_idx++] = *sample;

If data full, process with FFT class:

if (raw_data_idx >= WAVE_SIZE)
{
fft.exec((int16_t *)raw_data);
draw_fft_level_meter(canvasFFT_gfx);
lv_obj_invalidate(ui_CanvasFFT);
raw_data_idx = 0;
}

Note:

The visualization is drawing to a canvas, canvasFFT_gfx, separately. And the canvas is associated with the LVGL widget ui_CanvasFFT.

Step 16: Optional: Design Case

A beautiful case for the dev device can make it look more like a music player.

You can find the WT32-SC01 PLUS desktop case at Thingiverse:

https://www.thingiverse.com/thing:6030590

Step 17: Enjoy!

Step 18: What's Next?

Here are further Function Wishlist we can implement:

  • Shuffle play list order
  • LVGL roller cannot handle a large list, it is better able list song by folder
  • MP3 timeline seek
  • Support more cover image format
  • Connect to the Internet? No any idea yet