OpenAL: Bindings for the OpenAL sound library
(require libopenal-racket) | package: libopenal-racket |
1 Example: Playing sound from a raw buffer
In this example, we play an 8-bit sine wave generated from a bytestring.
#lang racket (require libopenal-racket) ;; Our sound data (define sinewave (list->bytes (for/list ([i (in-range 0 (* 2 pi) 0.07)]) (inexact->exact (floor (+ 128 (* (sin i) 64)))))))
First, we must open the sound device and create a context for it:
;; Initialize OpenAL (see the OpenAL guide) (define device (open-device #f)) (define context (create-context device)) (set-current-context context)
Once we have a device context, we must create a buffer to hold our sound data.
;; Make our OpenAL buffer (define buffer (car (gen-buffers 1))) ;; Copy the bytes to this buffer (buffer-data buffer AL_FORMAT_MONO8 sinewave 44100)
In this example, each unsigned byte of sinewave is a separate 8-bit sound sample. Realistically, a real-world application would want to use 16-bit samples, but this grainy, ugly example is fine for us.
Once our data is safe in an OpenAL buffer, we make a source and play it. Note that OpenAL internally copies that data – we are free to mutate it however we like after the call to buffer-data.
;; Make our OpenAL source (define source (car (gen-sources 1))) ;; Bind our buffer to the source -- 8-bit mono (set-source-buffer! source buffer)
OpenAL allows us to define several simultaneously playing sources. Though a single source can only have one buffer, note that a buffer can be bound to several sources at the same time to save space.
Now that we have a source and a sound buffer to play, we can set our source to play the data from the sound buffer.
;; Loop forever (without this, the source will play just once and then stop) (set-source-looping! source AL_TRUE) ;; Start playing (play-source source)
OpenAL uses a separate OS-level thread to play sounds. The play-source call will exit instantly and our sound will continue to play in the background until we choose to stop it.
;; Wait until we're fisished playing (sleep 5) ;; Clean up (delete-sources! (list source)) (delete-buffers! (list buffer)) (set-current-context #f) (destroy-context! context) (close-device! device)
2 Example: Playing OGG Vorbis Files
OpenAL works great with the (planet gcr/libvorbisfile) package, and this is no accident. This example shows you how to stream a vorbis file straight to an OpenAL source without loading it into a buffer like we did above.
First, we must dig out the spellbook and incant the OpenAL Summoning Mantra:
#lang racket (require libopenal-racket (planet gcr/libvorbisfile)) ;; Initialize OpenAL (see the docs for libopenal-racket) (define device (open-device #f)) (define context (create-context device)) (set-current-context context)
From here, it’s not rocket science to open a vorbis file and query basic information about it.
(define filename "/home/gcr/Music/Lights/Siberia/11 Flux and Flow.ogg") (printf "Playing file ~a\n" filename) (define m (open-vorbis-file filename))
To read the sound data, we use libvorbisfile’s make-vorbis-input-port function to create a port that decodes the sound data into binary bytes on-demand.
;; To read the PCM samples, we make a port that supplies us with ;; the binary decompressed data. (define vorbis-binary (make-vorbis-input-port m 0 2 1)) ;; OpenAL expects: ;; 0 (Little-endian) ;; 2 (Word size; 16 bits) ;; 1 (Signed)
Now that we have a port that gives us raw sample data, we can stream it straight to an OpenAL source. This avoids reading the entire file into memory – each block of sound is decoded right as it’s needed.
;; Make our OpenAL source (define source (car (gen-sources 1))) ;; Start streaming (define stream-thread (stream-port-to-source vorbis-binary source AL_FORMAT_STEREO16 (vorbis-frequency m))) ;; Start playing (play-source source) ;; OpenAL's stream-port-to-source returns a thread, so wait until we're ;; finished playing (thread-wait stream-thread)
Once we’re done, we should clean up our OpenAL mess:
(set-current-context #f) (destroy-context! context) (close-device! device)
You should probably close the vorbis file when you’re finished.
(close-vorbis-file! m)
3 Constants
All constants defined in al.h and alc.h are re-exported under their usual names. See the OpenAL Programmer’s Guide for information about what each constant means.
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
value
4 Device and context management
With OpenAL, we must manage our own devices and our device contexts. It’s good practice to close all devices at the end of your program – some platforms may get confused.
procedure
(open-device devicename) → any/c
devicename : (or/c string? #f)
procedure
(open-capture-device devicename frequency format buffersize) → any/c devicename : (or/c string? #f) frequency : integer?
format :
(or/c AL_FORMAT_MONO8 AL_FORMAT_MONO16 AL_FORMAT_STEREO8 AL_FORMAT_STEREO16) buffersize : integer?
frequency is the frequency at which the device should record, usually 44100.
format is the requested capture buffer format.
buffersize is the size of the capture buffer.
procedure
(close-device! device) → boolean?
device : any/c
procedure
(close-capture-device! device) → boolean?
device : any/c
procedure
(create-context device) → any/c
device : any/c
procedure
(set-current-context context) → any/c
context : any/c
procedure
procedure
(get-device-from-context context) → any/c
context : any/c
procedure
(destroy-context! context) → any/c
context : any/c
procedure
procedure
(start-capture device) → void?
device : any/c
procedure
(stop-capture device) → void?
device : any/c
procedure
(capture-samples device buffer samples) → void?
device : any/c buffer : bytes? samples : integer?
5 Buffers
OpenAL buffers hold sound data. They are kept in completely separate memory outside Racket’s garbage collection pool, so you must free buffers yourself when you’re finished with them to avoid memory leaks.
procedure
(gen-buffers num) → (listof exact-integer?)
num : exact-integer?
Once you have a buffer, you must load data with buffer-data, bind it to a source with set-source-buffer!, and then play the source with play-source. See the Example: Playing sound from a raw buffer section.
procedure
(delete-buffers! buffers) → any/c
buffers : (listof exact-integer?)
procedure
buffer : exact-integer?
procedure
(buffer-data bufid format data frequency) → any/c
bufid : exact-integer?
format :
(or/c AL_FORMAT_MONO8 AL_FORMAT_MONO16 AL_FORMAT_STEREO8 AL_FORMAT_STEREO16) data : bytes? frequency : exact-integer?
Format should be an OpenAL constant signifying the format of the samples in data. For 8-bit formats, a value of 128 means no DC offset, with 0 and 255 being the extreme DC offsets. For 16-bit formats, samples are signed little-endian 16-bit integers. Stereo formats include interleaved samples, left first.
The frequency of the data is how many samples per second the source should play, usually 44100.
Note that data is automatically copied, not referenced – you can reuse or mutate data however you like after this call.
5.1 Buffer properties
5.1.1 C-level buffer getters and setters
procedure
buffer : exact-integer? param : exact-integer? value : real?
procedure
(alBuffer3f buffer param value1 value2 value3) → any/c
buffer : exact-integer? param : exact-integer? value1 : real? value2 : real? value3 : real?
procedure
(alBufferfv buffer param values) → any/c
buffer : exact-integer? param : exact-integer? values : (listof real?)
procedure
buffer : exact-integer? param : exact-integer? value : exact-integer?
procedure
(alBuffer3i buffer param value1 value2 value3) → any/c
buffer : exact-integer? param : exact-integer? value1 : exact-integer? value2 : exact-integer? value3 : exact-integer?
procedure
(alBufferiv buffer param values) → any/c
buffer : exact-integer? param : exact-integer? values : (listof exact-integer?)
procedure
(alGetBufferf buffer param) → real?
buffer : exact-integer? param : exact-integer?
procedure
(alGetBuffer3f buffer param) → (list/c real? real? real?)
buffer : exact-integer? param : exact-integer?
procedure
(alGetBufferfv buffer param) → (listof real?)
buffer : exact-integer? param : exact-integer?
procedure
(alGetBufferi buffer param) → exact-integer?
buffer : exact-integer? param : exact-integer?
procedure
(alGetBuffer3i buffer param)
→ (list/c exact-integer? exact-integer? exact-integer?) buffer : exact-integer? param : exact-integer?
procedure
(alGetBufferiv buffer param) → (listof exact-integer?)
buffer : exact-integer? param : exact-integer?
5.1.2 Friendly buffer getters and setters
procedure
(buffer-frequency buffer) → exact-integer?
buffer : exact-integer?
procedure
(buffer-bits buffer) → exact-integer?
buffer : exact-integer?
procedure
(buffer-channels buffer) → exact-integer?
buffer : exact-integer?
procedure
(buffer-size buffer) → exact-integer?
buffer : exact-integer?
6 Sources
Sources are actual playing sounds in the world. Each source can either be bound to a single buffer or can contain a buffer queue that continuously streams sound data.
Sources have a 3D position and velocity in space. This means that OpenAL can optionally apply certain effects such as attenuation (softening far-away sounds) and pitch shifting (the doppler effect).
procedure
(gen-sources num-sources) → (listof exact-integer?)
num-sources : exact-integer?
procedure
(delete-sources! sources) → any/c
sources : (listof exact-integer?)
procedure
(play-source source) → any/c
source : exact-integer?
procedure
(pause-source source) → any/c
source : exact-integer?
procedure
(stop-source source) → any/c
source : exact-integer?
procedure
(rewind-source source) → any/c
source : exact-integer?
6.1 Source properties
These bindings define two “levels” of functions that set and retrieve properties. First, there are the low-level “C-like” functions like alSourcef that allow you to get and set individual properties of sources defined by an OpenAL constant. Alternatively, there are the friendlier functions like set-source-looping! that offer a simpler interface for setting the same properties.
6.1.1 C-like source getters and setters
procedure
source : exact-integer? param : exact-integer? value : real?
procedure
(alSource3f source param value1 value2 value3) → any/c
source : exact-integer? param : exact-integer? value1 : real? value2 : real? value3 : real?
procedure
(alSourcefv source param values) → any/c
source : exact-integer? param : exact-integer? values : (listof real?)
procedure
source : exact-integer? param : exact-integer? value : exact-integer?
procedure
(alSource3i source param value1 value2 value3) → any/c
source : exact-integer? param : exact-integer? value1 : exact-integer? value2 : exact-integer? value3 : exact-integer?
procedure
(alSourceiv source param values) → any/c
source : exact-integer? param : exact-integer? values : (listof exact-integer?)
procedure
(alGetSourcef source param) → real?
source : exact-integer? param : exact-integer?
procedure
(alGetSource3f source param) → (list/c real? real? real?)
source : exact-integer? param : exact-integer?
procedure
(alGetSourcefv source param) → (listof real?)
source : exact-integer? param : exact-integer?
procedure
(alGetSourcei source param) → exact-integer?
source : exact-integer? param : exact-integer?
procedure
(alGetSource3i source param)
→ (list/c exact-integer? exact-integer? exact-integer?) source : exact-integer? param : exact-integer?
procedure
(alGetSourceiv source param) → (listof exact-integer?)
source : exact-integer? param : exact-integer?
6.1.2 Friendly source getters and setters
procedure
(set-source-pitch! source value) → any/c
source : exact-integer? value : real?
procedure
(source-pitch source) → real?
source : exact-integer?
procedure
(set-source-gain! source value) → any/c
source : exact-integer? value : real?
procedure
(source-gain source) → real?
source : exact-integer?
procedure
(set-source-rolloff-factor! source value) → any/c
source : exact-integer? value : real?
procedure
(source-rolloff-factor source) → real?
source : exact-integer?
procedure
(set-source-reference-distance! source value) → any/c source : exact-integer? value : real?
procedure
(source-reference-distance source) → real?
source : exact-integer?
procedure
(set-source-min-gain! source value) → any/c
source : exact-integer? value : real?
procedure
(source-min-gain source) → real?
source : exact-integer?
procedure
(set-source-max-gain! source value) → any/c
source : exact-integer? value : real?
procedure
(source-max-gain source) → real?
source : exact-integer?
procedure
(set-source-max-distance! source value) → any/c
source : exact-integer? value : real?
procedure
(source-max-distance source) → real?
source : exact-integer?
procedure
(set-source-cone-outer-gain! source value) → any/c
source : exact-integer? value : real?
procedure
(source-cone-outer-gain source) → real?
source : exact-integer?
procedure
(set-source-cone-inner-angle! source value) → any/c
source : exact-integer? value : real?
procedure
(source-cone-inner-angle source) → real?
source : exact-integer?
procedure
(set-source-cone-outer-angle! source value) → any/c
source : exact-integer? value : real?
procedure
(source-cone-outer-angle source) → real?
source : exact-integer?
procedure
(set-source-position! source x y z) → any/c
source : exact-integer? x : real? y : real? z : real?
procedure
(source-position source) → (list/c real? real? real?)
source : exact-integer?
procedure
(set-source-direction! source x y z) → any/c
source : exact-integer? x : real? y : real? z : real?
procedure
(source-direction source) → (list/c real? real? real?)
source : exact-integer?
procedure
(set-source-velocity! source x y z) → any/c
source : exact-integer? x : real? y : real? z : real?
procedure
(source-velocity source) → (list/c real? real? real?)
source : exact-integer?
procedure
(set-source-source-relative! source value) → any/c
source : exact-integer? value : (or/c 0 1)
procedure
(source-source-relative source) → (or/c 0 1)
source : exact-integer?
procedure
(source-source-type source)
→
(or/c AL_STATIC AL_STREAMING AL_UNDETERMINED) source : exact-integer?
procedure
(set-source-looping! source value) → any/c
source : exact-integer? value : (or/c 0 1)
procedure
(source-looping source) → (or/c 0 1)
source : exact-integer?
procedure
(set-source-buffer! source buffer) → any/c
source : exact-integer? buffer : exact-integer?
procedure
(source-buffer source) → exact-integer
source : exact-integer?
If you want to build a streaming buffer, don’t use set-source-buffer! or else it may misbehave. You can, however, clear any queued buffers by running (set-source-buffer! source 0).
procedure
(source-source-state source) →
(or/c AL_STOPPED AL_PLAYING AL_PAUSED) source : exact-integer?
procedure
(source-buffers-queued source) → exact-integer?
source : exact-integer?
procedure
(source-buffers-processed source) → exact-integer?
source : exact-integer?
procedure
(source-sec-offset source) → real?
source : exact-integer?
procedure
(source-sample-offset source) → exact-integer?
source : exact-integer?
procedure
(source-byte-offset source) → exact-integer?
source : exact-integer?
7 Listener properties
The listener is the single entity in the world that listens for sound from all the sources. When the listener “hears” a source, it sends the output to the current sound device. The volume is adjusted based on the distance between the source and listener, and the sound’s pitch is adjusted based on the velocity and orientation thanks to the doppler effect.
7.1 C-like listener getters and setters
procedure
(alListenerf param value) → any/c
param : exact-integer? value : real?
procedure
(alListener3f param value1 value2 value3) → any/c
param : exact-integer? value1 : real? value2 : real? value3 : real?
procedure
(alListenerfv param values) → any/c
param : exact-integer? values : (listof real?)
procedure
(alListeneri param value) → any/c
param : exact-integer? value : exact-integer?
procedure
(alListener3i param value1 value2 value3) → any/c
param : exact-integer? value1 : exact-integer? value2 : exact-integer? value3 : exact-integer?
procedure
(alListeneriv param values) → any/c
param : exact-integer? values : (listof exact-integer?)
procedure
(alGetListenerf param) → real?
param : exact-integer?
procedure
(alGetListener3f param) → (list/c real? real? real?)
param : exact-integer?
procedure
(alGetListenerfv param) → (listof real?)
param : exact-integer?
procedure
(alGetListeneri param) → exact-integer?
param : exact-integer?
procedure
(alGetListener3i param)
→ (list/c exact-integer? exact-integer? exact-integer?) param : exact-integer?
procedure
(alGetListeneriv param) → (listof exact-integer?)
param : exact-integer?
7.2 Friendly listener getters and setters
procedure
(set-listener-gain! value) → any/c
value : real?
procedure
(listener-gain) → real?
procedure
(set-listener-position! x y z) → any/c
x : real? y : real? z : real?
procedure
(listener-position) → (list/c real? real? real?)
procedure
(set-listener-velocity! x y z) → any/c
x : real? y : real? z : real?
procedure
(listener-velocity) → (list/c real? real? real?)
procedure
(set-listener-orientation! x y z) → any/c
x : real? y : real? z : real?
procedure
8 Streaming sound
Each source usually has one buffer attached to it. If this were the only possible way of playing your sound, you would have to either load the entire sound into memory (imagine loading 90 minutes of raw sample data) or manage buffers yourselves, suffering through the inevitable clicks and hisses when OpenAL drains the buffer just before you can replace it.
Thankfully, OpenAL allows you to assign several buffers to a source using a queue of buffer objects. The sound will continue to play as long as there is a buffer left to play from. From time to time, your program should un-queue old buffers, refill them, and queue them at the end of the source’s queue.
You can either manage each source’s low-level buffer queue yourself or use the higher-level port streaming facilities to stream sounds straight from a Racket binary port.
procedure
(source-queue-buffers! source buffers) → any/c
source : exact-integer? buffers : (listof/c exact-integer?)
If you intend to build a streaming source with a buffer queue, don’t call set-source-buffer!. Likewise, don’t use this function if you only want an ordinary source backed by a single buffer.
procedure
(source-unqueue-buffers! source buffers) → any/c
source : exact-integer? buffers : (listof/c exact-integer?)
procedure
(stream-port-to-source port source format frequency [ at-end-of-loop num-buffers buffer-size poll-interval cleanup]) → thread? port : port? source : exact-integer?
format :
(or/c AL_FORMAT_MONO8 AL_FORMAT_MONO16 AL_FORMAT_STEREO8 AL_FORMAT_STEREO16) frequency : exact-integer? at-end-of-loop : (-> boolean?) = (λ() #f) num-buffers : exact-integer? = 5 buffer-size : exact-integer? = (* 4096 8) poll-interval : real? = 0.1 cleanup : (-> any/c) = (λ()(void))
Every poll-interval seconds, the background thread will ensure that the source has num-buffers unprocessed buffers of at most buffer-size bytes each, and the thread will refill processed buffers as necessary to ensure gapless playback.
The port should yield bytes suitable for OpenAL playback, with the given format, and sample rate of frequency (typically 44100).
Just after the last buffer is queued, the thread will run the at-end-of-loop procedure which returns #t if the thread should continue or #f if it should stop once the last buffer finishes. You might use the at-end-of-loop function to seek the port back to the beginning of the file to seamlessly start playing back at the beginning of the file when reaching the end.
The thread will run the cleanup function just before exiting. Use this function to delete the allocated source, close the port, or perhaps to transition to a different screen in your application. Note that this may run up to poll-interval seconds after the sound actually finishes.
If you want to terminate the thread early, just run kill-thread on it. Cleanup will be run automatically.
9 Distance models
OpenAL defines several models that select how sound is attenuated (softened) over distance.
gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + |
AL_ROLLOFF_FACTOR * |
(distance – AL_REFERENCE_DISTANCE)); |
distance = max(distance,AL_REFERENCE_DISTANCE); |
distance = min(distance,AL_MAX_DISTANCE); |
gain = AL_REFERENCE_DISTANCE / (AL_REFERENCE_DISTANCE + |
AL_ROLLOFF_FACTOR * |
(distance – AL_REFERENCE_DISTANCE)); |
distance = min(distance, AL_MAX_DISTANCE) // avoid negative gain |
gain = (1 – AL_ROLLOFF_FACTOR * (distance – |
AL_REFERENCE_DISTANCE) / |
(AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE)) |
distance = max(distance, AL_REFERENCE_DISTANCE) |
distance = min(distance, AL_MAX_DISTANCE) |
gain = (1 – AL_ROLLOFF_FACTOR * (distance – |
AL_REFERENCE_DISTANCE) / |
(AL_MAX_DISTANCE – AL_REFERENCE_DISTANCE)) |
gain = (distance / AL_REFERENCE_DISTANCE) ^ |
(- AL_ROLLOFF_FACTOR) |
distance = max(distance, AL_REFERENCE_DISTANCE) |
distance = min(distance, AL_MAX_DISTANCE) |
gain = (distance / AL_REFERENCE_DISTANCE) ^ |
(- AL_ROLLOFF_FACTOR) |
Without any distance model, the gain remains fixed at 1.
procedure
(set-doppler-factor! value) → any/c
value : real?
procedure
(set-speed-of-sound! value) → any/c
value : real?
10 License
The code in this package and this documentation is under the zlib license, reproduced below. Keep in mind that OpenAL itself has many different implementations – some of which are proprietary to Creative Labs and some of which LGPL-licensed.
Copyright (c) 2012 gcr |
|
This software is provided 'as-is', without any express or implied |
warranty. In no event will the authors be held liable for any damages |
arising from the use of this software. |
|
Permission is granted to anyone to use this software for any purpose, |
including commercial applications, and to alter it and redistribute it |
freely, subject to the following restrictions: |
|
1. The origin of this software must not be misrepresented; you must not |
claim that you wrote the original software. If you use this software |
in a product, an acknowledgment in the product documentation would be |
appreciated but is not required. |
|
2. Altered source versions must be plainly marked as such, and must not be |
misrepresented as being the original software. |
|
3. This notice may not be removed or altered from any source |
distribution. |