Play audio constantly, interrupt and play something else while button held down

I'm looking to make a toy that plays a sound continuously while on and when you hold a button down, it plays a different audio sound.
I'm using the DFPlayer mini and an Arduino Nano although the diagram shows a regular Arduino, the pinouts are the same.

This is the code that I have so far and the wiring diagram I have:

#include "mp3tf16p.h"

// constants won't change. 
MP3Player mp3(10,11);
const int buttonPin = 4;  // the number of the pushbutton pin

// variables will change:
int buttonState = 0;  // variable for reading the pushbutton status


// audio file names associated with the names on the SD card
#define idle 1
#define rev 2

void setup() {

pinMode(buttonPin, INPUT);
Serial.begin(9600);
mp3.initialize();

}

void loop() {

  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (buttonState == HIGH) {
    // Play Rev Sound:
  
    mp3.playTrackNumber(rev,10);
    ;
  } else {
    // Play Idle Sound:
  mp3.playTrackNumber(idle,30);
  ;
  }

mp3.serialPrintStatus(MP3_ALL_MESSAGE);
Serial.println(buttonState);

}

The code works, sort of. The audio DOES play constantly, and does change when I hold the button down, but only after the first audio file has completed its full length.
The second audio file also has to fully complete, but I want it to change the second the button is held and released.
What am I missing?

Thank you for your time.

By default, the mp3.playTrackNumber() function plays the entire track before anything else can happen, so even if you press the button, the current track needs to finish first.

What does this do if you try running it?

#include "mp3tf16p.h"

// constants won't change.
MP3Player mp3(10,11);
const int buttonPin = 4;  // the number of the pushbutton pin

// variables will change:
int buttonState = 0;  // variable for reading the pushbutton status
int lastButtonState = 0;  // to remember the previous button state

// audio file names associated with the names on the SD card
#define idle 1
#define rev 2

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  mp3.initialize();
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the button state has changed:
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      // Button is pressed, play Rev sound:
      mp3.stop();  // stop the current track
      mp3.playTrackNumber(rev, 10);
    } else {
      // Button is released, play Idle sound:
      mp3.stop();  // stop the current track
      mp3.playTrackNumber(idle, 30);
    }
    // Save the current button state for the next loop iteration
    lastButtonState = buttonState;
  }

  // Print the current status and button state for debugging
  mp3.serialPrintStatus(MP3_ALL_MESSAGE);
  Serial.println(buttonState);
}

1 Like

I get a compilation error stating

"Compilation error: 'class MP3Player' has no member named 'stop'"

But there is a "mp3.player.stop();"

But sadly even with that, it doesn't interrupt the current track.

You didn't provide a link to the library you used so I couldn't check it, but the DFRobotDFPlayerMini library has an advertise method that I think might do what you're asking for.

That is indeed the Library I used. Advertise method you say?

If you indeed used that library, you included a different sketch, which doesn't.

I'm an absolute beginner here sorry, is library the same as the .h file? I used the library manager in the Arduino IDE to install the DFPlayer library?
But I did use a .h file that's here:

#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

#define MP3_ERROR_ONLY 1
#define MP3_ALL_MESSAGE 2

class MP3Player
{
private:
    SoftwareSerial *mySoftwareSerial;
    void statusOnSerial(uint8_t type, int value);
    void waitPlayIsTerminated(void);
    int p_RX;
    int p_TX;

public:
    DFRobotDFPlayerMini player;
    MP3Player(int RX, int TX);
    ~MP3Player();
    void playTrackNumber(int trackNumber, int volume, boolean waitPlayTerminated = true);
    boolean playCompleted(void);
    void initialize(void);
    int serialPrintStatus(int errorOnly);
};

MP3Player::MP3Player(int RX, int TX)
{
    p_TX = TX;
    p_RX = RX;
}

MP3Player::~MP3Player()
{
}

void MP3Player::initialize(void)
{
    mySoftwareSerial = new SoftwareSerial(p_RX, p_TX);

    mySoftwareSerial->begin(9600);
    Serial.println(F("Initializing MP3Player ..."));

    if (!player.begin(*mySoftwareSerial,true,false))
    {
        Serial.println(F("Unable to begin:"));
        Serial.println(F("1.Please recheck the connection!"));
        Serial.println(F("2.Please insert the SD card!"));
        while (true)
            ;
    }
    player.volume(10);
    Serial.println(F("MP3Player online."));
}

void MP3Player::playTrackNumber(int trackNumber, int volume, boolean waitPlayTerminated)
{
    player.volume(volume);
    player.play(trackNumber);
    if (waitPlayTerminated)
    {
        waitPlayIsTerminated();
    }
}

void MP3Player::waitPlayIsTerminated(void)
{
    while (!playCompleted())
    {
    }
}

boolean MP3Player::playCompleted(void)
{
    if (player.available())
    {
        return player.readType() == DFPlayerPlayFinished;
    }
    return false;
}

// Print the detail message from DFPlayer to handle different errors and states.
// 
int MP3Player::serialPrintStatus(int verbose)
{
    if (player.available())
    {
        uint8_t type = player.readType();
        int value = player.read();
        if (verbose == MP3_ERROR_ONLY)
        {
            if (type == DFPlayerError)
            {
                statusOnSerial(type, value);
            }
        }
        else
        {
            statusOnSerial(type, value);
        }
        if(type == DFPlayerError) {
            return value;
        } else {
            return 0;
        }
    }
}

void MP3Player::statusOnSerial(uint8_t type, int value)
{
    switch (type)
    {
    case TimeOut:
        Serial.println(F("Time Out!"));
        break;
    case WrongStack:
        Serial.println(F("Stack Wrong!"));
        break;
    case DFPlayerCardInserted:
        Serial.println(F("Card Inserted!"));
        break;
    case DFPlayerCardRemoved:
        Serial.println(F("Card Removed!"));
        break;
    case DFPlayerCardOnline:
        Serial.println(F("Card Online!"));
        break;
    case DFPlayerPlayFinished:
        Serial.print(F("Number:"));
        Serial.print(value);
        Serial.println(F(" Play Finished!"));
        break;
    case DFPlayerError:
        Serial.print(F("DFPlayerError:"));
        switch (value)
        {
        case Busy:
            Serial.println(F("Card not found"));
            break;
        case Sleeping:
            Serial.println(F("Sleeping"));
            break;
        case SerialWrongStack:
            Serial.println(F("Get Wrong Stack"));
            break;
        case CheckSumNotMatch:
            Serial.println(F("Check Sum Not Match"));
            break;
        case FileIndexOut:
            Serial.println(F("File Index Out of Bound"));
            break;
        case FileMismatch:
            Serial.println(F("Cannot Find File"));
            break;
        case Advertise:
            Serial.println(F("In Advertise"));
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}

I don't know what that unnamed file is but if that's what you included it appears to be a different library that uses the DFRobotDFPlayerMini library. Which was completely hidden in your original file. I think I'll say good luck with your project and see myself out.

Have you tried the pause function?

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

// Create software serial for communication with DFPlayer Mini
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

const int buttonPin = 4;  // the number of the pushbutton pin

// Variables to track button state
int buttonState = 0;
int lastButtonState = 0;  // to remember the previous button state

// Audio file numbers on the SD card
#define idle 1
#define rev 2

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  mySoftwareSerial.begin(9600);

  if (!myDFPlayer.begin(mySoftwareSerial)) {  // Use software serial to communicate with MP3
    Serial.println("DFPlayer Mini not detected.");
    while (true);  // Halt execution if the DFPlayer isn't connected
  }

  myDFPlayer.volume(30);  // Set volume level (0-30)
}

void loop() {
  // Read the state of the pushbutton value
  buttonState = digitalRead(buttonPin);

  // Check if the button state has changed
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      // Button pressed, play the "rev" sound
      myDFPlayer.pause();  // Pause the current track
      myDFPlayer.play(rev);  // Play the rev sound
    } else {
      // Button released, play the "idle" sound
      myDFPlayer.pause();  // Pause the current track
      myDFPlayer.play(idle);  // Play the idle sound
    }
    // Save the current button state
    lastButtonState = buttonState;
  }

  // Optional: Print button state to the serial monitor for debugging
  Serial.println(buttonState);
}

This code doesn't include the DFplayer library. Go use that library, it will do what you want.

Try the code I've posted

1 Like

Okay that's significant progress!

It's so close to perfect! I was worried because it wasn't looping the files, but I've just made the files longer and it does exactly what I need it to.

There is a tiny delay in switching between files, is this a limitation of the processing power of the nano?

The Nano also gets stuck playing the rev file if the button is simply pressed for a fraction of a second and the Nano has to be reset to stop it, any ideas what's causing that?

Thank you SO MUCH! for your help, I'm genuinely ecstatic with what you've done for me, I'm having so much fun! :smiley:

1 Like
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

// Create software serial for communication with DFPlayer Mini
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

const int buttonPin = 4;  // the number of the pushbutton pin

// Variables to track button state
int buttonState = 0;
int lastButtonState = 0;  // to remember the previous button state

// Audio file numbers on the SD card
#define idle 1
#define rev 2

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  mySoftwareSerial.begin(9600);

  if (!myDFPlayer.begin(mySoftwareSerial)) {  // Use software serial to communicate with MP3
    Serial.println("DFPlayer Mini not detected.");
    while (true);  // Halt execution if the DFPlayer isn't connected
  }

  myDFPlayer.volume(30);  // Set volume level (0-30)
}

void loop() {
  // Read the state of the pushbutton value
  buttonState = digitalRead(buttonPin);

  // Check if the button state has changed
  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      // Button pressed, play and loop the "rev" sound
      myDFPlayer.pause();  // Pause the current track
      myDFPlayer.loop(rev);  // Play and loop the rev sound
    } else {
      // Button released, play and loop the "idle" sound
      myDFPlayer.pause();  // Pause the current track
      myDFPlayer.loop(idle);  // Play and loop the idle sound
    }
    // Save the current button state
    lastButtonState = buttonState;
  }

  // Optional: Print button state to the serial monitor for debugging
  Serial.println(buttonState);
}

1 Like

More likely the DFPlayer Mini. The Nano should be fast enough, but the DFPlayer has to close the file safely, switch files, open the new file, process it, and play it. I feel like there isn't much you can do about that.

1 Like

I'd try a simple debounce first, see if that's the issue.

1 Like

Fair enough.

Thank you again! So much!
I've got a lot to learn for sure, but you've given me a kick in the exact direction.

1 Like

This is a bit rough, but could do the job.

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

// Create software serial for communication with DFPlayer Mini
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

const int buttonPin = 4;  // the number of the pushbutton pin

// Variables to track button state
int buttonState = 0;
int lastButtonState = 0;  // to remember the previous button state
unsigned long lastDebounceTime = 0;  // the last time the button state was toggled
unsigned long debounceDelay = 50;    // debounce delay in milliseconds

// Audio file numbers on the SD card
#define idle 1
#define rev 2

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  mySoftwareSerial.begin(9600);

  if (!myDFPlayer.begin(mySoftwareSerial)) {  // Use software serial to communicate with MP3
    Serial.println("DFPlayer Mini not detected.");
    while (true);  // Halt execution if the DFPlayer isn't connected
  }

  myDFPlayer.volume(30);  // Set volume level (0-30)
}

void loop() {
  // Read the state of the pushbutton value
  int reading = digitalRead(buttonPin);

  // Debounce the button press
  if (reading != lastButtonState) {
    lastDebounceTime = millis();  // Reset the debounce timer
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // If the button state has changed and it's stable
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == HIGH) {
        // Button pressed, play and loop the "rev" sound
        myDFPlayer.pause();  // Pause the current track
        myDFPlayer.loop(rev);  // Play and loop the rev sound
      } else {
        // Button released, play and loop the "idle" sound
        myDFPlayer.pause();  // Pause the current track
        myDFPlayer.loop(idle);  // Play and loop the idle sound
      }
    }
  }

  // Save the last button state
  lastButtonState = reading;

  // Optional: Print button state to the serial monitor for debugging
  Serial.println(buttonState);
}

Sadly no joy with the debounce, I'm using a simple tactile button if that changes anything?

I'm curious, the Nano can play simple audio files, and seeing as I'm only using 2 files that are small. Could I completely omit the DRFplayer mini and simply power the speaker through a simple audio amp?
OR do you feel the DRF is my best option?

Honestly, no. I wouldn't recommend that at all. the Arduino itself doesn't make good sounding sounds, only tones.

1 Like

The DFPlayer isn't super fast. If you send too many instructions too quickly, it will ignore the second instruction.

Maybe this will fix it??

#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

// Create software serial for communication with DFPlayer Mini
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;

const int buttonPin = 4;  // the number of the pushbutton pin

// Variables to track button state
int buttonState = 0;        // Current state of the button
int lastButtonState = 0;    // Previous state of the button
unsigned long lastDebounceTime = 0;  // Last time the button state was toggled
unsigned long debounceDelay = 50;    // Debounce delay in milliseconds

// Audio file numbers on the SD card
#define idle 1
#define rev 2

// Button state flag
bool isRevPlaying = false;  // Flag to indicate if "rev" sound is playing

void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  mySoftwareSerial.begin(9600);

  if (!myDFPlayer.begin(mySoftwareSerial)) {  // Use software serial to communicate with MP3
    Serial.println("DFPlayer Mini not detected.");
    while (true);  // Halt execution if the DFPlayer isn't connected
  }

  myDFPlayer.volume(30);  // Set volume level (0-30)
  myDFPlayer.loop(idle);   // Start playing and looping the idle sound initially
}

void loop() {
  // Read the state of the pushbutton value
  int reading = digitalRead(buttonPin);

  // Debounce the button press
  if (reading != lastButtonState) {
    lastDebounceTime = millis();  // Reset the debounce timer
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // If the button state has changed and it's stable
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == HIGH && !isRevPlaying) {
        // Button pressed, play and loop the "rev" sound
        myDFPlayer.pause();  // Pause the current track
        myDFPlayer.loop(rev);  // Play and loop the rev sound
        isRevPlaying = true;   // Set the flag to indicate "rev" is playing
      } else if (buttonState == LOW && isRevPlaying) {
        // Button released, ensure the idle track is playing
        myDFPlayer.pause();  // Pause the current track
        myDFPlayer.loop(idle);  // Play and loop the idle sound
        isRevPlaying = false;   // Clear the flag to indicate "rev" is not playing
      }
    }
  }

  // Save the last button state
  lastButtonState = reading;

  // Optional: Print button state to the serial monitor for debugging
  Serial.println(buttonState);
}

But I would still expect that when you release the button after a quick press, the rev track will continue to play till the DFPlayer has finished the background tasks it is still doing, though should be very quick.

Let me know if I'm right about that...
:crazy_face:

Sadly it still gets caught up with the button press, but honestly, it's at a stage where the project can progress and fine tweaking can go to the back burner, I will continue to learn and try to find a solution.
Thank you for everything, if you have any other ideas, I'm open to trying :smiley: