cgv
Loading...
Searching...
No Matches
al_context.cxx
1#include "al_context.h"
2#include <AL/alext.h>
3
4#include <algorithm>
5#include <cassert>
6#include <filesystem>
7#include <iostream>
8#include <stdexcept>
9
10#include <AL/al.h>
11#include <AL/alc.h>
12
13#include <sndfile.hh>
14#include <vector>
15
16namespace cgv {
17namespace audio {
18
20{
21 const ALCchar* dev_string = alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
22 assert(dev_string);
23 return dev_string ? std::string(dev_string) : std::string();
24}
25std::vector<std::string> OALContext::enumerate_devices()
26{
27 assert(alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"));
28 const ALCchar* dev_strings = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
29 assert(dev_strings);
30
31 std::vector<std::string> device_names;
32 // alcGetString returns cstring array terminated by two successive null bytes
33 // (the first terminating the last cstring, the second terminating the array)
34 while (strlen(dev_strings) != 0) {
35 device_names.emplace_back(dev_strings);
36 // +1 for the null byte
37 dev_strings += device_names.back().length() + 1;
38 }
39 return device_names;
40}
41OALContext::OALContext(const std::string& device) :
42 oal_device(alcOpenDevice(device.empty() ? nullptr : device.c_str()))
43{
44 if (oal_device) {
45 oal_context = alcCreateContext(oal_device, nullptr);
46 alcMakeContextCurrent(oal_context);
47 }
48}
50{
51 alcMakeContextCurrent(oal_context);
52}
53std::string OALContext::get_device_name() const
54{
55 const ALCchar* device_name = alcGetString(oal_device, ALC_ALL_DEVICES_SPECIFIER);
56 return std::string(device_name);
57}
59{
60 // TODO first, delete all sources
61 for (const auto& buf : sample_buffers) {
62 alDeleteBuffers(1, &buf.second);
63 }
64 auto* const context = alcGetCurrentContext();
65 auto* const device = alcGetContextsDevice(context);
66 alcMakeContextCurrent(nullptr);
67 alcDestroyContext(context);
68 alcCloseDevice(device);
69}
70bool read_soundfile_to_memory_buffer(SndfileHandle& soundfile, OALSoundFormat& format, std::vector<int16_t>& memory_buffer)
71{
72 if (soundfile.error() != SF_ERR_NO_ERROR) {
73 std::cerr << soundfile.strError() << std::endl;
74 return false;
75 }
76 assert(soundfile.channels() == 1 || soundfile.channels() == 2);
77 format.nr_channels = OALSoundFormat::Channels(soundfile.channels());
78 format.nr_frames = int(soundfile.frames());
79 format.sampling_rate = soundfile.samplerate();
80 format.value_type = OALSoundFormat::SFT_INT16;
81 size_t num_values = soundfile.frames() * soundfile.channels();
82 memory_buffer.resize(num_values);
83 // libsndfile documentation notes that reading from float files into int buffers could require scaling
84 soundfile.command(SFC_SET_SCALE_FLOAT_INT_READ, nullptr, SF_TRUE);
85 soundfile.readf(memory_buffer.data(), soundfile.frames());
86 if (soundfile.error() != SF_ERR_NO_ERROR) {
87 std::cerr << soundfile.strError() << std::endl;
88 return false;
89 }
90 return true;
91}
92bool OALContext::decode_sound_file(const void* data, size_t data_length, OALSoundFormat& format, std::vector<int16_t>& memory_buffer)
93{
94 const char* data_ptr = reinterpret_cast<const char*>(data);
95 // The state describing a virtual file descriptor (as anonymous type)
96 struct VIO_state
97 {
98 const char* const data;
99 size_t data_length;
100 const char* current_byte_ptr;
101 } vio_state{ data_ptr, data_length, data_ptr }; // and a concrete variable with list initialization performed
102
103 SF_VIRTUAL_IO sfvio;
104 sfvio.get_filelen = [](void* user_data) -> sf_count_t {
105 return static_cast<const VIO_state*>(user_data)->data_length;
106 };
107 sfvio.seek = [](sf_count_t offset, int whence, void* user_data) -> sf_count_t {
108 auto* state = static_cast<VIO_state*>(user_data);
109 switch (whence) {
110 case SEEK_CUR:
111 state->current_byte_ptr += offset;
112 return state->current_byte_ptr - state->data;
113 case SEEK_SET:
114 state->current_byte_ptr = state->data + offset;
115 return offset;
116 case SEEK_END:
117 state->current_byte_ptr = (state->data + state->data_length - 1) - offset;
118 return state->current_byte_ptr - state->data;
119 default:
120 return 0;
121 }
122 };
123 sfvio.read = [](void* ptr, sf_count_t count, void* user_data) -> sf_count_t {
124 auto* state = static_cast<VIO_state*>(user_data);
125 memcpy(ptr, state->current_byte_ptr, count);
126 state->current_byte_ptr += count;
127 return count;
128 };
129 sfvio.write = [](const void* ptr, sf_count_t count, void* user_data) -> sf_count_t { return 0; };
130 sfvio.tell = [](void* user_data) -> sf_count_t {
131 auto* state = static_cast<const VIO_state*>(user_data);
132 return state->current_byte_ptr - state->data;
133 };
134 SndfileHandle soundfile(sfvio, &vio_state);
135 return read_soundfile_to_memory_buffer(soundfile, format, memory_buffer);
136}
137bool OALContext::load_sound_file(const std::string& filepath, OALSoundFormat& format, std::vector<int16_t>& memory_buffer)
138{
139 std::filesystem::path path(filepath);
140 if (path.empty() || !std::filesystem::exists(path))
141 return false;
142 SndfileHandle soundfile(path.string().c_str());
143 if (soundfile.error() != SF_ERR_NO_ERROR) {
144 std::cerr << soundfile.strError() << std::endl;
145 return false;
146 }
147 return read_soundfile_to_memory_buffer(soundfile, format, memory_buffer);
148}
149void OALContext::create_buffer(const std::string& symbolic_name, const OALSoundFormat& format, const void* data, size_t data_length)
150{
151 if (symbolic_name.empty())
152 throw std::invalid_argument("Symbolic file name can not be empty!");
153 if (std::end(sample_buffers) != sample_buffers.find(symbolic_name))
154 throw std::invalid_argument("Symbolic name \"" + symbolic_name + "\" already present");
155 assert(data);
156 assert(oal_context);
157 assert(oal_device);
158
159 ALuint buffer;
160 alGenBuffers(1, &buffer);
161 assert(AL_NO_ERROR == alcGetError(oal_device));
162 alBufferData(buffer, format.value_type == OALSoundFormat::SFT_UINT8 ?
163 (format.nr_channels == OALSoundFormat::Channels::SFC_STEREO ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8)
164 : (format.nr_channels == OALSoundFormat::Channels::SFC_STEREO ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16),
165 data, (ALsizei)data_length, (ALsizei)format.sampling_rate);
166 assert(AL_NO_ERROR == alcGetError(oal_device));
167 sample_buffers.emplace(symbolic_name, buffer);
168}
169void OALContext::load_sample(std::string filepath, std::string symbolic_name)
170{
171 if (symbolic_name.empty()) {
172 std::filesystem::path path(filepath);
173 if (path.empty() || !std::filesystem::exists(path))
174 return;
175 assert(path.has_filename());
176 symbolic_name = path.stem().string();
177 }
178 OALSoundFormat format;
179 std::vector<int16_t> memory_buffer;
180 if (!load_sound_file(filepath, format, memory_buffer))
181 return;
182 create_buffer(symbolic_name, format, memory_buffer.data(), sizeof(int16_t) * memory_buffer.size());
183}
184void OALContext::load_sample(std::string symbolic_name, const void* data, size_t data_length)
185{
186 std::vector<int16_t> memory_buffer;
187 OALSoundFormat format;
188 if (!decode_sound_file(data, data_length, format, memory_buffer))
189 return;
190 create_buffer(symbolic_name, format, reinterpret_cast<const char*>(memory_buffer.data()), sizeof(int16_t)*memory_buffer.size());
191}
192void OALContext::load_samples(std::string folder, bool recursive)
193{
194 std::filesystem::path f(folder);
195 if (!std::filesystem::is_directory(f)) {
196 std::cerr << __FUNCTION__ << ": no valid folder '" << folder << "'";
197 return;
198 }
199 // TODO code duplication should be removed via static tag dispatching
200 if (recursive) {
201 for (auto const& entry : std::filesystem::recursive_directory_iterator(f)) {
202 if (!entry.is_regular_file())
203 continue;
204 load_sample(static_cast<std::filesystem::path>(entry).string());
205 }
206 }
207 else {
208 for (auto const& entry : std::filesystem::directory_iterator(f)) {
209 if (!entry.is_regular_file())
210 continue;
211 load_sample(static_cast<std::filesystem::path>(entry).string());
212 }
213 }
214}
215ALuint OALContext::get_buffer_id(std::string sound_name) const
216{
217 auto buffer_id = sample_buffers.find(sound_name);
218 return buffer_id != sample_buffers.end() ? buffer_id->second : AL_NONE;
219}
220std::string OALContext::get_error_string() { return {alGetString(alGetError())}; }
221bool OALContext::is_no_error() { return AL_NO_ERROR == alGetError(); }
222void OALContext::set_HRTF(bool active)
223{
224 auto* oal_device = get_native_device();
225 //assert(alcIsExtensionPresent(oal_device, "SOFT_HRTF"));
226
227 auto reset_function = (LPALCRESETDEVICESOFT)alcGetProcAddress(oal_device, "alcResetDeviceSOFT");
228
229 std::array<ALCint, 2> attribs = {ALC_OUTPUT_MODE_SOFT, active ? ALC_STEREO_HRTF_SOFT : ALC_STEREO_BASIC_SOFT};
230 reset_function(oal_device, attribs.data());
231}
232
233ALCdevice* OALContext::get_native_device() { return oal_device; }
234
235ALCcontext* OALContext::get_native_context() { return oal_context; }
236
238{
239 if (get_state() == OALSS_PAUSED || get_state() == OALSS_PLAYING)
240 stop();
241 if (src_id != 0) {
242 alDeleteSources(1, &src_id);
243 src_id = 0;
244 }
245}
246
247OALSource::~OALSource()
248{
249 clear();
250}
251
252bool OALSource::init(OALContext& ctx, std::string sound_name)
253{
254 this->ctx_ptr = &ctx;
255
256 alcMakeContextCurrent(ctx.get_native_context());
257 alGenSources(1, &src_id);
258 if (!sound_name.empty())
259 append_sound_impl(sound_name);
260 return ctx.is_no_error();
261}
262
263bool OALSource::append_sound(std::string sound_name)
264{
265 alcMakeContextCurrent(ctx_ptr->get_native_context());
266 return append_sound_impl(sound_name);
267}
268
269bool OALSource::append_sound_impl(std::string sound_name)
270{
271 ALuint buf_id = ctx_ptr->get_buffer_id(sound_name);
272 switch (get_state()) {
273 case OALSS_INITIAL:
274 case OALSS_STOPPED:
275 alSourcei(src_id, AL_BUFFER, buf_id);
276 break;
277 case OALSS_PLAYING:
278 case OALSS_PAUSED:
279 alSourceQueueBuffers(src_id, 1, &buf_id);
280 break;
281 }
282 return ctx_ptr->is_no_error();
283}
284
285void OALSource::set_position(cgv::math::fvec<float, 3> pos) { alSourcefv(src_id, AL_POSITION, pos.data()); }
286
287void OALSource::set_velocity(cgv::math::fvec<float, 3> vel) { alSourcefv(src_id, AL_VELOCITY, vel.data()); }
288
289void OALSource::set_pitch(float pitch) { alSourcef(src_id, AL_PITCH, pitch); }
290
291void OALSource::set_gain(float gain) { alSourcef(src_id, AL_GAIN, gain); }
292
293void OALSource::set_looping(bool should_loop)
294{
295 alSourcei(src_id, AL_LOOPING, should_loop ? AL_TRUE : AL_FALSE);
296}
297
299{
301 alGetSourcefv(src_id, AL_POSITION, vec.data());
302 return vec;
303}
304
306{
308 alGetSourcefv(src_id, AL_VELOCITY, vec.data());
309 return vec;
310}
311
313{
314 float pitch;
315 alGetSourcef(src_id, AL_PITCH, &pitch);
316 return pitch;
317}
318
320{
321 float gain;
322 alGetSourcef(src_id, AL_GAIN, &gain);
323 return gain;
324}
325
327{
328 ALint looping;
329 alGetSourcei(src_id, AL_LOOPING, &looping);
330 return looping == AL_TRUE;
331}
332
334{
335 ALint playing;
336 alGetSourcei(src_id, AL_PLAYING, &playing);
337 return playing == AL_TRUE;
338}
339
340OALSourceState OALSource::get_state() const
341{
342 ALint state;
343 alGetSourcei(src_id, AL_SOURCE_STATE, &state);
344 return OALSourceState(state - AL_INITIAL);
345}
346
347void OALSource::play() { alSourcePlay(src_id); }
348
349void OALSource::pause() { alSourcePause(src_id); }
350
351void OALSource::play_pause(bool should_play)
352{
353 if (should_play) {
354 this->play();
355 }
356 else {
357 this->pause();
358 }
359}
360
361void OALSource::stop() { alSourceStop(src_id); }
362
363void OALSource::rewind() { alSourceRewind(src_id); }
364
366{
368 alGetListenerfv(AL_POSITION, pos.data());
369 return pos;
370}
371
373{
375 alGetListenerfv(AL_VELOCITY, vel.data());
376 return vel;
377}
378
380{
382 alGetListenerfv(AL_ORIENTATION, orientation.data());
383 return orientation;
384}
385
386void OALListener::set_position(cgv::math::fvec<float, 3> pos) { alListenerfv(AL_POSITION, pos.data()); }
387
388void OALListener::set_velocity(cgv::math::fvec<float, 3> vel) { alListenerfv(AL_VELOCITY, vel.data()); }
389
391{
392 const std::array<float, 6> orientation = {at[0], at[1], at[2], up[0], up[1], up[2]};
393 alListenerfv(AL_ORIENTATION, orientation.data());
394}
395
396} // namespace cgv
397} // namespace audio
This class provides easy sample loading, device enumeration and error retrieval.
Definition al_context.h:40
void load_samples(std::string folder, bool recursive=false)
Loads all audio files in a folder into the internal buffer list.
ALCdevice * get_native_device()
Gets the native device handle for direct OpenAL calls.
void make_current()
in case of multiple contexts, this makes this context current
std::string get_device_name() const
return device name of contexts device
static std::vector< std::string > enumerate_devices()
Enumerate audio output devices available on system.
std::string get_error_string()
Returns the last error from the OpenAL context.
static std::string get_default_device_name()
returns name of system's default device
static bool decode_sound_file(const void *data, size_t data_length, OALSoundFormat &format, std::vector< int16_t > &memory_buffer)
decode sound file that is already in memory data with data_length bytes to a newly constructed memory...
~OALContext()
detach context from output device and destruct
void create_buffer(const std::string &symbolic_name, const OALSoundFormat &format, const void *data, size_t data_length)
create a named sound buffer from a format descriptor and a data buffer of given length in bytes
void load_sample(std::string filepath, std::string symbolic_name="")
Loads a sound into the internal buffer list.
static bool load_sound_file(const std::string &filepath, OALSoundFormat &format, std::vector< int16_t > &memory_buffer)
load and decode sound file to newly constructed memory buffer
bool is_no_error()
Determines if there were any errors since last call.
void set_HRTF(bool active)
Toggles the OpenAL output mode to use HRTF.
OALContext(const std::string &device_name)
Construct context, connect to output device and make context current.
ALuint get_buffer_id(std::string sound_name) const
Gets the buffer identifier for the symbolic name.
ALCcontext * get_native_context()
Gets the native context handle for direct OpenAL calls.
static cgv::math::fvec< float, 3 > get_position()
Gets the listener position.
static cgv::math::fvec< float, 3 > get_velocity()
Gets the listener velocity.
static void set_position(cgv::math::fvec< float, 3 > pos)
Sets the listener position.
static void set_orientation(cgv::math::fvec< float, 3 > at, cgv::math::fvec< float, 3 > up)
Sets the listener orientation.
static cgv::math::fmat< float, 3, 2 > get_orientation()
Gets the listener orientation.
static void set_velocity(cgv::math::fvec< float, 3 > vel)
Sets the listener velocity.
void clear()
destruct source and restore uninitialized state
bool append_sound(std::string sound_name)
Append buffer of named sound to source's playback list.
void stop()
Stops the playback of the sound buffer and moves the playhead to the beginning.
void set_looping(bool should_loop)
Sets the looping property of the source.
void set_position(cgv::math::fvec< float, 3 > pos)
Sets the source position.
void play_pause(bool should_play)
Explicitly toggles between pausing and playing the sound buffer.
bool init(OALContext &ctx, std::string sound_name="")
This function links a source to a context.
void set_velocity(cgv::math::fvec< float, 3 > vel)
Sets the source velocity.
void play()
Commences the playback of the sound buffer.
void rewind()
Moves the playhead to the beginning of the buffer.
void pause()
Pauses the playback of the sound buffer without moving the playhead.
cgv::math::fvec< float, 3 > get_velocity() const
Gets the source velocity.
bool is_looping() const
Determines if the source has the looping property.
bool is_playing() const
Determines if the source is playing.
OALSourceState get_state() const
returns state of this source
float get_gain() const
Gets the source gain.
float get_pitch() const
Gets the source pitch.
cgv::math::fvec< float, 3 > get_position() const
Gets the source position.
void set_gain(float gain)
Sets the source gain.
void set_pitch(float pitch)
Sets the source pitch.
matrix of fixed size dimensions
Definition fmat.h:23
A vector with zero based index.
Definition fvec.h:26
T * data()
cast into array. This allows calls like glVertex<N><T>v(p.data()) instead of glVertex<N><T,...
Definition fvec.h:181
the cgv namespace
Definition print.h:11
simple descriptor for sounds that can be played back in OAL
Definition al_context.h:22