AnnAudioEngine.cpp
Go to the documentation of this file.
1 // This is an open source non-commercial project. Dear PVS-Studio, please check it.
2 // PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3 #include "AnnAudioEngine.hpp"
4 #include "AnnLogger.hpp"
5 #include "AnnGetter.hpp"
6 #include "Annwvyn.h"
7 #include <string>
8 
9 using namespace Annwvyn;
10 
12  AnnSubSystem("AudioEngine"),
13  lastError("Initialize OpenAL based sound system"),
14  alDevice(nullptr),
15  alContext(nullptr),
16  audioFileManager(nullptr)
17 {
18  //Try to init OpenAL
19  if(!initOpenAL())
20  logError();
21 
22  //Set the listener to base position
23  const auto player = AnnGetPlayer();
24  const auto position = player->getPosition();
25  alListener3f(AL_POSITION, position.x, position.y, position.z);
26 
27  //Define the default orientation
28  const AnnQuaternion orientation = player->getOrientation().toQuaternion();
29  const auto at = orientation.getAtVector();
30  const auto up = orientation.getUpVector();
31  ALfloat alOrientation[] = { at.x, at.y, at.z, up.x, up.y, up.z };
32 
33  //Apply the orientation
34  alListenerfv(AL_ORIENTATION, alOrientation);
35 
36  //Create a source for the BGM
37  alGenSources(1, &bgmSource);
38  locked = false;
39 
41 }
42 
44 {
45  AnnDebug() << lastError;
46 }
47 
49 {
50  locked = true;
52 
53  delete audioFileManager;
54 }
55 
57 {
58  //If list not null or fist character end of string
59  if(!list || *list == '\0')
60  {
61  AnnDebug(" !!! none !!!\n");
62  }
63  else
64  {
65  do
66  {
67  //Get the first string from the list
68  std::string deviceName(list);
69 
70  AnnDebug() << "detected device : " << deviceName;
71  detectedDevices.push_back(deviceName);
72 
73  list += strlen(list) + 1; //This advance the start of the string after the end of the current one, because sizeof(char) = 1
74  } while(*list != '\0'); //End of the list is marked by \0\0 instead of \0
75  }
76 }
77 
79 {
80  //Open audio playback device
81  //Check if OpenAL support device enumeration extension here
82  if(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")
83  && AnnGetVRRenderer()->usesCustomAudioDevice())
84  {
85  AnnDebug() << "VR device want's to use : " << AnnGetVRRenderer()->getAudioDeviceIdentifierSubString() << " for audio playback...";
86  //Get the list of all devices
87  detectPlaybackDevices(alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER));
88 
89  //Iterate through the name of each device and check if we can find the substring the renderer ask for
90  for(auto& deviceName : detectedDevices)
91  if(deviceName.find(AnnGetVRRenderer()->getAudioDeviceIdentifierSubString())
92  != std::string::npos)
93  {
94  AnnDebug() << "Found " << deviceName << " alDevice!";
95  //Open the selected device
96  alDevice = alcOpenDevice(deviceName.c_str());
97  break;
98  }
99  }
100 
101  //If no device has been set above :
102  if(!alDevice)
103  {
104  AnnDebug() << "No specific OpenAL device set, opening windows default";
105  alDevice = alcOpenDevice(nullptr);
106  }
107  if(!alDevice)
108  {
109  lastError = "Failed to open an OpenAL device";
110  return false;
111  }
112 
113  //Create context and make it current
114  alContext = alcCreateContext(alDevice, nullptr);
115  if(!alContext)
116  {
117  lastError = "Failed to create an OpenAL Context";
118  return false;
119  }
120  if(!alcMakeContextCurrent(alContext))
121  {
122  lastError = "failed to make " + std::to_string(reinterpret_cast<uint64_t>(alContext)) + " as current context";
123  return false;
124  }
125 
126  //Display information
127  const std::string alVendor{ alGetString(AL_VENDOR) };
128  AnnDebug(Log::Important) << "OpenAL version : " << alGetString(AL_VERSION);
129  AnnDebug(Log::Important) << "OpenAL vendor : " << alVendor;
130 
131  if(alVendor != "OpenAL Community")
132  {
133  displayWin32ErrorMessage("Wrong version of OpenAL loaded",
134  "The audio engine loaded an OpenAL DLL that isn't the implementation from the OpenAL soft project.\n"
135  "This probably means the game has an installation or configuration problem. OpenAL32.dll should be loaded from the Annwvyn SDK");
136 
138  "Loaded OpenAL library shipped by " + alVendor + " Instead of openal-soft");
139  }
140 
141  return true;
142 }
143 
145 {
146  //Stop and delete the bgmSource buffer
147  alSourceStop(bgmSource);
148  alDeleteSources(1, &bgmSource);
149 
150  //Stop and delete other audio sources
151  audioSources.clear();
152 
153  //Delete the BGM buffer if it has been initialized
154  if(alIsBuffer(bgmBuffer) == AL_TRUE)
155  alDeleteBuffers(1, &bgmBuffer);
156 
157  //Delete all buffers created here
158  for(auto buffer : buffers)
159  alDeleteBuffers(1, &buffer.second);
160 
161  //Close the AL environment
162  alcMakeContextCurrent(nullptr);
163  alcDestroyContext(alContext);
164  alcCloseDevice(alDevice);
165  alGetError(); //Purge pending error.
166 
168 }
169 
171 {
172  loadBuffer(filename);
173 }
174 
176 {
177  auto query = buffers.find(filename);
178  if(query != buffers.end())
179  return query->second;
180  return false;
181 }
182 
184 {
185  if(auto buffer = isBufferLoader(filename)) return buffer;
186 
187  AnnDebug() << filename << " Not loaded on an OpenAL buffer, loading from file...";
188 
189  //Attempt to retrieve the resource...
190  auto audioFileResource = audioFileManager->getResourceByName(filename).staticCast<AnnAudioFile>();
191  if(!audioFileResource) //Cannot get it? Load that resource by hand to see
192  {
193  audioFileResource = audioFileManager->load(filename, AnnGetResourceManager()->getDefaultResourceGroupName());
194  if(!audioFileResource) //Okay, that file doesn't exist or something.
195  {
196  AnnDebug() << "Error, cannot load file " << filename << " as a recognized audio file";
197  return 0;
198  }
199  }
200 
201  // Open Audio file with libsndfile
202  SF_INFO fileInfos;
203  auto File = sf_open_virtual(audioFileResource->getSndFileVioStruct(), SFM_READ, &fileInfos, audioFileResource.getPointer());
204 
205  //get the number of sample and the sample-rate (in samples by seconds)
206  auto nbSamples = static_cast<ALsizei>(fileInfos.channels * fileInfos.frames);
207  auto sampleRate = static_cast<ALsizei>(fileInfos.samplerate);
208 
209  AnnDebug(Log::Important) << "Loading " << nbSamples << " samples. Playback sample-rate : " << sampleRate << "Hz";
210 
211  //Read samples in 16bits signed
212  std::vector<float> samplesBuffer(nbSamples);
213  auto readSamples = sf_read_float(File, samplesBuffer.data(), nbSamples);
214 
215  //This sometimes happen with OGG files, but it seems to run fine anyway. This is probably due to meta-data/tags present at the end of files
216  if(readSamples < nbSamples)
217  {
218  lastError = "Warning: It looks like the " + std::to_string(nbSamples - readSamples);
219  lastError += " last samples of the file have been omitted. Ignore if file has meta-data appended at the end. ";
220  logError();
221  }
222 
223  if(sf_error(File) != SF_ERR_NO_ERROR)
224  {
225  lastError = "Error while reading the file " + filename + " through sndfile library: ";
226  lastError += sf_error_number(sf_error(File));
227  logError();
228  return 0;
229  }
230 
231  std::vector<ALshort> alSamplesBuffer(nbSamples);
232  for(size_t i(0); i < alSamplesBuffer.size(); i++)
233  //This will step down a bit the amplitude (88% of max) of the signal to prevent saturation while using some formats (OGG)
234  alSamplesBuffer[i] = static_cast<ALshort>(0x7FFF * samplesBuffer[i] * 0.88f);
235 
236  //close file
237  sf_close(File);
238 
239  //Read the number of channels. sound effects should be mono and background music should be stereo
240  ALenum Format;
241  switch(fileInfos.channels)
242  {
243  case 1:
244  AnnEngine::writeToLog("Mono 16bits sound loaded");
245  Format = AL_FORMAT_MONO16;
246  break;
247  case 2:
248  AnnEngine::writeToLog("Stereo 16bits sound loaded");
249  Format = AL_FORMAT_STEREO16;
250  break;
251 
252  default:
253  lastError = "Error : File has to have either one or two audio channel to be loaded";
254  logError();
255  return 0;
256  }
257 
258  //create OpenAL buffer
259  ALuint buffer;
260  alGenBuffers(1, &buffer);
261  AnnDebug() << "Created OpenAL buffer at index " << buffer;
262 
263  //load data into buffer
264  alBufferData(buffer, Format, &alSamplesBuffer[0], nbSamples * sizeof(ALshort), sampleRate);
265 
266  //check errors
267  if(alGetError() != AL_NO_ERROR)
268  {
269  lastError = "Error : cannot create an audio buffer for : " + filename;
270  logError();
271  return 0;
272  }
273 
274  AnnDebug() << filename << " successfully loaded into audio engine";
275  buffers[filename] = buffer;
276  return buffer;
277 }
278 
280 {
281  if(locked) return;
282 
283  //Search for the buffer
284  AnnDebug() << "Unloading sound file " << filename;
285  auto query = buffers.find(filename);
286  if(query == buffers.end())
287  {
288  lastError = "Error: cannot unload buffer " + filename + " is unknown";
289  logError();
290  return; //if query is equal to iterator::end(), buffer isn't known
291  }
292 
293  //Get the buffer from the iterator
294  AnnDebug() << "Sound file found by the Audio resource system. OpenAL buffer " << query->second;
295  auto buffer = query->second;
296 
297  //Free it from memory and remove the object from the buffer list
298  alDeleteBuffers(1, &buffer);
299  AnnDebug() << "Buffer deleted";
300  buffers.erase(query);
301 }
302 
303 void AnnAudioEngine::playBGM(const std::string& filename, const float volume)
304 {
305  AnnDebug() << "Using " << filename << " as BGM";
306 
307  //Load buffer from disk or cache
308  bgmBuffer = loadBuffer(filename);
309 
310  //Set parameters to the source
311  alSourcei(bgmSource, AL_BUFFER, bgmBuffer);
312  alSourcei(bgmSource, AL_LOOPING, AL_TRUE);
313  alSourcef(bgmSource, AL_GAIN, volume);
314 
315  //Put the source in play mode
316  alSourcePlay(bgmSource);
317 }
318 
320 {
321  AnnDebug() << "Stop any BGM playing";
322  alSourceStop(bgmSource);
323 }
324 
326 {
327  alListener3f(AL_POSITION, pos.x, pos.y, pos.z);
328  //make sure that all positions are synced
329  for(auto source : audioSources)
330  if(source->posRelToPlayer)
331  {
332  AnnVect3 absolute = pos + source->pos;
333  alSource3f(source->source, AL_POSITION, absolute.x, absolute.y, absolute.z);
334  }
335 }
336 
338 {
339  const auto at = orient.getAtVector(); // Direction object facing
340  const auto up = orient.getUpVector(); // Up Vector
341 
342  const ALfloat orientation[] = { at.x, at.y, at.z, up.x, up.y, up.z };
343 
344  alListenerfv(AL_ORIENTATION, orientation);
345 }
346 
348 {
349  const auto pose = AnnGetVRRenderer()->trackedHeadPose;
350  updateListenerPos(pose.position);
351  updateListenerOrient(pose.orientation);
352 }
353 
355 {
356  return lastError;
357 }
358 
360 {
361  auto source = createSource();
362  source->changeSound(filename);
363  return source;
364 }
365 
367 {
368  audioSources.remove(source);
369 }
370 
372 {
373  auto audioSource = std::make_shared<AnnAudioSource>();
374  audioSource->bufferName = "Nothing";
375  alGenSources(1, &audioSource->source);
376 
377  audioSources.push_back(audioSource);
378  AnnDebug() << "OpenAL:source:" << audioSource->source << " successfully created";
379 
380  //Return it to the caller
381  return audioSource;
382 }
383 
385  source(0),
386  pos(AnnVect3::ZERO),
387  posRelToPlayer(false)
388 {
389 }
390 
392 {
393  AnnDebug() << "Destroying source";
394  stop();
395  alDeleteSources(1, &source);
396  source = AL_NONE;
397 }
398 
400 {
401  alSource3f(source, AL_POSITION, position.x, position.y, position.z);
402  pos = position;
403 }
404 
405 void AnnAudioSource::setVolume(float gain) const
406 {
407  alSourcef(source, AL_GAIN, gain);
408 }
409 
411 {
412  alSourceRewind(source);
413 }
414 
416 {
417  alSourcePlay(source);
418 }
419 
421 {
422  alSourcePause(source);
423 }
424 
426 {
427  alSourceStop(source);
428 }
429 
431 {
432  if(filename.empty()) return;
433  bufferName = filename;
434 
435  auto buffer = AnnGetAudioEngine()->loadBuffer(bufferName);
436  if(buffer) alSourcei(source, AL_BUFFER, buffer);
437 }
438 
439 void AnnAudioSource::setLooping(bool looping) const
440 {
441  alSourcei(source, AL_LOOPING, looping ? AL_TRUE : AL_FALSE);
442 }
443 
445 {
446  posRelToPlayer = rel;
447 }
ALCdevice * alDevice
AL alDevice.
std::unordered_map< std::string, ALuint > buffers
Map between audio filenames and OpenAL buffer.
void setLooping(bool looping=true) const
If looping is activated, the sound will replay when finished.
virtual AnnAudioFilePtr load(const Ogre::String &name, const Ogre::String &group)
Load a file via the AudioFileManager.
void changeSound(std::string name)
Change the sound buffer this source plays.
T empty(T... args)
ALuint source
OpenAL source object.
void displayWin32ErrorMessage(const std::string &title, const std::string &content)
AnnVect3 getUpVector() const
Get a vector pointing upwards from this quaternion.
void stopBGM() const
stop the current background music from playing
void rewind() const
Put the audio read position at the origin.
T to_string(T... args)
void play() const
Play the sound.
A 3D Vector.
Definition: AnnVect3.hpp:16
AnnAudioEngine()
class constructor
T end(T... args)
void setVolume(float gain) const
Namespace containing the totality of Annwvyn components.
Definition: AnnGetter.cpp:8
AnnOgreVRRendererPtr AnnGetVRRenderer()
Get the VR renderer.
Definition: AnnGetter.cpp:20
void playBGM(const std::string &filename, float volume=0.5f)
std::string lastError
The last error this class has generated.
Exception regarding engine utilisation. See message.
void setPositon(AnnVect3 position)
Put the audio source at this position in space.
~AnnAudioEngine()
class destructor
ALuint bgmSource
Audio source for background music.
void shutdownOpenAL()
shutdown and cleanup OpenAL
T push_back(T... args)
Ogre resource that contain the data from a binary file for the audio engine importing.
bool locked
Prevent some operation if set to true.
T data(T... args)
static void clearSndFileVioStruct()
For cleanup. Will deallocate the VioStruct and set the pointer back to nullptr.
OpenAL audio handling for Annwvyn handle the OpenAL context creation and the loading of sound files h...
Create a ostream to the Ogre logger.
bool initOpenAL()
init OpenAL
AnnAudioFileManager * audioFileManager
Custom Ogre resource manager that loads binary files used to load audio files.
ALuint loadBuffer(const std::string &filename)
static void updateListenerOrient(AnnQuaternion orient)
std::list< AnnAudioSourcePtr > audioSources
List of the audio sources object present in the audio engine.
T erase(T... args)
Audio file ResourceManager.
void setPositionRelToPlayer(bool relToPlayer=true)
If true, the sound position will use (and update) the player&#39;s current position as origin...
void pause() const
Pause the sound.
AnnAudioEnginePtr AnnGetAudioEngine()
Get the audio engine.
Definition: AnnGetter.cpp:12
Represent a Quaternion.
Open an output stream to the engine log.
Definition: AnnLogger.hpp:24
ALuint bgmBuffer
Audio buffer for background music.
void stop() const
Stop playing the sound.
static void writeToLog(std::string message, bool flag=true)
Definition: AnnEngine.cpp:356
Parent class of all Annwvyn SubSystem.
T find(T... args)
Main Annwvyn include file (to be used by client application)
T size(T... args)
void logError() const
Write last error text to the log.
std::string bufferName
Name of the buffer (filename)
ALuint isBufferLoader(const std::string &filename)
Return "false" if buffer not loaded. Return buffer index if buffer is loaded.
AnnAudioSource()
Private constructor. You have to call AnnAudioEngine::createAudioSource() to get an AnnAudioSource ob...
AnnVect3 getAtVector() const
Get a vector pointing in the direction if this quaternion.
~AnnAudioSource()
Destroy audio source.
AnnVect3 pos
Position of this source.
#define ANN_ERR_NOTINIT
std::vector< std::string > detectedDevices
List of audio device names.
AnnAudioSourcePtr createSource()
Create an audio source.
ALCcontext * alContext
AL Context.
bool posRelToPlayer
Relative to player, or not.
AnnResourceManagerPtr AnnGetResourceManager()
Get the resource manager.
Definition: AnnGetter.cpp:18
AnnPlayerBodyPtr AnnGetPlayer()
Get the player object.
Definition: AnnGetter.cpp:17
void detectPlaybackDevices(const char *list)
Detect playback devices from the device enumeration string.
void unloadBuffer(const std::string &filename)
std::string getLastError() const
Get the last error message that occurred in-engine.
void updateListenerPos(AnnVect3 pos)
void update() override
Update the subsystem : set the listener position.
void removeSource(AnnAudioSourcePtr source)
Remove an audio source frop the engine.
void preLoadBuffer(const std::string &filename)