Raspberry Pi Baby White Noise Machine
Home Personal Raspberry Pi Baby White Noise Machine

Raspberry Pi Baby White Noise Machine

5 comments

With our 8-month-old daughter Julia, my wife and I have made liberal use of various types of white noise to help her sleep. Most recently, we’ve employed a spare old Android phone with the White Noise Baby app, a pairing which is honestly pretty darn good at what it does. However, not only is the hardware overkill for what it’s doing, it also has a shortcoming for the type of scheduled and repetitive but also slightly varied usage style we want: namely, there’s no simple way to either automate or remotely control it.

Now, before anyone suggests an Android VNC server or something like DroidMote, note that generic screen-level remote control is not what I was hoping to find. Instead, I wanted something more like a simple phone app or even a home screen widget that I could use to start or stop multiple types of audio with very few taps—you know, the kind of interface you can use at 2 AM without fumbling around for too long.

On top of this, we have a more unique requirement for ultimate convenience: the same device should also allow multiple simultaneous audio streams, each with independent volume control, to combine some kind of background noise with other baby audio (like Winnie the Pooh stories) while still being controllable via the same remote interface. To summarize:

  • Remote controlled via simple smartphone interface
  • Multiple audio stream output
  • Independent audio stream control
  • User-provided noise type (white, brown, pink, air conditioner, etc.)
  • User-provided storybook audio library

Good luck finding a device that does all that, especially for anything remotely cheap.

Wait a minute—let’s make one instead! Enter the Raspberry Pi, the answer to so many simple automation challenges like this. It turns out that you can do all of the above with a stock Raspberry Pi device running the standard Raspbian OS, if you’re willing to do a bit of programming (or, if you’re reading this post, copypasting).

Preparation

Here’s the basic list of requirements:

  • Raspberry Pi (B, B+, or 2 should all work) with network connection
  • Some kind of stereo or amplified speaker system
  • Standard 1/8″ stereo audio cable between RasPi and speakers
  • If controlling via Android, the free S-Remote Control app
  • If controlling via iPhone, the $0.99 cmd app

There are certainly other options on the app end of things; I may roll my own in the near future as well, but the above two work. (Ignore the few one-star reviews of S-Remote Control. It works perfectly. I think those people must not have known what they were doing.)

For the sake of this post, I will assume that you already have some familiarity with the Raspberry Pi platform and Raspbian operating system, or some functional equivalent. In case you don’t, there are plenty of other resources online to help with that. To continue, you should know how to log into your RasPi system via SSH, and use the shell to run simple commands. You should also know how to edit files directly on the RasPi system; I use the vim editor for this purpose, but nano works fine, as will countless others. Your Pi should also have internet access, since without this, you won’t be able to install the required packages.

Installing Packages

Assuming a stock Raspbian OS installation, you first need to install the mpd and mpc packages, which provide a media player daemon and client. I found these to work well for what I wanted to do, although it is certainly possible that some others exist that would work as well. The main atypical requirement that I have, as noted above, is that the system has to support multiple playback channels simultaneously. The stock omxplayer can easily do one at a time, but not if you want multiple streams with independent volume control.

So, start with the following command:

sudo apt-get install mpd mpc

No other new packages are necessary.

Adding a Second Channel with mpd

A typical mpd installation gives you one server with one output channel. This works fine by itself with no changes to the /etc/mpd.conf configuration file. However, for this project, I need one channel for white noise and another for some kind of music, story books, or anything else that we might want to phase in and out without requiring mutual exclusivity with the white noise. This means creating a new set of configuration and service files for another instance of mpd. This can be done first copying the service and configuration files to new versions of themselves. However, before we do that, there’s one change that must be made to /etc/mpd.conf to allow independent volume control. Open this files in your editor of choice, and add the mixer_type definition to the audio_output { ... } definition section which has type set to alsa:

audio_output {
        type            "alsa"
        name            "My ALSA Device"
        device          "hw:0,0"        # optional
        format          "44100:16:2"    # optional
        mixer_device    "default"       # optional
        mixer_control   "PCM"           # optional
        mixer_index     "0"             # optional
        mixer_type      "software"
}

Setting the mixer type to software results in some increased CPU usage for audio processing, but I couldn’t figure out any simpler way to do it. Leaving the ALSA hardware mixer in place results in both channels using the same volume level.

Once you have done this, copy the configuration file and service definition file to new versions of themselves:

sudo cp /etc/mpd.conf /etc/mpd2.conf
sudo cp /etc/init.d/mpd /etc/init.d/mpd2

…and then edit these two new files to prevent overlapping resources. First, open /etc/mpd2.conf in your editor of choice, and modify these options:

db_file                 "/var/lib/mpd/tag_cache2"
log_file                "/var/log/mpd/mpd2.log"
pid_file                "/var/run/mpd/pid2"
state_file              "/var/lib/mpd/state2"
sticker_file            "/var/lib/mpd/sticker2.sql"
port                    "6601"

Then, open /etc/init.d/mpd2 and modify these lines:

  • Line 4:
    # Provides:          mpd2
  • Line 11:
    # Short-Description: Music Player Daemon Channel 2
  • Line 20:
    DESC="Music Player Daemon Channel 2"
  • Line 22:
    MPDCONF=/etc/mpd2.conf

Finally, (re)start both services with the following commands:

sudo /etc/init.d/mpd restart
sudo /etc/init.d/mpd2 restart

You will see some errors about port 6600 and 6601 binding, but everything will work anyway.

Populating the Media Library

This confused me at first, but it makes sense to me now. The mpd daemon and corresponding mpc client are designed to work together, which means mpc gives you command-line playback control/access to the resources that mpd knows about. You can’t just arbitrarily play any song or load any playlist with a relative (or absolute) path using a call to mpc, which is what I tried to do initially. You can load some songs on demand, particularly streaming internet radio stations, but everything else needs to be loaded into the correct location. For playlists (*.pls), this is /var/lib/mpd/playlists/, and for songs (*.mp3, *.wav, etc.), this is /var/lib/mpd/music. These folders can be modified in the the configuration files found in the /etc folder, but there’s usually no compelling reason to do this.

In my case, I have one MP3 for white noise (Star Trek TNG engine idling, trimmed to 30 minutes), and eight or nine songs. I used Media Player Classic to create one playlist file with the engine noise, and then to create a second playlist with all of the songs on it. MPC is overkill for this job, but it works well enough. You can find some directions on how to make these by hand here or here.

Testing Playback Manually

If you have set up two mpd instances as described above, you can now access them using mpc along with the correct port argument. The default port is 6600, so accessing the first server doesn’t require any specific argument. You should be able to see something like this:

pi@raspberrypi ~ $ mpc
volume: 80%   repeat: off   random: off   single: off   consume: off

A response like what is shown above indicates that the client connection to mpd succeeded. You can then test the second channel with the -p 6601 argument:

pi@raspberrypi ~ $ mpc -p 6601
volume: 80%   repeat: off   random: off   single: off   consume: off

Now, you can try adding a song to the client’s playlist and playing it:

mpc add mysong.mp3
mpc play

…or loading one of the playlists you defined and playing it, for example on the other channel:

mpc -p 6601 load myplaylist.pls
mpc -p 6601 play

You can then change the volume, stop playback, clear the playlist, etc. as desired:

mpc volume 50
mpc stop
mpc clear

Assuming all of this is working (yay!), all we need now is some way to control it remotely.

Creating a Remote Control Listener with Python

Along with the Raspberry Pi platform for the hardware, the Python programming language is fantastic for quick software tools like this. It’s not idea for every situation, of course, but it’s quick and clean and powerful and…well, awesome. Everything you need already comes with the Raspbian OS, so you don’t even have to install anything new.

Create a file called tcplistener.py (or whatever you want) in your favorite editor, and give it the following content:

#!/usr/bin/env python

import sys, socket, subprocess

host = ''
port = 1234
backlog = 5
size = 16
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(backlog)
while 1:
    try:
        client, address = s.accept()
    except KeyboardInterrupt:
        sys.exit()
    try:
        data = client.recv(size)
        if data:
            data = data.strip()
            mpdport = "6600"
            if data[-1:] == '1':
                mpdport = "6600"
                data = data[:-1]
            if data[-1:] == '2':
                mpdport = "6601"
                data = data[:-1]
            #print data
            response = "\xAA"
            if data == 'volumeup':
                subprocess.call(["mpc", "-p", mpdport, "-q", "volume", "+5"])
            elif data == 'volumedown':
                subprocess.call(["mpc", "-p", mpdport, "-q", "volume", "-5"])
            elif data == 'play':
                subprocess.call(["mpc", "-p", mpdport, "-q", "play"])
            elif data == 'pause':
                subprocess.call(["mpc", "-p", mpdport, "-q", "pause"])
            elif data == 'toggle':
                subprocess.call(["mpc", "-p", mpdport, "-q", "toggle"])
            elif data == 'stop':
                subprocess.call(["mpc", "-p", mpdport, "-q", "stop"])
            elif data == 'previous':
                subprocess.call(["mpc", "-p", mpdport, "-q", "prev"])
            elif data == 'next':
                subprocess.call(["mpc", "-p", mpdport, "-q", "next"])
            elif data == 'fastforward':
                subprocess.call(["mpc", "-p", mpdport, "-q", "seek", "+15"])
            elif data == 'rewind':
                subprocess.call(["mpc", "-p", mpdport, "-q", "seek", "-15"])

            elif data == 'story':
                subprocess.call(["mpc", "-p", mpdport, "-q", "clear"])
                subprocess.call(["mpc", "-p", mpdport, "-q", "load", "story.pls"])
                subprocess.call(["mpc", "-p", mpdport, "-q", "play"])
            elif data == 'noise':
                subprocess.call(["mpc", "-p", mpdport, "-q", "clear"])
                subprocess.call(["mpc", "-p", mpdport, "-q", "load", "noise.pls"])
                subprocess.call(["mpc", "-p", mpdport, "-q", "play"])

            else:
                response = "\xEE"
            client.send(response)
    finally:
        client.close()

Now, there are a few things to note about the way this script works. Those of you fluent (or even semi-fluent) in Python can figure this all out pretty easily, and make changes if you want to. The code is pretty self-explanatory, but here is feature list:

  • Starts a TCP server on power 1234, no specific IP
  • Does not implement forking and socket selection (no simultaneous clients), but supports a connection backlog of up to 5 clients
  • Expects rapid open-command-close TCP exchanges, so no permanent connections
  • Implements the following generic commands:
    • volumeup – increase volume by 5%
    • volumedown – decrease volume by 5%
    • play – begin or resume playback
    • pause – pause ongoing playback
    • toggle – either play or pause, depending on current state
    • stop – stop playback (playing again will start at beginning of track)
    • previous – jump to the previous track in the playlist
    • next – jump to the next track in the playlist
    • fastforward – skip ahead 15 seconds in the current track
    • rewind – skip backward 15 seconds in the current track
  • Implements the following special commands:
    • story – clear playlist, load “story.pls” playlist, start playback
    • noise – clear playlist, load “noise.pls” playlist, start playback
  • Allows sending any of the above commands to the second mpd server by appending “2” to the command, for example:
    • volumeup2
    • toggle2
    • noise2
  • Sends back single 0xAA byte if command accepted, 0xEE byte if command fails

Most of the playback control commands are passed almost directly through to mpc. You can see a list of all the commands that mpc provides by running mpc help from the shell. The way you set up your Python remote script may involve different keywords; that’s up to you.

Once you’ve created the script file and saved it, you have to make it executable and then run it as a background process:

chmod +x ./tcplistener.py
./tcplistener.py &

If you want to kill it later, you can use ps ax to find the PID and kill it with kill {pid}. If this is too cumbersome, you could get fancy and create a service management script for it like /etc/init.d/soundremote or something. Since this was a Q&D set-it-and-forget-it project for me, that seemed like overkill, so I didn’t. Using ps and kill isn’t that hard.

Finally, before geting started with the smartphone app configuration, it is important to make a note of the IP address of the Raspberry Pi device. Ideally, you should configure this to be a static IP, either via appropriate values in /etc/network/interfaces or else by setting up a static DHCP lease on your router for the RasPi’s unique MAC address. In any case, you can get the current IP with ifconfig eth0 (if using wired Ethernet) or ifconfig wlan0 (if using wireless):

pi@raspberrypi /home/pi $ ifconfig wlan0
wlan0     Link encap:Ethernet  HWaddr 00:13:ef:40:1a:45
          inet addr:192.168.1.225  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:207132 errors:0 dropped:8642 overruns:0 frame:0
          TX packets:76488 errors:0 dropped:3 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:138093274 (131.6 MiB)  TX bytes:64037474 (61.0 MiB)

The inet addr value is the one you want to note.

Configuring the Remote Control App (Android / S-Control)

Once you’ve installed the S-Remote Control app from the Play Store, start it and do the following:

  1. Tap the round green icon to access the configuration menu
  2. Tap Advanced
  3. Tap Layout and choose the option that you need (probably “6 buttons“)
  4. Tap IP and enter the Raspberry Pi’s IP address that you noted above
  5. Tap Port and enter 1234
  6. Tap back, then tap Keys
  7. For each key you want to define:
    1. Tap Name and enter the value that should be displayed on the button
    2. Tap Data and enter the command to be sent, e.g. “play” or “noise2
    3. Check the TCP option
    4. Tap back to store the settings

For example, here is the simple setup that I have on my phone:

s-remote-control

This app unfortunately doesn’t let you have more than six buttons at the same time, which is somewhat limiting, but it does at least allow something like the above configuration. If I do end up building a custom app to provide more specifically appropriate control of this system, I’ll come back and update this post.

Configuring the Remote Control App (iOS / cmd)

The process here is very similar to S-Remote, though slightly different since cmd allows you to specify multiple targets (a nice feature, though we’ll only need one here). Once you’ve installed the cmd app from the App Store, start it and do the following:

  1. Tap the Settings icon at the bottom right
  2. Tap Manage Destinations
  3. Tap the arrow icon in the top right to add a new destination
  4. Enter a friendly name, the noted IP address, port 1234, and a timeout of 3000 ms
  5. Tap Save to store the new destination
  6. Tap Settings to go back to the main settings area
  7. If desired, enter a suitable screen title (I used “BabySound”)
  8. Tap Manage Control Buttons
  9. For each button you want to define:
    1. Tap the arrow icon in the top right
    2. Tap Add New Command to create a new button
    3. Enter a name to be displayed on the button
    4. Tap ASCII and enter the command to be sent, e.g. “play” or “noise2
    5. Tap Assign Destination… and select the destination that you defined earlier
    6. Choose a custom button color, if desired
    7. Tap Save to store the new button
  10. Tap Settings to go back to the main settings area
  11. Tap Remote in the bottom icon bar to return to the standard button view

Here is a screenshot of part of the setup that I have on my iPhone:

cmd-screenshot

Again, this isn’t an absolutely perfect app for this specific use case, but it is very good and certainly gets the job done.

Conclusion

That’s it, everyone! With only a Raspberry Pi, speakers, and existing smartphone apps, you can too can have a cheap, capable remote-controlled white noise machine. Or, you can go nuts and customize it even further–put it on a schedule with cron, for instance. I’d love to hear about your own implementations.

You may also like

5 comments

Mayo February 16, 2017 - 12:08 pm

you’re the man – thanks SO MUCH for this!! exactly what i needed!!

to make alsa work aka make raspberrypi sound-capable you need to execute “amixer cset numid=3 1” which i put in /etc/rc.local so that the command runs at boot. i spent one night and one morning looking for a “real” fix but no luck.

since there is a limited amount of buttons for what i wanted i made the “Noise” button into a toggle so that press once means “play,” and press same button again means “stop:”
while 1:
playstatus = os.system(‘mpc status|grep play’)

this system call AS A PRINT STATEMENT returns 0 when mpc file is playing and 256 when mpc is stopped
and then i use the variable playstatus which produces 0 when mdc is playing. so if i get a 0 that means button press becomes a stop call:

elif data == ‘play’:
#If it’s already playing, stop it instead (or else if it’s already stopped then make it play)
if playstatus==0:
subprocess.call([“mpc”, “-p”, mpdport, “-q”, “stop”])
else:
subprocess.call([“mpc”, “-p”, mpdport, “-q”, “play”])

I’m sure that there is far more elegant logic but I just needed something quick that worked.

Thanks again man

Reply
Tony Ellis October 18, 2017 - 9:38 am

Thank you for an extremely useful tutorial. I hadn’t heard of mpc and my Python skills are non-existent, so this is something I never could have put together myself. Now I know a little bit about both, and I have a working brown noise player too. Thanks!

Reply
Mario March 15, 2018 - 10:30 pm

Very very useful tutorial. Expanding this to act like my “White Noise” app on my iOS app a bit. I also got one of those pricey aluminum cases with buttons on it so i can add volume, play/pause, and next functionality on the actual pi when my phone is somewhere else – in progress. Thanks again for this great tutorial.

Reply
Jason November 29, 2018 - 5:42 pm

Do you have a link to the case you bought that does this?

Reply
Bav November 24, 2018 - 8:23 pm

Thanks very much, Jeff – although I had trouble with the second channel. would run into an error that the mixer_type was a duplicate, yet I couldn’t see anywhere in the code that it was. Running raspbian on a Pi 3. Would love to have a second channel pushing lullabies over the top of the wave sounds!

Reply

Leave a Comment