cl-openal
2022-11-07
CFFI bindings for OpenAL sound system.
Upstream URL
Author
Kat Marchán <kzm@sykosomatic.org>
Maintainer
Kat Marchán <kzm@sykosomatic.org>
License
public domain
About
-----
cl-openal is a series of semi-lispy public domain bindings to the OpenAL API. It includes direct
CFFI bindings, as well as varying levels of lispy wrappings around AL, ALC, and ALUT.
The translation from the C bindings to lispy bindings is pretty straightforward, and follows
cl-opengl's example, in spirit.
Loading
-------
cl-openal depends on CFFI to load. It's split into three separate systems: cl-openal for the basic
OpenAL bindings, cl-alc for ALC, and cl-alut for ALUT. If you need anything from ALC or ALUT, you'll
have to specifically load them.
In order to use cl-openal and cl-alc, you'll need to have OpenAL installed on your system, or
available as a shared library/.dll. Additionally, an implementation of ALUT needs to be available in
order to use cl-alut (such as freealut).
cl-openal should run on all major platforms, including Linux, the BSDs, OSX, and Windows. It should
be usable by any Common Lisp implementation supported by CFFI.
Error handling
--------------
**BREAKING CHANGE NOTICE:** The new error handling API represents a breaking change. It introduces
`*PROPAGATE-ERRORS*` with the default value of `T`, which means that OpenAL operations which would
previously fail silently will now signal `AL-ERROR` in case of an error. This is a better default,
but will break well-written code using the old API that expects to do its own error checking using
`GET-ERROR`. To restore the old behaviour, set `*PROPAGATE-ERRORS*` to `NIL**.
**NOTE:** `AL:GET-ERROR`/`AL:CLEAR-ERROR` are distinct from `ALUT:GET-ERROR`/`ALUT:CLEAR-ERROR`,
because the error handling in ALUT differs from error handling in OpenAL/ALC. Similarly, the
condition reported for OpenAL/ALC errors is `AL-ERROR`, whereas for ALUT it's `ALUT-ERROR`. In
previous versions, these symbols were accidentally merged. Code relying on the old behaviour will
need to be updated (or converted to use automatic error checking, see below).
By default, cl-openal will call `alGetError()`/`alutGetError` after every C operation and signal any
errors via the `AL-ERROR`/`ALUT-ERROR` conditions. The specific error code be retrieved via
`(AL:ERRCODE CONDITION)`.
Previous versions of cl-openal did not have any provision for automatic error detection, and only
provided `GET-ERROR` as a very thin wrapper around `alGetError()`. This not only requires user code to
do its own error check after every operation, which is tedious and error-prone, but makes some
operations inherently unsafe, as certain wrappers perform foreign memory accesses immediately after
calling the underlying OpenAL functions, without any opportunity for the user to check for errors
and abort. This means memory corruption is highly likely in those instances. This issue has been
fixed, and even if `*PROPAGATE-ERRORS*` is `NIL`, all wrappers exported by packages `AL`, `ALC` and
`ALUT` will now abort if any underlying C operations signals an error. In this case, the wrapper
function will return `NIL`.
If for some reason you need to use one of the low-level bindings in the `%AL` or `%ALC` packages,
the macro `AL:DEFUN-AL` should be use instead of regular `DEFUN`. It automates clearing the error
state at the beginning of the function, and the checking at the end, and exposes a local macro
called `CHECKPOINT`, which should be inserted after every low-level operation, if it's not the last
operation in the function definition. It will also ensure that `*PROPAGATE-ERRORS*` is respected.
If you need to interact with the low-level bindings in `%ALUT`, a similar macro called `DEFUN-ALUT`
is provided. It also automates error clearing and handling, but because the ALUT error reporting is
different, it's simpler to use: there's no need to call `(CHECKPOINT)`, all CFFI calls are checked
automatically.
Thread safety
-------------
Unfortunately, the OpenAL specification is incomplete when it comes to thread safety. Although
OpenAL contexts are specified to be thread safe, and a single OpenAL operation is atomic, the
specification does not specify enough details to allow safe sharing of contexts between multiple
threads. In particular, error handling through `alGetError()` is inherently not thread-safe, and if
multiple threads perform operations on a single context simultaneously, they will have no way to
tell whose operation caused the error being reported.
cl-openal on its part is also not thread-safe in its error handling. If `*PROPAGATE-ERRORS*` is
`NIL`, the mechanism uses the value of a special variable to store and report errors. Because
special variable bindings are not visible across threads, retrieving the last error *will* break if
done from multiple threads.
For these reasons, **multiple threads should never access a single OpenAL context at the same
time. Doing so is inherently unsafe and cannot be done without introducing race conditions.**
Support
-------
If you have any questions, you may contact me at <kzm@sykosomatic.org>. Patches or similar
always welcome!
Extra Notes
-----------
The OpenAL Programming Guide says that buffers can not be destroyed while they are queued. Sources,
on the other hand, can be destroyed while buffers are queued on them.
For this reason it's probably a good idea to do:
(with-buffers ...
(with-sources ...
(queueing code)))
instead of
(with-sources ...
(with-buffers ...
(queueing code)))
Same goes for WITH-SOURCES which you want to wrap in WITH-CONTEXT.