OpenAL Tutorial – Introduction
OpenAL Tutorial – Introduction
Here is an example of a fully functioning, copy-paste ready code(as soon as you include libraries) you can use to play sounds in your program with OpenAL. Some part of it is made of tutorials I found on internet(unfortunately I couldn’t get any of them working right away and they all needed some tweaking and changing to get working – I hope you won’t have any such problems with my code).
For OpenAL code you will OpenAL SDK libraries, found here: https://www.openal.org/downloads/. You will also need some libraries for decoding ogg files. If I recall well, these were included in the SDK as well. If not, you should be able to find them here https://www.xiph.org/downloads/
Once the program is compiled, it will require OpenAL to be installed on the machine. You can download the installer from the same place as the sdk, it’s very small so you can include it easily in the setup of your software.
If you don’t want to do that, you can try using headers and libs in a rar file at the bottom of the page. You should always download the most recent version of any library you are using.
This code implements OpenGL_Class, which will initialize OpenAL upon construction. You should only hold one instance of it at a time. OpenAL itself has a lot more functionality but this should get you started and serve as a base for expansion. This class allows you to load sound files from disk and play/stop them. It contains data about each of them in a vector of T_Sound objects. You don’t have to deal with that as you can adress each sound by the path you loaded it from disk.
-> OpenAL_Class.h
#pragma once #include <iostream> #include <vector> #include <string> #include <string.h> #include <cstdlib> #include <windows.h> // OpenAL libraries to link against: libogg_static.lib;libvorbis_static.lib;libvorbisfile_static.lib;OpenAL32.lib; #include <AL/al.h> #include <AL/alc.h> #include <AL/Vorbis/vorbisfile.h> using namespace std; int endWithError(char* msg); #define BUFFER_SIZE 32768 // 32 KB buffers #define File_Type_WAV 0 #define File_Type_OGG 1 #define Internal_Error 3 // OpenAL_Class is used to initialize and access OpenAL device. It also handles access to each sound we want to use. class OpenAL_Class { // Intances of T_Sound class represent individual sound files used. It handles things such as play/stop, loading from hdd and decoding(I've made it able to use .wav and .ogg). // Each T_Sound is identified by the location on disk it was loaded from. class T_Sound { public: T_Sound(string path); ~T_Sound(void) {Shutdown();} ; int Play(); int Stop(); int Load(string path); // Load from hdd. bool IsPlaying; // This is true while the sound is playing. You can also use alGetSourcei(source, AL_SOURCE_STATE, &state); void Shutdown(); bool InvalidFile; string filepath; DWORD size,chunkSize; short formatType,channels; DWORD sampleRate,avgBytesPerSec; short bytesPerSample,bitsPerSample; DWORD dataSize; // Load from hdd into RAM and decode it so it's ready to be transfered into the OpenAL device. int LoadWAV(string path); bool LoadOGG(string fileName); int LoadIntoAL(); int GetSoundFileType(string filepath); char* buf; ALuint source; ALuint buffer; ALenum format; //The audio format (bits per sample, number of channels) }; public: // Nothing is done upon construcion. OpenAL_Class(void) { InitOpenAL(); }; ~OpenAL_Class(void) { Shutdown(); }; // This function returns the Sounds vector index of the file loaded from path in argument. int FindSound(string path); // Returns 1 on success. // Use: if( !AddASound(path) ) Error(); else PlayASound(path); int AddASound(string path); // This is more of a joke than it's actually useful, I just felt like making it possible to use += to add new sounds. int operator+= (string path) { return AddASound(path); } void RemoveASound(string path); void PlayASound(string path); void StopASound(string path); bool IsPlaying(string path); private: // This function does the same as FindSound() except it returns the T_Sound object instead of just the index. T_Sound& GetSound( string path) { return *Sounds[FindSound(path)]; } vector<T_Sound*> Sounds; // This vector holds all the sounds we have loaded so far. int InitOpenAL(); void Shutdown(); // OpenAL internal stuff, similar to OpenGL. ALCdevice *device; ALCcontext *context; };
-> OpenAL_Class.cpp
#include "OpenAL_Class.h" int endWithError (char* msg) { printf("%s\n", msg); return 0; } OpenAL_Class::T_Sound ::T_Sound (string path) { format=0; dataSize=0; if( Load(path) !=0 ) InvalidFile=true; IsPlaying = false; }; int OpenAL_Class::T_Sound ::GetSoundFileType (string filepath) { string extension = filepath.substr(filepath.length() - 4, 4); if (extension == ".ogg") return File_Type_OGG; else if (extension == ".wav") return File_Type_WAV; else return Internal_Error; } int OpenAL_Class::T_Sound ::Load (string path) { InvalidFile=false; filepath=path; // Find out what kind of file is given to us by checking it's extension. switch(GetSoundFileType(path)) { // Once we found it, use proper method to decode it and load into RAM. case File_Type_WAV: if( !LoadWAV(path) ) return 1; break; case File_Type_OGG: if( !LoadOGG(path) ) return 2; break; default: return Internal_Error; } // Do final preparations and load the sound from memory into OpenAL device(which is also in RAM but you can't see what it does behind the API). if( !LoadIntoAL()) return Internal_Error; return 0; } int OpenAL_Class::T_Sound:: LoadWAV (string path) { //Loading of the WAVE file FILE *fp = NULL; //Create FILE pointer for the WAVE file fp=fopen((char*)path.c_str(),"rb"); //Open the WAVE file if (!fp) return endWithError("Failed to open file"); //Could not open file char type[4]; //Check that the WAVE file is OK fread(type,sizeof(char),4,fp); //Reads the first bytes in the file if (type[0]!='R' || type[1]!='I' || type[2]!='F' || type[3]!='F') //Should be "RIFF" return endWithError ("No RIFF"); //Not RIFF fread(&size, sizeof(DWORD),1,fp); //Continue to read the file fread(type, sizeof(char),4,fp); //Continue to read the file if (type[0]!='W' || type[1]!='A' || type[2]!='V' || type[3]!='E') //This part should be "WAVE" return endWithError("not WAVE"); //Not WAVE fread(type,sizeof(char),4,fp); //Continue to read the file if (type[0]!='f' || type[1]!='m' || type[2]!='t' || type[3]!=' ') //This part should be "fmt " return endWithError("not fmt "); //Not fmt //Now we know that the file is an acceptable WAVE file //Info about the WAVE data is now read and stored fread(&chunkSize,sizeof(DWORD),1,fp); fread(&formatType,sizeof(short),1,fp); fread(&channels,sizeof(short),1,fp); fread(&sampleRate,sizeof(DWORD),1,fp); fread(&avgBytesPerSec,sizeof(DWORD),1,fp); fread(&bytesPerSample,sizeof(short),1,fp); fread(&bitsPerSample,sizeof(short),1,fp); fread(type,sizeof(char),4,fp); if (type[0]!='d' || type[1]!='a' || type[2]!='t' || type[3]!='a') //This part should be "data" return endWithError("Missing DATA"); //not data fread(&dataSize,sizeof(DWORD),1,fp); //The size of the sound data is read buf= new char[dataSize]; //Allocate memory for the sound data fread(buf,sizeof(BYTE),dataSize,fp) ; //Read the sound data and display the //Clean-up fclose(fp); //Close the WAVE file //Figure out the format of the WAVE file if(bitsPerSample == 8) { if(channels == 1) format = AL_FORMAT_MONO8; else if(channels == 2) format = AL_FORMAT_STEREO8; } else if(bitsPerSample == 16) { if(channels == 1) format = AL_FORMAT_MONO16; else if(channels == 2) format = AL_FORMAT_STEREO16; } if(!format) return endWithError("Wrong BitPerSample"); //Not valid format return true; } bool OpenAL_Class::T_Sound ::LoadOGG (string fileName) { int endian = 0; // 0 for Little-Endian, 1 for Big-Endian int bitStream; long bytes; char array[BUFFER_SIZE]; // Local fixed size array FILE *f; vector<char>buffer; //////////////////////////////////////// //////// OPEN FILE FOR DECODING //////// //////////////////////////////////////// // Open for binary reading f = fopen(fileName.c_str(), "rb"); if(f==NULL) { printf("\nFile not found: %s", fileName.c_str()); return false; } vorbis_info *pInfo; OggVorbis_File oggFile; // Let the SDK do its stuff ov_open(f, &oggFile, NULL, 0); //////////////////////////////////////// ////////////// GET INFO //////////////// //////////////////////////////////////// pInfo = ov_info(&oggFile, -1); if(pInfo==NULL) return false; // Check the number of channels... always use 16-bit samples if (pInfo->channels == 1) format = AL_FORMAT_MONO16; else if (pInfo->channels == 2) format = AL_FORMAT_STEREO16; else if (pInfo->channels == 4) format = alGetEnumValue("AL_FORMAT_QUAD16"); else if (pInfo->channels == 6) format = alGetEnumValue("AL_FORMAT_51CHN16"); // The frequency of the sampling rate sampleRate = pInfo->rate; //////////////////////////////////////// ////////// DECODE THE DATA ///////////// //////////////////////////////////////// do { // Read up to a buffer's worth of decoded sound data bytes = ov_read(&oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream); // Append to end of buffer buffer.insert(buffer.end(), array, array + bytes); } while (bytes > 0); dataSize=buffer.size(); buf=new char[dataSize]; memcpy(buf, buffer.data(), buffer.size()); ov_clear(&oggFile); return true; } int OpenAL_Class::T_Sound ::LoadIntoAL () { alGenBuffers(1, &buffer); //Generate one OpenAL Buffer and link to "buffer" alGenSources(1, &source); //Generate one OpenAL Source and link to "source" if(alGetError() != AL_NO_ERROR) return endWithError("Error GenSource"); //Error during buffer/source generation alBufferData(buffer, format, buf, dataSize, sampleRate); //Store the sound data in the OpenAL Buffer if(alGetError() != AL_NO_ERROR) return endWithError("Error loading ALBuffer"); //Error during buffer loading alBufferi(buffer, AL_CHANNELS, 1); //Sound setting variables. This is used for 3D spanning and other effects. ALfloat SourcePos[] = { 0.0, 0.0, 0.0 }; //Position of the source sound ALfloat SourceVel[] = { 0.0, 0.0, 0.0 }; //Velocity of the source sound ALfloat ListenerPos[] = { 0.0, 0.0, 0.0 }; //Position of the listener ALfloat ListenerVel[] = { 0.0, 0.0, 0.0 }; //Velocity of the listener ALfloat ListenerOri[] = { 0.0, 0.0, -1.0, 0.0, 1.0, 0.0 }; //Orientation of the listener //First direction vector, then vector pointing up) //Listener alListenerfv(AL_POSITION, ListenerPos); //Set position of the listener alListenerfv(AL_VELOCITY, ListenerVel); //Set velocity of the listener alListenerfv(AL_ORIENTATION, ListenerOri); //Set orientation of the listener //Source alSourcei (source, AL_BUFFER, buffer); //Link the buffer to the source alSourcef (source, AL_PITCH, 1.00f ); //Set the pitch of the source alSourcef (source, AL_GAIN, 1.00f ); //Set the gain of the source alSourcefv(source, AL_POSITION, SourcePos); //Set the position of the source alSourcefv(source, AL_VELOCITY, SourceVel); //Set the velocity of the source alSourcei (source, AL_LOOPING, AL_TRUE ); //Set if source is looping sound alSourcef (source, AL_MIN_GAIN, 0.00f); alSourcef (source, AL_MAX_GAIN, 1.00f); float globalMaxDistance = 1000; float globalRefDistance = 10; alSourcef(source, AL_REFERENCE_DISTANCE, globalRefDistance); alSourcef(source, AL_MAX_DISTANCE, globalMaxDistance); alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); //Already false by default. delete[] buf; //Delete the sound data buffer return 1; } int OpenAL_Class::T_Sound ::Play () { alSourcePlay(source); int error = alGetError();//Play the sound buffer linked to the source if(error != AL_NO_ERROR) return error; //Error when playing sound IsPlaying = true; return AL_NO_ERROR; } int OpenAL_Class::T_Sound ::Stop () { //PLAY alSourceStop(source); int error = alGetError(); //Play the sound buffer linked to the source if(error != AL_NO_ERROR) return error; //Error when playing sound IsPlaying = false; return AL_NO_ERROR; } void OpenAL_Class::T_Sound ::Shutdown () { alDeleteSources(1, &source); //Delete the OpenAL Source alDeleteBuffers(1, &buffer); //Delete the OpenAL Buffer } int OpenAL_Class ::InitOpenAL () { device = alcOpenDevice(NULL); //Open the device if(!device) return endWithError("no sound device"); //Error during device opening context = alcCreateContext(device, NULL); //Give the device a context alcMakeContextCurrent(context); //Make the context the current if(!context) return endWithError("no sound context"); //Error during context handeling alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); return 0; } int OpenAL_Class ::AddASound (string path) { Sounds.push_back(new T_Sound(path)); if(Sounds.back()->InvalidFile) return -1; else return Sounds.size()-1; } void OpenAL_Class ::RemoveASound (string path) { int i = FindSound(path); Sounds[i]->Shutdown(); Sounds.erase(Sounds.begin()+i); } void OpenAL_Class ::PlayASound (string path) { int i = FindSound(path); if (i==-1) return; if( !Sounds[i]->InvalidFile ) Sounds[i]->Play(); } void OpenAL_Class ::StopASound (string path) { int i = FindSound(path); if (i==-1) return; if( !Sounds[i]->InvalidFile ) Sounds[i]->Stop(); } int OpenAL_Class ::FindSound (string path) { for(unsigned int i=0;i<Sounds.size();i++) if(Sounds[i]->filepath==path) return i; cout << "File not loaded: " << path << endl; return -1; } bool OpenAL_Class ::IsPlaying (string path) { int i = FindSound(path); if (i==-1) return false; return Sounds[i]->IsPlaying; } void OpenAL_Class ::Shutdown () { alcMakeContextCurrent(NULL); //Make no context current alcDestroyContext(context); //Destroy the OpenAL Context alcCloseDevice(device); //Close the OpenAL Device }
Here is an example program that plays a sound using OpenAL_Class:
-> Source.cpp
#include "OpenAL_Class.h" int main(int argc, char** argv){ string sound_path; argc==1 ? sound_path = "Imperial_March.ogg" : sound_path = argv[1]; puts("Initializing"); OpenAL_Class OpenAL;// Constructor also initializes OpenAL device. puts("Loading music"); OpenAL.AddASound(sound_path); puts("Playing music..."); OpenAL.PlayASound(sound_path); int a; scanf("%d", &a); // Need to put this, otherwise console will close and music will stop. }
Download:
source.rar
libs.rar
music.rar