• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Razakhel / RaZ / 23564992011

25 Mar 2026 09:26PM UTC coverage: 74.502% (+0.004%) from 74.498%
23564992011

push

github

Razakhel
[Audio/AudioSystem] Replaced assertions by exceptions

- These cases can very easily happen to and end-user and should be protected against accordingly

- The unique listener check is now made in all configurations and when linking the entity to the system

- Added a unit test checking these behaviors

21 of 29 new or added lines in 6 files covered. (72.41%)

4 existing lines in 4 files now uncovered.

8643 of 11601 relevant lines covered (74.5%)

1704.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.32
/src/RaZ/Audio/AudioSystem.cpp
1
#include "RaZ/Audio/AudioSystem.hpp"
2
#include "RaZ/Audio/Listener.hpp"
3
#include "RaZ/Audio/Sound.hpp"
4
#include "RaZ/Math/Matrix.hpp"
5
#include "RaZ/Math/Transform.hpp"
6
#include "RaZ/Physics/RigidBody.hpp"
7
#include "RaZ/Utils/Logger.hpp"
8

9
#include "tracy/Tracy.hpp"
10

11
#include <AL/al.h>
12
#include <AL/alc.h>
13

14
namespace Raz {
15

16
namespace {
17

NEW
18
constexpr std::string_view recoverAlcErrorStr(int errorCode) {
×
19
  switch (errorCode) {
×
20
    case ALC_INVALID_DEVICE:  return "Invalid device";
×
21
    case ALC_INVALID_CONTEXT: return "Invalid context";
×
22
    case ALC_INVALID_ENUM:    return "Invalid enum";
×
23
    case ALC_INVALID_VALUE:   return "Invalid value";
×
24
    case ALC_OUT_OF_MEMORY:   return "Out of memory";
×
25
    case ALC_NO_ERROR:        return "No error";
×
26
    default:                  return "Unknown error";
×
27
  }
28
}
29

30
inline void checkError(void* device, std::string_view errorMsg) {
38✔
31
  if (const int errorCode = alcGetError(static_cast<ALCdevice*>(device)); errorCode != ALC_NO_ERROR)
38✔
UNCOV
32
    Logger::error("[OpenAL] {} ({})", errorMsg, recoverAlcErrorStr(errorCode));
×
33
}
38✔
34

35
} // namespace
36

37
AudioSystem::AudioSystem(const std::string& deviceName) {
19✔
38
  ZoneScopedN("AudioSystem::AudioSystem");
39

40
  registerComponents<Sound, Listener>();
19✔
41
  openDevice(deviceName);
19✔
42

43
  if (m_device == nullptr || m_context == nullptr)
19✔
44
    return;
1✔
45

46
  const std::string_view alRenderer = alGetString(AL_RENDERER);
18✔
47

48
  Logger::debug("[AudioSystem] OpenAL renderer: {}", alRenderer);
18✔
49

50
#if !defined(RAZ_PLATFORM_EMSCRIPTEN) // Emscripten has its own implementation with some OpenAL Soft extensions
51
  if (alRenderer != "OpenAL Soft")
18✔
52
    Logger::warn("[OpenAL] Standard OpenAL detected; make sure to use OpenAL Soft to get all possible features");
×
53
#endif
54
}
×
55

56
std::vector<std::string> AudioSystem::recoverDevices() {
2✔
57
  if (!alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
2✔
58
    return {};
×
59

60
  std::vector<std::string> devices;
2✔
61

62
  // This recovers all devices' names in a single string, each name separated by a null character ('\0'), and ending with two of those
63
  // For example: "First device\0Second device\0Third device\0\0"
64
  const char* devicesNames = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
2✔
65

66
  while (devicesNames[0] != '\0') {
4✔
67
    devices.emplace_back(devicesNames); // This automatically fills the string until the first \0
2✔
68
    devicesNames += devices.back().size() + 1;
2✔
69
  }
70

71
  return devices;
2✔
72
}
2✔
73

74
void AudioSystem::openDevice(const std::string& deviceName) {
21✔
75
  ZoneScopedN("AudioSystem::openDevice");
76

77
  Logger::debug("[AudioSystem] Opening {}...", (!deviceName.empty() ? std::format("device '{}'", deviceName) : "default device"));
40✔
78

79
  destroy();
21✔
80

81
  m_device = alcOpenDevice((!deviceName.empty() ? deviceName.c_str() : nullptr));
21✔
82
  if (!m_device) {
21✔
83
    Logger::error("[OpenAL] Failed to open an audio device");
2✔
84
    return;
2✔
85
  }
86

87
  m_context = alcCreateContext(static_cast<ALCdevice*>(m_device), nullptr);
19✔
88
  checkError(m_device, "Failed to create context");
19✔
89

90
  if (!alcMakeContextCurrent(static_cast<ALCcontext*>(m_context))) {
19✔
91
    Logger::error("[OpenAL] Failed to make the audio context current");
×
NEW
92
    alcGetError(static_cast<ALCdevice*>(m_device)); // Flushing errors, since alcMakeContextCurrent() produces one on failure which we already handled
×
93
  }
94

95
  Logger::debug("[AudioSystem] Opened device '{}'", recoverCurrentDevice());
19✔
96
}
97

98
std::string AudioSystem::recoverCurrentDevice() const {
24✔
99
  if (m_device == nullptr) // The system has failed to initialize; returning an empty device name
24✔
100
    return {};
2✔
101

102
  if (!alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"))
22✔
103
    return {};
×
104

105
  return alcGetString(static_cast<ALCdevice*>(m_device), ALC_ALL_DEVICES_SPECIFIER);
44✔
106
}
107

108
bool AudioSystem::update(const FrameTimeInfo&) {
6✔
109
  ZoneScopedN("AudioSystem::update");
110

111
  for (Entity* entity : m_entities) {
15✔
112
    if (entity->hasComponent<Sound>()) {
10✔
113
      const Sound& sound = entity->getComponent<Sound>();
5✔
114

115
      if (entity->hasComponent<Transform>()) {
5✔
116
        auto& soundTrans = entity->getComponent<Transform>();
4✔
117

118
        // TODO: Transform's update status may be reinitialized in the RenderSystem (and should theoretically be reset in every system, including here)
119
        //  A viable solution must be implemented to check for and reset this status in all systems
120
        //if (soundTrans.hasUpdated()) {
121
          sound.setPosition(soundTrans.getPosition());
4✔
122
          //soundTrans.setUpdated(false);
123
        //}
124
      }
125

126
      // TODO: velocity should be set only if it has been updated since last time
127
      if (entity->hasComponent<RigidBody>())
5✔
128
        sound.setVelocity(entity->getComponent<RigidBody>().getVelocity());
4✔
129
    }
130

131
    if (entity->hasComponent<Listener>()) {
10✔
132
      if (!entity->hasComponent<Transform>())
5✔
133
        throw std::runtime_error("[AudioSystem] A listener must have a transform component");
1✔
134

135
      auto& listener      = entity->getComponent<Listener>();
4✔
136
      auto& listenerTrans = entity->getComponent<Transform>();
4✔
137

138
      //if (listenerTrans.hasUpdated()) {
139
        listener.setPosition(listenerTrans.getPosition());
4✔
140
        listener.setOrientation(Mat3f(listenerTrans.getRotation().computeMatrix()));
4✔
141

142
        //listenerTrans.setUpdated(false);
143
      //}
144

145
      if (entity->hasComponent<RigidBody>())
4✔
146
        listener.setVelocity(entity->getComponent<RigidBody>().getVelocity());
2✔
147
    }
148
  }
149

150
  return true;
5✔
151
}
152

153
void AudioSystem::destroy() {
41✔
154
  ZoneScopedN("AudioSystem::destroy");
155

156
  if (m_context == nullptr && m_device == nullptr)
41✔
157
    return;
22✔
158

159
  Logger::debug("[AudioSystem] Destroying...");
19✔
160

161
  alcMakeContextCurrent(nullptr);
19✔
162

163
  if (m_context != nullptr) {
19✔
164
    alcDestroyContext(static_cast<ALCcontext*>(m_context));
19✔
165
    checkError(m_device, "Failed to destroy context");
19✔
166
    m_context = nullptr;
19✔
167
  }
168

169
  if (m_device != nullptr) {
19✔
170
    if (!alcCloseDevice(static_cast<ALCdevice*>(m_device)))
19✔
171
      Logger::error("[OpenAL] Failed to close the audio device");
×
172

173
    m_device = nullptr;
19✔
174
  }
175

176
  Logger::debug("[AudioSystem] Destroyed");
38✔
177
}
178

179
void AudioSystem::linkEntity(const EntityPtr& entity) {
7✔
180
  ZoneScopedN("AudioSystem::linkEntity");
181

182
  if (entity->hasComponent<Listener>()
7✔
183
   && std::ranges::find_if(m_entities, [] (const Entity* linkedEntity) { return linkedEntity->hasComponent<Listener>(); }) != m_entities.cend()) {
9✔
184
    throw std::runtime_error("[AudioSystem] Only one listener can exist in an audio system");
1✔
185
  }
186

187
  System::linkEntity(entity);
6✔
188
}
6✔
189

190
} // namespace Raz
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc