🤖Have you ever tried Chat.M5Stack.com before asking??😎
    M5Stack Community
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Register
    • Login

    Lesson 6.1. Speaker. MP3 player

    Lessons and Guides
    5
    9
    46.8k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • DimiD
      Dimi
      last edited by Dimi

      The purpose of this lesson

      Hi! Today we will learn how to play audio files of MP3 format using the built-in DAC. Write a simple player (Fig. 1).

      Figure 1. Welcome screen

      Brief theory

      Digital-to-analog Converter (DAC) – a device for converting digital (usually binary) code into an analog signal (current, voltage or charge). Digital-to-analog converters are the interface between the discrete digital world and analog signals. The signal from DAC without interpolation on the background of an ideal signal is shown in figure 2.

      Figure 2

      In M5STACK the DAC outputs correspond to the 25 and contacts 26 (Fig. 2.1).

      Note that the built-in speaker is connected to 25 pins in parallel. 26 the contact is free and can be used as a linear output. By default, both contacts are enabled, use AudioOutputI2S for configuration

      Figure 2.1

      MP3 audio of the third level, developed by a team of MPEG file format to store the audio information. MP3 is one of the most common and popular digital audio encoding formats. It is widely used in file sharing networks for evaluation download of music. The format can be played in almost all popular operating systems, on most portable audio players, and is supported by all modern models of the music centers and DVD players.

      More information on the Wiki: https://en.wikipedia.org/wiki/MP3

      The development of libraries for ESP32 and ESP8266 to work with popular audio formats, including MP3, is the user GitHub earlephilhower https://github.com/earlephilhower, reference to the library https://github.com/earlephilhower/ESP8266Audio

      List of components for the lesson

      • M5STACK;
      • USB-C cable.

      Begin!

      Step 1. Draw a sketch

      Draw a sketch of our player (Fig. 3). The name of the previous, current and next track will be displayed at the bottom of the screen. The name of the current track will be made black standard font size 3. The side tracks will be grayed out in standard size 2 font. In the center of the screen will be a time line of gray color, which will move the red label. In the upper right corner add four gray pillars that mimic the sound spectrum. The album cover will be located in the left corner.

      Figure 3. Sketch of the project

      Step 2. Logotype

      Let's use the standard graphical editor to make a logo (Fig. 3.1), which will be displayed on the screen when you turn on the device.

      Figure 3.1. The logo of the player

      Don't forget to convert and connect:

      extern unsigned char logo[];
      

      Let's draw our logo from the drawGUI () function of setup():

      void drawGUI() {
        M5.Lcd.drawBitmap(0, 0, 320, 150, (uint16_t *)logo);
        M5.Lcd.setTextColor(0x7bef);
        drawTrackList();
        while (true)
        {
          if (m5.BtnB.wasPressed())
          {
            M5.Lcd.fillRect(0, 0, 320, 240, 0x0000);
            M5.Lcd.fillRoundRect(0, 0, 320, 240, 7, 0xffff); 
            drawTrackList();
            break; 
          }
          m5.update();
        }
      }
      

      Please note that I use the design to work with the SD card-I told about it in the 5th lesson.

      void setup(){
        M5.begin();
        WiFi.mode(WIFI_OFF);
        M5.Lcd.fillRoundRect(0, 0, 320, 240, 7, 0xffff);
        M5.Lcd.setTextColor(0x7bef);
        M5.Lcd.setTextSize(2);
        M5.Lcd.drawBitmap(30, 75, 59, 59, (uint16_t *)timer_logo);
        M5.Lcd.setCursor(110, 90);
        M5.Lcd.print("STARTING...");
        M5.Lcd.setCursor(110, 110);
        M5.Lcd.print("WAIT A MOMENT");
        if (!SD.begin())
        {
          M5.Lcd.fillRoundRect(0, 0, 320, 240, 7, 0xffff);
          M5.Lcd.drawBitmap(50, 70, 62, 115, (uint16_t *)insertsd_logo);
          M5.Lcd.setCursor(130, 70);
          M5.Lcd.print("INSERT");
          M5.Lcd.setCursor(130, 90);
          M5.Lcd.print("THE TF-CARD");
          M5.Lcd.setCursor(130, 110);
          M5.Lcd.print("AND TAP");
          M5.Lcd.setCursor(130, 130);
          M5.Lcd.setTextColor(0xe8e4);
          M5.Lcd.print("POWER");
          M5.Lcd.setTextColor(0x7bef);
          M5.Lcd.print(" BUTTON"); 
          while(true);
        }
        if (!createTrackList("/"))
        {
          M5.Lcd.fillRoundRect(0, 0, 320, 240, 7, 0xffff);
          M5.Lcd.drawBitmap(30, 75, 59, 59, (uint16_t *)error_logo);
          M5.Lcd.setCursor(110, 70);
          M5.Lcd.print("ADD MP3 FILES");
          M5.Lcd.setCursor(110, 90);
          M5.Lcd.print("TO THE TF-CARD");
          M5.Lcd.setCursor(110, 110);
          M5.Lcd.print("AND TAP");
          M5.Lcd.setCursor(110, 130);
          M5.Lcd.setTextColor(0xe8e4);
          M5.Lcd.print("POWER");
          M5.Lcd.setTextColor(0x7bef);
          M5.Lcd.print(" BUTTON");
          while(true);
        }
        drawGUI();
        play('m');
      }
      

      Step 3. Adding libraries

      To use other libraries, you need to add it. You can download it in the appropriate paragraph in the section Download -> Library. In order to add a library you need to launch Arduino IDE select the menu section Sketch -> Include Library -> Add .ZIP Library... (rice. 4, 4.1).

      Figure 4. Adding a library in the Arduino IDE

      Figure 4.1. Required libraries into a ZIP-archive

      When libraries are added, you can attach them to a new project:

      #include <M5Stack.h>
      #include <WiFi.h>
      #include "AudioFileSourceSD.h"
      #include "AudioFileSourceID3.h"
      #include "AudioGeneratorMP3.h"
      #include "AudioOutputI2S.h"
      

      Step 4. Use the engine. The case for MP3

      Don't ask "what is it for?"- we'll know it later:

      AudioGeneratorMP3 *mp3;
      AudioFileSourceSD *file;
      AudioOutputI2S *out;
      AudioFileSourceID3 *id3; 
      bool playing = true;
      

      Step 5. Make a playlist

      Let's make a structure containing fields: label (path to mp3-file, the same track name), timePos - time (memory area) on which the track is paused, pointers to neighboring tracks left and right:

      struct Track
      {
        String label;
        int timePos;
        Track *left;
        Track *right;
      };
      

      Declare a dynamic list, in fact, our playlist:

      Track *trackList;
      

      To create a playlist, let's write a simple function that takes as an argument the path where MP3 files are located. If nothing is found, the function returns false:

      bool createTrackList(String dir) {
        int i = 0;
        File root = SD.open(strToChar(dir));
        if (root)
        {
          while (true)
          {
            File entry =  root.openNextFile();
            if (!entry) break;
            if (!entry.isDirectory())
            {
              String ext = parseString(cntChar(entry.name(), '.'), '.', entry.name());
              if (ext == "mp3") 
              {
                i++;
                Track *tmp = new Track;
                tmp->label = entry.name();
                tmp->timePos = 0;
                tmp->right = tmp; 
                if (trackList == NULL)
                {
                  tmp->left = tmp;
                  trackList = tmp;
                }
                else
                {
                  tmp->left = trackList;
                  trackList->right = tmp;
                  trackList = trackList->right;
                }
              }
            }
            entry.close();
          }
          if (i > 1)
          {
            do
            {
              trackList = trackList->left;
            } while(trackList != trackList->left);
          }
          root.close();
        }
        if (i > 0)
          return true;
        return false;
      }
      

      Note that the leftmost and rightmost tracks are self-contained, not NULL

      Step 6. Tracklist drawing is easy!

      String labelCut(int from, int to, String str) {    
        String tmp = str.substring(1, posChar(str, '.'));
        if (str.length() > to)
          tmp = tmp.substring(from, to);
        return tmp;
      }
      
      void drawTrackList() {
        M5.Lcd.fillRect(0, 130, 320, 75, 0xffff);
      
        if (trackList->left != trackList)
        {
          M5.Lcd.setTextSize(2);
          M5.Lcd.setTextColor(0x7bef);
          M5.Lcd.setCursor(10, 130);
          M5.Lcd.print(labelCut(0, 22, (trackList->left)->label));
        }
       
        M5.Lcd.setTextSize(3); 
        M5.Lcd.setTextColor(0x0000);
        M5.Lcd.setCursor(10, 155);
        M5.Lcd.print(labelCut(0, 16, (trackList->label)));
      
        if (trackList->right != trackList)
        {
          M5.Lcd.setTextSize(2);
          M5.Lcd.setTextColor(0x7bef);
          M5.Lcd.setCursor(10, 185);
          M5.Lcd.print(labelCut(0, 22, (trackList->right)->label));
        }
      }
      

      Step 7. Timeline

      unsigned long drawTimeline_previousMillis = 0;
      void drawTimeline() {
        currentMillis = millis();
        if (currentMillis - drawTimeline_previousMillis > 250)
        {
          int x = 30;
          int y = 110;
          int width = 260;
          int heightLine = 2;
          int heightMark = 20;
          int widthMark = 2;
          int yClear = y - (heightMark / 2);
          int wClear = width + (widthMark / 2);
          
          drawTimeline_previousMillis = currentMillis;
          M5.Lcd.fillRect(x, yClear, wClear, heightMark, 0xffff);
          M5.Lcd.fillRect(x, y, width, heightLine, 0x7bef);
          int size_ = id3->getSize();
          int pos_ = id3->getPos();
          int xPos = x + ((pos_ * (width - (widthMark / 2))) / size_);
          M5.Lcd.fillRect(xPos, yClear, widthMark, heightMark, 0xe8e4);
        }
      }
      

      Step 8. Spectrum emulator

      unsigned long genSpectrum_previousMillis = 0;
      void genSpectrum() {
        currentMillis = millis();
        if (currentMillis - genSpectrum_previousMillis > 100)
        {
          genSpectrum_previousMillis = currentMillis;
          drawSpectrum(random(0,101), random(0,101), random(0,101), random(0,101));
        }
      }
      
      void drawSpectrum(int a, int b, int c, int d) { // %
        int x = 195;
        int y = 30;
        int padding = 10;
        int height = 30;
        int width = 15;
      
        int aH = ((a * height) / 100);
        int aY = y + (height - aH);
        M5.Lcd.fillRect(x, y, width, height, 0xffff);
        M5.Lcd.fillRect(x, aY, width, aH, 0x7bef); //0xe8e4
        
        int bH = ((b * height) / 100);
        int bY = y + (height - bH);
        int bX = x + width + padding;
        M5.Lcd.fillRect(bX, y, width, height, 0xffff);
        M5.Lcd.fillRect(bX, bY, width, bH, 0x7bef); //0xff80
      
        int cH = ((c * height) / 100);
        int cY = y + (height - cH);
        int cX = bX + width + padding;
        M5.Lcd.fillRect(cX, y, width, height, 0xffff);
        M5.Lcd.fillRect(cX, cY, width, cH, 0x7bef);//0x2589
      
        int dH = ((d * height) / 100);
        int dY = y + (height - dH);
        int dX = cX + width + padding;;
        M5.Lcd.fillRect(dX, y, width, height, 0xffff);
        M5.Lcd.fillRect(dX, dY, width, dH, 0x7bef);//0x051d
      }
      

      Step 9. Work with engine MP3

      In order to play MP3's from a playlist write a function Play(char), which as argument takes the instruction. If the argument is set to' l', the pointer in the dynamic list will be shifted to the left and the track will start playing on the left. Similarly for the track on the right. If the argument is set to' m', it means play back silence. If you pass any other argument, it would mean 't' (this) - play current, i.e. the one pointed to by the pointer.

      bool play(char dir) {
        switch (dir)
        {
          case 'r':
            if (trackList == trackList->right) return false;
            trackList->timePos = 0;
            trackList = trackList->right;       
          break;
          case 'l':
          if (trackList == trackList->left) return false;
            trackList->timePos = 0;
            trackList = trackList->left;
          break;
          case 'm': // mute
            delete file;
            delete out;
            delete mp3;
            mp3 = NULL;
            file = NULL;
            out = NULL;
            file = new AudioFileSourceSD("/");
            id3 = new AudioFileSourceID3(file);
            out = new AudioOutputI2S(0, 1);
            out->SetOutputModeMono(true);
            mp3 = new AudioGeneratorMP3();
            mp3->begin(id3, out);
            playing = false;
          return true;
          default:
            if (playing)
            {
              trackList->timePos = id3->getPos();
              play('m');
              return true;
            }
          break;
        }
        drawCover();
        mp3->stop();
        delete file;
        delete out;
        delete mp3;
        mp3 = NULL;
        file = NULL;
        out = NULL;
        file = new AudioFileSourceSD(strToChar(trackList->label));
        id3 = new AudioFileSourceID3(file);
        id3->seek(trackList->timePos, 1);
        out = new AudioOutputI2S(0, 1);
        out->SetOutputModeMono(true);
        mp3 = new AudioGeneratorMP3();
        mp3->begin(id3, out);
        playing = true;
        return true;
      }
      

      Step 10. Loop

      void loop(){ 
        if (m5.BtnA.wasPressed())
        {
          play('l');
          drawTrackList();
        }
      
        if (m5.BtnB.wasPressed())
        {
          play('t');
        }
      
        if (m5.BtnC.wasPressed())
        {
          play('r');
          drawTrackList();
        }
      

      the design below is subject to little change. We will add playback in order of play ('r'), pause control of playing, rendering of dynamic data of genSpectrum () and drawTimeline():

        if (playing)
        {
          if (mp3->isRunning())
          {
            if (!mp3->loop())
            {
              mp3->stop();
              playing = false;
              play('r');
              drawTrackList();
            }
          }
          else
          {
            delay(1000);
          }
          genSpectrum();
          drawTimeline();
        }
        m5.update();
      }
      

      Step 11. LAUNCH!

      Everything works and looks good enough, the only thing is the crackle when switching songs. The cause has not yet been set.

      Figure 5. Playback screen

      Homework

      • Task 1 level of difficulty: add control of the existence of the JPG files of the album artwork on TF card. If the album cover is missing, add a draft instead (you can download in Download - > Images - > HomeWork1);
      • Task 2 difficulty levels: add a running line for long track titles.
      • Task 3 difficulty level: use ID3 to extract the album cover from the MP3 file to avoid using external JPG files;
      • Task 4 difficulty levels: instead of emulating the spectrum, implement a fast Fourier transform. The first pillar is 100 Hz, the second - 600 Hz, the third - 1500 Hz, the fourth 3000 Hz.

      Download

      • Video demonstration (YouTube): https://youtu.be/D6pnG0Ha0yw

      • Source codes (GitHub): https://github.com/dsiberia9s/mp3-player-m5stack

      • Image converters (Yandex Disk): https://yadi.sk/d/dOj_EyU_3TfhWV

      • Images (Yandex Disk): https://yadi.sk/d/_wGruXC73TfhZ6

      • Library (Yandex Disk): https://yadi.sk/d/GX--lQ3v3Tfhcf

      • TF card files (Yandex Disk): https://yadi.sk/d/15YnN5tC3Tfhg5

      JJJ 1 Reply Last reply Reply Quote 4
      • JJJ
        JJ @Dimi
        last edited by

        @dimi Hi Dimi,
        Another excellent lesson..... look forward to testing this and also testing the line out capability.

        1 Reply Last reply Reply Quote 1
        • M
          m5dude
          last edited by

          @Dimi great stuff! you are a true wizard!

          question - how to add headphone jack to m5? is there an easy and elegant solution for this? perhaps with the line out stuff you pointed us to?

          DimiD 1 Reply Last reply Reply Quote 1
          • DimiD
            Dimi @m5dude
            last edited by

            @m5dude thanks! you can use alt text

            M 1 Reply Last reply Reply Quote 0
            • M
              m5dude @Dimi
              last edited by

              @dimi
              where do the pins connect to? GND and 25?

              if i wanted to connect a headphone to M5 I just do the same thing but with the headphone jack input?

              DimiD 2 Replies Last reply Reply Quote 0
              • DimiD
                Dimi @m5dude
                last edited by

                This post is deleted!
                1 Reply Last reply Reply Quote 0
                • DimiD
                  Dimi @m5dude
                  last edited by Dimi

                  @m5dude @m5dude use 26, 25 is occupied by built-in speaker. Yes, use ordinary headphones and everything will work. just be careful not to spoil your hearing!

                  1 Reply Last reply Reply Quote 0
                  • C
                    craighissett
                    last edited by

                    He @Dimi , this is a great tutorial on creating a fantastic little music player!
                    I'm looking to use my m5Stack in my car, connected to the aux in of my stereo.
                    I'd also like to incorporate a mode to stream audio via Bluetooth from my phone, but that'll take a fair bit of further homework to get working 😂

                    1 Reply Last reply Reply Quote 0
                    • C
                      creativetabi
                      last edited by

                      Hi @Dimi, thanks for the amazing tutorial.

                      Can I ask a question?
                      I want to make an m5stack program that would play an mp3 file if I send a trigger via UDP, and would stop if I send a different trigger via UDP.
                      I followed some of your code.

                      But when I sent the trigger, the mp3 only played the first second and kept repeating the same time position. It wouldn't play the entire mp3 song.

                      This is the code that I used:


                      #include <M5Stack.h>
                      #include <WiFi.h>
                      #include <WiFiUdp.h>
                      #include "AudioFileSourceSD.h"
                      #include "AudioFileSourceID3.h"
                      #include "AudioGeneratorMP3.h"
                      #include "AudioOutputI2S.h"
                      #define N 1024

                      bool playing = true;
                      AudioGeneratorMP3 *mp3;
                      AudioFileSourceSD *file;
                      AudioOutputI2S *out;
                      AudioFileSourceID3 *id3;

                      const char* ssid = "wifiname";
                      const char* password = "wifipassword";
                      const int port = 5555;

                      // The udp library class
                      WiFiUDP udp;

                      void print_wifi_state(){
                      M5.Lcd.clear(BLACK); // clear LCD
                      M5.Lcd.setTextColor(YELLOW);
                      M5.Lcd.setCursor(3, 3);
                      M5.Lcd.println("");
                      M5.Lcd.println("WiFi connected.");
                      M5.Lcd.print("IP address: ");
                      M5.Lcd.println(WiFi.localIP());
                      M5.Lcd.print("Port: ");
                      M5.Lcd.println(port);
                      }

                      void setup_wifi(){
                      M5.Lcd.setTextColor(RED);
                      M5.Lcd.setTextSize(2);
                      M5.Lcd.setCursor(3, 10);
                      M5.Lcd.print("Connecting to ");
                      M5.Lcd.println(ssid);

                      // setup wifi
                      WiFi.mode(WIFI_STA); // WIFI_AP, WIFI_STA, WIFI_AP_STA or WIFI_OFF
                      WiFi.begin(ssid, password);
                      // WiFi.begin();

                      // Connecting ..
                      while (WiFi.status() != WL_CONNECTED) {
                      delay(100);
                      M5.Lcd.print(".");
                      }

                      // print state
                      print_wifi_state();

                      udp.begin(port);
                      

                      }

                      void setup() {
                      M5.begin();
                      M5.Speaker.setVolume(5);
                      play('m');
                      // setup wifi
                      setup_wifi();

                      }

                      bool play(char dir){
                      switch(dir)
                      {
                      case 'm':
                      delete file;
                      delete out;
                      delete mp3;
                      mp3 = NULL;
                      file = NULL;
                      out = NULL;
                      file = new AudioFileSourceSD("/");
                      id3 = new AudioFileSourceID3(file);
                      out = new AudioOutputI2S(0, 1);
                      out->SetOutputModeMono(true);
                      mp3 = new AudioGeneratorMP3();
                      mp3->begin(id3, out);
                      playing = false;
                      return true;
                      default:
                      if(playing){
                      play('m');
                      return true;
                      }
                      break;
                      }
                      mp3->stop();
                      delete file;
                      delete out;
                      delete mp3;
                      mp3 = NULL;
                      file = NULL;
                      out = NULL;
                      file = new AudioFileSourceSD("/RainDrizzle.mp3");
                      id3 = new AudioFileSourceID3(file);
                      id3->seek(trackList->timePos, 1);
                      out = new AudioOutputI2S(0, 1);
                      out->SetOutputModeMono(true);
                      mp3 = new AudioGeneratorMP3();
                      mp3->begin(id3, out);
                      playing = true;

                      return true;
                      }

                      void loop() {
                      char packetBuffer[N];
                      int packetSize = udp.parsePacket();

                      // get packet
                      if (packetSize){

                      int len = udp.read(packetBuffer, packetSize);
                      
                      if (len > 0){
                        packetBuffer[len] = '\0'; // end
                      }
                      

                      }

                      if(strcmp(packetBuffer,"start")==0){
                      // print param
                      M5.Lcd.clear(BLACK);
                      M5.Lcd.setCursor(3, 3);
                      M5.Lcd.setTextColor(GREEN);
                      M5.Lcd.println(packetBuffer);
                      
                      play('t');
                      
                      }
                      
                      if(strcmp(packetBuffer,"stop")==0){
                      M5.Lcd.clear(BLACK);
                      M5.Lcd.setCursor(3, 3);
                      M5.Lcd.setTextColor(GREEN);
                      M5.Lcd.println(packetBuffer);
                      play('m');
                      }
                      
                      if(playing){
                        if(mp3->isRunning()){
                          if(!mp3->loop()){
                            mp3->stop();
                            playing = false;
                          }
                        }
                        else{
                          delay(1000);
                        }
                      

                      }
                      M5.update();
                      }


                      Thank you in advance.

                      1 Reply Last reply Reply Quote 0
                      • First post
                        Last post