forked from Mirrorlandia_minetest/minetest
Split sound_openal_internal into serval files
This commit is contained in:
parent
606215fae9
commit
bbc64a2eb5
@ -2,8 +2,15 @@ set(sound_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/sound.cpp)
|
|||||||
|
|
||||||
if(USE_SOUND)
|
if(USE_SOUND)
|
||||||
set(sound_SRCS ${sound_SRCS}
|
set(sound_SRCS ${sound_SRCS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/al_helpers.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/ogg_file.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/playing_sound.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/proxy_sound_manager.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_data.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_manager.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_openal.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_openal.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_openal_internal.cpp)
|
${CMAKE_CURRENT_SOURCE_DIR}/sound/sound_singleton.cpp
|
||||||
|
)
|
||||||
set(SOUND_INCLUDE_DIRS
|
set(SOUND_INCLUDE_DIRS
|
||||||
${OPENAL_INCLUDE_DIR}
|
${OPENAL_INCLUDE_DIR}
|
||||||
${VORBIS_INCLUDE_DIR}
|
${VORBIS_INCLUDE_DIR}
|
||||||
|
53
src/client/sound/al_helpers.cpp
Normal file
53
src/client/sound/al_helpers.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "al_helpers.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RAIIALSoundBuffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
RAIIALSoundBuffer &RAIIALSoundBuffer::operator=(RAIIALSoundBuffer &&other) noexcept
|
||||||
|
{
|
||||||
|
if (&other != this)
|
||||||
|
reset(other.release());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAIIALSoundBuffer::reset(ALuint buf) noexcept
|
||||||
|
{
|
||||||
|
if (m_buffer != 0) {
|
||||||
|
alDeleteBuffers(1, &m_buffer);
|
||||||
|
warn_if_al_error("Failed to free sound buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buffer = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAIIALSoundBuffer RAIIALSoundBuffer::generate() noexcept
|
||||||
|
{
|
||||||
|
ALuint buf;
|
||||||
|
alGenBuffers(1, &buf);
|
||||||
|
return RAIIALSoundBuffer(buf);
|
||||||
|
}
|
118
src/client/sound/al_helpers.h
Normal file
118
src/client/sound/al_helpers.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "log.h"
|
||||||
|
#include "util/basic_macros.h"
|
||||||
|
#include "irr_v3d.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <al.h>
|
||||||
|
#include <alc.h>
|
||||||
|
//#include <alext.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#define OPENAL_DEPRECATED
|
||||||
|
#include <OpenAL/al.h>
|
||||||
|
#include <OpenAL/alc.h>
|
||||||
|
//#include <OpenAL/alext.h>
|
||||||
|
#else
|
||||||
|
#include <AL/al.h>
|
||||||
|
#include <AL/alc.h>
|
||||||
|
#include <AL/alext.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
inline const char *getAlErrorString(ALenum err) noexcept
|
||||||
|
{
|
||||||
|
switch (err) {
|
||||||
|
case AL_NO_ERROR:
|
||||||
|
return "no error";
|
||||||
|
case AL_INVALID_NAME:
|
||||||
|
return "invalid name";
|
||||||
|
case AL_INVALID_ENUM:
|
||||||
|
return "invalid enum";
|
||||||
|
case AL_INVALID_VALUE:
|
||||||
|
return "invalid value";
|
||||||
|
case AL_INVALID_OPERATION:
|
||||||
|
return "invalid operation";
|
||||||
|
case AL_OUT_OF_MEMORY:
|
||||||
|
return "out of memory";
|
||||||
|
default:
|
||||||
|
return "<unknown OpenAL error>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ALenum warn_if_al_error(const char *desc)
|
||||||
|
{
|
||||||
|
ALenum err = alGetError();
|
||||||
|
if (err == AL_NO_ERROR)
|
||||||
|
return err;
|
||||||
|
warningstream << "[OpenAL Error] " << desc << ": " << getAlErrorString(err)
|
||||||
|
<< std::endl;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms vectors from a left-handed coordinate system to a right-handed one
|
||||||
|
* and vice-versa.
|
||||||
|
* (Needed because Minetest uses a left-handed one and OpenAL a right-handed one.)
|
||||||
|
*/
|
||||||
|
inline v3f swap_handedness(v3f v) noexcept
|
||||||
|
{
|
||||||
|
return v3f(-v.X, v.Y, v.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAII wrapper for openal sound buffers.
|
||||||
|
*/
|
||||||
|
struct RAIIALSoundBuffer final
|
||||||
|
{
|
||||||
|
RAIIALSoundBuffer() noexcept = default;
|
||||||
|
explicit RAIIALSoundBuffer(ALuint buffer) noexcept : m_buffer(buffer) {};
|
||||||
|
|
||||||
|
~RAIIALSoundBuffer() noexcept { reset(0); }
|
||||||
|
|
||||||
|
DISABLE_CLASS_COPY(RAIIALSoundBuffer)
|
||||||
|
|
||||||
|
RAIIALSoundBuffer(RAIIALSoundBuffer &&other) noexcept : m_buffer(other.release()) {}
|
||||||
|
RAIIALSoundBuffer &operator=(RAIIALSoundBuffer &&other) noexcept;
|
||||||
|
|
||||||
|
ALuint get() noexcept { return m_buffer; }
|
||||||
|
|
||||||
|
ALuint release() noexcept { return std::exchange(m_buffer, 0); }
|
||||||
|
|
||||||
|
void reset(ALuint buf) noexcept;
|
||||||
|
|
||||||
|
static RAIIALSoundBuffer generate() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// According to openal specification:
|
||||||
|
// > Deleting buffer name 0 is a legal NOP.
|
||||||
|
//
|
||||||
|
// and:
|
||||||
|
// > [...] the NULL buffer (i.e., 0) which can always be queued.
|
||||||
|
ALuint m_buffer = 0;
|
||||||
|
};
|
179
src/client/sound/ogg_file.cpp
Normal file
179
src/client/sound/ogg_file.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ogg_file.h"
|
||||||
|
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggVorbisBufferSource struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t OggVorbisBufferSource::read_func(void *ptr, size_t size, size_t nmemb,
|
||||||
|
void *datasource) noexcept
|
||||||
|
{
|
||||||
|
OggVorbisBufferSource *s = (OggVorbisBufferSource *)datasource;
|
||||||
|
size_t copied_size = MYMIN(s->buf.size() - s->cur_offset, size);
|
||||||
|
memcpy(ptr, s->buf.data() + s->cur_offset, copied_size);
|
||||||
|
s->cur_offset += copied_size;
|
||||||
|
return copied_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OggVorbisBufferSource::seek_func(void *datasource, ogg_int64_t offset, int whence) noexcept
|
||||||
|
{
|
||||||
|
OggVorbisBufferSource *s = (OggVorbisBufferSource *)datasource;
|
||||||
|
if (whence == SEEK_SET) {
|
||||||
|
if (offset < 0 || (size_t)offset > s->buf.size()) {
|
||||||
|
// offset out of bounds
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur_offset = offset;
|
||||||
|
return 0;
|
||||||
|
} else if (whence == SEEK_CUR) {
|
||||||
|
if ((size_t)MYMIN(-offset, 0) > s->cur_offset
|
||||||
|
|| s->cur_offset + offset > s->buf.size()) {
|
||||||
|
// offset out of bounds
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur_offset += offset;
|
||||||
|
return 0;
|
||||||
|
} else if (whence == SEEK_END) {
|
||||||
|
if (offset > 0 || (size_t)-offset > s->buf.size()) {
|
||||||
|
// offset out of bounds
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur_offset = s->buf.size() - offset;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OggVorbisBufferSource::close_func(void *datasource) noexcept
|
||||||
|
{
|
||||||
|
auto s = reinterpret_cast<OggVorbisBufferSource *>(datasource);
|
||||||
|
delete s;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long OggVorbisBufferSource::tell_func(void *datasource) noexcept
|
||||||
|
{
|
||||||
|
OggVorbisBufferSource *s = (OggVorbisBufferSource *)datasource;
|
||||||
|
return s->cur_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ov_callbacks OggVorbisBufferSource::s_ov_callbacks = {
|
||||||
|
&OggVorbisBufferSource::read_func,
|
||||||
|
&OggVorbisBufferSource::seek_func,
|
||||||
|
&OggVorbisBufferSource::close_func,
|
||||||
|
&OggVorbisBufferSource::tell_func
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RAIIOggFile struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::optional<OggFileDecodeInfo> RAIIOggFile::getDecodeInfo(const std::string &filename_for_logging)
|
||||||
|
{
|
||||||
|
OggFileDecodeInfo ret;
|
||||||
|
|
||||||
|
vorbis_info *pInfo = ov_info(&m_file, -1);
|
||||||
|
if (!pInfo)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
ret.name_for_logging = filename_for_logging;
|
||||||
|
|
||||||
|
if (pInfo->channels == 1) {
|
||||||
|
ret.is_stereo = false;
|
||||||
|
ret.format = AL_FORMAT_MONO16;
|
||||||
|
ret.bytes_per_sample = 2;
|
||||||
|
} else if (pInfo->channels == 2) {
|
||||||
|
ret.is_stereo = true;
|
||||||
|
ret.format = AL_FORMAT_STEREO16;
|
||||||
|
ret.bytes_per_sample = 4;
|
||||||
|
} else {
|
||||||
|
warningstream << "Audio: Can't decode. Sound is neither mono nor stereo: "
|
||||||
|
<< ret.name_for_logging << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.freq = pInfo->rate;
|
||||||
|
|
||||||
|
ret.length_samples = static_cast<ALuint>(ov_pcm_total(&m_file, -1));
|
||||||
|
ret.length_seconds = static_cast<f32>(ov_time_total(&m_file, -1));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
RAIIALSoundBuffer RAIIOggFile::loadBuffer(const OggFileDecodeInfo &decode_info,
|
||||||
|
ALuint pcm_start, ALuint pcm_end)
|
||||||
|
{
|
||||||
|
constexpr int endian = 0; // 0 for Little-Endian, 1 for Big-Endian
|
||||||
|
constexpr int word_size = 2; // we use s16 samples
|
||||||
|
constexpr int word_signed = 1; // ^
|
||||||
|
|
||||||
|
// seek
|
||||||
|
if (ov_pcm_tell(&m_file) != pcm_start) {
|
||||||
|
if (ov_pcm_seek(&m_file, pcm_start) != 0) {
|
||||||
|
warningstream << "Audio: Error decoding (could not seek) "
|
||||||
|
<< decode_info.name_for_logging << std::endl;
|
||||||
|
return RAIIALSoundBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t size = static_cast<size_t>(pcm_end - pcm_start)
|
||||||
|
* decode_info.bytes_per_sample;
|
||||||
|
|
||||||
|
std::unique_ptr<char[]> snd_buffer(new char[size]);
|
||||||
|
|
||||||
|
// read size bytes
|
||||||
|
size_t read_count = 0;
|
||||||
|
int bitStream;
|
||||||
|
while (read_count < size) {
|
||||||
|
// Read up to a buffer's worth of decoded sound data
|
||||||
|
long num_bytes = ov_read(&m_file, &snd_buffer[read_count], size - read_count,
|
||||||
|
endian, word_size, word_signed, &bitStream);
|
||||||
|
|
||||||
|
if (num_bytes <= 0) {
|
||||||
|
warningstream << "Audio: Error decoding "
|
||||||
|
<< decode_info.name_for_logging << std::endl;
|
||||||
|
return RAIIALSoundBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
read_count += num_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load buffer to openal
|
||||||
|
RAIIALSoundBuffer snd_buffer_id = RAIIALSoundBuffer::generate();
|
||||||
|
alBufferData(snd_buffer_id.get(), decode_info.format, &(snd_buffer[0]), size,
|
||||||
|
decode_info.freq);
|
||||||
|
|
||||||
|
ALenum error = alGetError();
|
||||||
|
if (error != AL_NO_ERROR) {
|
||||||
|
warningstream << "Audio: OpenAL error: " << getAlErrorString(error)
|
||||||
|
<< "preparing sound buffer for sound \""
|
||||||
|
<< decode_info.name_for_logging << "\"" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return snd_buffer_id;
|
||||||
|
}
|
94
src/client/sound/ogg_file.h
Normal file
94
src/client/sound/ogg_file.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "al_helpers.h"
|
||||||
|
#include <vorbis/vorbisfile.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For vorbisfile to read from our buffer instead of from a file.
|
||||||
|
*/
|
||||||
|
struct OggVorbisBufferSource {
|
||||||
|
std::string buf;
|
||||||
|
size_t cur_offset = 0;
|
||||||
|
|
||||||
|
static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource) noexcept;
|
||||||
|
static int seek_func(void *datasource, ogg_int64_t offset, int whence) noexcept;
|
||||||
|
static int close_func(void *datasource) noexcept;
|
||||||
|
static long tell_func(void *datasource) noexcept;
|
||||||
|
|
||||||
|
static const ov_callbacks s_ov_callbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata of an Ogg-Vorbis file, used for decoding.
|
||||||
|
* We query this information once and store it in this struct.
|
||||||
|
*/
|
||||||
|
struct OggFileDecodeInfo {
|
||||||
|
std::string name_for_logging;
|
||||||
|
bool is_stereo;
|
||||||
|
ALenum format; // AL_FORMAT_MONO16 or AL_FORMAT_STEREO16
|
||||||
|
size_t bytes_per_sample;
|
||||||
|
ALsizei freq;
|
||||||
|
ALuint length_samples = 0;
|
||||||
|
f32 length_seconds = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAII wrapper for OggVorbis_File.
|
||||||
|
*/
|
||||||
|
struct RAIIOggFile {
|
||||||
|
bool m_needs_clear = false;
|
||||||
|
OggVorbis_File m_file;
|
||||||
|
|
||||||
|
RAIIOggFile() = default;
|
||||||
|
|
||||||
|
DISABLE_CLASS_COPY(RAIIOggFile)
|
||||||
|
|
||||||
|
~RAIIOggFile() noexcept
|
||||||
|
{
|
||||||
|
if (m_needs_clear)
|
||||||
|
ov_clear(&m_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
OggVorbis_File *get() { return &m_file; }
|
||||||
|
|
||||||
|
std::optional<OggFileDecodeInfo> getDecodeInfo(const std::string &filename_for_logging);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function for loading ogg vorbis sounds.
|
||||||
|
* Loads exactly the specified interval of PCM-data, and creates an OpenAL
|
||||||
|
* buffer with it.
|
||||||
|
*
|
||||||
|
* @param decode_info Cached meta information of the file.
|
||||||
|
* @param pcm_start First sample in the interval.
|
||||||
|
* @param pcm_end One after last sample of the interval (=> exclusive).
|
||||||
|
* @return An AL sound buffer, or a 0-buffer on failure.
|
||||||
|
*/
|
||||||
|
RAIIALSoundBuffer loadBuffer(const OggFileDecodeInfo &decode_info, ALuint pcm_start,
|
||||||
|
ALuint pcm_end);
|
||||||
|
};
|
241
src/client/sound/playing_sound.cpp
Normal file
241
src/client/sound/playing_sound.cpp
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "playing_sound.h"
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
PlayingSound::PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data,
|
||||||
|
bool loop, f32 volume, f32 pitch, f32 start_time,
|
||||||
|
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
|
||||||
|
: m_source_id(source_id), m_data(std::move(data)), m_looping(loop),
|
||||||
|
m_is_positional(pos_vel_opt.has_value())
|
||||||
|
{
|
||||||
|
// Calculate actual start_time (see lua_api.txt for specs)
|
||||||
|
f32 len_seconds = m_data->m_decode_info.length_seconds;
|
||||||
|
f32 len_samples = m_data->m_decode_info.length_samples;
|
||||||
|
if (!m_looping) {
|
||||||
|
if (start_time < 0.0f) {
|
||||||
|
start_time = std::fmax(start_time + len_seconds, 0.0f);
|
||||||
|
} else if (start_time >= len_seconds) {
|
||||||
|
// No sound
|
||||||
|
m_next_sample_pos = len_samples;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Modulo offset to be within looping time
|
||||||
|
start_time = start_time - std::floor(start_time / len_seconds) * len_seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue first buffers
|
||||||
|
|
||||||
|
m_next_sample_pos = std::min((start_time / len_seconds) * len_samples, len_samples);
|
||||||
|
|
||||||
|
if (m_looping && m_next_sample_pos == len_samples)
|
||||||
|
m_next_sample_pos = 0;
|
||||||
|
|
||||||
|
if (!m_data->isStreaming()) {
|
||||||
|
// If m_next_sample_pos >= len_samples, buf will be 0, and setting it as
|
||||||
|
// AL_BUFFER is a NOP (source stays AL_UNDETERMINED). => No sound will be
|
||||||
|
// played.
|
||||||
|
|
||||||
|
auto [buf, buf_end, offset_in_buf] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||||
|
m_next_sample_pos = buf_end;
|
||||||
|
|
||||||
|
alSourcei(m_source_id, AL_BUFFER, buf);
|
||||||
|
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf);
|
||||||
|
|
||||||
|
alSourcei(m_source_id, AL_LOOPING, m_looping ? AL_TRUE : AL_FALSE);
|
||||||
|
|
||||||
|
warn_if_al_error("when creating non-streaming sound");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Start with 2 buffers
|
||||||
|
ALuint buf_ids[2];
|
||||||
|
|
||||||
|
// If m_next_sample_pos >= len_samples (happens only if not looped), one
|
||||||
|
// or both of buf_ids will be 0. Queuing 0 is a NOP.
|
||||||
|
|
||||||
|
auto [buf0, buf0_end, offset_in_buf0] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||||
|
buf_ids[0] = buf0;
|
||||||
|
m_next_sample_pos = buf0_end;
|
||||||
|
|
||||||
|
if (m_looping && m_next_sample_pos == len_samples)
|
||||||
|
m_next_sample_pos = 0;
|
||||||
|
|
||||||
|
auto [buf1, buf1_end, offset_in_buf1] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||||
|
buf_ids[1] = buf1;
|
||||||
|
m_next_sample_pos = buf1_end;
|
||||||
|
assert(offset_in_buf1 == 0);
|
||||||
|
|
||||||
|
alSourceQueueBuffers(m_source_id, 2, buf_ids);
|
||||||
|
alSourcei(m_source_id, AL_SAMPLE_OFFSET, offset_in_buf0);
|
||||||
|
|
||||||
|
// We can't use AL_LOOPING because more buffers are queued later
|
||||||
|
// looping is therefore done manually
|
||||||
|
|
||||||
|
m_stopped_means_dead = false;
|
||||||
|
|
||||||
|
warn_if_al_error("when creating streaming sound");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set initial pos, volume, pitch
|
||||||
|
if (m_is_positional) {
|
||||||
|
updatePosVel(pos_vel_opt->first, pos_vel_opt->second);
|
||||||
|
} else {
|
||||||
|
// Make position-less
|
||||||
|
alSourcei(m_source_id, AL_SOURCE_RELATIVE, true);
|
||||||
|
alSource3f(m_source_id, AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||||
|
alSource3f(m_source_id, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||||
|
warn_if_al_error("PlayingSound::PlayingSound at making position-less");
|
||||||
|
}
|
||||||
|
setGain(volume);
|
||||||
|
setPitch(pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayingSound::stepStream()
|
||||||
|
{
|
||||||
|
if (isDead())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// unqueue finished buffers
|
||||||
|
ALint num_unqueued_bufs = 0;
|
||||||
|
alGetSourcei(m_source_id, AL_BUFFERS_PROCESSED, &num_unqueued_bufs);
|
||||||
|
if (num_unqueued_bufs == 0)
|
||||||
|
return true;
|
||||||
|
// We always have 2 buffers enqueued at most
|
||||||
|
SANITY_CHECK(num_unqueued_bufs <= 2);
|
||||||
|
ALuint unqueued_buffer_ids[2];
|
||||||
|
alSourceUnqueueBuffers(m_source_id, num_unqueued_bufs, unqueued_buffer_ids);
|
||||||
|
|
||||||
|
// Fill up again
|
||||||
|
for (ALint i = 0; i < num_unqueued_bufs; ++i) {
|
||||||
|
if (m_next_sample_pos == m_data->m_decode_info.length_samples) {
|
||||||
|
// Reached end
|
||||||
|
if (m_looping) {
|
||||||
|
m_next_sample_pos = 0;
|
||||||
|
} else {
|
||||||
|
m_stopped_means_dead = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [buf, buf_end, offset_in_buf] = m_data->getOrLoadBufferAt(m_next_sample_pos);
|
||||||
|
m_next_sample_pos = buf_end;
|
||||||
|
assert(offset_in_buf == 0);
|
||||||
|
|
||||||
|
alSourceQueueBuffers(m_source_id, 1, &buf);
|
||||||
|
|
||||||
|
// Start again if queue was empty and resulted in stop
|
||||||
|
if (getState() == AL_STOPPED) {
|
||||||
|
play();
|
||||||
|
warningstream << "PlayingSound::stepStream: Sound queue ran empty for \""
|
||||||
|
<< m_data->m_decode_info.name_for_logging << "\"" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayingSound::fade(f32 step, f32 target_gain) noexcept
|
||||||
|
{
|
||||||
|
bool already_fading = m_fade_state.has_value();
|
||||||
|
|
||||||
|
target_gain = MYMAX(target_gain, 0.0f); // 0.0f if nan
|
||||||
|
step = target_gain - getGain() > 0.0f ? std::abs(step) : -std::abs(step);
|
||||||
|
|
||||||
|
m_fade_state = FadeState{step, target_gain};
|
||||||
|
|
||||||
|
return !already_fading;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayingSound::doFade(f32 dtime) noexcept
|
||||||
|
{
|
||||||
|
if (!m_fade_state || isDead())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FadeState &fade = *m_fade_state;
|
||||||
|
assert(fade.step != 0.0f);
|
||||||
|
|
||||||
|
f32 current_gain = getGain();
|
||||||
|
current_gain += fade.step * dtime;
|
||||||
|
|
||||||
|
if (fade.step < 0.0f)
|
||||||
|
current_gain = std::max(current_gain, fade.target_gain);
|
||||||
|
else
|
||||||
|
current_gain = std::min(current_gain, fade.target_gain);
|
||||||
|
|
||||||
|
if (current_gain <= 0.0f) {
|
||||||
|
// stop sound
|
||||||
|
m_stopped_means_dead = true;
|
||||||
|
alSourceStop(m_source_id);
|
||||||
|
|
||||||
|
m_fade_state = std::nullopt;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setGain(current_gain);
|
||||||
|
|
||||||
|
if (current_gain == fade.target_gain) {
|
||||||
|
m_fade_state = std::nullopt;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingSound::updatePosVel(const v3f &pos, const v3f &vel) noexcept
|
||||||
|
{
|
||||||
|
alSourcei(m_source_id, AL_SOURCE_RELATIVE, false);
|
||||||
|
alSource3f(m_source_id, AL_POSITION, pos.X, pos.Y, pos.Z);
|
||||||
|
alSource3f(m_source_id, AL_VELOCITY, vel.X, vel.Y, vel.Z);
|
||||||
|
// Using alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED) and setting reference
|
||||||
|
// distance to clamp gain at <1 node distance avoids excessive volume when
|
||||||
|
// closer.
|
||||||
|
alSourcef(m_source_id, AL_REFERENCE_DISTANCE, 1.0f);
|
||||||
|
|
||||||
|
warn_if_al_error("PlayingSound::updatePosVel");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayingSound::setGain(f32 gain) noexcept
|
||||||
|
{
|
||||||
|
// AL_REFERENCE_DISTANCE was once reduced from 3 nodes to 1 node.
|
||||||
|
// We compensate this by multiplying the volume by 3.
|
||||||
|
if (m_is_positional)
|
||||||
|
gain *= 3.0f;
|
||||||
|
|
||||||
|
alSourcef(m_source_id, AL_GAIN, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 PlayingSound::getGain() noexcept
|
||||||
|
{
|
||||||
|
ALfloat gain;
|
||||||
|
alGetSourcef(m_source_id, AL_GAIN, &gain);
|
||||||
|
// Same as above, but inverse.
|
||||||
|
if (m_is_positional)
|
||||||
|
gain *= 1.0f/3.0f;
|
||||||
|
return gain;
|
||||||
|
}
|
107
src/client/sound/playing_sound.h
Normal file
107
src/client/sound/playing_sound.h
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sound_data.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sound that is currently played.
|
||||||
|
* Can be streaming.
|
||||||
|
* Can be fading.
|
||||||
|
*/
|
||||||
|
class PlayingSound final
|
||||||
|
{
|
||||||
|
struct FadeState {
|
||||||
|
f32 step;
|
||||||
|
f32 target_gain;
|
||||||
|
};
|
||||||
|
|
||||||
|
ALuint m_source_id;
|
||||||
|
std::shared_ptr<ISoundDataOpen> m_data;
|
||||||
|
ALuint m_next_sample_pos = 0;
|
||||||
|
bool m_looping;
|
||||||
|
bool m_is_positional;
|
||||||
|
bool m_stopped_means_dead = true;
|
||||||
|
std::optional<FadeState> m_fade_state = std::nullopt;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, bool loop,
|
||||||
|
f32 volume, f32 pitch, f32 start_time,
|
||||||
|
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||||
|
|
||||||
|
~PlayingSound() noexcept
|
||||||
|
{
|
||||||
|
alDeleteSources(1, &m_source_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
DISABLE_CLASS_COPY(PlayingSound)
|
||||||
|
|
||||||
|
// return false means streaming finished
|
||||||
|
bool stepStream();
|
||||||
|
|
||||||
|
// retruns true if it wasn't fading already
|
||||||
|
bool fade(f32 step, f32 target_gain) noexcept;
|
||||||
|
|
||||||
|
// returns true if more fade is needed later
|
||||||
|
bool doFade(f32 dtime) noexcept;
|
||||||
|
|
||||||
|
void updatePosVel(const v3f &pos, const v3f &vel) noexcept;
|
||||||
|
|
||||||
|
void setGain(f32 gain) noexcept;
|
||||||
|
|
||||||
|
f32 getGain() noexcept;
|
||||||
|
|
||||||
|
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
|
||||||
|
|
||||||
|
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
||||||
|
|
||||||
|
void play() noexcept { alSourcePlay(m_source_id); }
|
||||||
|
|
||||||
|
// returns one of AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED
|
||||||
|
ALint getState() noexcept
|
||||||
|
{
|
||||||
|
ALint state;
|
||||||
|
alGetSourcei(m_source_id, AL_SOURCE_STATE, &state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isDead() noexcept
|
||||||
|
{
|
||||||
|
// streaming sounds can (but should not) stop because the queue runs empty
|
||||||
|
return m_stopped_means_dead && getState() == AL_STOPPED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pause() noexcept
|
||||||
|
{
|
||||||
|
// this is a NOP if state != AL_PLAYING
|
||||||
|
alSourcePause(m_source_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume() noexcept
|
||||||
|
{
|
||||||
|
if (getState() == AL_PAUSED)
|
||||||
|
play();
|
||||||
|
}
|
||||||
|
};
|
163
src/client/sound/proxy_sound_manager.cpp
Normal file
163
src/client/sound/proxy_sound_manager.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2023 DS
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "proxy_sound_manager.h"
|
||||||
|
|
||||||
|
#include "filesys.h"
|
||||||
|
|
||||||
|
ProxySoundManager::MsgResult ProxySoundManager::handleMsg(SoundManagerMsgToProxy &&msg)
|
||||||
|
{
|
||||||
|
using namespace sound_manager_messages_to_proxy;
|
||||||
|
|
||||||
|
return std::visit([&](auto &&msg) {
|
||||||
|
using T = std::decay_t<decltype(msg)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, std::monostate>)
|
||||||
|
return MsgResult::Empty;
|
||||||
|
else if constexpr (std::is_same_v<T, ReportRemovedSound>)
|
||||||
|
reportRemovedSound(msg.id);
|
||||||
|
else if constexpr (std::is_same_v<T, Stopped>)
|
||||||
|
return MsgResult::Stopped;
|
||||||
|
|
||||||
|
return MsgResult::Ok;
|
||||||
|
},
|
||||||
|
std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxySoundManager::~ProxySoundManager()
|
||||||
|
{
|
||||||
|
if (m_sound_manager.isRunning()) {
|
||||||
|
send(sound_manager_messages_to_mgr::PleaseStop{});
|
||||||
|
|
||||||
|
// recv until it stopped
|
||||||
|
auto recv = [&] {
|
||||||
|
return m_sound_manager.m_queue_to_proxy.pop_frontNoEx();
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (handleMsg(recv()) == MsgResult::Stopped)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// join
|
||||||
|
m_sound_manager.stop();
|
||||||
|
SANITY_CHECK(m_sound_manager.wait());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::step(f32 dtime)
|
||||||
|
{
|
||||||
|
auto recv = [&] {
|
||||||
|
return m_sound_manager.m_queue_to_proxy.pop_frontNoEx(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
MsgResult res = handleMsg(recv());
|
||||||
|
if (res == MsgResult::Empty)
|
||||||
|
break;
|
||||||
|
else if (res == MsgResult::Stopped)
|
||||||
|
throw std::runtime_error("OpenALSoundManager stopped unexpectedly");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::pauseAll()
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::PauseAll{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::resumeAll()
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::ResumeAll{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::updateListener(const v3f &pos_, const v3f &vel_,
|
||||||
|
const v3f &at_, const v3f &up_)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::UpdateListener{pos_, vel_, at_, up_});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::setListenerGain(f32 gain)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::SetListenerGain{gain});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxySoundManager::loadSoundFile(const std::string &name,
|
||||||
|
const std::string &filepath)
|
||||||
|
{
|
||||||
|
// do not add twice
|
||||||
|
if (m_known_sound_names.count(name) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// coarse check
|
||||||
|
if (!fs::IsFile(filepath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
send(sound_manager_messages_to_mgr::LoadSoundFile{name, filepath});
|
||||||
|
|
||||||
|
m_known_sound_names.insert(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProxySoundManager::loadSoundData(const std::string &name, std::string &&filedata)
|
||||||
|
{
|
||||||
|
// do not add twice
|
||||||
|
if (m_known_sound_names.count(name) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
send(sound_manager_messages_to_mgr::LoadSoundData{name, std::move(filedata)});
|
||||||
|
|
||||||
|
m_known_sound_names.insert(name);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::addSoundToGroup(const std::string &sound_name,
|
||||||
|
const std::string &group_name)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::AddSoundToGroup{sound_name, group_name});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::playSound(sound_handle_t id, const SoundSpec &spec)
|
||||||
|
{
|
||||||
|
if (id == 0)
|
||||||
|
id = allocateId(1);
|
||||||
|
send(sound_manager_messages_to_mgr::PlaySound{id, spec});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
||||||
|
const v3f &vel_)
|
||||||
|
{
|
||||||
|
if (id == 0)
|
||||||
|
id = allocateId(1);
|
||||||
|
send(sound_manager_messages_to_mgr::PlaySoundAt{id, spec, pos_, vel_});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::stopSound(sound_handle_t sound)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::StopSound{sound});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::fadeSound(sound_handle_t soundid, f32 step, f32 target_gain)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::FadeSound{soundid, step, target_gain});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProxySoundManager::updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_mgr::UpdateSoundPosVel{sound, pos_, vel_});
|
||||||
|
}
|
71
src/client/sound/proxy_sound_manager.h
Normal file
71
src/client/sound/proxy_sound_manager.h
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2023 DS
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sound_manager.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The public ISoundManager interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ProxySoundManager final : public ISoundManager
|
||||||
|
{
|
||||||
|
OpenALSoundManager m_sound_manager;
|
||||||
|
// sound names from loadSoundData and loadSoundFile
|
||||||
|
std::unordered_set<std::string> m_known_sound_names;
|
||||||
|
|
||||||
|
void send(SoundManagerMsgToMgr msg)
|
||||||
|
{
|
||||||
|
m_sound_manager.m_queue_to_mgr.push_back(std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class MsgResult { Ok, Empty, Stopped};
|
||||||
|
MsgResult handleMsg(SoundManagerMsgToProxy &&msg);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ProxySoundManager(SoundManagerSingleton *smg,
|
||||||
|
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
|
||||||
|
m_sound_manager(smg, std::move(fallback_path_provider))
|
||||||
|
{
|
||||||
|
m_sound_manager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
~ProxySoundManager() override;
|
||||||
|
|
||||||
|
/* Interface */
|
||||||
|
|
||||||
|
void step(f32 dtime) override;
|
||||||
|
void pauseAll() override;
|
||||||
|
void resumeAll() override;
|
||||||
|
|
||||||
|
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_) override;
|
||||||
|
void setListenerGain(f32 gain) override;
|
||||||
|
|
||||||
|
bool loadSoundFile(const std::string &name, const std::string &filepath) override;
|
||||||
|
bool loadSoundData(const std::string &name, std::string &&filedata) override;
|
||||||
|
void addSoundToGroup(const std::string &sound_name, const std::string &group_name) override;
|
||||||
|
|
||||||
|
void playSound(sound_handle_t id, const SoundSpec &spec) override;
|
||||||
|
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
||||||
|
const v3f &vel_) override;
|
||||||
|
void stopSound(sound_handle_t sound) override;
|
||||||
|
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain) override;
|
||||||
|
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_) override;
|
||||||
|
};
|
119
src/client/sound/sound_constants.h
Normal file
119
src/client/sound/sound_constants.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* The coordinate space for sounds (sound-space):
|
||||||
|
* ----------------------------------------------
|
||||||
|
*
|
||||||
|
* * The functions from ISoundManager (see sound.h) take spatial vectors in node-space.
|
||||||
|
* * All other `v3f`s here are, if not told otherwise, in sound-space, which is
|
||||||
|
* defined as node-space mirrored along the x-axis.
|
||||||
|
* (This is needed because OpenAL uses a right-handed coordinate system.)
|
||||||
|
* * Use `swap_handedness()` from `al_helpers.h` to convert between those two
|
||||||
|
* coordinate spaces.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* How sounds are loaded:
|
||||||
|
* ----------------------
|
||||||
|
*
|
||||||
|
* * Step 1:
|
||||||
|
* `loadSoundFile` or `loadSoundFile` is called. This adds an unopen sound with
|
||||||
|
* the given name to `m_sound_datas_unopen`.
|
||||||
|
* Unopen / lazy sounds (`ISoundDataUnopen`) are ogg-vorbis files that we did not yet
|
||||||
|
* start to decode. (Decoding an unopen sound does not fail under normal circumstances
|
||||||
|
* (because we check whether the file exists at least), if it does fail anyways,
|
||||||
|
* we should notify the user.)
|
||||||
|
* * Step 2:
|
||||||
|
* `addSoundToGroup` is called, to add the name from step 1 to a group. If the
|
||||||
|
* group does not yet exist, a new one is created. A group can later be played.
|
||||||
|
* (The mapping is stored in `m_sound_groups`.)
|
||||||
|
* * Step 3:
|
||||||
|
* `playSound` or `playSoundAt` is called.
|
||||||
|
* * Step 3.1:
|
||||||
|
* If the group with the name `spec.name` does not exist, and `spec.use_local_fallback`
|
||||||
|
* is true, a new group is created using the user's sound-pack.
|
||||||
|
* * Step 3.2:
|
||||||
|
* We choose one random sound name from the given group.
|
||||||
|
* * Step 3.3:
|
||||||
|
* We open the sound (see `openSingleSound`).
|
||||||
|
* If the sound is already open (in `m_sound_datas_open`), we take that one.
|
||||||
|
* Otherwise we open it by calling `ISoundDataUnopen::open`. We choose (by
|
||||||
|
* sound length), whether it's a single-buffer (`SoundDataOpenBuffer`) or
|
||||||
|
* streamed (`SoundDataOpenStream`) sound.
|
||||||
|
* Single-buffer sounds are always completely loaded. Streamed sounds can be
|
||||||
|
* partially loaded.
|
||||||
|
* The sound is erased from `m_sound_datas_unopen` and added to `m_sound_datas_open`.
|
||||||
|
* Open sounds are kept forever.
|
||||||
|
* * Step 3.4:
|
||||||
|
* We create the new `PlayingSound`. It has a `shared_ptr` to its open sound.
|
||||||
|
* If the open sound is streaming, the playing sound needs to be stepped using
|
||||||
|
* `PlayingSound::stepStream` for enqueuing buffers. For this purpose, the sound
|
||||||
|
* is added to `m_sounds_streaming` (as `weak_ptr`).
|
||||||
|
* If the sound is fading, it is added to `m_sounds_fading` for regular fade-stepping.
|
||||||
|
* The sound is also added to `m_sounds_playing`, so that one can access it
|
||||||
|
* via its sound handle.
|
||||||
|
* * Step 4:
|
||||||
|
* Streaming sounds are updated. For details see [Streaming of sounds].
|
||||||
|
* * Step 5:
|
||||||
|
* At deinitialization, we can just let the destructors do their work.
|
||||||
|
* Sound sources are deleted (and with this also stopped) by ~PlayingSound.
|
||||||
|
* Buffers can't be deleted while sound sources using them exist, because
|
||||||
|
* PlayingSound has a shared_ptr to its ISoundData.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Streaming of sounds:
|
||||||
|
* --------------------
|
||||||
|
*
|
||||||
|
* In each "bigstep", all streamed sounds are stepStream()ed. This means a
|
||||||
|
* sound can be stepped at any point in time in the bigstep's interval.
|
||||||
|
*
|
||||||
|
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
||||||
|
* end of the next bigstep. So between two stepStream()-calls lie at most
|
||||||
|
* 2 * STREAM_BIGSTEP_TIME seconds.
|
||||||
|
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
|
||||||
|
* is still available after the first stepStream().
|
||||||
|
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
|
||||||
|
* not run into an empty queue.
|
||||||
|
*
|
||||||
|
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
|
||||||
|
* other sounds that may have taken long to stepStream(), and sounds being played
|
||||||
|
* faster due to Doppler effect.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// constants
|
||||||
|
|
||||||
|
// in seconds
|
||||||
|
constexpr f32 REMOVE_DEAD_SOUNDS_INTERVAL = 2.0f;
|
||||||
|
// maximum length in seconds that a sound can have without being streamed
|
||||||
|
constexpr f32 SOUND_DURATION_MAX_SINGLE = 3.0f;
|
||||||
|
// minimum time in seconds of a single buffer in a streamed sound
|
||||||
|
constexpr f32 MIN_STREAM_BUFFER_LENGTH = 1.0f;
|
||||||
|
// duration in seconds of one bigstep
|
||||||
|
constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
|
||||||
|
// step duration for the OpenALSoundManager thread, in seconds
|
||||||
|
constexpr f32 SOUNDTHREAD_DTIME = 0.016f;
|
||||||
|
|
||||||
|
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
|
||||||
|
"See [Streaming of sounds].");
|
||||||
|
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
||||||
|
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
231
src/client/sound/sound_data.cpp
Normal file
231
src/client/sound/sound_data.cpp
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sound_data.h"
|
||||||
|
|
||||||
|
#include "sound_constants.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ISoundDataOpen struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> ISoundDataOpen::fromOggFile(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const std::string &filename_for_logging)
|
||||||
|
{
|
||||||
|
// Get some information about the OGG file
|
||||||
|
std::optional<OggFileDecodeInfo> decode_info = oggfile->getDecodeInfo(filename_for_logging);
|
||||||
|
if (!decode_info.has_value()) {
|
||||||
|
warningstream << "Audio: Error decoding "
|
||||||
|
<< filename_for_logging << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use duration (in seconds) to decide whether to load all at once or to stream
|
||||||
|
if (decode_info->length_seconds <= SOUND_DURATION_MAX_SINGLE) {
|
||||||
|
return std::make_shared<SoundDataOpenBuffer>(std::move(oggfile), *decode_info);
|
||||||
|
} else {
|
||||||
|
return std::make_shared<SoundDataOpenStream>(std::move(oggfile), *decode_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SoundDataUnopenBuffer struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> SoundDataUnopenBuffer::open(const std::string &sound_name) &&
|
||||||
|
{
|
||||||
|
// load from m_buffer
|
||||||
|
|
||||||
|
auto oggfile = std::make_unique<RAIIOggFile>();
|
||||||
|
|
||||||
|
auto buffer_source = std::make_unique<OggVorbisBufferSource>();
|
||||||
|
buffer_source->buf = std::move(m_buffer);
|
||||||
|
|
||||||
|
oggfile->m_needs_clear = true;
|
||||||
|
if (ov_open_callbacks(buffer_source.release(), oggfile->get(), nullptr, 0,
|
||||||
|
OggVorbisBufferSource::s_ov_callbacks) != 0) {
|
||||||
|
warningstream << "Audio: Error opening " << sound_name << " for decoding"
|
||||||
|
<< std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ISoundDataOpen::fromOggFile(std::move(oggfile), sound_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SoundDataUnopenFile struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> SoundDataUnopenFile::open(const std::string &sound_name) &&
|
||||||
|
{
|
||||||
|
// load from file at m_path
|
||||||
|
|
||||||
|
auto oggfile = std::make_unique<RAIIOggFile>();
|
||||||
|
|
||||||
|
if (ov_fopen(m_path.c_str(), oggfile->get()) != 0) {
|
||||||
|
warningstream << "Audio: Error opening " << m_path << " for decoding"
|
||||||
|
<< std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
oggfile->m_needs_clear = true;
|
||||||
|
|
||||||
|
return ISoundDataOpen::fromOggFile(std::move(oggfile), sound_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SoundDataOpenBuffer struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
SoundDataOpenBuffer::SoundDataOpenBuffer(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const OggFileDecodeInfo &decode_info) : ISoundDataOpen(decode_info)
|
||||||
|
{
|
||||||
|
m_buffer = oggfile->loadBuffer(m_decode_info, 0, m_decode_info.length_samples);
|
||||||
|
if (m_buffer.get() == 0) {
|
||||||
|
warningstream << "SoundDataOpenBuffer: Failed to load sound \""
|
||||||
|
<< m_decode_info.name_for_logging << "\"" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SoundDataOpenStream struct
|
||||||
|
*/
|
||||||
|
|
||||||
|
SoundDataOpenStream::SoundDataOpenStream(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const OggFileDecodeInfo &decode_info) :
|
||||||
|
ISoundDataOpen(decode_info), m_oggfile(std::move(oggfile))
|
||||||
|
{
|
||||||
|
// do nothing here. buffers are loaded at getOrLoadBufferAt
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<ALuint, ALuint, ALuint> SoundDataOpenStream::getOrLoadBufferAt(ALuint offset)
|
||||||
|
{
|
||||||
|
if (offset >= m_decode_info.length_samples)
|
||||||
|
return {0, m_decode_info.length_samples, 0};
|
||||||
|
|
||||||
|
// find the right-most ContiguousBuffers, such that `m_start <= offset`
|
||||||
|
// equivalent: the first element from the right such that `!(m_start > offset)`
|
||||||
|
// (from the right, `offset` is a lower bound to the `m_start`s)
|
||||||
|
auto lower_rit = std::lower_bound(m_bufferss.rbegin(), m_bufferss.rend(), offset,
|
||||||
|
[](const ContiguousBuffers &bufs, ALuint offset) {
|
||||||
|
return bufs.m_start > offset;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lower_rit != m_bufferss.rend()) {
|
||||||
|
std::vector<SoundBufferUntil> &bufs = lower_rit->m_buffers;
|
||||||
|
// find the left-most SoundBufferUntil, such that `m_end > offset`
|
||||||
|
// equivalent: the first element from the left such that `m_end > offset`
|
||||||
|
// (returns first element where comp gives true)
|
||||||
|
auto upper_it = std::upper_bound(bufs.begin(), bufs.end(), offset,
|
||||||
|
[](ALuint offset, const SoundBufferUntil &buf) {
|
||||||
|
return offset < buf.m_end;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (upper_it != bufs.end()) {
|
||||||
|
ALuint start = upper_it == bufs.begin() ? lower_rit->m_start
|
||||||
|
: (upper_it - 1)->m_end;
|
||||||
|
return {upper_it->m_buffer.get(), upper_it->m_end, offset - start};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no loaded buffer starts before or at `offset`
|
||||||
|
// or no loaded buffer (that starts before or at `offset`) ends after `offset`
|
||||||
|
|
||||||
|
// lower_rit, but not reverse and 1 farther
|
||||||
|
auto after_it = m_bufferss.begin() + (m_bufferss.rend() - lower_rit);
|
||||||
|
|
||||||
|
return loadBufferAt(offset, after_it);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<ALuint, ALuint, ALuint> SoundDataOpenStream::loadBufferAt(ALuint offset,
|
||||||
|
std::vector<ContiguousBuffers>::iterator after_it)
|
||||||
|
{
|
||||||
|
bool has_before = after_it != m_bufferss.begin();
|
||||||
|
bool has_after = after_it != m_bufferss.end();
|
||||||
|
|
||||||
|
ALuint end_before = has_before ? (after_it - 1)->m_buffers.back().m_end : 0;
|
||||||
|
ALuint start_after = has_after ? after_it->m_start : m_decode_info.length_samples;
|
||||||
|
|
||||||
|
const ALuint min_buf_len_samples = m_decode_info.freq * MIN_STREAM_BUFFER_LENGTH;
|
||||||
|
|
||||||
|
//
|
||||||
|
// 1) Find the actual start and end of the new buffer
|
||||||
|
//
|
||||||
|
|
||||||
|
ALuint new_buf_start = offset;
|
||||||
|
ALuint new_buf_end = offset + min_buf_len_samples;
|
||||||
|
|
||||||
|
// Don't load into next buffer, or past the end
|
||||||
|
if (new_buf_end > start_after) {
|
||||||
|
new_buf_end = start_after;
|
||||||
|
// Also move start (for min buf size) (but not *into* previous buffer)
|
||||||
|
if (new_buf_end - new_buf_start < min_buf_len_samples) {
|
||||||
|
new_buf_start = std::max(
|
||||||
|
end_before,
|
||||||
|
new_buf_end < min_buf_len_samples ? 0
|
||||||
|
: new_buf_end - min_buf_len_samples
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widen if space to right or left is smaller than min buf size
|
||||||
|
if (new_buf_start - end_before < min_buf_len_samples)
|
||||||
|
new_buf_start = end_before;
|
||||||
|
if (start_after - new_buf_end < min_buf_len_samples)
|
||||||
|
new_buf_end = start_after;
|
||||||
|
|
||||||
|
//
|
||||||
|
// 2) Load [new_buf_start, new_buf_end)
|
||||||
|
//
|
||||||
|
|
||||||
|
// If it fails, we get a 0-buffer. we store it and won't try loading again
|
||||||
|
RAIIALSoundBuffer new_buf = m_oggfile->loadBuffer(m_decode_info, new_buf_start,
|
||||||
|
new_buf_end);
|
||||||
|
|
||||||
|
//
|
||||||
|
// 3) Insert before after_it
|
||||||
|
//
|
||||||
|
|
||||||
|
// Choose ContiguousBuffers to add the new SoundBufferUntil into:
|
||||||
|
// * `after_it - 1` (=before) if existent and if there's no space between its
|
||||||
|
// last buffer and the new buffer
|
||||||
|
// * A new ContiguousBuffers otherwise
|
||||||
|
auto it = has_before && new_buf_start == end_before ? after_it - 1
|
||||||
|
: m_bufferss.insert(after_it, ContiguousBuffers{new_buf_start, {}});
|
||||||
|
|
||||||
|
// Add the new SoundBufferUntil
|
||||||
|
size_t new_buf_i = it->m_buffers.size();
|
||||||
|
it->m_buffers.push_back(SoundBufferUntil{new_buf_end, std::move(new_buf)});
|
||||||
|
|
||||||
|
if (has_after && new_buf_end == start_after) {
|
||||||
|
// Merge after into my ContiguousBuffers
|
||||||
|
auto &bufs = it->m_buffers;
|
||||||
|
auto &bufs_after = (it + 1)->m_buffers;
|
||||||
|
bufs.insert(bufs.end(), std::make_move_iterator(bufs_after.begin()),
|
||||||
|
std::make_move_iterator(bufs_after.end()));
|
||||||
|
it = m_bufferss.erase(it + 1) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {it->m_buffers[new_buf_i].m_buffer.get(), new_buf_end, offset - new_buf_start};
|
||||||
|
}
|
173
src/client/sound/sound_data.h
Normal file
173
src/client/sound/sound_data.h
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ogg_file.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores sound pcm data buffers.
|
||||||
|
*/
|
||||||
|
struct ISoundDataOpen
|
||||||
|
{
|
||||||
|
OggFileDecodeInfo m_decode_info;
|
||||||
|
|
||||||
|
explicit ISoundDataOpen(const OggFileDecodeInfo &decode_info) :
|
||||||
|
m_decode_info(decode_info) {}
|
||||||
|
|
||||||
|
virtual ~ISoundDataOpen() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iff the data is streaming, there is more than one buffer.
|
||||||
|
* @return Whether it's streaming data.
|
||||||
|
*/
|
||||||
|
virtual bool isStreaming() const noexcept = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a buffer containing data starting at the given offset. Or just get it
|
||||||
|
* if it was already loaded.
|
||||||
|
*
|
||||||
|
* This function returns multiple values:
|
||||||
|
* * `buffer`: The OpenAL buffer.
|
||||||
|
* * `buffer_end`: The offset (in the file) where `buffer` ends (exclusive).
|
||||||
|
* * `offset_in_buffer`: Offset relative to `buffer`'s start where the requested
|
||||||
|
* `offset` is.
|
||||||
|
* `offset_in_buffer == 0` is guaranteed if some loaded buffer ends at
|
||||||
|
* `offset`.
|
||||||
|
*
|
||||||
|
* @param offset The start of the buffer.
|
||||||
|
* @return `{buffer, buffer_end, offset_in_buffer}` or `{0, sound_data_end, 0}`
|
||||||
|
* if `offset` is invalid.
|
||||||
|
*/
|
||||||
|
virtual std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) = 0;
|
||||||
|
|
||||||
|
static std::shared_ptr<ISoundDataOpen> fromOggFile(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const std::string &filename_for_logging);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be opened lazily when first used.
|
||||||
|
*/
|
||||||
|
struct ISoundDataUnopen
|
||||||
|
{
|
||||||
|
virtual ~ISoundDataUnopen() = default;
|
||||||
|
|
||||||
|
// Note: The ISoundDataUnopen is moved (see &&). It is not meant to be kept
|
||||||
|
// after opening.
|
||||||
|
virtual std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound file is in a memory buffer.
|
||||||
|
*/
|
||||||
|
struct SoundDataUnopenBuffer final : ISoundDataUnopen
|
||||||
|
{
|
||||||
|
std::string m_buffer;
|
||||||
|
|
||||||
|
explicit SoundDataUnopenBuffer(std::string &&buffer) : m_buffer(std::move(buffer)) {}
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sound file is in file system.
|
||||||
|
*/
|
||||||
|
struct SoundDataUnopenFile final : ISoundDataUnopen
|
||||||
|
{
|
||||||
|
std::string m_path;
|
||||||
|
|
||||||
|
explicit SoundDataUnopenFile(const std::string &path) : m_path(path) {}
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-streaming opened sound data.
|
||||||
|
* All data is completely loaded in one buffer.
|
||||||
|
*/
|
||||||
|
struct SoundDataOpenBuffer final : ISoundDataOpen
|
||||||
|
{
|
||||||
|
RAIIALSoundBuffer m_buffer;
|
||||||
|
|
||||||
|
SoundDataOpenBuffer(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const OggFileDecodeInfo &decode_info);
|
||||||
|
|
||||||
|
bool isStreaming() const noexcept override { return false; }
|
||||||
|
|
||||||
|
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override
|
||||||
|
{
|
||||||
|
if (offset >= m_decode_info.length_samples)
|
||||||
|
return {0, m_decode_info.length_samples, 0};
|
||||||
|
return {m_buffer.get(), m_decode_info.length_samples, offset};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming opened sound data.
|
||||||
|
*
|
||||||
|
* Uses a sorted list of contiguous sound data regions (`ContiguousBuffers`s) for
|
||||||
|
* efficient seeking.
|
||||||
|
*/
|
||||||
|
struct SoundDataOpenStream final : ISoundDataOpen
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* An OpenAL buffer that goes until `m_end` (exclusive).
|
||||||
|
*/
|
||||||
|
struct SoundBufferUntil final
|
||||||
|
{
|
||||||
|
ALuint m_end;
|
||||||
|
RAIIALSoundBuffer m_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sorted non-empty vector of contiguous buffers.
|
||||||
|
* The start (inclusive) of each buffer is the end of its predecessor, or
|
||||||
|
* `m_start` for the first buffer.
|
||||||
|
*/
|
||||||
|
struct ContiguousBuffers final
|
||||||
|
{
|
||||||
|
ALuint m_start;
|
||||||
|
std::vector<SoundBufferUntil> m_buffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<RAIIOggFile> m_oggfile;
|
||||||
|
// A sorted vector of non-overlapping, non-contiguous `ContiguousBuffers`s.
|
||||||
|
std::vector<ContiguousBuffers> m_bufferss;
|
||||||
|
|
||||||
|
SoundDataOpenStream(std::unique_ptr<RAIIOggFile> oggfile,
|
||||||
|
const OggFileDecodeInfo &decode_info);
|
||||||
|
|
||||||
|
bool isStreaming() const noexcept override { return true; }
|
||||||
|
|
||||||
|
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// offset must be before after_it's m_start and after (after_it-1)'s last m_end
|
||||||
|
// new buffer will be inserted into m_bufferss before after_it
|
||||||
|
// returns same as getOrLoadBufferAt
|
||||||
|
std::tuple<ALuint, ALuint, ALuint> loadBufferAt(ALuint offset,
|
||||||
|
std::vector<ContiguousBuffers>::iterator after_it);
|
||||||
|
};
|
523
src/client/sound/sound_manager.cpp
Normal file
523
src/client/sound/sound_manager.cpp
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sound_manager.h"
|
||||||
|
|
||||||
|
#include "sound_singleton.h"
|
||||||
|
#include "util/numeric.h" // myrand()
|
||||||
|
#include "filesys.h"
|
||||||
|
#include "porting.h"
|
||||||
|
|
||||||
|
void OpenALSoundManager::stepStreams(f32 dtime)
|
||||||
|
{
|
||||||
|
// spread work across steps
|
||||||
|
const size_t num_issued_sounds = std::min(
|
||||||
|
m_sounds_streaming_current_bigstep.size(),
|
||||||
|
(size_t)std::ceil(m_sounds_streaming_current_bigstep.size()
|
||||||
|
* dtime / m_stream_timer)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_issued_sounds; ++i) {
|
||||||
|
auto wptr = std::move(m_sounds_streaming_current_bigstep.back());
|
||||||
|
m_sounds_streaming_current_bigstep.pop_back();
|
||||||
|
|
||||||
|
std::shared_ptr<PlayingSound> snd = wptr.lock();
|
||||||
|
if (!snd)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!snd->stepStream())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// sound still lives and needs more stream-stepping => add to next bigstep
|
||||||
|
m_sounds_streaming_next_bigstep.push_back(std::move(wptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_stream_timer -= dtime;
|
||||||
|
if (m_stream_timer <= 0.0f) {
|
||||||
|
m_stream_timer = STREAM_BIGSTEP_TIME;
|
||||||
|
using std::swap;
|
||||||
|
swap(m_sounds_streaming_current_bigstep, m_sounds_streaming_next_bigstep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::doFades(f32 dtime)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < m_sounds_fading.size();) {
|
||||||
|
std::shared_ptr<PlayingSound> snd = m_sounds_fading[i].lock();
|
||||||
|
if (snd) {
|
||||||
|
if (snd->doFade(dtime)) {
|
||||||
|
// needs more fading later, keep in m_sounds_fading
|
||||||
|
++i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sound no longer needs to be faded
|
||||||
|
m_sounds_fading[i] = std::move(m_sounds_fading.back());
|
||||||
|
m_sounds_fading.pop_back();
|
||||||
|
// continue with same i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> OpenALSoundManager::openSingleSound(const std::string &sound_name)
|
||||||
|
{
|
||||||
|
// if already open, nothing to do
|
||||||
|
auto it = m_sound_datas_open.find(sound_name);
|
||||||
|
if (it != m_sound_datas_open.end())
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
// find unopened data
|
||||||
|
auto it_unopen = m_sound_datas_unopen.find(sound_name);
|
||||||
|
if (it_unopen == m_sound_datas_unopen.end())
|
||||||
|
return nullptr;
|
||||||
|
std::unique_ptr<ISoundDataUnopen> unopn_snd = std::move(it_unopen->second);
|
||||||
|
m_sound_datas_unopen.erase(it_unopen);
|
||||||
|
|
||||||
|
// open
|
||||||
|
std::shared_ptr<ISoundDataOpen> opn_snd = std::move(*unopn_snd).open(sound_name);
|
||||||
|
if (!opn_snd)
|
||||||
|
return nullptr;
|
||||||
|
m_sound_datas_open.emplace(sound_name, opn_snd);
|
||||||
|
return opn_snd;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenALSoundManager::getLoadedSoundNameFromGroup(const std::string &group_name)
|
||||||
|
{
|
||||||
|
std::string chosen_sound_name = "";
|
||||||
|
|
||||||
|
auto it_groups = m_sound_groups.find(group_name);
|
||||||
|
if (it_groups == m_sound_groups.end())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
std::vector<std::string> &group_sounds = it_groups->second;
|
||||||
|
while (!group_sounds.empty()) {
|
||||||
|
// choose one by random
|
||||||
|
int j = myrand() % group_sounds.size();
|
||||||
|
chosen_sound_name = group_sounds[j];
|
||||||
|
|
||||||
|
// find chosen one
|
||||||
|
std::shared_ptr<ISoundDataOpen> snd = openSingleSound(chosen_sound_name);
|
||||||
|
if (snd)
|
||||||
|
return chosen_sound_name;
|
||||||
|
|
||||||
|
// it doesn't exist
|
||||||
|
// remove it from the group and try again
|
||||||
|
group_sounds[j] = std::move(group_sounds.back());
|
||||||
|
group_sounds.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OpenALSoundManager::getOrLoadLoadedSoundNameFromGroup(const std::string &group_name)
|
||||||
|
{
|
||||||
|
std::string sound_name = getLoadedSoundNameFromGroup(group_name);
|
||||||
|
if (!sound_name.empty())
|
||||||
|
return sound_name;
|
||||||
|
|
||||||
|
// load
|
||||||
|
std::vector<std::string> paths = m_fallback_path_provider
|
||||||
|
->getLocalFallbackPathsForSoundname(group_name);
|
||||||
|
for (const std::string &path : paths) {
|
||||||
|
if (loadSoundFile(path, path))
|
||||||
|
addSoundToGroup(path, group_name);
|
||||||
|
}
|
||||||
|
return getLoadedSoundNameFromGroup(group_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<PlayingSound> OpenALSoundManager::createPlayingSound(
|
||||||
|
const std::string &sound_name, bool loop, f32 volume, f32 pitch,
|
||||||
|
f32 start_time, const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
|
||||||
|
{
|
||||||
|
infostream << "OpenALSoundManager: Creating playing sound \"" << sound_name
|
||||||
|
<< "\"" << std::endl;
|
||||||
|
warn_if_al_error("before createPlayingSound");
|
||||||
|
|
||||||
|
std::shared_ptr<ISoundDataOpen> lsnd = openSingleSound(sound_name);
|
||||||
|
if (!lsnd) {
|
||||||
|
// does not happen because of the call to getLoadedSoundNameFromGroup
|
||||||
|
errorstream << "OpenALSoundManager::createPlayingSound: Sound \""
|
||||||
|
<< sound_name << "\" disappeared." << std::endl;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lsnd->m_decode_info.is_stereo && pos_vel_opt.has_value()) {
|
||||||
|
warningstream << "OpenALSoundManager::createPlayingSound: "
|
||||||
|
<< "Creating positional stereo sound \"" << sound_name << "\"."
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALuint source_id;
|
||||||
|
alGenSources(1, &source_id);
|
||||||
|
if (warn_if_al_error("createPlayingSound (alGenSources)") != AL_NO_ERROR) {
|
||||||
|
// happens ie. if there are too many sources (out of memory)
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sound = std::make_shared<PlayingSound>(source_id, std::move(lsnd), loop,
|
||||||
|
volume, pitch, start_time, pos_vel_opt);
|
||||||
|
|
||||||
|
sound->play();
|
||||||
|
if (m_is_paused)
|
||||||
|
sound->pause();
|
||||||
|
warn_if_al_error("createPlayingSound");
|
||||||
|
return sound;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::playSoundGeneric(sound_handle_t id, const std::string &group_name,
|
||||||
|
bool loop, f32 volume, f32 fade, f32 pitch, bool use_local_fallback,
|
||||||
|
f32 start_time, const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
|
||||||
|
{
|
||||||
|
assert(id != 0);
|
||||||
|
|
||||||
|
if (group_name.empty()) {
|
||||||
|
reportRemovedSound(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose random sound name from group name
|
||||||
|
std::string sound_name = use_local_fallback ?
|
||||||
|
getOrLoadLoadedSoundNameFromGroup(group_name) :
|
||||||
|
getLoadedSoundNameFromGroup(group_name);
|
||||||
|
if (sound_name.empty()) {
|
||||||
|
infostream << "OpenALSoundManager: \"" << group_name << "\" not found."
|
||||||
|
<< std::endl;
|
||||||
|
reportRemovedSound(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
volume = std::max(0.0f, volume);
|
||||||
|
f32 target_fade_volume = volume;
|
||||||
|
if (fade > 0.0f)
|
||||||
|
volume = 0.0f;
|
||||||
|
|
||||||
|
if (!(pitch > 0.0f)) {
|
||||||
|
warningstream << "OpenALSoundManager::playSoundGeneric: Illegal pitch value: "
|
||||||
|
<< start_time << std::endl;
|
||||||
|
pitch = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(start_time)) {
|
||||||
|
warningstream << "OpenALSoundManager::playSoundGeneric: Illegal start_time value: "
|
||||||
|
<< start_time << std::endl;
|
||||||
|
start_time = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// play it
|
||||||
|
std::shared_ptr<PlayingSound> sound = createPlayingSound(sound_name, loop,
|
||||||
|
volume, pitch, start_time, pos_vel_opt);
|
||||||
|
if (!sound) {
|
||||||
|
reportRemovedSound(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to streaming sounds if streaming
|
||||||
|
if (sound->isStreaming())
|
||||||
|
m_sounds_streaming_next_bigstep.push_back(sound);
|
||||||
|
|
||||||
|
m_sounds_playing.emplace(id, std::move(sound));
|
||||||
|
|
||||||
|
if (fade > 0.0f)
|
||||||
|
fadeSound(id, fade, target_fade_volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OpenALSoundManager::removeDeadSounds()
|
||||||
|
{
|
||||||
|
int num_deleted_sounds = 0;
|
||||||
|
|
||||||
|
for (auto it = m_sounds_playing.begin(); it != m_sounds_playing.end();) {
|
||||||
|
sound_handle_t id = it->first;
|
||||||
|
PlayingSound &sound = *it->second;
|
||||||
|
// If dead, remove it
|
||||||
|
if (sound.isDead()) {
|
||||||
|
it = m_sounds_playing.erase(it);
|
||||||
|
reportRemovedSound(id);
|
||||||
|
++num_deleted_sounds;
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return num_deleted_sounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenALSoundManager::OpenALSoundManager(SoundManagerSingleton *smg,
|
||||||
|
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
|
||||||
|
Thread("OpenALSoundManager"),
|
||||||
|
m_fallback_path_provider(std::move(fallback_path_provider)),
|
||||||
|
m_device(smg->m_device.get()),
|
||||||
|
m_context(smg->m_context.get())
|
||||||
|
{
|
||||||
|
SANITY_CHECK(!!m_fallback_path_provider);
|
||||||
|
|
||||||
|
infostream << "Audio: Initialized: OpenAL " << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenALSoundManager::~OpenALSoundManager()
|
||||||
|
{
|
||||||
|
infostream << "Audio: Deinitializing..." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interface */
|
||||||
|
|
||||||
|
void OpenALSoundManager::step(f32 dtime)
|
||||||
|
{
|
||||||
|
m_time_until_dead_removal -= dtime;
|
||||||
|
if (m_time_until_dead_removal <= 0.0f) {
|
||||||
|
if (!m_sounds_playing.empty()) {
|
||||||
|
verbosestream << "OpenALSoundManager::step(): "
|
||||||
|
<< m_sounds_playing.size() << " playing sounds, "
|
||||||
|
<< m_sound_datas_unopen.size() << " unopen sounds, "
|
||||||
|
<< m_sound_datas_open.size() << " open sounds and "
|
||||||
|
<< m_sound_groups.size() << " sound groups loaded."
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_deleted_sounds = removeDeadSounds();
|
||||||
|
|
||||||
|
if (num_deleted_sounds != 0)
|
||||||
|
verbosestream << "OpenALSoundManager::step(): Deleted "
|
||||||
|
<< num_deleted_sounds << " dead playing sounds." << std::endl;
|
||||||
|
|
||||||
|
m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
doFades(dtime);
|
||||||
|
stepStreams(dtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::pauseAll()
|
||||||
|
{
|
||||||
|
for (auto &snd_p : m_sounds_playing) {
|
||||||
|
PlayingSound &snd = *snd_p.second;
|
||||||
|
snd.pause();
|
||||||
|
}
|
||||||
|
m_is_paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::resumeAll()
|
||||||
|
{
|
||||||
|
for (auto &snd_p : m_sounds_playing) {
|
||||||
|
PlayingSound &snd = *snd_p.second;
|
||||||
|
snd.resume();
|
||||||
|
}
|
||||||
|
m_is_paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::updateListener(const v3f &pos_, const v3f &vel_,
|
||||||
|
const v3f &at_, const v3f &up_)
|
||||||
|
{
|
||||||
|
v3f pos = swap_handedness(pos_);
|
||||||
|
v3f vel = swap_handedness(vel_);
|
||||||
|
v3f at = swap_handedness(at_);
|
||||||
|
v3f up = swap_handedness(up_);
|
||||||
|
ALfloat orientation[6] = {at.X, at.Y, at.Z, up.X, up.Y, up.Z};
|
||||||
|
|
||||||
|
alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
|
||||||
|
alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
|
||||||
|
alListenerfv(AL_ORIENTATION, orientation);
|
||||||
|
warn_if_al_error("updateListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::setListenerGain(f32 gain)
|
||||||
|
{
|
||||||
|
alListenerf(AL_GAIN, gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenALSoundManager::loadSoundFile(const std::string &name, const std::string &filepath)
|
||||||
|
{
|
||||||
|
// do not add twice
|
||||||
|
if (m_sound_datas_open.count(name) != 0 || m_sound_datas_unopen.count(name) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// coarse check
|
||||||
|
if (!fs::IsFile(filepath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
loadSoundFileNoCheck(name, filepath);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenALSoundManager::loadSoundData(const std::string &name, std::string &&filedata)
|
||||||
|
{
|
||||||
|
// do not add twice
|
||||||
|
if (m_sound_datas_open.count(name) != 0 || m_sound_datas_unopen.count(name) != 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
loadSoundDataNoCheck(name, std::move(filedata));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::loadSoundFileNoCheck(const std::string &name, const std::string &filepath)
|
||||||
|
{
|
||||||
|
// remember for lazy loading
|
||||||
|
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenFile>(filepath));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::loadSoundDataNoCheck(const std::string &name, std::string &&filedata)
|
||||||
|
{
|
||||||
|
// remember for lazy loading
|
||||||
|
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenBuffer>(std::move(filedata)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::addSoundToGroup(const std::string &sound_name, const std::string &group_name)
|
||||||
|
{
|
||||||
|
auto it_groups = m_sound_groups.find(group_name);
|
||||||
|
if (it_groups != m_sound_groups.end())
|
||||||
|
it_groups->second.push_back(sound_name);
|
||||||
|
else
|
||||||
|
m_sound_groups.emplace(group_name, std::vector<std::string>{sound_name});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::playSound(sound_handle_t id, const SoundSpec &spec)
|
||||||
|
{
|
||||||
|
return playSoundGeneric(id, spec.name, spec.loop, spec.gain, spec.fade, spec.pitch,
|
||||||
|
spec.use_local_fallback, spec.start_time, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::playSoundAt(sound_handle_t id, const SoundSpec &spec,
|
||||||
|
const v3f &pos_, const v3f &vel_)
|
||||||
|
{
|
||||||
|
std::optional<std::pair<v3f, v3f>> pos_vel_opt({
|
||||||
|
swap_handedness(pos_),
|
||||||
|
swap_handedness(vel_)
|
||||||
|
});
|
||||||
|
|
||||||
|
return playSoundGeneric(id, spec.name, spec.loop, spec.gain, spec.fade, spec.pitch,
|
||||||
|
spec.use_local_fallback, spec.start_time, pos_vel_opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::stopSound(sound_handle_t sound)
|
||||||
|
{
|
||||||
|
m_sounds_playing.erase(sound);
|
||||||
|
reportRemovedSound(sound);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::fadeSound(sound_handle_t soundid, f32 step, f32 target_gain)
|
||||||
|
{
|
||||||
|
// Ignore the command if step isn't valid.
|
||||||
|
if (step == 0.0f)
|
||||||
|
return;
|
||||||
|
auto sound_it = m_sounds_playing.find(soundid);
|
||||||
|
if (sound_it == m_sounds_playing.end())
|
||||||
|
return; // No sound to fade
|
||||||
|
PlayingSound &sound = *sound_it->second;
|
||||||
|
if (sound.fade(step, target_gain))
|
||||||
|
m_sounds_fading.emplace_back(sound_it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenALSoundManager::updateSoundPosVel(sound_handle_t id, const v3f &pos_,
|
||||||
|
const v3f &vel_)
|
||||||
|
{
|
||||||
|
v3f pos = swap_handedness(pos_);
|
||||||
|
v3f vel = swap_handedness(vel_);
|
||||||
|
|
||||||
|
auto i = m_sounds_playing.find(id);
|
||||||
|
if (i == m_sounds_playing.end())
|
||||||
|
return;
|
||||||
|
i->second->updatePosVel(pos, vel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Thread stuff */
|
||||||
|
|
||||||
|
void *OpenALSoundManager::run()
|
||||||
|
{
|
||||||
|
using namespace sound_manager_messages_to_mgr;
|
||||||
|
|
||||||
|
struct MsgVisitor {
|
||||||
|
enum class Result { Ok, Empty, StopRequested };
|
||||||
|
|
||||||
|
OpenALSoundManager &mgr;
|
||||||
|
|
||||||
|
Result operator()(std::monostate &&) {
|
||||||
|
return Result::Empty; }
|
||||||
|
|
||||||
|
Result operator()(PauseAll &&) {
|
||||||
|
mgr.pauseAll(); return Result::Ok; }
|
||||||
|
Result operator()(ResumeAll &&) {
|
||||||
|
mgr.resumeAll(); return Result::Ok; }
|
||||||
|
|
||||||
|
Result operator()(UpdateListener &&msg) {
|
||||||
|
mgr.updateListener(msg.pos_, msg.vel_, msg.at_, msg.up_); return Result::Ok; }
|
||||||
|
Result operator()(SetListenerGain &&msg) {
|
||||||
|
mgr.setListenerGain(msg.gain); return Result::Ok; }
|
||||||
|
|
||||||
|
Result operator()(LoadSoundFile &&msg) {
|
||||||
|
mgr.loadSoundFileNoCheck(msg.name, msg.filepath); return Result::Ok; }
|
||||||
|
Result operator()(LoadSoundData &&msg) {
|
||||||
|
mgr.loadSoundDataNoCheck(msg.name, std::move(msg.filedata)); return Result::Ok; }
|
||||||
|
Result operator()(AddSoundToGroup &&msg) {
|
||||||
|
mgr.addSoundToGroup(msg.sound_name, msg.group_name); return Result::Ok; }
|
||||||
|
|
||||||
|
Result operator()(PlaySound &&msg) {
|
||||||
|
mgr.playSound(msg.id, msg.spec); return Result::Ok; }
|
||||||
|
Result operator()(PlaySoundAt &&msg) {
|
||||||
|
mgr.playSoundAt(msg.id, msg.spec, msg.pos_, msg.vel_); return Result::Ok; }
|
||||||
|
Result operator()(StopSound &&msg) {
|
||||||
|
mgr.stopSound(msg.sound); return Result::Ok; }
|
||||||
|
Result operator()(FadeSound &&msg) {
|
||||||
|
mgr.fadeSound(msg.soundid, msg.step, msg.target_gain); return Result::Ok; }
|
||||||
|
Result operator()(UpdateSoundPosVel &&msg) {
|
||||||
|
mgr.updateSoundPosVel(msg.sound, msg.pos_, msg.vel_); return Result::Ok; }
|
||||||
|
|
||||||
|
Result operator()(PleaseStop &&msg) {
|
||||||
|
return Result::StopRequested; }
|
||||||
|
};
|
||||||
|
|
||||||
|
u64 t_step_start = porting::getTimeMs();
|
||||||
|
while (true) {
|
||||||
|
auto get_time_since_last_step = [&] {
|
||||||
|
return (f32)(porting::getTimeMs() - t_step_start);
|
||||||
|
};
|
||||||
|
auto get_remaining_timeout = [&] {
|
||||||
|
return (s32)((1.0e3f * SOUNDTHREAD_DTIME) - get_time_since_last_step());
|
||||||
|
};
|
||||||
|
|
||||||
|
bool stop_requested = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
SoundManagerMsgToMgr msg =
|
||||||
|
m_queue_to_mgr.pop_frontNoEx(std::max(get_remaining_timeout(), 0));
|
||||||
|
|
||||||
|
MsgVisitor::Result res = std::visit(MsgVisitor{*this}, std::move(msg));
|
||||||
|
|
||||||
|
if (res == MsgVisitor::Result::Empty && get_remaining_timeout() <= 0) {
|
||||||
|
break; // finished sleeping
|
||||||
|
} else if (res == MsgVisitor::Result::StopRequested) {
|
||||||
|
stop_requested = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stop_requested)
|
||||||
|
break;
|
||||||
|
|
||||||
|
f32 dtime = get_time_since_last_step();
|
||||||
|
t_step_start = porting::getTimeMs();
|
||||||
|
step(dtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
send(sound_manager_messages_to_proxy::Stopped{});
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
171
src/client/sound/sound_manager.h
Normal file
171
src/client/sound/sound_manager.h
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "playing_sound.h"
|
||||||
|
#include "sound_constants.h"
|
||||||
|
#include "sound_manager_messages.h"
|
||||||
|
#include "../sound.h"
|
||||||
|
#include "threading/thread.h"
|
||||||
|
#include "util/container.h" // MutexedQueue
|
||||||
|
|
||||||
|
class SoundManagerSingleton;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The SoundManager thread
|
||||||
|
*
|
||||||
|
* It's not an ISoundManager. It doesn't allocate ids, and doesn't accept id 0.
|
||||||
|
* All sound loading and interaction with OpenAL happens in this thread.
|
||||||
|
* Access from other threads happens via ProxySoundManager.
|
||||||
|
*
|
||||||
|
* See sound_constants.h for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class OpenALSoundManager final : public Thread
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider;
|
||||||
|
|
||||||
|
ALCdevice *m_device;
|
||||||
|
ALCcontext *m_context;
|
||||||
|
|
||||||
|
// time in seconds until which removeDeadSounds will be called again
|
||||||
|
f32 m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;
|
||||||
|
|
||||||
|
// loaded sounds
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<ISoundDataUnopen>> m_sound_datas_unopen;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<ISoundDataOpen>> m_sound_datas_open;
|
||||||
|
// sound groups
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> m_sound_groups;
|
||||||
|
|
||||||
|
// currently playing sounds
|
||||||
|
std::unordered_map<sound_handle_t, std::shared_ptr<PlayingSound>> m_sounds_playing;
|
||||||
|
|
||||||
|
// streamed sounds
|
||||||
|
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_current_bigstep;
|
||||||
|
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_next_bigstep;
|
||||||
|
// time left until current bigstep finishes
|
||||||
|
f32 m_stream_timer = STREAM_BIGSTEP_TIME;
|
||||||
|
|
||||||
|
std::vector<std::weak_ptr<PlayingSound>> m_sounds_fading;
|
||||||
|
|
||||||
|
// if true, all sounds will be directly paused after creation
|
||||||
|
bool m_is_paused = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// used for communication with ProxySoundManager
|
||||||
|
MutexedQueue<SoundManagerMsgToMgr> m_queue_to_mgr;
|
||||||
|
MutexedQueue<SoundManagerMsgToProxy> m_queue_to_proxy;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void stepStreams(f32 dtime);
|
||||||
|
void doFades(f32 dtime);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives the open sound for a loaded sound.
|
||||||
|
* Opens the sound if currently unopened.
|
||||||
|
*
|
||||||
|
* @param sound_name Name of the sound.
|
||||||
|
* @return The open sound.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<ISoundDataOpen> openSingleSound(const std::string &sound_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random sound name from a group.
|
||||||
|
*
|
||||||
|
* @param group_name The name of the sound group.
|
||||||
|
* @return The name of a sound in the group, or "" on failure. Getting the
|
||||||
|
* sound with `openSingleSound` directly afterwards will not fail.
|
||||||
|
*/
|
||||||
|
std::string getLoadedSoundNameFromGroup(const std::string &group_name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as `getLoadedSoundNameFromGroup`, but if sound does not exist, try to
|
||||||
|
* load from local files.
|
||||||
|
*/
|
||||||
|
std::string getOrLoadLoadedSoundNameFromGroup(const std::string &group_name);
|
||||||
|
|
||||||
|
std::shared_ptr<PlayingSound> createPlayingSound(const std::string &sound_name,
|
||||||
|
bool loop, f32 volume, f32 pitch, f32 start_time,
|
||||||
|
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||||
|
|
||||||
|
void playSoundGeneric(sound_handle_t id, const std::string &group_name, bool loop,
|
||||||
|
f32 volume, f32 fade, f32 pitch, bool use_local_fallback, f32 start_time,
|
||||||
|
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes sounds that are dead (=finished).
|
||||||
|
*
|
||||||
|
* @return Number of removed sounds.
|
||||||
|
*/
|
||||||
|
int removeDeadSounds();
|
||||||
|
|
||||||
|
public:
|
||||||
|
OpenALSoundManager(SoundManagerSingleton *smg,
|
||||||
|
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider);
|
||||||
|
|
||||||
|
~OpenALSoundManager() override;
|
||||||
|
|
||||||
|
DISABLE_CLASS_COPY(OpenALSoundManager)
|
||||||
|
|
||||||
|
private:
|
||||||
|
/* Similar to ISoundManager */
|
||||||
|
|
||||||
|
void step(f32 dtime);
|
||||||
|
void pauseAll();
|
||||||
|
void resumeAll();
|
||||||
|
|
||||||
|
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_);
|
||||||
|
void setListenerGain(f32 gain);
|
||||||
|
|
||||||
|
bool loadSoundFile(const std::string &name, const std::string &filepath);
|
||||||
|
bool loadSoundData(const std::string &name, std::string &&filedata);
|
||||||
|
void loadSoundFileNoCheck(const std::string &name, const std::string &filepath);
|
||||||
|
void loadSoundDataNoCheck(const std::string &name, std::string &&filedata);
|
||||||
|
void addSoundToGroup(const std::string &sound_name, const std::string &group_name);
|
||||||
|
|
||||||
|
void playSound(sound_handle_t id, const SoundSpec &spec);
|
||||||
|
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
||||||
|
const v3f &vel_);
|
||||||
|
void stopSound(sound_handle_t sound);
|
||||||
|
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain);
|
||||||
|
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Thread stuff */
|
||||||
|
|
||||||
|
void *run() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void send(SoundManagerMsgToProxy msg)
|
||||||
|
{
|
||||||
|
m_queue_to_proxy.push_back(std::move(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
void reportRemovedSound(sound_handle_t id)
|
||||||
|
{
|
||||||
|
send(sound_manager_messages_to_proxy::ReportRemovedSound{id});
|
||||||
|
}
|
||||||
|
};
|
80
src/client/sound/sound_manager_messages.h
Normal file
80
src/client/sound/sound_manager_messages.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2023 DS
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../sound.h"
|
||||||
|
#include "../../sound.h"
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace sound_manager_messages_to_mgr {
|
||||||
|
struct PauseAll {};
|
||||||
|
struct ResumeAll {};
|
||||||
|
|
||||||
|
struct UpdateListener { v3f pos_; v3f vel_; v3f at_; v3f up_; };
|
||||||
|
struct SetListenerGain { f32 gain; };
|
||||||
|
|
||||||
|
struct LoadSoundFile { std::string name; std::string filepath; };
|
||||||
|
struct LoadSoundData { std::string name; std::string filedata; };
|
||||||
|
struct AddSoundToGroup { std::string sound_name; std::string group_name; };
|
||||||
|
|
||||||
|
struct PlaySound { sound_handle_t id; SoundSpec spec; };
|
||||||
|
struct PlaySoundAt { sound_handle_t id; SoundSpec spec; v3f pos_; v3f vel_; };
|
||||||
|
struct StopSound { sound_handle_t sound; };
|
||||||
|
struct FadeSound { sound_handle_t soundid; f32 step; f32 target_gain; };
|
||||||
|
struct UpdateSoundPosVel { sound_handle_t sound; v3f pos_; v3f vel_; };
|
||||||
|
|
||||||
|
struct PleaseStop {};
|
||||||
|
}
|
||||||
|
|
||||||
|
using SoundManagerMsgToMgr = std::variant<
|
||||||
|
std::monostate,
|
||||||
|
|
||||||
|
sound_manager_messages_to_mgr::PauseAll,
|
||||||
|
sound_manager_messages_to_mgr::ResumeAll,
|
||||||
|
|
||||||
|
sound_manager_messages_to_mgr::UpdateListener,
|
||||||
|
sound_manager_messages_to_mgr::SetListenerGain,
|
||||||
|
|
||||||
|
sound_manager_messages_to_mgr::LoadSoundFile,
|
||||||
|
sound_manager_messages_to_mgr::LoadSoundData,
|
||||||
|
sound_manager_messages_to_mgr::AddSoundToGroup,
|
||||||
|
|
||||||
|
sound_manager_messages_to_mgr::PlaySound,
|
||||||
|
sound_manager_messages_to_mgr::PlaySoundAt,
|
||||||
|
sound_manager_messages_to_mgr::StopSound,
|
||||||
|
sound_manager_messages_to_mgr::FadeSound,
|
||||||
|
sound_manager_messages_to_mgr::UpdateSoundPosVel,
|
||||||
|
|
||||||
|
sound_manager_messages_to_mgr::PleaseStop
|
||||||
|
>;
|
||||||
|
|
||||||
|
namespace sound_manager_messages_to_proxy {
|
||||||
|
struct ReportRemovedSound { sound_handle_t id; };
|
||||||
|
|
||||||
|
struct Stopped {};
|
||||||
|
}
|
||||||
|
|
||||||
|
using SoundManagerMsgToProxy = std::variant<
|
||||||
|
std::monostate,
|
||||||
|
|
||||||
|
sound_manager_messages_to_proxy::ReportRemovedSound,
|
||||||
|
|
||||||
|
sound_manager_messages_to_proxy::Stopped
|
||||||
|
>;
|
@ -22,7 +22,9 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "sound_openal.h"
|
#include "sound_openal.h"
|
||||||
#include "sound_openal_internal.h"
|
|
||||||
|
#include "sound_singleton.h"
|
||||||
|
#include "proxy_sound_manager.h"
|
||||||
|
|
||||||
std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
std::shared_ptr<SoundManagerSingleton> g_sound_manager_singleton;
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,750 +0,0 @@
|
|||||||
/*
|
|
||||||
Minetest
|
|
||||||
Copyright (C) 2022 DS
|
|
||||||
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
|
||||||
OpenAL support based on work by:
|
|
||||||
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
|
||||||
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
|
||||||
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2.1 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public License along
|
|
||||||
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "log.h"
|
|
||||||
#include "porting.h"
|
|
||||||
#include "sound_openal.h"
|
|
||||||
#include "../../sound.h"
|
|
||||||
#include "threading/thread.h"
|
|
||||||
#include "util/basic_macros.h"
|
|
||||||
#include "util/container.h"
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
#include <al.h>
|
|
||||||
#include <alc.h>
|
|
||||||
//#include <alext.h>
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
#define OPENAL_DEPRECATED
|
|
||||||
#include <OpenAL/al.h>
|
|
||||||
#include <OpenAL/alc.h>
|
|
||||||
//#include <OpenAL/alext.h>
|
|
||||||
#else
|
|
||||||
#include <AL/al.h>
|
|
||||||
#include <AL/alc.h>
|
|
||||||
#include <AL/alext.h>
|
|
||||||
#endif
|
|
||||||
#include <vorbis/vorbisfile.h>
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
*
|
|
||||||
* The coordinate space for sounds (sound-space):
|
|
||||||
* ----------------------------------------------
|
|
||||||
*
|
|
||||||
* * The functions from ISoundManager (see sound.h) take spatial vectors in node-space.
|
|
||||||
* * All other `v3f`s here are, if not told otherwise, in sound-space, which is
|
|
||||||
* defined as node-space mirrored along the x-axis.
|
|
||||||
* (This is needed because OpenAL uses a right-handed coordinate system.)
|
|
||||||
* * Use `swap_handedness()` to convert between those two coordinate spaces.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* How sounds are loaded:
|
|
||||||
* ----------------------
|
|
||||||
*
|
|
||||||
* * Step 1:
|
|
||||||
* `loadSoundFile` or `loadSoundFile` is called. This adds an unopen sound with
|
|
||||||
* the given name to `m_sound_datas_unopen`.
|
|
||||||
* Unopen / lazy sounds (`ISoundDataUnopen`) are ogg-vorbis files that we did not yet
|
|
||||||
* start to decode. (Decoding an unopen sound does not fail under normal circumstances
|
|
||||||
* (because we check whether the file exists at least), if it does fail anyways,
|
|
||||||
* we should notify the user.)
|
|
||||||
* * Step 2:
|
|
||||||
* `addSoundToGroup` is called, to add the name from step 1 to a group. If the
|
|
||||||
* group does not yet exist, a new one is created. A group can later be played.
|
|
||||||
* (The mapping is stored in `m_sound_groups`.)
|
|
||||||
* * Step 3:
|
|
||||||
* `playSound` or `playSoundAt` is called.
|
|
||||||
* * Step 3.1:
|
|
||||||
* If the group with the name `spec.name` does not exist, and `spec.use_local_fallback`
|
|
||||||
* is true, a new group is created using the user's sound-pack.
|
|
||||||
* * Step 3.2:
|
|
||||||
* We choose one random sound name from the given group.
|
|
||||||
* * Step 3.3:
|
|
||||||
* We open the sound (see `openSingleSound`).
|
|
||||||
* If the sound is already open (in `m_sound_datas_open`), we take that one.
|
|
||||||
* Otherwise we open it by calling `ISoundDataUnopen::open`. We choose (by
|
|
||||||
* sound length), whether it's a single-buffer (`SoundDataOpenBuffer`) or
|
|
||||||
* streamed (`SoundDataOpenStream`) sound.
|
|
||||||
* Single-buffer sounds are always completely loaded. Streamed sounds can be
|
|
||||||
* partially loaded.
|
|
||||||
* The sound is erased from `m_sound_datas_unopen` and added to `m_sound_datas_open`.
|
|
||||||
* Open sounds are kept forever.
|
|
||||||
* * Step 3.4:
|
|
||||||
* We create the new `PlayingSound`. It has a `shared_ptr` to its open sound.
|
|
||||||
* If the open sound is streaming, the playing sound needs to be stepped using
|
|
||||||
* `PlayingSound::stepStream` for enqueuing buffers. For this purpose, the sound
|
|
||||||
* is added to `m_sounds_streaming` (as `weak_ptr`).
|
|
||||||
* If the sound is fading, it is added to `m_sounds_fading` for regular fade-stepping.
|
|
||||||
* The sound is also added to `m_sounds_playing`, so that one can access it
|
|
||||||
* via its sound handle.
|
|
||||||
* * Step 4:
|
|
||||||
* Streaming sounds are updated. For details see [Streaming of sounds].
|
|
||||||
* * Step 5:
|
|
||||||
* At deinitialization, we can just let the destructors do their work.
|
|
||||||
* Sound sources are deleted (and with this also stopped) by ~PlayingSound.
|
|
||||||
* Buffers can't be deleted while sound sources using them exist, because
|
|
||||||
* PlayingSound has a shared_ptr to its ISoundData.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Streaming of sounds:
|
|
||||||
* --------------------
|
|
||||||
*
|
|
||||||
* In each "bigstep", all streamed sounds are stepStream()ed. This means a
|
|
||||||
* sound can be stepped at any point in time in the bigstep's interval.
|
|
||||||
*
|
|
||||||
* In the worst case, a sound is stepped at the start of one bigstep and in the
|
|
||||||
* end of the next bigstep. So between two stepStream()-calls lie at most
|
|
||||||
* 2 * STREAM_BIGSTEP_TIME seconds.
|
|
||||||
* As there are always 2 sound buffers enqueued, at least one untouched full buffer
|
|
||||||
* is still available after the first stepStream().
|
|
||||||
* If we take a MIN_STREAM_BUFFER_LENGTH > 2 * STREAM_BIGSTEP_TIME, we can hence
|
|
||||||
* not run into an empty queue.
|
|
||||||
*
|
|
||||||
* The MIN_STREAM_BUFFER_LENGTH needs to be a little bigger because of dtime jitter,
|
|
||||||
* other sounds that may have taken long to stepStream(), and sounds being played
|
|
||||||
* faster due to Doppler effect.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
// constants
|
|
||||||
|
|
||||||
// in seconds
|
|
||||||
constexpr f32 REMOVE_DEAD_SOUNDS_INTERVAL = 2.0f;
|
|
||||||
// maximum length in seconds that a sound can have without being streamed
|
|
||||||
constexpr f32 SOUND_DURATION_MAX_SINGLE = 3.0f;
|
|
||||||
// minimum time in seconds of a single buffer in a streamed sound
|
|
||||||
constexpr f32 MIN_STREAM_BUFFER_LENGTH = 1.0f;
|
|
||||||
// duration in seconds of one bigstep
|
|
||||||
constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
|
|
||||||
// step duration for the ProxySoundManager, in seconds
|
|
||||||
constexpr f32 PROXYSOUNDMGR_DTIME = 0.016f;
|
|
||||||
|
|
||||||
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
|
|
||||||
"See [Streaming of sounds].");
|
|
||||||
static_assert(SOUND_DURATION_MAX_SINGLE >= MIN_STREAM_BUFFER_LENGTH * 2.0f,
|
|
||||||
"There's no benefit in streaming if we can't queue more than 2 buffers.");
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RAII wrapper for openal sound buffers.
|
|
||||||
*/
|
|
||||||
struct RAIIALSoundBuffer final
|
|
||||||
{
|
|
||||||
RAIIALSoundBuffer() noexcept = default;
|
|
||||||
explicit RAIIALSoundBuffer(ALuint buffer) noexcept : m_buffer(buffer) {};
|
|
||||||
|
|
||||||
~RAIIALSoundBuffer() noexcept { reset(0); }
|
|
||||||
|
|
||||||
DISABLE_CLASS_COPY(RAIIALSoundBuffer)
|
|
||||||
|
|
||||||
RAIIALSoundBuffer(RAIIALSoundBuffer &&other) noexcept : m_buffer(other.release()) {}
|
|
||||||
RAIIALSoundBuffer &operator=(RAIIALSoundBuffer &&other) noexcept;
|
|
||||||
|
|
||||||
ALuint get() noexcept { return m_buffer; }
|
|
||||||
|
|
||||||
ALuint release() noexcept { return std::exchange(m_buffer, 0); }
|
|
||||||
|
|
||||||
void reset(ALuint buf) noexcept;
|
|
||||||
|
|
||||||
static RAIIALSoundBuffer generate() noexcept;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// According to openal specification:
|
|
||||||
// > Deleting buffer name 0 is a legal NOP.
|
|
||||||
//
|
|
||||||
// and:
|
|
||||||
// > [...] the NULL buffer (i.e., 0) which can always be queued.
|
|
||||||
ALuint m_buffer = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For vorbisfile to read from our buffer instead of from a file.
|
|
||||||
*/
|
|
||||||
struct OggVorbisBufferSource {
|
|
||||||
std::string buf;
|
|
||||||
size_t cur_offset = 0;
|
|
||||||
|
|
||||||
static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource) noexcept;
|
|
||||||
static int seek_func(void *datasource, ogg_int64_t offset, int whence) noexcept;
|
|
||||||
static int close_func(void *datasource) noexcept;
|
|
||||||
static long tell_func(void *datasource) noexcept;
|
|
||||||
|
|
||||||
static const ov_callbacks s_ov_callbacks;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata of an Ogg-Vorbis file, used for decoding.
|
|
||||||
* We query this information once and store it in this struct.
|
|
||||||
*/
|
|
||||||
struct OggFileDecodeInfo {
|
|
||||||
std::string name_for_logging;
|
|
||||||
bool is_stereo;
|
|
||||||
ALenum format; // AL_FORMAT_MONO16 or AL_FORMAT_STEREO16
|
|
||||||
size_t bytes_per_sample;
|
|
||||||
ALsizei freq;
|
|
||||||
ALuint length_samples = 0;
|
|
||||||
f32 length_seconds = 0.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RAII wrapper for OggVorbis_File.
|
|
||||||
*/
|
|
||||||
struct RAIIOggFile {
|
|
||||||
bool m_needs_clear = false;
|
|
||||||
OggVorbis_File m_file;
|
|
||||||
|
|
||||||
RAIIOggFile() = default;
|
|
||||||
|
|
||||||
DISABLE_CLASS_COPY(RAIIOggFile)
|
|
||||||
|
|
||||||
~RAIIOggFile() noexcept
|
|
||||||
{
|
|
||||||
if (m_needs_clear)
|
|
||||||
ov_clear(&m_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
OggVorbis_File *get() { return &m_file; }
|
|
||||||
|
|
||||||
std::optional<OggFileDecodeInfo> getDecodeInfo(const std::string &filename_for_logging);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main function for loading ogg vorbis sounds.
|
|
||||||
* Loads exactly the specified interval of PCM-data, and creates an OpenAL
|
|
||||||
* buffer with it.
|
|
||||||
*
|
|
||||||
* @param decode_info Cached meta information of the file.
|
|
||||||
* @param pcm_start First sample in the interval.
|
|
||||||
* @param pcm_end One after last sample of the interval (=> exclusive).
|
|
||||||
* @return An AL sound buffer, or a 0-buffer on failure.
|
|
||||||
*/
|
|
||||||
RAIIALSoundBuffer loadBuffer(const OggFileDecodeInfo &decode_info, ALuint pcm_start,
|
|
||||||
ALuint pcm_end);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for the openal device and context
|
|
||||||
*/
|
|
||||||
class SoundManagerSingleton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
struct AlcDeviceDeleter {
|
|
||||||
void operator()(ALCdevice *p)
|
|
||||||
{
|
|
||||||
alcCloseDevice(p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AlcContextDeleter {
|
|
||||||
void operator()(ALCcontext *p)
|
|
||||||
{
|
|
||||||
alcMakeContextCurrent(nullptr);
|
|
||||||
alcDestroyContext(p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using unique_ptr_alcdevice = std::unique_ptr<ALCdevice, AlcDeviceDeleter>;
|
|
||||||
using unique_ptr_alccontext = std::unique_ptr<ALCcontext, AlcContextDeleter>;
|
|
||||||
|
|
||||||
unique_ptr_alcdevice m_device;
|
|
||||||
unique_ptr_alccontext m_context;
|
|
||||||
|
|
||||||
public:
|
|
||||||
bool init();
|
|
||||||
|
|
||||||
~SoundManagerSingleton();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores sound pcm data buffers.
|
|
||||||
*/
|
|
||||||
struct ISoundDataOpen
|
|
||||||
{
|
|
||||||
OggFileDecodeInfo m_decode_info;
|
|
||||||
|
|
||||||
explicit ISoundDataOpen(const OggFileDecodeInfo &decode_info) :
|
|
||||||
m_decode_info(decode_info) {}
|
|
||||||
|
|
||||||
virtual ~ISoundDataOpen() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iff the data is streaming, there is more than one buffer.
|
|
||||||
* @return Whether it's streaming data.
|
|
||||||
*/
|
|
||||||
virtual bool isStreaming() const noexcept = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a buffer containing data starting at the given offset. Or just get it
|
|
||||||
* if it was already loaded.
|
|
||||||
*
|
|
||||||
* This function returns multiple values:
|
|
||||||
* * `buffer`: The OpenAL buffer.
|
|
||||||
* * `buffer_end`: The offset (in the file) where `buffer` ends (exclusive).
|
|
||||||
* * `offset_in_buffer`: Offset relative to `buffer`'s start where the requested
|
|
||||||
* `offset` is.
|
|
||||||
* `offset_in_buffer == 0` is guaranteed if some loaded buffer ends at
|
|
||||||
* `offset`.
|
|
||||||
*
|
|
||||||
* @param offset The start of the buffer.
|
|
||||||
* @return `{buffer, buffer_end, offset_in_buffer}` or `{0, sound_data_end, 0}`
|
|
||||||
* if `offset` is invalid.
|
|
||||||
*/
|
|
||||||
virtual std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) = 0;
|
|
||||||
|
|
||||||
static std::shared_ptr<ISoundDataOpen> fromOggFile(std::unique_ptr<RAIIOggFile> oggfile,
|
|
||||||
const std::string &filename_for_logging);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be opened lazily when first used.
|
|
||||||
*/
|
|
||||||
struct ISoundDataUnopen
|
|
||||||
{
|
|
||||||
virtual ~ISoundDataUnopen() = default;
|
|
||||||
|
|
||||||
// Note: The ISoundDataUnopen is moved (see &&). It is not meant to be kept
|
|
||||||
// after opening.
|
|
||||||
virtual std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sound file is in a memory buffer.
|
|
||||||
*/
|
|
||||||
struct SoundDataUnopenBuffer final : ISoundDataUnopen
|
|
||||||
{
|
|
||||||
std::string m_buffer;
|
|
||||||
|
|
||||||
explicit SoundDataUnopenBuffer(std::string &&buffer) : m_buffer(std::move(buffer)) {}
|
|
||||||
|
|
||||||
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sound file is in file system.
|
|
||||||
*/
|
|
||||||
struct SoundDataUnopenFile final : ISoundDataUnopen
|
|
||||||
{
|
|
||||||
std::string m_path;
|
|
||||||
|
|
||||||
explicit SoundDataUnopenFile(const std::string &path) : m_path(path) {}
|
|
||||||
|
|
||||||
std::shared_ptr<ISoundDataOpen> open(const std::string &sound_name) && override;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-streaming opened sound data.
|
|
||||||
* All data is completely loaded in one buffer.
|
|
||||||
*/
|
|
||||||
struct SoundDataOpenBuffer final : ISoundDataOpen
|
|
||||||
{
|
|
||||||
RAIIALSoundBuffer m_buffer;
|
|
||||||
|
|
||||||
SoundDataOpenBuffer(std::unique_ptr<RAIIOggFile> oggfile,
|
|
||||||
const OggFileDecodeInfo &decode_info);
|
|
||||||
|
|
||||||
bool isStreaming() const noexcept override { return false; }
|
|
||||||
|
|
||||||
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override
|
|
||||||
{
|
|
||||||
if (offset >= m_decode_info.length_samples)
|
|
||||||
return {0, m_decode_info.length_samples, 0};
|
|
||||||
return {m_buffer.get(), m_decode_info.length_samples, offset};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Streaming opened sound data.
|
|
||||||
*
|
|
||||||
* Uses a sorted list of contiguous sound data regions (`ContiguousBuffers`s) for
|
|
||||||
* efficient seeking.
|
|
||||||
*/
|
|
||||||
struct SoundDataOpenStream final : ISoundDataOpen
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* An OpenAL buffer that goes until `m_end` (exclusive).
|
|
||||||
*/
|
|
||||||
struct SoundBufferUntil final
|
|
||||||
{
|
|
||||||
ALuint m_end;
|
|
||||||
RAIIALSoundBuffer m_buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sorted non-empty vector of contiguous buffers.
|
|
||||||
* The start (inclusive) of each buffer is the end of its predecessor, or
|
|
||||||
* `m_start` for the first buffer.
|
|
||||||
*/
|
|
||||||
struct ContiguousBuffers final
|
|
||||||
{
|
|
||||||
ALuint m_start;
|
|
||||||
std::vector<SoundBufferUntil> m_buffers;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<RAIIOggFile> m_oggfile;
|
|
||||||
// A sorted vector of non-overlapping, non-contiguous `ContiguousBuffers`s.
|
|
||||||
std::vector<ContiguousBuffers> m_bufferss;
|
|
||||||
|
|
||||||
SoundDataOpenStream(std::unique_ptr<RAIIOggFile> oggfile,
|
|
||||||
const OggFileDecodeInfo &decode_info);
|
|
||||||
|
|
||||||
bool isStreaming() const noexcept override { return true; }
|
|
||||||
|
|
||||||
std::tuple<ALuint, ALuint, ALuint> getOrLoadBufferAt(ALuint offset) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// offset must be before after_it's m_start and after (after_it-1)'s last m_end
|
|
||||||
// new buffer will be inserted into m_bufferss before after_it
|
|
||||||
// returns same as getOrLoadBufferAt
|
|
||||||
std::tuple<ALuint, ALuint, ALuint> loadBufferAt(ALuint offset,
|
|
||||||
std::vector<ContiguousBuffers>::iterator after_it);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sound that is currently played.
|
|
||||||
* Can be streaming.
|
|
||||||
* Can be fading.
|
|
||||||
*/
|
|
||||||
class PlayingSound final
|
|
||||||
{
|
|
||||||
struct FadeState {
|
|
||||||
f32 step;
|
|
||||||
f32 target_gain;
|
|
||||||
};
|
|
||||||
|
|
||||||
ALuint m_source_id;
|
|
||||||
std::shared_ptr<ISoundDataOpen> m_data;
|
|
||||||
ALuint m_next_sample_pos = 0;
|
|
||||||
bool m_looping;
|
|
||||||
bool m_is_positional;
|
|
||||||
bool m_stopped_means_dead = true;
|
|
||||||
std::optional<FadeState> m_fade_state = std::nullopt;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PlayingSound(ALuint source_id, std::shared_ptr<ISoundDataOpen> data, bool loop,
|
|
||||||
f32 volume, f32 pitch, f32 start_time,
|
|
||||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
|
||||||
|
|
||||||
~PlayingSound() noexcept
|
|
||||||
{
|
|
||||||
alDeleteSources(1, &m_source_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
DISABLE_CLASS_COPY(PlayingSound)
|
|
||||||
|
|
||||||
// return false means streaming finished
|
|
||||||
bool stepStream();
|
|
||||||
|
|
||||||
// retruns true if it wasn't fading already
|
|
||||||
bool fade(f32 step, f32 target_gain) noexcept;
|
|
||||||
|
|
||||||
// returns true if more fade is needed later
|
|
||||||
bool doFade(f32 dtime) noexcept;
|
|
||||||
|
|
||||||
void updatePosVel(const v3f &pos, const v3f &vel) noexcept;
|
|
||||||
|
|
||||||
void setGain(f32 gain) noexcept;
|
|
||||||
|
|
||||||
f32 getGain() noexcept;
|
|
||||||
|
|
||||||
void setPitch(f32 pitch) noexcept { alSourcef(m_source_id, AL_PITCH, pitch); }
|
|
||||||
|
|
||||||
bool isStreaming() const noexcept { return m_data->isStreaming(); }
|
|
||||||
|
|
||||||
void play() noexcept { alSourcePlay(m_source_id); }
|
|
||||||
|
|
||||||
// returns one of AL_INITIAL, AL_PLAYING, AL_PAUSED, AL_STOPPED
|
|
||||||
ALint getState() noexcept
|
|
||||||
{
|
|
||||||
ALint state;
|
|
||||||
alGetSourcei(m_source_id, AL_SOURCE_STATE, &state);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isDead() noexcept
|
|
||||||
{
|
|
||||||
// streaming sounds can (but should not) stop because the queue runs empty
|
|
||||||
return m_stopped_means_dead && getState() == AL_STOPPED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pause() noexcept
|
|
||||||
{
|
|
||||||
// this is a NOP if state != AL_PLAYING
|
|
||||||
alSourcePause(m_source_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void resume() noexcept
|
|
||||||
{
|
|
||||||
if (getState() == AL_PAUSED)
|
|
||||||
play();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The SoundManager thread
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace sound_manager_messages_to_mgr {
|
|
||||||
struct PauseAll {};
|
|
||||||
struct ResumeAll {};
|
|
||||||
|
|
||||||
struct UpdateListener { v3f pos_; v3f vel_; v3f at_; v3f up_; };
|
|
||||||
struct SetListenerGain { f32 gain; };
|
|
||||||
|
|
||||||
struct LoadSoundFile { std::string name; std::string filepath; };
|
|
||||||
struct LoadSoundData { std::string name; std::string filedata; };
|
|
||||||
struct AddSoundToGroup { std::string sound_name; std::string group_name; };
|
|
||||||
|
|
||||||
struct PlaySound { sound_handle_t id; SoundSpec spec; };
|
|
||||||
struct PlaySoundAt { sound_handle_t id; SoundSpec spec; v3f pos_; v3f vel_; };
|
|
||||||
struct StopSound { sound_handle_t sound; };
|
|
||||||
struct FadeSound { sound_handle_t soundid; f32 step; f32 target_gain; };
|
|
||||||
struct UpdateSoundPosVel { sound_handle_t sound; v3f pos_; v3f vel_; };
|
|
||||||
|
|
||||||
struct PleaseStop {};
|
|
||||||
}
|
|
||||||
|
|
||||||
using SoundManagerMsgToMgr = std::variant<
|
|
||||||
std::monostate,
|
|
||||||
|
|
||||||
sound_manager_messages_to_mgr::PauseAll,
|
|
||||||
sound_manager_messages_to_mgr::ResumeAll,
|
|
||||||
|
|
||||||
sound_manager_messages_to_mgr::UpdateListener,
|
|
||||||
sound_manager_messages_to_mgr::SetListenerGain,
|
|
||||||
|
|
||||||
sound_manager_messages_to_mgr::LoadSoundFile,
|
|
||||||
sound_manager_messages_to_mgr::LoadSoundData,
|
|
||||||
sound_manager_messages_to_mgr::AddSoundToGroup,
|
|
||||||
|
|
||||||
sound_manager_messages_to_mgr::PlaySound,
|
|
||||||
sound_manager_messages_to_mgr::PlaySoundAt,
|
|
||||||
sound_manager_messages_to_mgr::StopSound,
|
|
||||||
sound_manager_messages_to_mgr::FadeSound,
|
|
||||||
sound_manager_messages_to_mgr::UpdateSoundPosVel,
|
|
||||||
|
|
||||||
sound_manager_messages_to_mgr::PleaseStop
|
|
||||||
>;
|
|
||||||
|
|
||||||
namespace sound_manager_messages_to_proxy {
|
|
||||||
struct ReportRemovedSound { sound_handle_t id; };
|
|
||||||
|
|
||||||
struct Stopped {};
|
|
||||||
}
|
|
||||||
|
|
||||||
using SoundManagerMsgToProxy = std::variant<
|
|
||||||
std::monostate,
|
|
||||||
|
|
||||||
sound_manager_messages_to_proxy::ReportRemovedSound,
|
|
||||||
|
|
||||||
sound_manager_messages_to_proxy::Stopped
|
|
||||||
>;
|
|
||||||
|
|
||||||
// not an ISoundManager. doesn't allocate ids, and doesn't accept id 0
|
|
||||||
class OpenALSoundManager final : public Thread
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider;
|
|
||||||
|
|
||||||
ALCdevice *m_device;
|
|
||||||
ALCcontext *m_context;
|
|
||||||
|
|
||||||
// time in seconds until which removeDeadSounds will be called again
|
|
||||||
f32 m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;
|
|
||||||
|
|
||||||
// loaded sounds
|
|
||||||
std::unordered_map<std::string, std::unique_ptr<ISoundDataUnopen>> m_sound_datas_unopen;
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<ISoundDataOpen>> m_sound_datas_open;
|
|
||||||
// sound groups
|
|
||||||
std::unordered_map<std::string, std::vector<std::string>> m_sound_groups;
|
|
||||||
|
|
||||||
// currently playing sounds
|
|
||||||
std::unordered_map<sound_handle_t, std::shared_ptr<PlayingSound>> m_sounds_playing;
|
|
||||||
|
|
||||||
// streamed sounds
|
|
||||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_current_bigstep;
|
|
||||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_streaming_next_bigstep;
|
|
||||||
// time left until current bigstep finishes
|
|
||||||
f32 m_stream_timer = STREAM_BIGSTEP_TIME;
|
|
||||||
|
|
||||||
std::vector<std::weak_ptr<PlayingSound>> m_sounds_fading;
|
|
||||||
|
|
||||||
// if true, all sounds will be directly paused after creation
|
|
||||||
bool m_is_paused = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
// used for communication with ProxySoundManager
|
|
||||||
MutexedQueue<SoundManagerMsgToMgr> m_queue_to_mgr;
|
|
||||||
MutexedQueue<SoundManagerMsgToProxy> m_queue_to_proxy;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void stepStreams(f32 dtime);
|
|
||||||
void doFades(f32 dtime);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gives the open sound for a loaded sound.
|
|
||||||
* Opens the sound if currently unopened.
|
|
||||||
*
|
|
||||||
* @param sound_name Name of the sound.
|
|
||||||
* @return The open sound.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<ISoundDataOpen> openSingleSound(const std::string &sound_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a random sound name from a group.
|
|
||||||
*
|
|
||||||
* @param group_name The name of the sound group.
|
|
||||||
* @return The name of a sound in the group, or "" on failure. Getting the
|
|
||||||
* sound with `openSingleSound` directly afterwards will not fail.
|
|
||||||
*/
|
|
||||||
std::string getLoadedSoundNameFromGroup(const std::string &group_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as `getLoadedSoundNameFromGroup`, but if sound does not exist, try to
|
|
||||||
* load from local files.
|
|
||||||
*/
|
|
||||||
std::string getOrLoadLoadedSoundNameFromGroup(const std::string &group_name);
|
|
||||||
|
|
||||||
std::shared_ptr<PlayingSound> createPlayingSound(const std::string &sound_name,
|
|
||||||
bool loop, f32 volume, f32 pitch, f32 start_time,
|
|
||||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
|
||||||
|
|
||||||
void playSoundGeneric(sound_handle_t id, const std::string &group_name, bool loop,
|
|
||||||
f32 volume, f32 fade, f32 pitch, bool use_local_fallback, f32 start_time,
|
|
||||||
const std::optional<std::pair<v3f, v3f>> &pos_vel_opt);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes sounds that are dead (=finished).
|
|
||||||
*
|
|
||||||
* @return Number of removed sounds.
|
|
||||||
*/
|
|
||||||
int removeDeadSounds();
|
|
||||||
|
|
||||||
public:
|
|
||||||
OpenALSoundManager(SoundManagerSingleton *smg,
|
|
||||||
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider);
|
|
||||||
|
|
||||||
~OpenALSoundManager() override;
|
|
||||||
|
|
||||||
DISABLE_CLASS_COPY(OpenALSoundManager)
|
|
||||||
|
|
||||||
private:
|
|
||||||
/* Similar to ISoundManager */
|
|
||||||
|
|
||||||
void step(f32 dtime);
|
|
||||||
void pauseAll();
|
|
||||||
void resumeAll();
|
|
||||||
|
|
||||||
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_);
|
|
||||||
void setListenerGain(f32 gain);
|
|
||||||
|
|
||||||
bool loadSoundFile(const std::string &name, const std::string &filepath);
|
|
||||||
bool loadSoundData(const std::string &name, std::string &&filedata);
|
|
||||||
void loadSoundFileNoCheck(const std::string &name, const std::string &filepath);
|
|
||||||
void loadSoundDataNoCheck(const std::string &name, std::string &&filedata);
|
|
||||||
void addSoundToGroup(const std::string &sound_name, const std::string &group_name);
|
|
||||||
|
|
||||||
void playSound(sound_handle_t id, const SoundSpec &spec);
|
|
||||||
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
|
||||||
const v3f &vel_);
|
|
||||||
void stopSound(sound_handle_t sound);
|
|
||||||
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain);
|
|
||||||
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Thread stuff */
|
|
||||||
|
|
||||||
void *run() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void send(SoundManagerMsgToProxy msg)
|
|
||||||
{
|
|
||||||
m_queue_to_proxy.push_back(std::move(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
void reportRemovedSound(sound_handle_t id)
|
|
||||||
{
|
|
||||||
send(sound_manager_messages_to_proxy::ReportRemovedSound{id});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The public ISoundManager interface
|
|
||||||
*/
|
|
||||||
|
|
||||||
class ProxySoundManager final : public ISoundManager
|
|
||||||
{
|
|
||||||
OpenALSoundManager m_sound_manager;
|
|
||||||
// sound names from loadSoundData and loadSoundFile
|
|
||||||
std::unordered_set<std::string> m_known_sound_names;
|
|
||||||
|
|
||||||
void send(SoundManagerMsgToMgr msg)
|
|
||||||
{
|
|
||||||
m_sound_manager.m_queue_to_mgr.push_back(std::move(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class MsgResult { Ok, Empty, Stopped};
|
|
||||||
MsgResult handleMsg(SoundManagerMsgToProxy &&msg);
|
|
||||||
|
|
||||||
public:
|
|
||||||
ProxySoundManager(SoundManagerSingleton *smg,
|
|
||||||
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
|
|
||||||
m_sound_manager(smg, std::move(fallback_path_provider))
|
|
||||||
{
|
|
||||||
m_sound_manager.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
~ProxySoundManager() override;
|
|
||||||
|
|
||||||
/* Interface */
|
|
||||||
|
|
||||||
void step(f32 dtime) override;
|
|
||||||
void pauseAll() override;
|
|
||||||
void resumeAll() override;
|
|
||||||
|
|
||||||
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_) override;
|
|
||||||
void setListenerGain(f32 gain) override;
|
|
||||||
|
|
||||||
bool loadSoundFile(const std::string &name, const std::string &filepath) override;
|
|
||||||
bool loadSoundData(const std::string &name, std::string &&filedata) override;
|
|
||||||
void addSoundToGroup(const std::string &sound_name, const std::string &group_name) override;
|
|
||||||
|
|
||||||
void playSound(sound_handle_t id, const SoundSpec &spec) override;
|
|
||||||
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
|
|
||||||
const v3f &vel_) override;
|
|
||||||
void stopSound(sound_handle_t sound) override;
|
|
||||||
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain) override;
|
|
||||||
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_) override;
|
|
||||||
};
|
|
69
src/client/sound/sound_singleton.cpp
Normal file
69
src/client/sound/sound_singleton.cpp
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sound_singleton.h"
|
||||||
|
|
||||||
|
bool SoundManagerSingleton::init()
|
||||||
|
{
|
||||||
|
if (!(m_device = unique_ptr_alcdevice(alcOpenDevice(nullptr)))) {
|
||||||
|
errorstream << "Audio: Global Initialization: Failed to open device" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(m_context = unique_ptr_alccontext(alcCreateContext(m_device.get(), nullptr)))) {
|
||||||
|
errorstream << "Audio: Global Initialization: Failed to create context" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alcMakeContextCurrent(m_context.get())) {
|
||||||
|
errorstream << "Audio: Global Initialization: Failed to make current context" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
|
||||||
|
|
||||||
|
// Speed of sound in nodes per second
|
||||||
|
// FIXME: This value assumes 1 node sidelength = 1 meter, and "normal" air.
|
||||||
|
// Ideally this should be mod-controlled.
|
||||||
|
alSpeedOfSound(343.3f);
|
||||||
|
|
||||||
|
// doppler effect turned off for now, for best backwards compatibility
|
||||||
|
alDopplerFactor(0.0f);
|
||||||
|
|
||||||
|
if (alGetError() != AL_NO_ERROR) {
|
||||||
|
errorstream << "Audio: Global Initialization: OpenAL Error " << alGetError() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
infostream << "Audio: Global Initialized: OpenAL " << alGetString(AL_VERSION)
|
||||||
|
<< ", using " << alcGetString(m_device.get(), ALC_DEVICE_SPECIFIER)
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundManagerSingleton::~SoundManagerSingleton()
|
||||||
|
{
|
||||||
|
infostream << "Audio: Global Deinitialized." << std::endl;
|
||||||
|
}
|
60
src/client/sound/sound_singleton.h
Normal file
60
src/client/sound/sound_singleton.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Minetest
|
||||||
|
Copyright (C) 2022 DS
|
||||||
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||||
|
OpenAL support based on work by:
|
||||||
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
||||||
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
||||||
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2.1 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License along
|
||||||
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "al_helpers.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for the openal device and context
|
||||||
|
*/
|
||||||
|
class SoundManagerSingleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct AlcDeviceDeleter {
|
||||||
|
void operator()(ALCdevice *p)
|
||||||
|
{
|
||||||
|
alcCloseDevice(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AlcContextDeleter {
|
||||||
|
void operator()(ALCcontext *p)
|
||||||
|
{
|
||||||
|
alcMakeContextCurrent(nullptr);
|
||||||
|
alcDestroyContext(p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using unique_ptr_alcdevice = std::unique_ptr<ALCdevice, AlcDeviceDeleter>;
|
||||||
|
using unique_ptr_alccontext = std::unique_ptr<ALCcontext, AlcContextDeleter>;
|
||||||
|
|
||||||
|
unique_ptr_alcdevice m_device;
|
||||||
|
unique_ptr_alccontext m_context;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
~SoundManagerSingleton();
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user