Python Exemplary - RPi Tutorial
deutsch     english    

SOUND CARDS, SOUND SENSORS

The source code of all examples can be downloaded from here.

 

Raspberry Pi Sound System

 

The Raspberry Pi is equipped with an internal sound card that outputs sound to an audio jack. Sound in different audio formats (most commonly WAV and MP3) can be played with standard sound players. From a terminal (console) you type the command aplay <mysong.wav> to play the file mysong.wav with the ALSA (Advanced Linux Sound Architecture) player via the default sound card that outputs to the audio jack. The quality is not overwhelming, so you better purchase a Raspberry Pi compatible USB sound adapter. You find many information how to change the default sound output to the USB card, but most of them are obsolete due to changes in the Linux distribution. For the current NOOBS distribution you simply edit the file .asoundrc that you find in the /home/pi folder. Change the entry card 0 to card 1.

Built-in Sound Card (default)

pcm.!default {
type hw
card 0
}

ctl.!default {
type hw
card 0
}

  External USB Sound Adapter

pcm.!default {
type hw
card 1
}

ctl.!default {
type hw
card 1
}

We recommend to use the SoX (Sound eXchange) software that is considered to be "The Swiss Army knife of sound processing programs". SoX is preinstalled on our SD card distribution. To install it on pure NOOBS system, connect the Raspberry Pi to the internet and type

sudo apt-get install sox
sudo apt-get install libsox-fmt-mp3

Now you play a song from a terminal window with play <mysong.mp3>. Many audio formats of your sound file are supported (type sox to display them). (Consult the SoX website for more information).

With SoX you can change the sound card on-the-fly by setting the environment variable AUDIODEV to hw:0 or hw:1. From the command line you enter

AUDIODEV=hw:1 play <mysong.mp3>

with hw:0 or hw:1 to output the song on the USB adapter of your choice without modifying the .asoundrc file. If you are uncertain about the available sound devices type aplay -l. To check the properties of the default sound card, type amixer.

 

 

Accessing The Sound System From Python

 

For the Alsa sound system the Python module pygame may be used. Unfortunately it only supports output to the built-in audio card. To use the USB sound adapter, we wrote a small Python class wrapper that spawns Linux shell commands for the SoX player. This allows you to select the sound card programmatically when constructing the player instance. You find the Python documentation here.

 

 

Experiment 1: Play, Pause, Resume A Song

 

Copy the song you like to play into a subdirectory of the /home/pi folder (e.g. salza1.wav in /home/pi/sound). Connect the audio jack to the input of any audio amplifier/music box/radio. The program plays the sound with half the default volume for 10 s, then pauses a while, resumes and finally stops. Download the module soundplayer.py from here and copy it into the same directory where your program resides. Have a look on the code to see how the library works.

Program:[►]

# Sound1a.py

import time
from soundplayer import SoundPlayer

# Use device with ID 1  (mostly USB audio adapter)
p = SoundPlayer("/home/pi/sound/salza1.wav", 1)        
print "play for 10 s with volume 0.5"
p.play(0.5) # non-blocking, volume = 0.5
print "isPlaying:", p.isPlaying()
time.sleep(10)
print "pause for 5 s"
p.pause()
print "isPlaying:", p.isPlaying()
time.sleep(5)
print "resume for 10 s"
p.resume()
time.sleep(10)
print "stop"
p.stop()
print "isPlaying:", p.isPlaying()
print "done"
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
Since the music is played by starting a new Linux process, it continues to play even when the Python program terminates. To stop the Linux process, you should invoke the stop() function at the end of your program. As you see, isPlaying() returns True, as long as the process is running, so it remains True, when the music is paused.

 

 

Experiment 2: Raspberry Pi As A Four Button Music Player

 

Now you are accustomed to the basic player code, you want to use the Raspberry Pi as a multimedia desk to select and play the collection of your favorite songs (playlist). In this example we use 4 buttons, a Run, Pause, Stop and Select button and implement the following specifications:

  • At program start, the first song is automatically played
  • The songs are played one after the other with a restart of the playlist
  • The Run button starts or resumes the play
  • The Select button is only active when the play is stopped. It starts the selected song.

It is highly recommended to use a state machine algorithm. Here the states are called

  • STOPPED: play process terminated
  • PAUSED: play process paused (playing still underway)
  • PLAYING: play process executing

Program:[►]

# Sound1b.py

from soundplayer import SoundPlayer
import RPi.GPIO as GPIO
import time

'''
states:
STOPPED : play process terminated
PAUSED: play process stopped (playing still underway)
PLAYING: play process executing
'''

# Button pins, adapt to your configuration
P_PLAY = 24 
P_PAUSE = 16
P_STOP = 22
P_SELECT = 12

def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(P_STOP, GPIO.IN, GPIO.PUD_UP)
    GPIO.setup(P_PAUSE, GPIO.IN, GPIO.PUD_UP)
    GPIO.setup(P_PLAY, GPIO.IN, GPIO.PUD_UP)
    GPIO.setup(P_SELECT, GPIO.IN, GPIO.PUD_UP)
 
setup()
nbSongs = 4
songID = 0
p = SoundPlayer("/home/pi/songs/song" + str(songID) + ".mp3", 1)        
p.play()
print "playing, song ID", songID
state = "PLAYING"

while True:
    if GPIO.input(P_PAUSE) == GPIO.LOW and state == "PLAYING":
        state = "PAUSED"
        print "playing->paused"
        p.pause()
    elif GPIO.input(P_PLAY) == GPIO.LOW and state == "STOPPED":
        state = "PLAYING"
        print "stopped->playing, song ID", songID
        p.play()
    elif GPIO.input(P_PLAY) == GPIO.LOW and state == "PAUSED":
        state = "PLAYING"
        print "paused->playing"
        p.resume()
    elif GPIO.input(P_STOP) == GPIO.LOW and (state == "PAUSED" or 
           state == "PLAYING"):
        state = "STOPPED"
        print "paused/playing->stopped"
        p.stop()
    elif (GPIO.input(P_SELECT) == GPIO.LOW and state == "STOPPED") \
          or (state == "PLAYING" and not p.isPlaying()):
        songID += 1
        if songID == nbSongs:
            songID = 0
        p = SoundPlayer("/home/pi/songs/song" + str(songID) + ".mp3", 1)
        print "stopped->playing, song ID", songID
        p.play()
        state = "PLAYING"
    time.sleep(0.1) # Do not waste processor time   
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
As you see, button hit events have two impacts: They change the state and perform a player action. Since the state is changed, contact bouncing (multiple press-release events with the same button) causes no problem.

 

 

Experiment 3: Sine Tone Generator

 

For simple robotics applications the internal sound card quality may be good enough. The soundplayer module contains a playTone() function to play a sine tone with given frequency and duration. In the following example you use the blocking version to emit one tone after the other. Then you play all three tones together using the list notation of the frequencies parameter.

Program:[►]

# Sound2a.py

from soundplayer import SoundPlayer
import time

# Sine tone during 0.1 s, blocking, device 0
dev = 0
SoundPlayer.playTone(900, 0.1, True, dev) # 900 Hz
SoundPlayer.playTone(800, 0.1, True, dev) # 600 Hz
SoundPlayer.playTone(600, 0.1, True, dev) # 600 Hz
time.sleep(1)
SoundPlayer.playTone([900, 800, 600], 5, True, dev) # 3 tones together
print "done" 
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Next we use the non-blocking version of playSound(), so our program is able to blink a LED during the emission of the tone.

Program:[►]

# Sound2b.py

import RPi.GPIO as GPIO
from soundplayer import SoundPlayer
import time

P_LED = 18 # adapt to your wiring

def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(P_LED, GPIO.OUT)
    
setup()
# Sine of 1000 Hz during 5 s, non-blocking, device 0
SoundPlayer.playTone(1000, 5, False, 0)
while SoundPlayer.isPlaying():
    GPIO.output(P_LED, GPIO.HIGH)
    print "on"
    time.sleep(0.5)
    GPIO.output(P_LED, GPIO.LOW)
    print "off"
    time.sleep(0.5)

print "done" 
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

 

Experiment 4: Using A Sound Module To Detect A Sound Incident

 

Sometimes it is amusing to perform some action by clapping, crying or whistling. To do this, a microphone has to been used that captures the sound. Normally it is not necessary to digitize the signal by an ADC and analyze it by the Raspberry Pi, which can be difficult because of the rapid change in the signal response. It is sufficient to create a low/high voltage change when the signal is greater than a certain level (or threshold). This can be done in a cheap external sound module (like the KY-038 purchased on Ebay or as part of an Arduino sensor kit).

 

Caution: All GPIO input voltages must be in the range 0..3.3V. Power the module with 3.3V or use a voltage divider with two resistors (or a voltage level converter).

The KY-038 is a low-sensitive device and can only detect very loud sound incidents. Adjust the potentiometer so that the status LED just turns off. When you generate a sound near the microphone (e.g. by tapping on the microphone), the status LED should flash for a short time.

Aim:
Turn on/off a LED by a sound incident. Each time an incident happens, the LED changes its state.

Program:[►]

# Sound3a.py
# Sound Sensor Module KY-038
# Using GPIO events

import RPi.GPIO as GPIO
import time

P_SOUND = 22 # adapt to your wiring
P_LED = 24 # adapt to your wiring

def setup():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(P_LED, GPIO.OUT)
    GPIO.output(P_LED, GPIO.LOW)
    GPIO.setup(P_SOUND, GPIO.IN)
    GPIO.add_event_detect(P_SOUND, GPIO.BOTH, onButtonEvent)

def onButtonEvent(channel):
    global isOn
    if GPIO.input(P_SOUND) == GPIO.HIGH:
        isOn = not isOn
        if isOn:
            GPIO.output(P_LED, GPIO.HIGH)
        else:
            GPIO.output(P_LED, GPIO.LOW)
        # Have to wait a while
        # because event is triggered several times (like button bouncing)    
        time.sleep(0.5) 
       
setup()
isOn = False
while True:
    time.sleep(0.1)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Remarks:
The GPIO is used in event detection mode with the callback function onSoundEvent that is triggered whenever the GPIO input changes its state. Because there are many high/low transitions in a single sound incident, the event registration is deactivated by putting the callback process to sleep for 0.5 s.