OpenAL Soft and Alure Sandbox

Previously on my blog I wrote about experimenting with audio solutions that could provide hrtf support to audio on your app.

Back then I came up with a limited solution, only compatible with the Windows operating system, to allow rapid prototyping via the .NET framework.
It even sparked a small discussion pushing forward this APO effect on the same issue I was referring to in my previous blog post SharpDX: if things move forward over there I'm sure my small library can be considered somewhat deprecated.

I'm writing again on this topic because I never felt completely satisfied of the limitations the .NET solution has: even if SharpDX finalizes it's support for HRTFs, it would lack the cross-platform support that I find so valuable.
Given this, I never stopped experimenting and trying alternatives. Today, after some testing and valuable help from OpenAL Soft mantainer, I can release a small sandbox that helps setting up a cross platform development environment for native audio applications on Mac, Linux and Windows.

I will explain here how I came up with the current solution, how to run the apps with hrtf effects you can build with it and some of the limitations that are still in place.

Some background

OpenAL really needs no introduction: it is one of the most important apis available providing immersive 3D audio experiences for applications.
Built with OpenGL in mind, many imporant games and apps are built with it, and you can find many implementations for different platforms.
OpenAL Soft is one of them, a software implementation that adds, on top of the OpenAL specification, multiple addons and features.

Although providing great tools to the developer, OpenAL alone doesn't include any mechanics to load or stream audio files or codecs support, so additional libraries should be used.

Between many of them, alure caught my attention. It is relatively young piece of software, but it is in heavy development by the same OpenAL Soft mantainer, Chris "kcat" Robinson.
He has been very helpful in my little experiments, and briefly explained me some of his plans with his work.

Alure already has on official 1.x release, but many features are lacking that will eventually be released under a version 2.
There are no official release calendars, but should be safe to expect it during next year.

Still, pulling from the master branch of the Github repository, gives you a working and beautifully designed piece of code, exposing everything you need for andvanced audio coding: I started fiddling around with it and the project you see here is the result of my research.

My project

My sandbox is a C++ project, and you will need to install CMake, that will take care of generating the build environment for the system you are using (Makefiles on Linux and Mac, Visual Studio projects on Windows): I will cover here some of the details of the build system set up in the environment.

CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
INCLUDE(ExternalProject)

PROJECT(Base)

SET(CMAKE_BUILD_TYPE Release)
SET(CMAKE_CXX_STANDARD 11)

We start by defining the required version of CMake and including a special directive that we will use to handle the two external libraries.

We then name the project and set a couple of variables for compilation.

We then set up two folders to allow OpenAL and Alure compilation, and include the two source libraries.

SET(OPENAL_BINARY_DIR "${PROJECT_BINARY_DIR}/openal")
SET(ALURE_BINARY_DIR "${PROJECT_BINARY_DIR}/alure")

ExternalProject_Add(
  OpenAL
  SOURCE_DIR "${PROJECT_SOURCE_DIR}/dist/openal-soft-1.18.2"
  BINARY_DIR "${OPENAL_BINARY_DIR}"
  UPDATE_COMMAND ""
  INSTALL_COMMAND ""
)

ExternalProject_Add(
  alure
  DEPENDS OpenAL
  SOURCE_DIR "${PROJECT_SOURCE_DIR}/dist/alure"
  BINARY_DIR "${ALURE_BINARY_DIR}"
  UPDATE_COMMAND ""
  INSTALL_COMMAND ""
)

Since these libraries will not be installed on the usual system location, add a FindOpenAL.cmake file in dist/alure/cmake, that will tell the build system where to find the local compiled libraries:

GET_FILENAME_COMPONENT(PARENT_BINARY_DIR ${PROJECT_BINARY_DIR} DIRECTORY)
GET_FILENAME_COMPONENT(PARENT_SOURCE_DIR ${PROJECT_SOURCE_DIR} DIRECTORY)

# Check if a better way exists, and if possible to build and link statically?
IF(WIN32)
  SET(OPENAL_LIBRARY "${PARENT_BINARY_DIR}/openal/Debug/OpenAL32.lib")
ELSE()
  IF(APPLE)
    SET(OPENAL_LIBRARY "${PARENT_BINARY_DIR}/openal/libopenal.dylib")
  ELSE()
    SET(OPENAL_LIBRARY "${PARENT_BINARY_DIR}/openal/libopenal.so")
  ENDIF()
ENDIF()

SET(OPENAL_INCLUDE_DIR "${PARENT_SOURCE_DIR}/openal-soft-1.18.2/include/AL")

SET(OPENAL_FOUND TRUE)

Then we link the two libraries, to allow our program to search the compiled libraries there:

LINK_DIRECTORIES(
  "${OPENAL_BINARY_DIR}"
  "${ALURE_BINARY_DIR}"
)

Now, if you are a CMake expert, I perfectly now what you are thinking: linking directories like this is asking for trouble.

Thing is, you are right: the correct thing to do would be removing the void INSTALL_COMMAND directive from the external projects, have the binaries installed in the system and just retrieve them via FIND_PACKAGE.

I decided to go this way because Alure is still in heavy development, and I just prefer, right now, to keep concerns separated. You are free to edit this bit if you still think it's a better strategy.

Now, let's expose the include directories and add our program to be compiled:

INCLUDE_DIRECTORIES(
  "${PROJECT_SOURCE_DIR}/dist/openal-soft-1.18.2/include"
  "${PROJECT_SOURCE_DIR}/dist/alure/include"
)

ADD_EXECUTABLE(main main.cpp)
ADD_DEPENDENCIES(main OpenAL alure)

IF(WIN32)
  TARGET_LINK_LIBRARIES(main OpenAL32.lib alure2)
ELSE()
  TARGET_LINK_LIBRARIES(main openal alure2)
ENDIF()

Now that our project is built, we want to add a couple of commands to perfect this standalone build system.

On Windows, we want to copy the library binaries to the same folder of our program, and we also want to copy the MHR hrtf file definitions to a location where OpenAL would find them.

If you plan, during development, to always launch your program from the build directory like me, this is how I do it:

IF(WIN32)
  ADD_CUSTOM_COMMAND(TARGET OpenAL POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      "${OPENAL_BINARY_DIR}/Debug/OpenAL32.dll"
      "${PROJECT_BINARY_DIR}/Debug")
  ADD_CUSTOM_COMMAND(TARGET alure POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
      "${ALURE_BINARY_DIR}/Debug/alure2.dll"
      "${PROJECT_BINARY_DIR}/Debug")
  SET(MHR_COPY_DIR "${PROJECT_BINARY_DIR}/Debug")
ELSE()
  SET(MHR_COPY_DIR "${PROJECT_BINARY_DIR}")
ENDIF()

ADD_CUSTOM_COMMAND(TARGET main POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_if_different
    "${CMAKE_SOURCE_DIR}/dist/openal-soft-1.18.2/hrtf/default-44100.mhr"
    "${MHR_COPY_DIR}")

ADD_CUSTOM_COMMAND(TARGET main POST_BUILD
  COMMAND ${CMAKE_COMMAND} -E copy_if_different
    "${CMAKE_SOURCE_DIR}/dist/openal-soft-1.18.2/hrtf/default-48000.mhr"
    "${MHR_COPY_DIR}")

Build

Now that the project is clear, let's try to build it: on Windows, I highly suggest you to add your CMake executable available in your path: this way you can perform the following steps via command line as you see them here. Also, don't forget to install Visual Studio, as this project expects you to compile with it on Windows.

Clone this repository, then create a build folder in it's root. Navigate inside it, then run:

cmake ..

This will configure the project that you can build like this:

cmake --build .

If everything went well, just run you main executable. The one included in the project is basically a copy of the HRTF alure sample with a bit of motion added.

Limitations

Just by seeing the CMake setup, I'm sure you are aware of some of the limitations this setup has:

Library binaries location

On Windows the operating system allows searching libraries in the same location as the executable: in Linux and MacOS this does not happen, and we hard linked the libraries location as explained above: you can solve this as explained above, or setting up some RPATH that will allow for fully relocatable packages.

HRTF file definitions

Also, MHR file definitions will only be loaded if the program will be launched within the executable directory, as OpenAL will search for them in the process current working directory: if you need to go around this, place these are the file location you need.

On windows:

%appdata%\openal\hrtf

And Linux/MacOS

$HOME/.local/share/openal/hrtf
/usr/local/share/openal/hrtf
/usr/share/openal/hrtf

As Chris reminded me, a very useful debugging tool is to set the environment variable ALSOFT_LOGLEVEL to 3 and check the folders OpenAL is visiting to find these files.

Download

You can find the simple project here, on my Github profile.

PRs and issues are welcome.