Leviathan  0.8.0.0
Leviathan game engine
Leviathan::SoundDevice Class Reference

Manages loading the audio library and provides some helpers. More...

#include <SoundDevice.h>

Classes

struct  Implementation
 

Public Member Functions

DLLEXPORT SoundDevice ()
 
DLLEXPORT ~SoundDevice ()
 
DLLEXPORT bool Init (bool simulatesound=false)
 
DLLEXPORT void Release ()
 
DLLEXPORT void Tick (float elapsed)
 
DLLEXPORT void SetSoundListenerPosition (const Float3 &pos, const Float4 &orientation)
 Loads the file and plays the sound. More...
 
DLLEXPORT void SetGlobalVolume (float vol)
 
DLLEXPORT void Play2DSoundEffect (const std::string &filename)
 Plays a 2d sound without possibility of interrupting. More...
 
DLLEXPORT AudioSource::pointer Play2DSound (const std::string &filename, bool looping)
 Plays a 2d sound with options. More...
 
DLLEXPORT AudioSource::pointer CreateProceduralSound (const Sound::ProceduralSoundData::pointer &data, size_t chunksize=56000, size_t chunkstoqueue=4)
 Opens an audio source from a procedural data stream. More...
 
DLLEXPORT Sound::AudioBuffer::pointer GetBufferFromFile (const std::string &filename, bool cache=true)
 Creates a sound buffer from a file. More...
 
DLLEXPORT AudioSource::pointer GetAudioSource ()
 Creates an audio source with no settings applied. More...
 
DLLEXPORT void BabysitAudio (AudioSource::pointer audio)
 This class holds the audio source until it has finished playing and then releases the reference. More...
 
DLLEXPORT void ReportDestroyedBuffer (const alure::Buffer &buffer)
 

Detailed Description

Manages loading the audio library and provides some helpers.

Definition at line 15 of file SoundDevice.h.

Constructor & Destructor Documentation

◆ SoundDevice()

SoundDevice::SoundDevice ( )

Definition at line 64 of file SoundDevice.cpp.

64 : Pimpl(std::make_unique<Implementation>()) {}

◆ ~SoundDevice()

SoundDevice::~SoundDevice ( )

Definition at line 65 of file SoundDevice.cpp.

66 {
67  Release();
68  Pimpl.reset();
69 }
DLLEXPORT void Release()

Member Function Documentation

◆ BabysitAudio()

DLLEXPORT void SoundDevice::BabysitAudio ( AudioSource::pointer  audio)

This class holds the audio source until it has finished playing and then releases the reference.

Definition at line 433 of file SoundDevice.cpp.

434 {
436 
437  Pimpl->HandledAudioSources.push_back(audio);
438 }
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ CreateProceduralSound()

DLLEXPORT AudioSource::pointer SoundDevice::CreateProceduralSound ( const Sound::ProceduralSoundData::pointer data,
size_t  chunksize = 56000,
size_t  chunkstoqueue = 4 
)

Opens an audio source from a procedural data stream.

Definition at line 349 of file SoundDevice.cpp.

352 {
354 
355  if(!Pimpl || !data)
356  return nullptr;
357 
358  const auto source = GetAudioSource();
359 
360  if(!source)
361  return nullptr;
362 
363  source->PlayWithDecoder(data, chunksize, chunkstoqueue);
364  return source;
365 }
DLLEXPORT AudioSource::pointer GetAudioSource()
Creates an audio source with no settings applied.
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ GetAudioSource()

DLLEXPORT AudioSource::pointer SoundDevice::GetAudioSource ( )

Creates an audio source with no settings applied.

Definition at line 419 of file SoundDevice.cpp.

420 {
422 
423  auto alureSource = Pimpl->Context.createSource();
424 
425  if(!alureSource) {
426  LOG_ERROR("SoundDevice: GetAudioSource: couldn't create alure source");
427  return nullptr;
428  }
429 
430  return AudioSource::MakeShared<AudioSource>(alureSource);
431 }
#define LOG_ERROR(x)
Definition: Define.h:92
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ GetBufferFromFile()

DLLEXPORT Sound::AudioBuffer::pointer SoundDevice::GetBufferFromFile ( const std::string &  filename,
bool  cache = true 
)

Creates a sound buffer from a file.

Definition at line 367 of file SoundDevice.cpp.

369 {
371 
372  const auto now = Time::GetCurrentTimePoint();
373 
374  AudioBuffer::pointer buffer;
375 
376  // Find buffer from cache
377  if(cache) {
378  const auto found = Pimpl->BufferCache.find(filename);
379 
380  if(found != Pimpl->BufferCache.end()) {
381  buffer = std::get<0>(found->second);
382  std::get<1>(found->second) = now;
383  }
384  }
385 
386  if(!buffer) {
387  // Not cached
388 
389  const auto found = Pimpl->FilenameOpenBuffers.find(filename);
390 
391  if(found != Pimpl->FilenameOpenBuffers.end()) {
392  return found->second;
393  }
394 
395  try {
396  auto alureBuffer = Pimpl->Context.getBuffer(filename);
397 
398  if(!alureBuffer || alureBuffer.getFrequency() < 1) {
399  LOG_ERROR("SoundDevice: failed to create buffer from file: " + filename);
400  return nullptr;
401  }
402 
403  buffer = AudioBuffer::MakeShared<AudioBuffer>(std::move(alureBuffer), this);
404 
405  } catch(const std::runtime_error& e) {
406  LOG_ERROR("SoundDevice: failed to create buffer from file: " + filename +
407  ", exception: " + e.what());
408  return nullptr;
409  }
410 
411  if(cache)
412  Pimpl->BufferCache[filename] = {buffer, now};
413  Pimpl->FilenameOpenBuffers[filename] = buffer;
414  }
415 
416  return buffer;
417 }
#define LOG_ERROR(x)
Definition: Define.h:92
static DLLEXPORT TimePoint GetCurrentTimePoint()
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ Init()

bool SoundDevice::Init ( bool  simulatesound = false)
Parameters
simulatenosoundIf true the sound device isn't initialized to simulate not having a valid audio device (or if the user just doesn't want sound)

Definition at line 71 of file SoundDevice.cpp.

72 {
73  if(simulatesound) {
74  LOG_WARNING("SoundDevice: simulating not having a playing device");
75  return true;
76  }
77 
78  Pimpl->DeviceManager = alure::DeviceManager::getInstance();
79 
80  const auto defaultChoice =
81  Pimpl->DeviceManager.defaultDeviceName(alure::DefaultDeviceType::Full);
82 
83  auto devices = Pimpl->DeviceManager.enumerate(alure::DeviceEnumeration::Full);
84 
85  if(defaultChoice.empty()) {
86 
87  LOG_ERROR("SoundDevice: no default sound device detected");
88  return false;
89  }
90 
91  if(devices.empty()) {
92 
93  LOG_ERROR("SoundDevice: no audio devices detected");
94  return false;
95  }
96 
97  std::string selectedDevice;
98  // There's no print error here if missing to make tests run
99  ObjectFileProcessor::LoadValueFromNamedVars<std::string>(
100  Engine::Get()->GetDefinition()->GetValues(), "AudioDevice", selectedDevice, "default");
101 
102  if(selectedDevice == "default") {
103 
104  selectedDevice = defaultChoice;
105 
106  } else if(std::find(devices.begin(), devices.end(), selectedDevice) == devices.end()) {
107  LOG_ERROR("SoundDevice: selected audio device \"" + selectedDevice +
108  "\" doesn't exists. Using default");
109  selectedDevice = defaultChoice;
110  }
111 
112  LOG_INFO("SoundDevice: Initializing sound with device: " + selectedDevice);
113 
114  std::stringstream sstream;
115 
116  sstream << "Start of audio system information:\n"
117  << "// ------------------------------------ //\n";
118 
119  auto defaultDeviceName =
120  Pimpl->DeviceManager.defaultDeviceName(alure::DefaultDeviceType::Basic);
121  devices = Pimpl->DeviceManager.enumerate(alure::DeviceEnumeration::Basic);
122 
123  sstream << "Available basic devices:\n";
124  for(const auto& name : devices) {
125  sstream << "\t> " << name;
126  if(name == defaultDeviceName)
127  sstream << " [DEFAULT]";
128  sstream << "\n";
129  }
130 
131  sstream << "\n";
132 
133  devices = Pimpl->DeviceManager.enumerate(alure::DeviceEnumeration::Full);
134  defaultDeviceName = Pimpl->DeviceManager.defaultDeviceName(alure::DefaultDeviceType::Full);
135 
136  sstream << "Available devices:\n";
137  for(const auto& name : devices) {
138  sstream << "\t> " << name;
139  if(name == defaultDeviceName)
140  sstream << " [DEFAULT]";
141  sstream << "\n";
142  }
143  sstream << "\n";
144 
145  devices = Pimpl->DeviceManager.enumerate(alure::DeviceEnumeration::Capture);
146  defaultDeviceName =
147  Pimpl->DeviceManager.defaultDeviceName(alure::DefaultDeviceType::Capture);
148  sstream << "Available capture devices:\n";
149  for(const auto& name : devices) {
150  sstream << "\t> " << name;
151  if(name == defaultDeviceName)
152  sstream << " [DEFAULT]";
153  sstream << "\n";
154  }
155  sstream << "\n";
156 
157  try {
158  Pimpl->Device = Pimpl->DeviceManager.openPlayback(selectedDevice);
159  } catch(const std::exception& e) {
160  LOG_INFO(sstream.str());
161  LOG_ERROR("SoundDevice: opening playback failed: " + std::string(e.what()));
162  return false;
163  }
164 
165  sstream << "Info for device \"" << Pimpl->Device.getName(alure::PlaybackName::Full)
166  << "\":" << std::endl;
167  auto version = Pimpl->Device.getALCVersion();
168  sstream << "ALC version: " << version.getMajor() << "." << version.getMinor() << "\n";
169  version = Pimpl->Device.getEFXVersion();
170  if(!version.isZero()) {
171  sstream << "EFX version: " << version.getMajor() << "." << version.getMinor() << "\n";
172  sstream << "Max auxiliary sends: " << Pimpl->Device.getMaxAuxiliarySends() << "\n";
173  } else
174  sstream << "EFX not supported"
175  << "\n";
176 
177  // TODO: extensions
178  // Pimpl->Device.queryExtension(const String &name)
179 
180  sstream << "// ------------------------------------ //";
181 
182  LOG_INFO(sstream.str());
183 
184  try {
185  Pimpl->Context = Pimpl->Device.createContext();
186 
187  if(!Pimpl->Context)
188  throw Exception("returned context is null");
189 
190  } catch(const std::exception& e) {
191  LOG_ERROR("SoundDevice: opening context failed: " + std::string(e.what()));
192  return false;
193  }
194 
195  alure::Context::MakeCurrent(Pimpl->Context);
196  Pimpl->Listener = Pimpl->Context.getListener();
197 
198  Pimpl->Context.setMessageHandler(alure::MakeShared<SoundMessageHandler>());
199 
200  // Setup global volume //
201  SetGlobalVolume(1.f);
202 
203  Pimpl->Listener.setPosition({0, 0, 0});
204  Pimpl->Listener.set3DParameters(
205  // Pos
206  {0, 0, 0},
207  // Velocity
208  {0, 0, 0},
209  // Orientation, at and up
210  {{0, 0, 0}, {0, 1, 0}});
211 
212  return true;
213 }
#define LOG_INFO(x)
Definition: Define.h:90
#define LOG_ERROR(x)
Definition: Define.h:92
DLLEXPORT void SetGlobalVolume(float vol)
Base class for all exceptions thrown by Leviathan.
Definition: Exceptions.h:10
#define LOG_WARNING(x)
Definition: Define.h:91
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86

◆ Play2DSound()

DLLEXPORT AudioSource::pointer SoundDevice::Play2DSound ( const std::string &  filename,
bool  looping 
)

Plays a 2d sound with options.

Returns
The audio source that is playing the sound (this must be held onto until it is done playing, can be passed to BabysitAudio if not manually wanted to be managed or use the SoundEffect variant of this method) may be null on error

Definition at line 328 of file SoundDevice.cpp.

330 {
332 
333  const auto buffer = GetBufferFromFile(filename);
334  if(!buffer)
335  return nullptr;
336 
337  const auto source = GetAudioSource();
338 
339  if(!source)
340  return nullptr;
341 
342  source->Play2D(buffer);
343 
344  if(looping)
345  source->SetLooping(true);
346  return source;
347 }
DLLEXPORT Sound::AudioBuffer::pointer GetBufferFromFile(const std::string &filename, bool cache=true)
Creates a sound buffer from a file.
DLLEXPORT AudioSource::pointer GetAudioSource()
Creates an audio source with no settings applied.
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ Play2DSoundEffect()

DLLEXPORT void SoundDevice::Play2DSoundEffect ( const std::string &  filename)

Plays a 2d sound without possibility of interrupting.

Definition at line 310 of file SoundDevice.cpp.

311 {
313 
314  const auto buffer = GetBufferFromFile(filename);
315  if(!buffer)
316  return;
317 
318  const auto source = GetAudioSource();
319 
320  if(!source)
321  return;
322 
323  source->Play2D(buffer);
324 
325  Engine::Get()->RunOnMainThread([=]() { this->BabysitAudio(source); });
326 }
DLLEXPORT Sound::AudioBuffer::pointer GetBufferFromFile(const std::string &filename, bool cache=true)
Creates a sound buffer from a file.
DLLEXPORT AudioSource::pointer GetAudioSource()
Creates an audio source with no settings applied.
DLLEXPORT void RunOnMainThread(const std::function< void()> &function)
Runs the function now if on the main thread otherwise calls Invoke.
Definition: Engine.cpp:1190
DLLEXPORT void BabysitAudio(AudioSource::pointer audio)
This class holds the audio source until it has finished playing and then releases the reference.
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ Release()

void SoundDevice::Release ( )

Definition at line 215 of file SoundDevice.cpp.

216 {
217  if(!Pimpl->Device)
218  return;
219 
220  Pimpl->HandledAudioSources.clear();
221  Pimpl->BufferCache.clear();
222  Pimpl->FilenameOpenBuffers.clear();
223 
224  alure::Context::MakeCurrent(nullptr);
225  Pimpl->Listener = nullptr;
226  Pimpl->Context.destroy();
227  Pimpl->Device.close();
228 
229  Pimpl->Device = nullptr;
230 }

◆ ReportDestroyedBuffer()

DLLEXPORT void SoundDevice::ReportDestroyedBuffer ( const alure::Buffer &  buffer)

Definition at line 440 of file SoundDevice.cpp.

441 {
442  if(!buffer)
443  return;
444 
446 
447  LEVIATHAN_ASSERT(buffer.getName() != "test stuff", "test compare failed");
448 
449  Pimpl->Context.removeBuffer(buffer);
450 }
#define LEVIATHAN_ASSERT(x, msg)
Definition: Define.h:100
static DLLEXPORT Engine * Get()
Definition: Engine.cpp:86
DLLEXPORT void AssertIfNotMainThread() const
Asserts if not called on the main thread.
Definition: Engine.h:111

◆ SetGlobalVolume()

DLLEXPORT void SoundDevice::SetGlobalVolume ( float  vol)
Parameters
volThe volume [0.f, 1.f]

Definition at line 303 of file SoundDevice.cpp.

304 {
305  vol = std::clamp(vol, 0.f, 1.f);
306 
307  Pimpl->Listener.setGain(vol);
308 }
uint8_t clamp(int16_t value)
Definition: YUVToRGB.cpp:17

◆ SetSoundListenerPosition()

DLLEXPORT void SoundDevice::SetSoundListenerPosition ( const Float3 pos,
const Float4 orientation 
)

Loads the file and plays the sound.

This creates a temporary SoundEffect on a background thread that is loaded from the file and destroyed once it finishes

Returns
False if the file doesn't exist or the sound couldn't be played for some other reason

Definition at line 277 of file SoundDevice.cpp.

279 {
280  if(!Pimpl->Listener)
281  return;
282 
283  bs::Quaternion quaternion(orientation);
284 
285  bs::Radian angle;
286  bs::Vector3 direction;
287 
288  // TODO: does this do the right thing?
289  quaternion.toAxisAngle(direction, angle); // toAngleAxis(angle, direction);
290 
291  alure::Vector3 at(0, 0, 0);
292 
293  // TODO: better velocity calculation
294  const auto temp = pos - Pimpl->PreviousPosition;
295  alure::Vector3 velocity(temp.X, temp.Y, temp.Z);
296 
297  Pimpl->Listener.set3DParameters(
298  {pos.X, pos.Y, pos.Z}, velocity, {at, {direction.x, direction.y, direction.z}});
299 
300  Pimpl->PreviousPosition = pos;
301 }

◆ Tick()

void SoundDevice::Tick ( float  elapsed)

Definition at line 232 of file SoundDevice.cpp.

233 {
234  ElapsedSinceLastClean += elapsed;
235 
236  Pimpl->Context.update();
237 
238  if(ElapsedSinceLastClean > 0.2f) {
239  ElapsedSinceLastClean = 0;
240 
241  for(auto iter = Pimpl->HandledAudioSources.begin();
242  iter != Pimpl->HandledAudioSources.end();) {
243 
244  if(!(*iter)->IsPlaying()) {
245 
246  iter = Pimpl->HandledAudioSources.erase(iter);
247  } else {
248  ++iter;
249  }
250  }
251 
252  for(auto iter = Pimpl->FilenameOpenBuffers.begin();
253  iter != Pimpl->FilenameOpenBuffers.end();) {
254 
255  if(iter->second->GetRefCount() == 1) {
256  iter = Pimpl->FilenameOpenBuffers.erase(iter);
257  } else {
258  ++iter;
259  }
260  }
261 
262  const auto now = Time::GetCurrentTimePoint();
263 
264  // Clear cache entries
265  for(auto iter = Pimpl->BufferCache.begin(); iter != Pimpl->BufferCache.end();) {
266  if(now - std::get<1>(iter->second) > Pimpl->CacheSoundSeconds) {
267  // Remove from cache
268 
269  iter = Pimpl->BufferCache.erase(iter);
270  } else {
271  ++iter;
272  }
273  }
274  }
275 }
static DLLEXPORT TimePoint GetCurrentTimePoint()

The documentation for this class was generated from the following files: