Up until now we have been letting Alut do all the real tricky stuff for us. For example handling the audio devices. It's really nice that the Alut library is there to provide this functionality, but any smart coder will want to know exactly what their doing. We may want to, at some point, use the Alc directly. In this tutorial we will expose the Alc layer and take a look at how to handle the devices on our own.
ALCdevice* pDevice; ALCubyte DeviceName = "DirectSound3D"; pDevice = alcOpenDevice(DeviceName);
So what is an Alc device? Try to think of it in terms of a resource. OpenAL grabs a handle to the hardware being used, which must in turn be shared with the entire system. A device can be of a specific implementation as well, as in this case where we are using DirectSound as the audio device. This code grabs a handle to the hardware device and readies it to be used by the application. Eventually we should see more devices made for specific soundcards.
Passing NULL to 'alcOpenDevice' is a perfectly valid argument. It forces the Alc to use a default device.
ALCcontext* pContext; pContext = alcCreateContext(pDevice, NULL); alcMakeContextCurrent(pContext);
What is an Alc context? OpenGL coders will recall that there was rendering contexts used by OpenGL that controlled the state management across different windows. An 'HGLRC' as they are called could be created several times to enable multiple rendering windows. And different rendering states for each context could be achieved. An Alc context works on the same principal. First we tell it which device to use (which we have already created), then we make that context current. In theory you could create multiple rendering contexts for different windows, and set the state variables differently and have it work just fine. Although the term "rendering context" usually applies to a visual rendering, this is the term preferred in the sdk docs and should be the term used.
You may notice too that the second parameter in 'alcCreateContext' has been set to NULL. The OpenAL sdk from Creative Labs defines the following variables which are optional flags to that parameter.
If you were to create multiple contexts you could make them interchangeable by making a call to 'alcMakeContextCurrent'. Sending NULL to 'alcMakeContextCurrent' is also a perfectly valid argument. It will prevent processing of any audio data. Be aware that even if you have multiple rendering contexts, you can only have one current at a time, and when your application needs to use two contexts interchangeably you must be the one to make sure the appropriate context is current. And if you do decide to do this, then there may be times when you want to know exactly which context is current without going through a big check.
ALcontext* pCurContext; pCurContext = alcGetCurrentContext();
Once you have your context you can also obtain the device in use by that context.
ALdevice* pCurDevice; pCurDevice = alcGetContextsDevice(pCurContext);
Above we used the context we retrieved to find out which device it was using. There is also one other cool feature that was built into Alc for handling contexts.
alcSuspendContext(pContext); // Processing has been suspended to pContext. alcProcessContext(pContext); // Processing has been re-enabled to pContext.
What we have done above was stop, and then resume processing of audio data to the context. When processing has been suspended, no sound will be generated from data sent through that context. A further note on the rendering context: the OpenAL 1.0 spec does imply, but does not explicitly say, that sources and buffers may be used across contexts. The "lifetime" of a source or buffer during the application, is said to be valid as long as the source and buffer id is valid (i.e. they have not been deleted).
alcMakeContextCurrent(NULL); alcDestroyContext(pContext); alcCloseDevice(pDevice);
And that is how we clean up. The current context is defaulted to NULL, the context we created is released, and the handle to the device is given back to the system resources. There is but a few more Alc functions we have not yet covered.
ALenum alcGetError(ALvoid); ALboolean alcIsExtensionPresent(ALCdevice* device, ALubyte* extName); ALvoid* alcGetProcAddress(ALCdevice* device, ALubyte* funcName); ALenum alcGetEnumValue(ALCdevice* device, ALubyte* enumName); ALubyte* alcGetString(ALCdevice* device, ALenum token); ALvoid alcGetIntegerv(ALCdevice* device, ALenum token, ALsizei size, ALint* dest);
It may be pretty obvious to you what these do, but lets humour ourselves and have a closer look. First we have 'alcGetError' which is just like 'alGetError' but will return Alc errors. The next three functions are for querying Alc extensions. This was just the creators planning ahead, as there are no Alc extensions either. The last function, 'alcGetInteger', will return the Alc version when passed 'ALC_MAJOR_VERSION' or 'ALC_MINOR_VERSION'.
The function 'alcGetString' is pretty cool. It can take any of the following three parameters to 'token':
The first will return the device string which your OpenAL implementation will prefer you to use. In current OpenAL this should be "DirectSound3D", like we used above. The second token will return a list of specifiers, but in current OpenAL will only return "DirectSound" (without the "3D" for some reason). The last will return a list of Alc extensions, of which none exist yet.
Well that's most of Alc for you. I hope it gave you a better understanding of how OpenAL interacts with the operation system. You might try writing your own initialization routines so you can cast off Alut altogether. Either way have fun with it.