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