Greetings from Prague!
I should develop an audiogame in c# as part of my bachelory thesis and because I'm under time pressure I need to learn 3d sound programming as quickly as possible. After a frenetic internet investigation and a little experimenting I finally made decission to learn OpenAL via OpenTK wrapper. I am interested in HRTF and all the effects like reverb or distance attenuation. Which OpenAL extension should I use? Do you know about a good tutorial on the internet? Would you write a demonstrative code snippet for me?
Greetings from Prague!
Don't have much experience with C#, but you can find a few examples [here]. You'll want the [OpenAL-Soft] version to work with. Feel free to check out the developer guide [here], and EFX guide for reverb and such [here]. If it helps any, I have a number of example scripts written in python for hrtf, efx, recording, and positional audio, which you can find [here]. It also includes a collection of hrtf tables with links and guides on where to get and compile them yourself.
Thanks for a quick reaction and the nice Python examples. However, there might be a problem with compatibility. I found many c# wrappers but most of them are obsolete. The newest one works with OpenAL-soft 1.16.0 and is four years old. Are there any crucial features missing in the older version?
#4 (edited by Ethin 2019-04-15 18:20:33)
I'd like to point out that, since .NET is a platform, you can always write your own. You can easily set up a C++/CX project inside your solution and write your own that way. Or, hell, you can utilize Kcat/Alure, which is a wrapper around the OpenAL library written in C++ and incorporate that into your projects.
Hm, looks like ALC_SOFT_HRTF was implemented in 1.17.0, you could download the source for 1.19.1 [here] and check the change log for any other relevant changes, you could also check the header files and compare them, and ad in any new bindings as necessary.
Digging around a bit, [OpenAL#] seems like a good choice, its thin enough that you could probably work with it and update the extensions without much trouble and is pretty similar to the thin python wrapper script my examples use, openal.py. Looking into the AL and ALC bindings though it doesn't look like it has any support for HRTF, so you'll have to add those bindings yourself.
@5, just curious: checking out your OpenAL examples from the link you gave in post 2. I have PyOpenAL in Pip installed in my Python installation. I've found that the Listener class remains, but the Player one doesn't exist. Would I replace Player() with Source()/...?
#7 (edited by magurp244 2019-04-15 20:25:04)
Probably. In the official OpenAL documentation the functions for playing sounds are called "Source", which symnatically speaking seems a bit odd, so I called my playback handlers Player instead which may be a bit easier to pick up for beginners and general users. I mean it makes sense from a technical standpoint, a source being a source of sound in a 3D space, but from a general end user standpoint referencing it as a sound player that can be positioned in 3D space might seem a bit more... intuitive?
Technically speaking though the Listener, Player, and Sound classes are for convience. You could still directly access the bindings yourself and write closer to the metal so to speak.
#8 (edited by Ethin 2019-04-15 20:29:57)
@7, aha. I'll try that. Trying now...
OK, here's the equivalent PyOpenAl code:
from openal import * import time class Example(object): def __init__(self): #load listener self.listener = Listener() #initialize sound self.sound = oalOpen("tone5.wav") #set listener position self.listener.set_position((320,240,0)) #set player position self.sound.set_position((0,240,0)) #enable loop sound so it plays forever self.sound.set_looping(True) #set rolloff factor self.sound.set_rolloff_factor(0.01) #play sound self.sound.play() #move sound from left to right for a in range(0,704,64): self.sound.set_position((a,240,0)) time.sleep(1) #stop player self.sound.stop() #clean up resources self.sound.destroy() Example()
The differences here is that oalOpen() returns a source object immediately, which is really neat. The listener object, also, does not need destruction -- I presume that's taken care of automatically when oalQuit() is called. This could be changed to manually initialize OpenAL, but the default behavior is to initialize it automatically.
If you like, I can convert your OpenAL examples to use PyOpenAL so that people who choose to use that one can use that one instead.
Ah yes, I seem to recall that about PyOpenAL, it autoloading sounds into sources for immediate playback. That is rather convienent, but kind of gets a bid less convienent if you start using filters and EFX that have to be bound to specific players, or swapping multiple samples on individual players. Its not a huge issue for PyOpenAL though since it doesn't seem to have any support for the EFX extensions, and HRTF support isn't fully implemented yet. To be fair, the convience classes in my own scripts don't have all the functions implemented either, largely for things like orientation or streaming, but everything else is more or less there.
This might make converting the examples to purely PyOpenAL a bit problematic, since all except the 3D positional audio example likely won't work, at least going by the github.
@9, true. I'm now curious on exactly which OpenAL implementation to use. PyOpenAL which is the number 1 hit on Google, doesn't hav HRTF or EFX, which is quite sad, and as you stated, you don't have everything either. So I'm quite curious which one is the most feature-complete now...
For Python its hard to say, PyAL has some EFX support but lacks HRTF support too. I think, generally speaking my own wrapper is probably the most up to date, since while the convience classes aren't 100% implemented, all the underlying bindings are. So, even if the handlers don't have a feature you want you can bypass them and either write your own or access the bindings directly yourself.
@11, good to know. How would you go about calling OpenAL/ALC directly? (I wonder if we should make quick small little wrappers that take the normal Python types as parameters and auto-convert between them. Mmm... I found SteamAudio.... wonder if I should check that out and see if its Python-wrapper-able.
The convienence classes make those kinds of calls in openal.py, its fairly straightforward really. The listener for example:
class Listener(object): def __init__(self): #load device/context/listener self.device = alc.alcOpenDevice(None) self.context = alc.alcCreateContext(self.device, None) alc.alcMakeContextCurrent(self.context) alc.alcProcessContext(self.context)
Or for creating and loading sound buffers:
self.buf = al.ALuint(0) al.alGenBuffers(1, self.buf) #allocate buffer space to: buffer, format, data, len(data), and samplerate al.alBufferData(self.buf, alformat, wavbuf, len(wavbuf), samplerate)
I've been digging into the OpenAL-Soft include headers and it seems there's some extra support for more dense audio formats like AL_MONO_FLOAT32 and AL_STEREO_FLOAT32, vorbis, etc. There's also a header with EFX environmental reverb presets for all sorts of environments like underwater, plains, mountains, cities, caves, etc. Interesting stuff, so some things left to mess around with and implement as time allows.
As for steam audio, maybe? It seems specifically designed to work with FMOD, Unity, or Unreal, so i'm not sure. Maybe using its Phonon bindings and using PyQt.Phonon?
Thank you really much for your ideas. I wasn't aware thaht I can enuse c# and c++ in one project this way. It probabbly would take quite much time but I should be able to manage it. I just have to decide if it's faster to edit source code of OpenAL# or make a c++/cx wrapper. Do you think I can use the same effects with Alure as in straight OpenAL-soft implementation?
Hm... Digging around in Alure i'm going to have to say no, you wouldn't get the full range of features out of it. It seems to have a fairly solid framework of the core functions, including HRTF, positional audio, and EFX, but it only supports 3 effects, EAX Reverb, Reverb, and Chorus, no filters or anything else, although it does have environmental presets implemented. This might be enough for your current uses, but for comparison OpenAL# has all the effects implemented, including filters, just not HRTF. To implement that you'd need to add a few lines into the ALC code, I had come across [this] document that explains what the HRTF bindings are and how they work. In the python implementation it was as simple as adding them into the lib_alc() class as:
self.ALC_HRTF_SOFT = 6546 self.ALC_HRTF_ID_SOFT = 6550 self.ALC_DONT_CARE_SOFT = 2 self.ALC_HRTF_STATUS_SOFT = 6547 self.ALC_NUM_HRTF_SPECIFIERS_SOFT = 6548 self.ALC_HRTF_SPECIFIER_SOFT = 6549 self.ALC_HRTF_DISABLED_SOFT = 0 self.ALC_HRTF_ENABLED_SOFT = 1 self.ALC_HRTF_DENIED_SOFT = 2 self.ALC_HRTF_REQUIRED_SOFT = 3 self.ALC_HRTF_HEADPHONES_DETECTED_SOFT = 4 self.ALC_HRTF_UNSUPPORTED_FORMAT_SOFT = 5 self.alcGetStringiSOFT = self._lib.alcGetStringiSOFT self.alcGetStringiSOFT.restype = None self.alcGetStringiSOFT.argtypes = [POINTER(self.ALCdevice),POINTER(self.ALCenum),POINTER(self.ALCsizei)] self.alcResetDeviceSOFT = self._lib.alcResetDeviceSOFT self.alcResetDeviceSOFT.restype = None self.alcResetDeviceSOFT.argtypes = [POINTER(self.ALCdevice),POINTER(self.ALCint)] ... self.LPALCGETSTRINGISOFT = CFUNCTYPE(POINTER(self.ALCdevice), POINTER(self.ALCenum), POINTER(self.ALCsizei)) self.LPALCRESETDEVICESOFT = CFUNCTYPE(POINTER(self.ALCdevice), POINTER(self.ALCint)) ... self.__all__ = [... 'ALC_HRTF_STATUS_SOFT', 'ALC_NUM_HRTF_SPECIFIERS_SOFT', 'ALC_HRTF_SPECIFIER_SOFT', 'ALC_HRTF_DISABLED_SOFT', 'ALC_HRTF_ENABLED_SOFT', 'ALC_HRTF_DENIED_SOFT', 'ALC_HRTF_REQUIRED_SOFT', 'ALC_HRTF_HEADPHONES_DETECTED_SOFT', 'ALC_HRTF_UNSUPPORTED_FORMAT_SOFT', 'alcGetStringiSOFT', 'alcResetDeviceSOFT', ...]
Then writing them into the listener class handler.
@16, true. Its a good start. The reason that Alure doesn't have all the features is that the developer of it also develops OpenAL-soft, so has that on top of Open-AL soft. If someone forked the project, you could always add those filters yourself. I personally wish the OpenaL API was a bit simpler. Or that I could declare an error handler callback so I wouldn't have to check for every single OpenAL call.
Huh, hadn't notice that. So its more of a convience handler then with file handling and such over the lower level API? That makes a bit more sense, at least if your using C/C++. Actually, this makes me wonder whether OpenAL Soft is written or compiled in straight C or C++, because its looking like all the files use cpp prefixes. Hm, could probably straight wrap OpenAL Soft at that point, but if your going to end up doing that OpenAL# is further along and would probably take less effort. Well, I guess it really depends on what you want out of it really.
OpenAL-Soft is entirely written in C. Alure is written in C++ though.