sandbox
Loading...
Searching...
No Matches
audio_module.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_AUDIO_AUDIO_MODULE_HPP_
3#define LIBSBX_AUDIO_AUDIO_MODULE_HPP_
4
5#include <cstdint>
6
7#include <vector>
8#include <stdexcept>
9
10#include <dr_wav.h>
11
12#include <AL/al.h>
13#include <AL/alc.h>
14
15#include <libsbx/utility/logger.hpp>
16
17#include <libsbx/core/module.hpp>
18#include <libsbx/core/engine.hpp>
19
20#include <libsbx/scenes/scenes_module.hpp>
21
22#include <libsbx/assets/assets_module.hpp>
23
24namespace sbx::audio {
25
27 std::float_t gain = 1.0f;
28}; // struct sound_listener
29
31 std::string uri;
32}; // struct sound_clip_reference
33
34enum class playback : std::uint8_t {
35 stopped,
36 playing,
37 paused
38}; // enum class playback
39
42
43 std::float_t gain = 1.0f;
44 std::float_t pitch = 1.0f;
45 bool looping = false;
46
47 bool spatial = true;
48 std::float_t reference_distance = 1.0f;
49 std::float_t max_distance = 100.0f;
50 std::float_t rolloff = 1.0f;
51
52 playback desired = playback::stopped;
53
54 // Runtime (owned by audio system)
55 ALuint al_source = 0; // 0 => not allocated yet
56 ALuint bound_buffer = 0; // last buffer bound (avoid redundant calls)
57 bool dirty_params = true; // when any param changes
58}; // struct sound_source
59
60class audio_module final : public core::module<audio_module> {
61
62 inline static const auto is_registered = register_module(stage::normal, dependencies<assets::assets_module>{});
63
64
65public:
66
67 struct sound_clip {
68 ALuint buffer = 0;
69 ALenum format = 0;
70 ALsizei sample_rate = 0;
71 }; // struct sound_clip
72
73 audio_module() {
74 _device = alcOpenDevice(nullptr);
75
76 if (!_device) {
77 throw std::runtime_error{"Failed to open OpenAL device."};
78 }
79
80 _context = alcCreateContext(_device, nullptr);
81
82 if (!_context) {
83 alcCloseDevice(_device);
84
85 throw std::runtime_error{"Failed to create OpenAL context."};
86 }
87
88 if (!alcMakeContextCurrent(_context)) {
89 alcDestroyContext(_context);
90 alcCloseDevice(_device);
91
92 throw std::runtime_error{"Failed to make OpenAL context current."};
93 }
94 }
95
96 ~audio_module() override {
97 alcMakeContextCurrent(nullptr);
98 alcDestroyContext(_context);
99 alcCloseDevice(_device);
100 }
101
102 auto get_or_load_clip_buffer(const std::string& uri) -> sound_clip& {
103 if (auto it = _clip_cache.find(uri); it != _clip_cache.end()) {
104 return it->second;
105 }
106
107 auto& assets = core::engine::get_module<assets::assets_module>();
108
109 auto path = assets.resolve_path(uri).string();
110
111 auto wav = _load_wav(path);
112
113 auto format = ALenum{};
114
115 if (wav.channels == 1) {
116 format = AL_FORMAT_MONO16;
117 } else if (wav.channels == 2) {
118 format = AL_FORMAT_STEREO16;
119 } else {
120 throw std::runtime_error{"Unsupported WAV channel count"};
121 }
122
123 auto clip = sound_clip{};
124 clip.format = format;
125 clip.sample_rate = static_cast<ALsizei>(wav.sample_rate);
126
127 alGenBuffers(1, &clip.buffer);
128
129 if (alGetError() != AL_NO_ERROR) {
130 throw std::runtime_error{"alGenBuffers failed."};
131 }
132
133 alBufferData(clip.buffer, clip.format, wav.samples.data(), static_cast<ALsizei>(wav.samples.size() * sizeof(std::int16_t)), clip.sample_rate);
134
135 if (alGetError() != AL_NO_ERROR) {
136 alDeleteBuffers(1, &clip.buffer);
137
138 throw std::runtime_error{"alBufferData failed."};
139 }
140
141 auto [it, _] = _clip_cache.emplace(uri, clip);
142
143 return it->second;
144 }
145
146 auto update() -> void override {
147 auto& scenes_module = sbx::core::engine::get_module<sbx::scenes::scenes_module>();
148
149 auto& scene = scenes_module.active_scene();
150 auto& environment = scene.environment();
151 auto& graph = scene.graph();
152
153 auto camera_node = environment.camera();
154
155 const auto& camera_transform = graph.get_component<scenes::transform>(camera_node);
156
157 alListener3f(AL_POSITION, camera_transform.position().x(), camera_transform.position().y(), camera_transform.position().z());
158 alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f);
159
160 if (graph.has_component<audio::sound_listener>(camera_node)) {
161 const auto& listener = graph.get_component<audio::sound_listener>(camera_node);
162
163 alListenerf(AL_GAIN, listener.gain);
164 }
165
166 const auto forward = camera_transform.forward();
167 const auto up = camera_transform.up();
168
169 auto orientation = std::array<std::float_t, 6u>{
170 forward.x(), forward.y(), forward.z(),
171 up.x(), up.y(), up.z()
172 };
173
174 alListenerfv(AL_ORIENTATION, orientation.data());
175 }
176
177private:
178
179 struct wav_data {
180 std::vector<std::int16_t> samples;
181 std::uint32_t sample_rate;
182 std::uint16_t channels;
183 }; // struct wav_data
184
185 auto _load_wav(const std::string& filepath) -> wav_data {
186 auto wav = drwav{};
187
188 if (!drwav_init_file(&wav, filepath.c_str(), nullptr)) {
189 throw std::runtime_error("Failed to load WAV");
190 }
191
192 auto out = wav_data{};
193 out.sample_rate = wav.sampleRate;
194 out.channels = wav.channels;
195
196 out.samples.resize(wav.totalPCMFrameCount * wav.channels);
197 drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, out.samples.data());
198
199 drwav_uninit(&wav);
200
201 return out;
202 }
203
204 ALCdevice* _device;
205 ALCcontext* _context;
206
207 std::unordered_map<std::string, sound_clip> _clip_cache;
208
209}; // class audio_module
210
211} // namespace sbx::audio
212
213#endif // LIBSBX_AUDIO_AUDIO_MODULE_HPP_
Definition: audio_module.hpp:60
Definition: module.hpp:92
Definition: audio_module.hpp:67
Definition: audio_module.hpp:30
Definition: audio_module.hpp:26
Definition: audio_module.hpp:40