-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update audio engine #366
base: master
Are you sure you want to change the base?
Update audio engine #366
Conversation
Provides a more backend-independent audio system Also fixes some sounds being played at half speed (chapter introductions)
src/audio/AudioWorld.cpp
Outdated
alGenBuffers(RE_NUM_MUSIC_BUFFERS, m_musicBuffers); | ||
alGenSources(1, &m_musicSource); | ||
m_MusicSource = m_Engine.getAudioEngine().createSound( | ||
[&](std::int16_t* buf, std::size_t len) -> int { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: as types of buf
and len
aren't directly needed in lambda, you can use auto
.
src/audio/AudioWorld.cpp
Outdated
return Handle::SfxHandle::makeInvalidHandle(); | ||
} | ||
return Handle::SfxHandle::makeInvalidHandle(); | ||
const std::int16_t* dataPtr = (std::int16_t*)(wav.getData()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try to avoid old style cast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try to avoid old style cast.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't really know what is happening there. And it's quite hard to refactor.
src/audio/AudioEngine.h
Outdated
virtual void stop() = 0; | ||
}; | ||
|
||
using SoundRef = std::shared_ptr<Sound>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SoundPtr
?
src/audio/OpenALAudioEngine.cpp
Outdated
, m_engine(engine) | ||
{} | ||
public: | ||
OpenALSound(OpenALAudioEngine* engine, const std::int16_t* buf, std::size_t len, Format fmt, std::size_t samplingFreq) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line is way too long
src/audio/OpenALAudioEngine.cpp
Outdated
captureContext(); | ||
// Remove all expired sounds | ||
std::remove_if(std::begin(m_bufferedSounds), std::end(m_bufferedSounds), | ||
[](const std::shared_ptr<OpenALSound>& sound) { return sound.use_count() == 1; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auto
for parametr/arg
src/audio/OpenALAudioEngine.cpp
Outdated
std::remove_if(std::begin(m_bufferedSounds), std::end(m_bufferedSounds), | ||
[](const std::shared_ptr<OpenALSound>& sound) { return sound.use_count() == 1; }); | ||
std::remove_if(std::begin(m_streamingSounds), std::end(m_streamingSounds), | ||
[](const std::shared_ptr<OpenALSound>& sound) { return sound.use_count() == 1; }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lamda is needlessly written twice.
{ | ||
std::array<std::int16_t, BUFFER_SIZE> dataBuffer{}; | ||
|
||
for(auto buf : m_buffers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are referring to auto buf
, m_buffers
is a vector of integers, it should not make any difference here
src/audio/AudioWorld.h
Outdated
struct Sound : public Handle::HandleTypeDescriptor<Handle::SfxHandle> | ||
{ | ||
Daedalus::GEngineClasses::C_SFX sfx; | ||
std::vector<Handle::SfxHandle> variants; // Instances ending with "_Ax" | ||
unsigned m_Handle = 0; | ||
Audio::SoundRef sound = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
= nullptr
is redundant
src/audio/OpenALAudioEngine.cpp
Outdated
|
||
OpenALAudioEngine::~OpenALAudioEngine() | ||
{ | ||
m_bufferedSounds.erase(std::begin(m_bufferedSounds), std::end(m_bufferedSounds)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_bufferedSounds.clear();
will do the same // I'd add comment why you do it
Tested your build. The chapter music works fine, but it seems that musiczones got somehow messed up. When you enter Khorinis, for example, music doesn't switch until you reach the place in front of Vatras. Also, I had the impression that just by turning the Hero, it had an impact on the music (directly in front of Lobart, there was a weird switching in between the lobart and the regular world theme). |
@ShFil119 I addressed most of the criticisms, thanks. |
I'm marking this as WIP, I'd like to make some more updates like making the AudioWorld live in the BaseEngine instead of the World (there are no ties between it and the AudioWorld), to allow stuff like playing music and sound effects before a game is started |
src/audio/OpenALAudioEngine.cpp
Outdated
|
||
using namespace Audio; | ||
|
||
static std::vector<std::string> enumeratedDevices; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe instead of global static
use anonymous namespace?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really see the need, it's just going to be used in this file only, I think. Actually, it could even be declared as static inside the function that uses it 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even better, we could also stick to the YAGNI principle and remove the functionality outright...!
src/audio/OpenALAudioEngine.cpp
Outdated
// can be accessed in a thread-safe manner | ||
static std::mutex mtx_enumeratedDevices; | ||
|
||
constexpr std::size_t OPENAL_SOURCES_POOL = 256; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't know regoth naming convection, but it looks like macro name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah the rationale is that that variable is essentially to be considered a macro, but let the compiler treat it as a variable. If that makes sense anyway.
src/audio/OpenALAudioEngine.cpp
Outdated
} | ||
else | ||
{ | ||
enumeratedDevices.push_back(""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can emplace_back()
src/audio/OpenALAudioEngine.cpp
Outdated
|
||
OpenALAudioEngine::~OpenALAudioEngine() | ||
{ | ||
m_bufferedSounds.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC buffers of openAL should be removed earlier than "closing device". I guess comment explaining why it is here will be helpful.
src/audio/OpenALAudioEngine.cpp
Outdated
{ | ||
protected: | ||
OpenALSound(OpenALAudioEngine* engine) | ||
: m_buffer(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use default member initializer and make ctors simpler.
src/engine/BaseEngine.h
Outdated
@@ -218,7 +218,7 @@ namespace Engine | |||
*/ | |||
EngineArgs m_Args; | |||
|
|||
Audio::AudioEngine* m_AudioEngine = nullptr; | |||
std::shared_ptr<Audio::AudioEngine> m_AudioEngine; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be unique_ptr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think so 👍
|
||
#include <string> | ||
|
||
typedef struct ALCdevice_struct ALCdevice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's going to interfere with #include <al/al.h>
when it's included in OpenALAudioEngine
@ShFil119 Done addressing the new reviews. During testing Gothic 1 I noticed that some sounds are played even though they should not be heard, like In Extremo (which is now playing correctly 👍 ) that starts right as the player is spawned. Maybe it's got something to do with the |
Last commit should fix the issue, marking as ready for review & merge |
{ | ||
protected: | ||
OpenALSound(OpenALAudioEngine* engine) | ||
: m_hasBuffer(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can used default member initializer.
, m_stream(stream) | ||
, m_format(fmt == Format::Mono ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16) | ||
, m_sampleRate(sampleRate) | ||
, m_stop(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can used default member initializer.
… large music zones
src/logic/MusicController.cpp
Outdated
"_NGT_STD", | ||
"_NGT_THR", | ||
"_NGT_FGT", | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also unwanted spaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this one is unwanted 😨
src/audio/OpenALAudioEngine.cpp
Outdated
auto lambda = [](const auto& sound) { return sound.use_count() == 1; }; | ||
|
||
// Remove all expired sounds | ||
std::remove_if(std::begin(m_bufferedSounds), std::end(m_bufferedSounds), lambda); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw Is this needed? Actually it just moves objects in vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean if it is needed? The shared_ptr
s are never going to get deleted if they stay inside the array, this allows them to be freed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think about missing erase
, https://cpppatterns.com/patterns/remove-elements-from-container.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doh! 🤦♂️ Yeah my C++-fu is still not very strong
src/audio/OpenALAudioEngine.cpp
Outdated
|
||
if(!renderBuffer(dataBuffer)) return; | ||
|
||
while(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without further reading it's hard to determine what's the purpose of loop. Some boolean with name explaining what's going on will be useful. ;)
src/audio/OpenALAudioEngine.cpp
Outdated
ALint processedBuffers; | ||
alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processedBuffers); | ||
if(processedBuffers <= 0) continue; | ||
while(processedBuffers--) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also readability. Can be used range loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think at most a standard for loop could be used
unsigned m_Handle = 0; | ||
Utils::Ticket<AudioWorld> soundTicket; | ||
}; | ||
Daedalus::DaedalusVM *m_SoundVM = nullptr, *m_MusicVM = nullptr; | ||
|
||
struct Sound : public Handle::HandleTypeDescriptor<Handle::SfxHandle> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't seen virtual dtor of HandleTypeDescriptor
, isn't there problem with leaking memory? (by vector and string)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know the details of the memory management inside the engine, this is something @ataulien or @markusobi know better than me
: public Sound | ||
{ | ||
public: | ||
void pitch(float p) override {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As some parts of interface aren't implemented you can think about splitting interface into smaller ones. Idea from SOLID. ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not usre I understand what do you mean? The interface is fully implemented, it just doesn't do anything :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well im not cpp guy but he means that having methods from contract which are not doing anything in class which implements this contract(like no body at all or throwing exeption) is overall a bad concept and you should rethink your interfaces. It should be separated interface then which this class just shouldn't implement.
It's pretty much Liskov Subtitution Principle and also ISP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current way this is implemented is to allow compiling for platforms that don't support audio, or for running the game without any audio, without having to change anything about the frontend code. The NullAudioEngine provides a way to let the game engine use an AudioEngine without actually doing anything.
This is the best I could come up with, suggestions are welcome!
src/logic/MusicController.cpp
Outdated
@@ -24,7 +28,9 @@ std::string MusicController::m_defaultZone = "DEF"; | |||
|
|||
MusicController::MusicController(World::WorldInstance& world, Handle::EntityHandle entity) | |||
: Controller(world, entity) | |||
, m_isPlaying(false) {} | |||
, m_isPlaying(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also can be default member initializer.
src/logic/MusicController.cpp
Outdated
|
||
// We need to play a new segment in either one of two scenarios: | ||
// if the character has just entered the zone or | ||
// if the character was already in the zone but the time of day | ||
// changed. | ||
// FIXME: It'll also be needed to check if the character is | ||
// threatened or fighting to play the correct music. | ||
if ((!m_isPlaying && isInBoundingBox()) | ||
|| (m_isPlaying && m_currentTime != time)) | ||
if (((!m_isPlaying || currentTheme.find(m_instancePrefix) != 0) && isInBoundingBox()) || (m_isPlaying && m_currentTime != time)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too long, I'd try to reformat all your code with clang-format. ;)
The game crashes when you load a savegame after you started a new game. |
Can you confirm this is not present in master? I cannot reproduce this on my machine Also this looks like it's got something to do with bgfx/3d rendering, I'm not sure what could have introduced it from this PR |
@frabert Oh, I'm sorry... This crash is indeed present at the latest master-build. Just stumbled over it because I was testing your branch. |
That's a bug on its own right, anyway. I remember seeing something similar some time ago but could never pinpoint it to something precise, it happens sometimes when sometimes calls |
virtual void stop() = 0; | ||
}; | ||
|
||
using SoundPtr = std::shared_ptr<Sound>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: it should be doable with unique_ptr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't able to do it with a unique_ptr
because OpenGLAudioEngine keeps reference of the sounds that have been created
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the destruction order of Sound
and OpenGLAudioEngine
unspecified?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering that OpenGLAudioEngine
is basically guaranteed to never be destroyed (it should be destroyed when the application closes), Sound
s are always going to die before the engine. The issue is that the Engine
gives out Sound
s to its users, and these objects must stay alive even if they go out of scope, otherwise the sounds would stop playing. So the Engine
keeps reference of all created sounds and destroys them when they are not tracked anymore by anyone else and they are not playing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't have assets to test behavior, but code looks good to me.
{ | ||
if (!m_hasSource) return State::Stopped; | ||
ALint s; | ||
alGetSourcei(m_source, AL_SOURCE_STATE, &s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: as it returns number, you can assign these numbers in enum and cast value of s
to State
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I particularly like this idea, because it ties it specifically to the states that OpenAL returns, which is the reason I wrote a wrapper around it...
std::size_t m_sampleRate; | ||
std::atomic_bool m_stop; | ||
std::array<ALuint, BUFFER_NUM> m_buffers{}; | ||
std::thread m_thread; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Probably one thread for sounds is enough.
// But probably (also) it's not convenient to implement this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it is a bit wasteful to have a separate thread for each streaming sound, it would have be nice to have a single thread that manages all streaming sounds at once, but it complicates the design as you said.
However, there should be very few streaming sounds going on generally (one for music, one for video sound playback when it will get implemented), so I don't think it is too bad of a perf hit.
Provides a more backend-independent audio system
Also fixes some sounds being played at half speed (chapter introductions)