PLAYING AUDIO

  This page is part of my Model Remodel series of articles and a subset of my Arduino section.

DISCLAIMER: If you choose to attempt any of these modifications, you assume all risks thereof. I just wanted to share my experiences here. Neither Fanhome, nor myself, are responsible for any damages that may occur.


The stock version of this Enterprise D partwork model does not play any sounds. After I added the ability to control my new lights with the Infrared Remote Control, I thought it would be interesting to see I could also play music or sounds effects. Thankfully, there are a few pre-built  sound player modules out there that make this possible with Arduino. But first, we need some audio to play!


The Audio Files

Star Trek-themed Audio Files

I found all of the audio files I wanted to use on the TrekCore website. Their sound files are all in MP3 format and most of them are really good! I downloaded a bunch of these sounds and picked a few to use for my Model Remodel project.

Editing the Audio

For the most part, I did not need to edit the audio files I downloaded. However, some of the files were a bit quiet and there were a couple files I wanted to join together. For these files, I used a free open-source audio editing program called Audacity to amplify and/or join them.

To change the format/filetype of an audio file or to convert a stereo file to mono, I used an online file converter service such as CloudConvert.


The Player Hardware

Adafruit

When I first searched for Arduino-compatible audio players, I discovered Adafruit Audio FX Sound Boards. Adafruit makes a few different audio boards, depending on how much storage you need, what size board, and if you want an onboard amplifier. Due to my desire for external powered speakers and needing a lot of audio file space, I picked up an Adafruit Audio FX Sound Board – 16GB with 2x2W Amplifier:

While this Adafruit board worked as advertised and sounded nice when connected to my speakers, I found there were a few things that really bugged me:

  • It can only play uncompressed .WAV files or compressed .OGG files. WAV files take up a lot of space, so I quickly filled up the onboard 16GB memory chip. This severely limited how many sounds I could store on it.
  • I tried using smaller .OGG file formats instead, but the player took a moment before it played each sound as it had to decompress it first
  • The USB transfer process between my computer and this board is painfully slow!
  • To control this board via a Serial connection, it requires three wires (a TX, a RX, and a RESET pin to put the board in Serial mode)
  • I could never get the volume control to work via Serial connection
  • The pin headers are included, but they need to be soldered onto the board yourself
  • It costs about $30 USD

I knew there had to be a better audio solution for my Enterprise D project. After chatting with electronics guru Chloe Powell over at Model Modz, she recommended trying a DFRobot player board.

DFRobot

I had never heard of DFRobot before, nor did it readily show up when I was searching for an Arduino-compatible audio board. Well, it turns out that DFRobot sells a ton of different modules that can be used in electronic projects! One of these modules is this DFPlayer Mini:

Chloe was right, this little guy is amazing! Take a look at some of the features of the DFPlayer Mini module:

  • It is only 20x20mm in size
  • The header pins are already soldered in place
  • It natively plays .MP3 files and also supports .WAV and .WMA files
  • It plays the requested sound immediately
  • It is available in three versions: One that reads files from a standard Micro SD card (not included). This is the version I am using because it supports a SD card as big as 32GB in size! The other two versions include 8MB or 128MB onboard memory storage options, but they have a USB port so they are a slightly larger in size.
  • Reading/writing to a Micro SD card via my USB card reader is incredibly fast, so updating the audio files was easy
  • It includes a onboard mono amplifier with dual speaker outputs
  • It only needs two wires for Arduino Serial communication
  • The volume control via Serial works perfectly
  • It costs about $6 USD

The only downside I could find to the DFPlayer was the delivery time since it ships from China. To make the trip worth it, I actually bought three of them. I will surely find a use for the other boards. To be completely honest, after using this DFPlayer Mini board, my AdaFruit Audio FX was put into storage probably never to be used again!


Loading the Audio Files

Since the DFPlayer will play MP3s right out of the box, I did not need to change the file type of my audio files. However, as I was testing the DFPlayer I found that it is very important how the audio files are stored on the Micro SD card. Two things in particular are required:

First, make sure the filename of each audio file starts with four numbers. The first file should start with 0001, the next file should start with 0002, etc. You can then put anything you want after the four numbers, for example 0001-MyAudioFile.mp3

Second, regardless of what the four numbers are in the filename, the FIRST file saved to the SD card will be considered sound #1 to the DFPlayer. The SECOND file saved to the SD card will be sound #2. This continues on through all of your audio files. Because of this, I recommend copying each audio file to the SD card one at a time, in numerical order. Do not copy all of them to the card at once. If you need to add/move/change a file, it is best to erase all of the files on the SD card and start over.

As I try to share as much as I can, here are the actual MP3 files I am using for my Model Remodel project. I named them after either the content of the file or after the ship function I plan on using them with. The links below should open in a new browser tab where you can download them if you like. The media player bar under each link will play the sound file right here in your browser:

0001-Lets’s-See-Galaxy-Class.mp3

0002-Dismissed.mp3

0003-ImpulseEngine.mp3

0004-Windows.mp3

0005-Formation.mp3

0006-Strobes.mp3

0007-NacelleStart.mp3

0008-NacelleShutdown.mp3

0009-GotoWarp.mp3

0010-Torpedo.mp3

0011-Bridge-Hum.mp3

0012-Engine-Hum.mp3

0013-Theme.mp3

0014-Make-It-So.mp3

0015-Red-Alert.mp3

0016-Phasers.mp3

0017-Volume.mp3

0018-DemoStop.mp3

0019-DockingLatches.mp3

NOTE: You will not find this last sound on the TrekCore website. I created it myself by modifying a freeware sound file of a gate closing and latching. I then slowed it down, lowered the pitch, and extended the play time to make it sound bigger and more like how I feel the Enterprise latches would sound.


The Speakers

The DFPlayer Mini can drive one or two external speakers that are rated for at least 3W (Watts) with an impedance of at least 4Ω each. You could also use 8Ω or 16Ω speakers at 3W, but they will not be as loud.

Because these speakers need to fit inside my Enterprise D Saucer section, they need to be small and as powerful as possible. Thankfully, I found this pair of sealed-cavity 3W 4Ω Speakers on Amazon that work very well and have two mounting tabs we can use to install them:


Connecting the Player

The pins on the DFPlayer Mini are allocated as follows:

The pins we will need for this project are all found along the left side of the DFPlayer:

  • VCC = 3.2-5 VDC (+)
  • RX = Serial UART Receive
  • TX = Serial UART Transmit
  • SPK_1 = Speaker 1 (+)
  • GND = Common Ground (-)
  • SPK_2 = Speaker 2 (+)

I decided to use two speakers for better sound output, but you don’t have to. The DFPlayer will drive one or two speakers just fine.

I am using DIGITAL pins 11 and 12 on my saucer Arduino for the Serial communication to the DFPlayer. However, it is important to realize that the Arduino outputs 5V and the DFPlayer expects 3.3V on the Serial pins. Therefore, we need to create a voltage divider between the Arduino pin and the RX pin of the DFPlayer.

Creating a Voltage Divider

A voltage divider is a common circuit that passively produces an output voltage that is a fraction of the input voltage. My Model Remodel will end up using a few of these. We can create a voltage divider using a couple of basic resistors, like this:

If we connect our Arduino output pin +5V at Vin, then use a 1KΩ resistor at Z1 and use a 2KΩ resistor at Z2, the Vout would be exactly +3.3V. This is because Z1 consumes exactly one-third (1K) of the total resistance (1K + 2K = 3K) of the voltage divider. The Vout between the resistors sees positive voltage that is only affected by one-third of the resistance, dropping the 5V by one-third down to 3.3V. Cool!

With this in mind, we can now connect up our components for a sound test. Then, we will create a basic sketch to play sounds:


Controlling the DFPlayer via Serial

Required Arduino Libraries

  • SoftwareSerial.h
  • DFRobotDFPlayerMini.h

TIP: If you remember back to our Infrared Remote Control sketch, a library is pre-built code that we can attach to our sketch and easily put to work.

SoftwareSerial.h

While Arduinos have a built-in hardware-based Serial connection that uses pins 0 for RX and 1 for TX, these pins are also connected to the USB data lines. If we were to use these pins for our own Serial connection, we could no longer communicate with the Arduino via USB. That means we could neither upload code to the Arduino nor use the Serial Monitor, which is not helpful at all.

Luckily for us, Arduino themselves have created a built-in library called SoftwareSerial.h that allows us to create software-based Serial connections using any two of the other DIGITAL pins on our Arduino.

NOTE: There are some limitations when using the SoftwareSerial library, but we will not run into them during this project.

DFRobotDFPlayerMini.h

To make controlling the DFPlayer much easier, there is a library called DFRobotDFPlayerMini.h that will let us use basic plain-text commands. This library may need to be installed into your offline Arduino IDE or online Arduino Editor, but it will readily show up in any library search.

Creating a Sketch to Test Some Sounds

To get us started, here is a sketch I created to play a few of my custom audio files. Be sure my first three audio files have been copied to your SD card, one at a time in numerical order!

/*
  A sketch that will play sounds through a DFPlayer Mini
*/
// SoftwareSerial
#include <SoftwareSerial.h>
// DFRobot DFPlayer Mini
#include <DFRobotDFPlayerMini.h>
// DFPlayer Audio Board Declarations
#define SFX_RX 11                          // DIGITAL 11 pin for Serial Receive from DFPlayer
#define SFX_TX 12                          // DIGITAL 12 pin for Serial Transmit to DFPlayer
SoftwareSerial DFSerial(SFX_RX, SFX_TX);   // Create DFSerial object via SoftwareSerial defined pins
DFRobotDFPlayerMini myDFPlayer;            // Call DFRobot library to create myDFPlayer object
byte sfxVolume = 30;                       // Declare Initial audio volume variable and value: 0 NONE to 30 MAX
void setup() {
  // Initialize Serial Monitor Output @ 9600 baud
  Serial.begin(9600);
  // Setup DFPlayer
  DFSerial.begin(9600);                 // Start DFSerial (SoftwareSerial) communication @ 9600 baud
  if (!myDFPlayer.begin(DFSerial)) {    // Start DFPlayer board and wait for initialization to complete
     while (true);
  }
  myDFPlayer.setTimeOut(100);     // Set Timeout on DFPlayer commands
  myDFPlayer.volume(sfxVolume);   // Set initial DFPlayer volume 
}
void loop() {
  myDFPlayer.play(1); // Play 'Power ON' sound 0001-Lets's-See-Galaxy-Class.mp3
  delay(3000);
  myDFPlayer.play(2); // Play 'Power OFF' sound 0002-Dismissed.mp3
  delay(2000);
  myDFPlayer.loop(3); // Play 'Impulse Engine' sound 0003-ImpulseEngine.mp3
  delay(5000);
  myDFPlayer.stop(); // Stop all sounds (including the looping sound)
  delay(1000);
}

How this Sketch Works

Libraries

I first #include the SoftwareSerial.h and DFRobotDFPlayerMini.h libraries.

Declarations

I declare the two DIGITAL pins I am using to connect to the TX/RX pins of the DFPlayer.

Next, I create two objects:

  • A SoftwareSerial object named DFSerial to use those pins for Serial communications
  • A DFRobotDFPlayerMini object named myDFPlayer that we will use throughout the sketch to control the DFPlayer board

Finally, I create a variable sfxVolume to hold the initial value of the DFPlayer volume setting, in this case maximum volume.

void setup()

I first initialize the Arduino’s Serial Monitor output at 9600 baud with the Serial.begin(9600) command.

The DFSerial.begin(9600) command starts the DFSerial (SoftwareSerial) Serial connection at 9600 baud.

The myDFPlayer.begin(DFSerial) if statement then uses this DFSerial connection to start the actual DFPlayer board and waits for it to complete initialization. When this happens, you may hear a slight ‘click’ sound from the speakers. This is normal and lets us know that the Serial connection and DFPlayer board are working correctly.

Finally, I configure the DFPlayer to timeout after to 100ms and set its initial volume to the maximum of 30 (the value of sfxVolume).

TIP: This .volume() command can be used again later on in your sketch to change the volume to any value you want between 0 and 30, as needed.

void loop()

Here, I tell the DFPlayer to play the first audio file that was stored on the Micro SD card with the myDFPlayer.play(1) command. If you are using my audio files, it will play my 0001-Lets’s-See-Galaxy-Class.mp3 sound.

Once this command is received by the DFPlayer, it will start playing the sound immediately. However, since the DFPlayer operates independently of the Arduino, the Arduino will continue to process code even as the sound is playing. Therefore, I use a delay() command to have the Arduino wait 2.5 second (2500 ms) which allows the MP3 file to finish playing.

NOTE: If you send a .play() command to the DFPlayer while it is already playing something, it will stop playing the current sound and play the new one.

Next, I tell the DFPlayer to play my second sound file, in this case my 0002-Dismissed.mp3 sound. Again, I make the Arduino wait 2 seconds before moving on to allow the sound to finish playing.

Then, I tell the DFPlayer to play the third sound file on the Micro SD card, in this case my 0003-ImpulseEngine.mp3 sound. But, this time I want the sound to repeat over and over until we tell it to stop. To do this, I use the myDFPlayer.loop(3) command. I let this sound loop for 5 seconds using yet another delay() command.

NOTE: Since the DFPlayer runs separately from the Arduino, even if you power down the Arduino, a looping sound will continue to play as long as the DFPlayer has power. It will only quit playing if: the DFPlayer loses power, the DFPlayer is re-initialized, the DFPlayer receives another .play() command, or the DFPlayer receives a .stop() command.

Finally, I tell the DFPlayer to stop playing sounds all together. Here, I use the myDFPlayer.stop() command. This last delay() command is used to wait 1 second before starting the loop() function over again.

Download my Sketch

To help others, I have shared this sketch via the Arduino Editor. Simply click the Open Code or Download buttons!

Final Thoughts on the DFRobot DFPlayer Mini

I am extremely satisfied with this DFRobot DFPlayer Mini board! It is tiny, easy to use, and plays my audio files perfectly. It can actually do much more than we used used it for in the above example, but it fits my Enterprise Model Remodel needs perfectly. While we did not use them here, but there are many other Serial commands we can send to the DFPlayer board. Some of the most useful commands I am using include:

  • .next() = Play next audio file, in numerical order
  • .previous() = Play previous audio file, in reverse numerical order
  • .pause() = Pause current file playback
  • .start() = Resume paused file playback
  • .volumeUp() = Increase volume by 1
  • .volumeDown() = Decrease volume by 1

TIP: While the .volumeUp() and .volumeDown() commands do work, they take a while to process and can screw up any timing you might have in your sketch. Instead, I recommend setting the volume directly using the much faster .volume() command.


Timing Sounds to Events

Using the millis() command and some variables, we have implemented timing before to make events happen in Arduino at different times. We can do the same thing when playing sounds.

For example, my custom 0009-GotoWarp.mp3 audio file has Captain Picard saying ‘Engage’ before the go-to-warp sound. If I played the audio file at the same time I started my warp sequence lighting effect, they would not be in sync. Therefore, I use more millis() timing to delay the warp sequence effect until approximately 1.75 seconds after the sound file begins playing.

Updating our DFPlayer Circuit

To demonstrate this, I added a simple momentary switch and LED/resistor to our existing DFPlayer circuit. The momentary switch is connected between Arduino pins 8 and 9. The anode (+) of the LED is connected to pin 6, while the cathode (-) of the LED is connected to Ground via a 150Ω resistor:

Pull-Down Resistors

In the wiring diagram above, you may notice the 10KΩ resistor (with the cyan-colored wires) connecting DIGITAL pin 9 to Ground. This is called a pull-down resistor and may or may not be needed. What this does is makes sure that our switch’s input pin is ‘pulled down’ to 0V (Ground) when there is NO input signal voltage applied. Without it, once you close the switch and let go, you may find that your Arduino keeps thinking the switch is closed and the sound event will trigger multiple times (or may randomly activate on its own). This is called digital gate/logic ‘float’ and the pull-down resistor will keep it from happening.

Updating our DFPlayer Sketch

First, we need to configure these additional pins. This code is inserted before the end of the setup() function:

...
  myDFPlayer.setTimeOut(100);   // Set Timeout on DFPlayer Commands
  myDFPlayer.volume(sfxVolume); // Set initial DFPlayer volume
  pinMode(6, OUTPUT);           // DIGITAL pin 6 Output to LED
  pinMode(8, OUTPUT);           // DIGITAL pin 8 Output to switch
  digitalWrite(8, HIGH);        // Turn DIGITAL pin 8 ON to send 5V towards switch
  pinMode(9, INPUT);            // DIGITAL pin 9 Input from other side of switch
}
...

Next, to delay an event from happening until a certain time after a sound is triggered, I create three new variables to track the event in our DFPlayer sketch. These are inserted right before the setup() function.

  • A boolean variable to track the state of the event (true if active or false if not active), defaults to false
  • A unsigned long variable to store the timestamp value of when the sound started playing, defaults to zero
  • A constant integer variable to store the number of milliseconds (ms) to wait after the sound starts playing before triggering the event. I like to name these variables as ‘preempt delays’, but of course you can name them anything you like. Here is the 1.75 second delay.
...
byte sfxVolume = 30; // Declare Initial audio volume variable: 0 - 30 MAX
bool eventActive = false;
unsigned long eventStartTime = 0;
const int eventPreemptDelay = 1750;
void setup() {
...

Finally, I replace all of the previous void() loop with new code:

void loop() {
  // On button press, start event sequence
  if (eventActive == false && digitalRead(9) == HIGH) {
    myDFPlayer.play(9);          // Start playing 'Go to Warp' sound 0009-GotoWarp.mp3
    eventStartTime = millis();   // Save the timestamp when event started
    eventActive = true;          // Set event state to ON (true)
  }
  // IF event is active, wait preempt delay time, then trigger light
  if (eventActive == true && (millis() - eventStartTime >= eventPreemptDelay)) {
    digitalWrite(6, HIGH); // Turn LED ON
    delay(750);            // Wait 750 ms
    digitalWrite(6, LOW);  // Turn LED OFF
    eventActive = false;   // Set event state to OFF (false)
  }
}

How this Sketch Works

First, I use an if statement to check that BOTH of these conditions are true:

  • eventActive == false checks that our sound event is not currently running – this keeps us from triggering it on top of itself
  • digitalRead(9) == HIGH checks that our switch input pin is seeing 5V (the switch is closed/pushed)

If these two conditions are met, we do the following three things:

  • We start playing the sound file with myDFPlayer.play(9)
  • We assign the current timestamp to the eventStartTime variable using millis()
  • We set the state of the boolean variable eventActive to true

Then, in a second if statement, I check that BOTH of these conditions are true:

  • eventActive == true checks that we want the event to occur
  • The current timestamp (time) minus the eventStartTime is greater or equal to the assigned eventPreemptDelay time value

If these two conditions are met, it will turn the LED ON for 750ms. Once the LED is turned OFF again, the event has completed so we can set the eventActive state back to false to wait for the next switch activation.

Download my Sketch

To help others, I have shared this sketch via the Arduino Editor. Simply click the Open Code or Download buttons!

In Conclusion

Not only have we added the ability to play sounds with our Arduino, we have learned how to control them and even time them with other events. Take some time to play with the various features of the DFPlayer and see what else you can come up with. I feel the experimentation is what keeps me interested in models like our Enterprise. We are only limited by our imagination, so give things a try and see what happens!

Next Arduino Page


BLUETOOTH CONTROL – Controlling our Arduino via Bluetooth (Coming Soon)