Advanced Realtime Software Mixer
Copyright 2008, Nathan Whitehead Released under the LGPL
This module implements a realtime sound mixer suitable for use in games or other audio applications. It supports loading sounds in uncompressed WAV format and also MP3 format. It can mix several sounds together during playback. The volume and position of each sound can be finely controlled. Sounds are automatically resampled and stereo converted for correct playback. Samples can also be looped any number of times. Longer sounds can be streamed from a file to save memory. In addition, the mixer supports audio input during playback (if supported in pyaudio with your sound card).
PyAudio 0.2.0 (or more recent) http://people.csail.mit.edu/hubert/pyaudio/
NumPy 1.0 (or more recent) http://numpy.scipy.org/
Optional for MP3 support:
MPEG Audio Decoder (MAD) http://www.underbit.com/products/mad/
PyMAD bindings for MAD http://spacepants.org/src/pymad/
SWMixer is packaged as Python source using distutils. To install, run the following command as root:
python setup.py install
For more information and options about using distutils, read: http://docs.python.org/inst/inst.html
This README file along with the pydoc documentation in the doc/ directory are the documentation for SWMixer.
HOW CAN IT POSSIBLY WORK IN PYTHON?
Realtime mixing of sample data is done entirely in python using the high performance of array operations in NumPy. Converting between sound formats (e.g. mono->stereo) is done using various NumPy operations. Resampling is done using the linear interpolation function of NumPy. Simultaneous playback and recording is possibly using PyAudio.
See the pydoc documentation for details on all the functions and for all the options and default values.
A very short example of using swmixer to play a sound.
import swmixer import time swmixer.init(samplerate=44100, chunksize=1024, stereo=False) swmixer.start() snd = swmixer.Sound("test1.wav") snd.play() time.sleep(2.0) #don't quit before we hear the sound!
Here is an example showing some more options and how to control the sound after it starts playing.
import swmixer import time swmixer.init(samplerate=44100, chunksize=1024, stereo=False) swmixer.start() snd = swmixer.Sound("test1.wav") chan = snd.play(fadein=22050) #fade in sound over 0.5 seconds time.sleep(1.0) # rewind 20000 samples now just for kicks chan.set_position(chan.get_position() - 20000) time.sleep(1.0) chan.stop() time.sleep(1.0)
Normally sounds are loaded entirely into memory before playback begins. For long sounds this might result in too much memory being wasted. The solution is to create a StreamingSound object.
The interface for StreamingSounds is almost identical to regular Sounds, but there are some limitations. Most importantly, the streaming sound must already be in the correct format for playing. The samplerate of the streaming sound must match the output samplerate. If the output is stereo then the streaming sound must be stereo. If the streaming sound is an MP3 then the output must be stereo.
Here's a very simple example showing a streaming sound along with a regular sound.
import swmixer import time swmixer.init(samplerate=44100, chunksize=1024, stereo=True) swmixer.start() snd1 = swmixer.StreamingSound("Beat_77.mp3") snd2 = swmixer.Sound("test2.wav") snd1.play(volume=0.2) snd2.play() time.sleep(10.0) #don't quit before we hear the sound!
StreamingSounds have most of the functionality of regular Sounds, but some operations are not allowed. For example, WAV streams do not allow arbitrary jumping to a position; MP3 streams do. MP3 streams allow checking the total length with get_length(), while WAV streams do not. (However WAV Sounds do have get_length()).
You can have any number of StreamingSounds and Sounds playing at once.
EXPLICIT TICK INTERFACE
Instead of calling swmixer.start() you may also call swmixer.tick() every frame in your main loop. This gives you greater control over synchronizing the video framerate with audio events for music applications and games.
The samplerate and chunksize will limit your framerate. If you set the samplerate to 44100 samples per second, and each chunk is 1024 samples, then each call to swmixer.tick() will process 1024 samples corresponding to 0.0232 seconds of audio. This will lock your framerate at 1/.0232=43.1 frames per second. If you call swmixer.tick() faster than this, that's OK, it will just block until more audio can be send to the soundcard. If you call swmixer.tick() slower than 43.1 times a second, there will be audio glitches.
Note that by choosing your samplerate and chunksize wisely you can get any framerate you want. Larger chunksizes correspond to slower framerates. You may also call swmixer.tick() every other frame, or every third frame. This way your video framerate will be a fixed multiple of your audio framerate.
Here is a silly example showing a moving green square with a background sound. The square should move at 43 pixels / second.
import sys import swmixer import pygame swmixer.init(samplerate=44100, chunksize=1024, stereo=False) snd = swmixer.Sound("test1.wav") pygame.display.init() screen = pygame.display.set_mode((1024, 768)) snd.play() x = 0 while True: swmixer.tick() x += 1 screen.fill((0, 0, 0)) pygame.draw.rect(screen, (0, 255, 0), (x, 100, 50, 50)) pygame.display.flip() for evt in pygame.event.get(): if evt.type == pygame.QUIT: sys.exit()
You can also call swmixer.set_buffersize(size) at any time to change the buffer size and thus change the framerate. Switching the buffer size to 512 will double the framerate. SWMixer does not impose any requirements on the buffer size, it can be anything. As the buffer size gets smaller you will have to call swmixer.tick() very quickly to avoid audio glitches.
To enable sound recording using the microphone, either pass microphone=True to swmixer.init() or call swmixer.microphone_on() while the mixer is running. To make any sense out of the microphone data it is recommended that you use the explicit swmixer.tick() interface rather than calling swmixer.start().
To get the data from the microphone, call swmixer.get_microphone() after every swmixer.tick(). The data is in an array. The format will match the output format, i.e. signed 16-bit mono or stereo.
To playback recorded sound from the microphone, concatenate the arrays from several frames and then create a new Sound using: snd=swmixer.Sound(data=s)
Here is an annoying example program that records and plays back data from the microphone while playing a test sound in the background.
import sys import swmixer import numpy swmixer.init(samplerate=44100, chunksize=1024, stereo=False, microphone=True) snd = swmixer.Sound("test1.wav") snd.play(loops=-1) micdata =  frame = 0 while True: swmixer.tick() frame += 1 if frame < 50: micdata = numpy.append(micdata, swmixer.get_microphone()) if frame == 50: micsnd = swmixer.Sound(data=micdata) micsnd.play() micdata =  frame = 0
SWMIXER WITH PYGAME
You can use swmixer as an almost drop-in replacement for pygame.mixer. You may want to do this for the following reasons:
- alternate audio driver, avoid bugs in SDL
- more control over precise sample positioning
- automatic resampling of loaded WAV files to correct playback speed
- support for simultaneous audio input
- allow multiple streams of audio at once
To use swmixer with pygame, it is best to initialize swmixer before initializing pygame. You cannot start both the pygame mixer and swmixer at the same time. Alternatively you can initialize pygame but explicitly say to not start the mixer, then initialize swmixer.
There are some differences between swmixer and pygame.mixer. The most important is that after initializing swmixer with swmixer.init(...), you must call swmixer.start() to create the background mixing thread. Once the background thread is running, the commands for sound playback are similar to pygame.mixer. Many of the commands have more options or behave slightly differently, so look at the documentation for each command.
Conceptually, swmixer does not have a fixed number of channels that must be allocated. Each time a Sound is played a new Channel object is returned. This Channel object is used to control playback of that particular instance of the Sound.
BUGS AND LIMITATIONS
Always outputs in 16-bit mode.
Resampling can be slow for longer files.
Does not detect samplerates that differ from requested samplerates. I.e. if you request a rate your card cannot handle, you might get incorrect playback rates.
Currently there is no way to limit the number of sounds mixed at once to prevent excessive CPU usage.
No way to pan mono sounds to different positions in stereo output.
StreamingSounds may not be sample accurate for looping and setting position.
Threading behavior may not be optimal on some platforms.