From BlenderWiki

Jump to: navigation, search

Audaspace Python API

Library usage

The module aud is now available in my blender GSoC branch.

Comparison between PyGame and Audaspace

Here is a example PyGame interactive session to try out PyGame audio:

import pygame
pygame.mixer.init(frequency=44100, channels=2)
s = pygame.mixer.Sound('prodigy.ogg')
c = s.play()
c2 = s.play()
c.stop()
c2.stop()
pygame.mixer.music.load('prodigy.ogg')
pygame.mixer.music.play()
pygame.mixer.music.load('pink_panther.ogg')
pygame.mixer.music.play()
pygame.mixer.music.stop()
pygame.mixer.quit()

You probably notice the following differences:

  • PyGame needs a lot of time to load the sound into RAM.
  • PyGame can only stream one "music" at a time.

And here is, how the same works in Audaspace:

import aud
device = aud.device()
s = aud.Factory('music.ogg')
c = device.play(s)
buffered = aud.Factory.buffer(s)
c2 = device.play(buffered)
c.stop()
c2.stop()

What's better than in PyGame:

  • You can use blender's audio device.
  • Audaspace supports as many streaming sources as your computer can handle, there's no differentiation between music and sounds.
  • If you want to load a sound into RAM with Audaspace (for performance reason, recommended for short sounds that don't use to much space in RAM when they are decoded) you can use aud.Sound.buffer().

Audaspace feature demo

Audaspace is much more advanced than other audio libraries, you can even synthesise sounds with it and put filters over sounds. Check the following (quickly hacked, so not very pretty) code example to synthesise the tetris melody out of a notes string:

import aud
import math
 
def parseNotes(notes, bpm, basefreq, rate = 44100,
               notechars = "XXXCXDXEFXGXAXHcXdXefXgXaXhp"):
	pos = 0
	sound = None
	fadelength = 60/bpm/10
	halfchars = "#b"
	durationchars = "2345678"
 
	while pos < len(notes):
		char = notes[pos]
		mod = None
		dur = 1
		pos += 1
		while pos < len(notes) and notes[pos] not in notechars:
			if notes[pos] in halfchars:
				mod = notes[pos]
			elif notes[pos] in durationchars:
				dur = notes[pos]
			pos += 1
 
		freq = notechars.find(char)
		if mod == '#':
			freq += 1
		elif mod == 'b':
			freq -= 1
 
		freq = math.pow(2, freq/12)*basefreq
		length = float(dur)*60/bpm
 
		snd = aud.Factory.sine(freq, rate)
		if char == 'p':
			snd = snd.volume(0)
		else:
			snd = snd.square()
		snd = snd.limit(0, length)
		snd = snd.fadein(0, fadelength)
		snd = snd.fadeout(length - fadelength, fadelength)
 
		if sound:
			sound = sound.join(snd)
		else:
			sound = snd
	return sound
 
def tetris(bpm = 300, freq = 220, rate = 44100):
	notes = "e2Hcd2cH A2Ace2dc H3cd2e2 c2A2A4 pd2fa2gf e3ce2dc H2Hcd2e2 c2A2A2p2"
	s11 = parseNotes(notes, bpm, freq, rate)
 
	notes = "e4c4 d4H4 c4A4 G#4p4 e4c4 d4H4 A2c2a4 g#4p4"
	s12 = parseNotes(notes, bpm, freq, rate)
 
	notes = "EeEeEeEe AaAaAaAa AbabAbabAbabAbab AaAaAAHC DdDdDdDd CcCcCcCc HhHhHhHh AaAaA2p2"
	s21 = parseNotes(notes, bpm, freq, rate, notechars = "AXHCXDXEFXGXaXhcXdXefXgXp")
 
	notes = "aeaeaeae g#dg#dg#dg#d aeaeaeae g#dg#dg#2p2 aeaeaeae g#dg#dg#dg#d aeaeaeae g#dg#dg#2p2"
	s22 = parseNotes(notes, bpm, freq/2, rate)
 
	return s11.join(s12).join(s11).volume(0.5).mix(s21.join(s22).join(s21).volume(0.3))
 
def play(bpm = 300, freq = 220):
	dev = aud.device()
	return dev.play(tetris(bpm, freq, dev.rate))

You can watch a video on vimeo where I'm executing this script and play the sound here: http://vimeo.com/13205947

Sorry for the bad quality and that the sound is not in sync with the video, if you want to have the full quality, try it out yourself!

Audaspace filters

Audaspace now supports LTI IIR filters via the Python API, check out the following example:

import aud
d = aud.device()
s = aud.Factory("soundfile.ogg")
# this are filter coefficients for a 4th order butterworth lowpass
# with a cut-off frequency of 400 Hz for a 44.1 kHz signal
b = (6.1265e-07, 2.4506e-06, 3.6759e-06, 2.4506e-06, 6.1265e-07)
a = (1.00000,-3.85109, 5.56425,-3.57477, 0.86162)
f = s.filter(b, a)
h = d.play(f)

How the C++ Library works

The C++ Library of Audaspace has three mayor Classes/Interfaces:

  • AUD_IDevice: This is an output device/backend like OpenAL or SDL which. The class defines all necessary functions to play sounds like play(), stop(), pause(), resume(), setVolume(), etc.
  • AUD_IFactory: This class represents a sound that can be played back, this can be an audio file that's read via ffmpeg or libsoundfile or a sound that builds on another sound (let's call that an effect) like a lowpass.
  • AUD_IReader: This an actually played back sound, of a factory. This class does the real audio data processing while the factory is as the name implies only a factory for objects of this type. The reason behind is that you can play the same sound multiple times simultaneously at the same time.

So in the implementation of these interface classes there's always a pair of a factory and a reader that belong together.

How this is mapped to Python

At a first glance a python user of audaspace only wants to use the library and functionality it provides and doesn't want to write his own factory + reader pairs (as this audio stuff is really CPU consuming it's better to not implement such low level stuff in python anyway).

So basically there is only a need for a Factory and a Device class in Python. As the name Factory confuses here without the Reader, I call this class simply Sound.

Here's the class diagram of the Python classes that will be in the aud Python module with the C++ attributes marked as private:

Aud.png

The Device class

This class will basically provide the same functionality that it's corresponding C++ class does, including the functionality of the AUD_I3DDevice class.

The Handle class

In C++ an AUD_Handle is basically a void Pointer that points to some per device defined playback handle and is used for playback control like pause and stop.

The Sound class

This single class will hold a pointer to any AUD_IFactory class. The concept behind Audaspace is, that a Factory once created cannot be changed anymore, so there is no need for subclassing in Python here. What will be provided are functions to create the wanted Sounds/Factories.

As the filters build upon other Factories, the Factory objects aren't allowed to be deleted before the Factory that builds on the other Factory is deleted. For this to not be a problem every Sound in Python keeps a reference to the Sounds it uses so that those Sounds will not be deleted as long as this sound exists.

The immutability of created Sound/Factory objects moreover has the big advantage that cycles are prevented by the simple fact that you cannot assign a Sound to one lower in the hierarchy.