Simpleaudio Package¶
The simplaudio package provides cross-platform, dependency-free audio playback capability for Python 3 on macOS, Windows, and Linux.
MIT Licensed.
Quick Function Check¶
import simpleaudio.functionchecks as fc
fc.LeftRightCheck.run()
More on simpleaudio.functionchecks.
Simple Example¶
import simpleaudio as sa
wave_obj = sa.WaveObject.from_wave_file("path/to/file.wav")
play_obj = wave_obj.play()
play_obj.wait_done()
Contents¶
Installation¶
Make sure you have pip installed. For Linux, this is
usually done with the distro package manager (example:
sudo apt-get install python3-pip
). For Windows and macOS, have a look at
the pip documentation.
Note
The actual pip
command may vary between platforms and Python versions.
Substitute the correct one for usages in these examples. Some common
variants for Python 3.x are: pip3
, pip3.x
, and pip-3.x
.
Also, in some cases you may need to manually add pip’s location to the ‘path’ environment variable.
Upgrade pip and setuptools:
pip install --upgrade pip setuptools
Install with:
pip install simpleaudio
Linux Dependencies¶
The Python 3 and ALSA development packages are required for pip to build the extension. For Debian variants (including Raspbian), this will usually get the job done:
sudo apt-get install -y python3-dev libasound2-dev
Capabilities & Support¶
Compatibility¶
Platforms¶
- Windows: 7 or newer
- macOS: 10.6 (Snow Leopard) or newer
- Linux: should support any distro with a working ALSA implementation
Python¶
Python 3.7 and up is officially supported and wheels (with pre-built binaries) are available for Windows and macOS. Earlier versions of Python may work as well when building the extension from source (as on Linux).
Asynchronous Interface¶
The module implements an asynchronous interface, meaning that program execution continues immediately after audio playback is started and a background thread takes care of the rest. This makes it easy to incorporate audio playback into GUI-driven applications that need to remain responsive. This also means that real-time audio applications (such as a synthesizer) are not possible since the entire audio clip to be played must be ready before playback.
Audio Formats¶
Simpleaudio supports standard integer 8-, 16-, and 24-bit integer formats, as well as 32-bit floating point. 8-bit is unsigned. 16-bit and 24-bit are signed little-endian. 24-bit is three bytes per sample packed - i.e. each sample is three bytes followed immediately by the next three-byte sample, without any padding byte to align to a 4-byte word boundary). 32-bit is little-endian floating point.
Mono (1-channel) and stereo (2-channel) audio is supported.
The following sample rates are allowed (though not necessarily guaranteed to be supported on your platform/hardware): 8, 11.025, 16, 22.05, 32, 44.1, 48, 88.2, 96, and 192 kHz.
Comparison to PyAudio¶
PyAudio is another cross-platform audio library for Python. While it has more capability than simpleaudio, such as recording and continuous audio streaming, it depends on having PortAudio which makes for a more complicated installation. Simpleaudio is intended to be one-stop shopping for a cross-platform audio interface intended for sound clip playback.
Short Tutorial¶
Playing audio directly¶
The simplest way to play audio is with play_buffer()
. The
audio_data
parameter must be an object which supports the buffer interface.
(bytes
objects, Python arrays, and Numpy arrays all qualify.):
play_obj = sa.play_buffer(audio_data, 2, 2, 44100)
The play_obj object is an instance of PlayObject
which could be viewed as a ‘handle’ to the audio playback initiated by the
play_buffer()
call. This can be used to stop playback
of the audio clip:
play_obj.stop()
It can used to check whether a sound clip is still playing:
if play_obj.is_playing():
print("still playing")
It can also be used to wait for the audio playback to finish. This is espcially useful when a script or program would otherwise exit before playback is done (stopping the playback thread and consequently the audio):
play_obj.wait_done()
# script exit
WaveObject’s¶
In order to facilitate cleaner code, the WaveObject
class is provided which stores a reference to the object containing the
audio as well as a copy of the playback parameters. These can be instantiated
like so:
wave_obj = sa.WaveObject(audio_data, 2, 2, 44100)
Playback is started with play()
and a
PlayObject
is returned as
with play_buffer()
:
play_obj = wave_obj.play()
A class method exists in order to conveniently create WaveObject instances directly from WAV files on disk:
wave_obj = sa.WaveObject.from_wave_file(path_to_file)
Similarly, instances can be created from Wave_read objects returned from
wave.open()
from the Python standard library:
wave_read = wave.open(path_to_file, 'rb')
wave_obj = sa.WaveObject.from_wave_read(wave_read)
Using Numpy¶
Numpy arrays can be used to store audio but there are a few crucial requirements. If they are to store stereo audio, the array must have two columns since each column contains one channel of audio data. They must also have a signed 16-bit integer dtype and the sample amplitude values must consequently fall in the range of -32768 to 32767. Here is an example of a simple way to ‘normalize’ the audio (making it cover the whole amplitude rage but not exceeding it):
audio_array *= 32767 / max(abs(audio_array))
And here is an example of converting it to the proper data type (note that this should always be done after normalization or other amplitude changes):
audio_array = audio_array.astype(np.int16)
Here is a full example that plays a few sinewave notes in succession:
import numpy as np
import simpleaudio as sa
# calculate note frequencies
A_freq = 440
Csh_freq = A_freq * 2 ** (4 / 12)
E_freq = A_freq * 2 ** (7 / 12)
# get timesteps for each sample, T is note duration in seconds
sample_rate = 44100
T = 0.25
t = np.linspace(0, T, T * sample_rate, False)
# generate sine wave notes
A_note = np.sin(A_freq * t * 2 * np.pi)
Csh_note = np.sin(Csh_freq * t * 2 * np.pi)
E_note = np.sin(E_freq * t * 2 * np.pi)
# concatenate notes
audio = np.hstack((A_note, Csh_note, E_note))
# normalize to 16-bit range
audio *= 32767 / np.max(np.abs(audio))
# convert to 16-bit data
audio = audio.astype(np.int16)
# start playback
play_obj = sa.play_buffer(audio, 1, 2, sample_rate)
# wait for playback to finish before exiting
play_obj.wait_done()
In order to play stereo audio, the Numpy array should have 2 columns. For example, one second of (silent) stereo audio could be produced with:
silence = np.zeros((44100, 2))
We can then use addition to layer additional audio onto it - in other words, ‘mixing’ it together. If a signal/audio clip is added to both channels (array columns) equally, then the audio will be perfectly centered and sound just as if it were played in mono. If the proportions vary between the two channels, then the sound will be stronger in one speaker than the other, ‘panning’ it to one side or the other. The full example below demonstrates this:
import numpy as np
import simpleaudio as sa
# calculate note frequencies
A_freq = 440
Csh_freq = A_freq * 2 ** (4 / 12)
E_freq = A_freq * 2 ** (7 / 12)
# get timesteps for each sample, T is note duration in seconds
sample_rate = 44100
T = 0.5
t = np.linspace(0, T, T * sample_rate, False)
# generate sine wave notes
A_note = np.sin(A_freq * t * 2 * np.pi)
Csh_note = np.sin(Csh_freq * t * 2 * np.pi)
E_note = np.sin(E_freq * t * 2 * np.pi)
# mix audio together
audio = np.zeros((44100, 2))
n = len(t)
offset = 0
audio[0 + offset: n + offset, 0] += A_note
audio[0 + offset: n + offset, 1] += 0.125 * A_note
offset = 5500
audio[0 + offset: n + offset, 0] += 0.5 * Csh_note
audio[0 + offset: n + offset, 1] += 0.5 * Csh_note
offset = 11000
audio[0 + offset: n + offset, 0] += 0.125 * E_note
audio[0 + offset: n + offset, 1] += E_note
# normalize to 16-bit range
audio *= 32767 / np.max(np.abs(audio))
# convert to 16-bit data
audio = audio.astype(np.int16)
# start playback
play_obj = sa.play_buffer(audio, 2, 2, sample_rate)
# wait for playback to finish before exiting
play_obj.wait_done()
24-bit audio can be also be created using Numpy but since Numpy doesn’t have a 24-bit integer dtype, a conversion
step is needed. Note also that the max sample value is different for 24-bit audio. A simple (if inefficient) conversion
algorithm is demonstrated below, converting an array of 32-bit integers into a bytes
object which contains
the packed 24-bit audio to be played:
import numpy as np
import simpleaudio as sa
# calculate note frequencies
A_freq = 440
Csh_freq = A_freq * 2 ** (4 / 12)
E_freq = A_freq * 2 ** (7 / 12)
# get timesteps for each sample, T is note duration in seconds
sample_rate = 44100
T = 0.5
t = np.linspace(0, T, T * sample_rate, False)
# generate sine wave tone
tone = np.sin(440 * t * 2 * np.pi)
# normalize to 24-bit range
tone *= 8388607 / np.max(np.abs(tone))
# convert to 32-bit data
tone = tone.astype(np.int32)
# convert from 32-bit to 24-bit by building a new byte buffer, skipping every fourth bit
# note: this also works for 2-channel audio
i = 0
byte_array = []
for b in tone.tobytes():
if i % 4 != 3:
byte_array.append(b)
i += 1
audio = bytearray(byte_array)
# start playback
play_obj = sa.play_buffer(audio, 1, 3, sample_rate)
# wait for playback to finish before exiting
play_obj.wait_done()
simpleaudio
¶
API¶
-
class
simpleaudio.
WaveObject
(audio_data, num_channels=2, bytes_per_sample=2, sample_rate=44100)¶ Instances of
WaveObject
represent pieces of audio ready for playback. It encapsulates the audio data buffer, playback parameters (such as sample rate), and provides a method to initiate playback.Parameters: - audio_data – object with audio data (must support the buffer interface)
- num_channels (int) – the number of audio channels
- bytes_per_sample (int) – the number of bytes per single-channel sample
- sample_rate (int) – the sample rate in Hz
-
WaveObject.
play
()¶ Starts playback of the audio
Return type: rtype: a PlayObject
instance for the playback job
-
classmethod
WaveObject.
from_wave_file
(wave_file)¶ Creates a WaveObject from a wave file on disk.
Parameters: wave_file (str) – a path to a wave file
-
classmethod
WaveObject.
from_wave_read
(wave_read)¶ Creates a WaveObject from a
Wave_read
object as returned bywave.open()
.Parameters: wave_read – a Wave_read
object
-
class
simpleaudio.
PlayObject
¶ Instances of
PlayObject
are returned byWaveObject.play()
andplay_buffer()
and are essentially handles to the audio playback jobs initiated and allow basic actions to be taken on the job (such as stopping playback).
-
PlayObject.
stop
()¶ Stops the playback job.
-
PlayObject.
is_playing
()¶ Returns true if the playback job is still running or false if it has finished.
Return type: bool
-
PlayObject.
wait_done
()¶ Waits for the playback job to finish before returning.
-
simpleaudio.
stop_all
()¶ Stop all currently playing audio.
-
simpleaudio.
play_buffer
(audio_data, num_channels, bytes_per_sample, sample_rate)¶ Start playback of audio data from an object supporting the buffer interface and with the given playback parameters.
Parameters: - audio_data – object with audio data (must support the buffer interface)
- num_channels (int) – the number of audio channels
- bytes_per_sample (int) – the number of bytes per single-channel sample
- sample_rate (int) – the sample rate in Hz
Return type: a
PlayObject
instance for the playback job
Examples¶
Playing a file:
import simpleaudio as sa
wave_obj = sa.WaveObject.from_wave_file(path_to_file)
play_obj = wave_obj.play()
play_obj.wait_done()
Playing a Wave_read object:
import simpleaudio as sa
import wave
wave_read = wave.open(path_to_file, 'rb')
wave_obj = sa.WaveObject.from_wave_read(wave_read)
play_obj = wave_obj.play()
play_obj.wait_done()
Playing an object supporting the buffer interface:
import simpleaudio as sa
import wave
wave_read = wave.open(path_to_file, 'rb')
audio_data = wave_read.readframes(wave_read.getnframes())
num_channels = wave_read.getnchannels()
bytes_per_sample = wave_read.getsampwidth()
sample_rate = wave_read.getframerate()
wave_obj = sa.WaveObject(audio_data, num_channels, bytes_per_sample, sample_rate)
play_obj = wave_obj.play()
play_obj.wait_done()
Play an object directly (without creating a WaveObject
):
import simpleaudio as sa
import wave
wave_read = wave.open(path_to_file, 'rb')
audio_data = wave_read.readframes(wave_read.getnframes())
num_channels = wave_read.getnchannels()
bytes_per_sample = wave_read.getsampwidth()
sample_rate = wave_read.getframerate()
play_obj = sa.play_buffer(audio_data, num_channels, bytes_per_sample, sample_rate)
play_obj.wait_done()
simpleaudio.functionchecks
¶
The functionchecks
module contains a number of classes
that all inherit from FunctionCheckBase
which provides
run()
class method common toall function checks.
These may be run individually with run()
or all test may be run with run_all()
. If the module is run from the
command line using Python’s -m
flag, it will automatically run all
function checks.
API¶
-
simpleaudio.functionchecks.
run_all
(countdown=3)¶ Runs all function checks in succession.
Parameters: countdown (int) – the number of seconds to pause before running each test
-
class
simpleaudio.functionchecks.
FunctionCheckBase
¶ A base class for all function checks.
-
FunctionCheckBase.
run
(countdown=0)¶ Runs a particular function check.
Parameters: countdown (int) – the number of seconds to pause before running the test
-
class
simpleaudio.functionchecks.
LeftRightCheck
¶ Checks stereo playback by first playing a note in the left channel only, then a different note in the right channel only.
-
class
simpleaudio.functionchecks.
OverlappingCheck
¶ Checks overlapped playback by playing three different notes spaced approximately a half-second apart but still overlapping.
-
class
simpleaudio.functionchecks.
StopCheck
¶ Checks stopping playback by playing three different notes simultaneously and stopping two after approximately a half-second, leaving only one note playing for two more seconds.
-
class
simpleaudio.functionchecks.
StopAllCheck
¶ Checks stopping playback of all audio by playing three different notes simultaneously and stopping all of them after approximately a half-second.
-
class
simpleaudio.functionchecks.
IsPlayingCheck
¶ Checks functionality of the is_playing() method by calling during playback (when it should return True) and calling it again after all playback has stopped (when it should return False). The output is printed.
-
class
simpleaudio.functionchecks.
WaitDoneCheck
¶ Checks functionality of the wait_done() method by using it to allow the three-note clip to play until finished (before attempting to stop playback).
Examples¶
Run all checks:
import simpleaudio.functionchecks as fc
fc.run_all()
Run a single check:
import simpleaudio.functionchecks as fc
fc.LeftRightCheck.run()
Release Notes¶
1.0.4
- Added 24-bit and 32-bit playback capability
1.0.3
- Added CI builds of pre-built binaries for Python 3.7 and 3.8, dropped others
1.0.2
- Fixed Linux and Windows thread resource leaks
- Dropped creation of pre-built binaries for Python 3.3
1.0.1
- Fixed OSX and Linux 8-bit playback
1.0.0
- Initial Release