1.. module:: openal.audio 2 :synopsis: Advanced OpenAL audio module 3 4openal.audio - advanced sound support 5====================================== 6:mod:`openal.audio` is a set of advanced, pythonic classes for 3D positional 7audio support via the OpenAL standard. It utilises :mod:`openal`, but hides all 8the :mod:`ctypes` related, sequential programming workflow from you. It is 9designed to be non-invasive within a component-based application. 10 11At least three classes need to be used for playing back audio data. 12:class:`SoundSink` handles the audio device connection and controls the overall 13playback mechanisms. The :class:`SoundSource` represents an in-application 14object that emits sounds and a :class:`SoundData` contains the PCM audio data 15to be played. 16 17Device handling 18^^^^^^^^^^^^^^^ 19To actually play back sound or to stream sound to a third-party system (e.g. a 20sound server or file), an audio output device needs to be opened. It usually 21allows the software to access the audio hardware via the operating system, so 22that audio data can be recorded or played back. :: 23 24 >>> sink = SoundSink() # Open the default audio output device 25 >>> sink = SoundSink("oss") # Open the OSS audio output device 26 >>> sink = SoundSink("winmm") # Open the Windows MM audio output device 27 ... 28 29.. note:: 30 31 Depending on what to accomplish and what kind of quality for audio output to 32 have, you might want to use a specific audio output device to be passed as 33 argument to the :class:`SoundSink` constructor. 34 35It is possible to create multiple :class:`SoundSink` instances for the same 36device. OpenAL specifies an additional device-dependent execution context, so 37that multiple contexts (with e.g. different settings) can be used on one 38device. Likewise, multiple :class:`SoundSink` objects can use the same device, 39while each of them uses its own execution context. 40 41.. note:: 42 43 Several OpenAL functions perform context-specific operations. If you mix 44 function calls from :mod:`openal` with the :mod:`openal.audio` 45 module, you should ensure that the correct :class:`SoundSink` is activated 46 via :meth:`SoundSink.activate()`. 47 48Placing the listener 49-------------------- 50The OpenAL standard supports 3D positional audio, so that a source of sound can 51be placed anywhere relative to the listener (the user of the application or 52some in-application avatar). 53 54.. image:: images/openalaudio.png 55 56The image above shows a listener surrounded by three sources of sound. Two are 57in front of them, while one is behind the listener, moving from left to right. 58 59OpenAL only knows about a single listener at each time. Each :class:`SoundSink` 60can manage its own listener, which represents the user or in-application 61avatar. As such, it represents the 'pick-up' point of sounds. 62 63Placing and moving the listener (as well as sound sources in OpenAL) is done in 64a RHS coordinate system. That said, the horizontal extent of your monitor 65represents the x-axis, the vertical the y-axis and the visual line between your 66eyes and the monitor surface reprensents the z-axis. 67 68.. image:: images/coordinate_rhs.png 69 70It is crucial to understand how placing and moving sound sources and the 71listener will influence the audio experience. By default, the listener for each 72individual :class:`SoundSink` is placed at the center of the coordinate system, 73``(0, 0, 0)``. It does not move and looks along the z-axis "into" the monitor 74(most likely the same direction you are looking at right now). :: 75 76 >>> listener = SoundListener() 77 >>> listener.position = (0, 0, 0) 78 >>> listener.velocity = (0, 0, 0) 79 >>> listener.orientation = (0, 0, -1, 0, 1, 0) 80 ... 81 82.. image:: images/listener_default.png 83 84While the :attr:`SoundListener.position` and :attr:`SoundListener.velocity` are 85quite obvious in their doing, namely giving the listener a (initial) position 86and movement, :attr:`SoundListener.orientation` denotes the direction the 87listener "looks at". The orientation consists of two components, the general 88direction the listener is headed at and rotation. Both are expressed as 3-value 89tuples for the x-, y- and z-axis of the coordinate system. :: 90 91 >>> listener.orientation = (0, 0, -1, 0, 1, 0) 92 >>> # ^^^^^^^^ ^^^^^^^ 93 >>> # direction rotation 94 95Changing the first 3 values will influence the direction, the listener looks at. 96 :: 97 98 >>> listener.orientation = (1, 0, 1, 0, 1, 0) 99 100.. image:: images/listener_xz.png 101 102Changing the last 3 values will influence the rotation of the looking direction. 103 104.. image:: images/listener_xyz.png 105 106The orientation defines a orthogonal listening direction, so that any sounds the 107user (or avatar) hears, are processed correctly. If you imagine a car driving 108by on your right side, while you are looking straight ahead (parallel to the 109car's driving direction), you will hear the car on your right side (with your 110right ear receiving the most noise). If you look on the street, following the 111car with your eyes and head, the listening experience will differ (since both 112ears of you receive the noise in nearly the same way). 113 114.. note:: 115 116 Setting the orientation in OpenAL is somehat similar ot OpenGL's 117 ``gluLookAt`` function, which adjusts the view direction. You might want 118 to take a look at http://www.glprogramming.com/red/chapter03.html#name2 for 119 further details about that. 120 121Creating sound sources 122---------------------- 123A :class:`SoundSource` represents an object that can emit sounds. It can be any 124kind of object and allows you to play any sound, you put into it. In an 125application you can enable objects to emit sounds, by binding a 126:class:`SoundSource` to them.:: 127 128 >>> source = SoundSource() 129 130.. todo:: 131 132 more details 133 134Creating and playing sounds 135--------------------------- 136To create and play sounds you use :class:`SoundData` objects, which contain the 137raw PCM data to be played. To play the sound, the :class:`SoundData` needs to 138be queued on a :class:`SoundSource`, which provides all the necessary 139information about the volume, the position relative to the listener and so 140on. :: 141 142 >>> wavsound = load_wav_file("vroom.wav") 143 144There are some helper functions, which create :class:`SoundData` objects from 145audio files. If you have a raw PCM data buffer, you can create a 146:class:`SoundData` from it directly. :: 147 148 >>> rawsound = SoundData(pcmbuf, size_of_buf, channels, bitrate, frequency_in_hz) 149 150Queueing the loaded sound is done via the :meth:`SoundSource.queue()` method, 151which appends the sound to the source for processing and playback. :: 152 153 >>> wavsound = load_wav_file("vroom.wav") 154 >>> source.queue(wavsound) 155 156You just need to inform the :class:`SoundSink` about the :class:`SoundSource` 157afterwards, so that it knows that a new sound emitter is available. :: 158 159 >>> soundsink.play(source) 160 161When you add other sounds to play to the source, they will be picked up 162automatically for playback, as long as the :class:`SoundSource` is not paused 163or ran out of something to play. 164 165API 166^^^ 167 168.. class:: OpenALError([msg=None[, alcdevice=None]]) 169 170 An OpenAL specific exception class. If a new :class:`OpenALError` is created 171 and no *msg* is provided, the message will be set a mapped value of 172 :meth:`openal.al.alGetError()`. If an :class:`openal.alc.ALCdevice` is 173 provided as *alcdevice*, :meth:`openal.alc.alcGetError()` will be used 174 instead of :meth:`openal.al.alGetError()`. 175 176.. class:: SoundData(data=None, channels=None, bitrate=None, size=None, \ 177 frequency=None, dformat=None) 178 179 The :class:`SoundData` consists of a PCM audio data buffer, the audio 180 frequency and additional format information to allow easy buffering through 181 OpenAL. 182 183 .. attribute:: channels 184 185 The channel count for the sound data. 186 187 .. attribute:: bitrate 188 189 The bitrate of the sound data. 190 191 .. attribute:: size 192 193 The buffer size in bytes. 194 195 .. attribute:: frequency 196 197 The sound frequency in Hz. 198 199 .. attribute:: data 200 201 The buffered audio data. 202 203.. class:: SoundListener(position=[0, 0, 0], velocity=[0, 0, 0], \ 204 orientation=[0, 0, -1, 0, 1, 0]) 205 206 A listener object within the 3D audio space. 207 208 .. attribute:: orientation 209 210 The listening orientation as 6-value list. 211 212 .. attribute:: position 213 214 The listener position as 3-value list. 215 216 .. attribute:: velocity 217 218 The movement velocity as 3-value list. 219 220 .. attribute:: gain 221 222 The relative sound volume (perceiptive for the listener). 223 224 .. attribute:: changed 225 226 Indicates, if an attribute has been changed. 227 228.. class:: SoundSource(gain=1.0, pitch=1.0, position=[0, 0, 0], \ 229 velocity=[0, 0, 0]) 230 231 An object within the application world, which can emit sounds. 232 233 .. attribute:: gain 234 235 The volume gain of the source. 236 237 .. attribute:: pitch 238 239 The pitch of the source. 240 241 .. attribute:: position 242 243 The (initial) position of the source as 3-value tuple in a x-y-z 244 coordinate system. 245 246 .. attribute:: velocity 247 248 The velocity of the source as 3-value tuple in a x-y-z coordinate system. 249 250 .. method:: queue(sounddata : SoundData) -> None 251 252 Adds a :class:`SoundData` audio buffer to the source's processing and 253 playback queue. 254 255.. class:: SoundSink(device=None) 256 257 Audio playback system. 258 259 The SoundSink handles audio output for sound sources. It connects to an 260 audio output device and manages the source settings, their buffer queues 261 and the playback of them. 262 263 .. attribute:: device 264 265 The used OpenAL :class:`openal.alc.ALCdevice`. 266 267 .. attribute:: context 268 269 The used :class:`openal.alc.ALCcontext`. 270 271 .. method:: activate() -> None 272 273 Activates the :class:`SoundSink`, marking its :attr:`context` as the 274 currently active one. 275 276 Subsequent OpenAL operations are done in the context of the 277 SoundSink's bindings. 278 279 .. method:: set_listener(listener : SoundListener) -> None 280 281 Sets the listener position for the :class:`SoundSink`. 282 283 .. note:: 284 285 This implicitly activates the :class:`SoundSink`. 286 287 .. method:: process_source(source : SoundSource) -> None 288 289 Processes a single :class:`SoundSource`. 290 291 .. note:: 292 293 This does *not* activate the :class:`SoundSink`. If another 294 :class:`SoundSink` is active, chances are good that the 295 source is processed in that :class:`SoundSink`. 296 297 .. method:: process(world, components) -> None 298 299 Processes :class:`SoundSource` components, according to their 300 :attr:`SoundSource.request` 301 302 .. note:: 303 304 This implicitly activates the :class:`SoundSink`. 305