SWMixer 0.1
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.  It can mix several sounds together during
playback.  The volume and position of each sound can be finely
controlled.  Samples can also be looped any number of times.  Looping
is accurate down to a single sample, so well designed loops will play
seamlessly without clicks or pops.  In addition, the mixer supports
audio input during playback (if supported in pyaudio with your sound
card).


REQUIREMENTS

PyAudio 0.2.0 (or more recent)
http://people.csail.mit.edu/hubert/pyaudio/

NumPy 1.0 (or more recent)
http://numpy.scipy.org/


INSTALLATION

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


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.


EXAMPLES

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)
--


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()
--


RECORDING

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 raw format, as returned by
PyAudio.  This will usually be signed 16-bit mono format.

To playback recorded sound from the microphone, concatenate the
strings 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

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 += 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

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.

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.

Cannot deal with 24-bit WAV files, but CAN handle 32-bit ones
(limitation of NumPy).

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.
