Skip to content
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

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open

Update audio engine #366

wants to merge 29 commits into from

Conversation

frabert
Copy link
Contributor

@frabert frabert commented Sep 12, 2018

Provides a more backend-independent audio system
Also fixes some sounds being played at half speed (chapter introductions)

Provides a more backend-independent audio system
Also fixes some sounds being played at half speed (chapter introductions)
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 {
Copy link

@ghost ghost Sep 13, 2018

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.

return Handle::SfxHandle::makeInvalidHandle();
}
return Handle::SfxHandle::makeInvalidHandle();
const std::int16_t* dataPtr = (std::int16_t*)(wav.getData());
Copy link

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.

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?

Copy link

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.

virtual void stop() = 0;
};

using SoundRef = std::shared_ptr<Sound>;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SoundPtr?

, m_engine(engine)
{}
public:
OpenALSound(OpenALAudioEngine* engine, const std::int16_t* buf, std::size_t len, Format fmt, std::size_t samplingFreq)
Copy link

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

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; });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto for parametr/arg

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; });
Copy link

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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref?

Copy link
Contributor Author

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

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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

= nullptris redundant


OpenALAudioEngine::~OpenALAudioEngine()
{
m_bufferedSounds.erase(std::begin(m_bufferedSounds), std::end(m_bufferedSounds));
Copy link

@ghost ghost Sep 13, 2018

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

@tomedi
Copy link

tomedi commented Sep 13, 2018

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).

@frabert
Copy link
Contributor Author

frabert commented Sep 14, 2018

@ShFil119 I addressed most of the criticisms, thanks.
@tomedi I don't think those issues could be introduced with this PR. If you toggle the debug music zones draw, you can see that indeed the city zone starts in front of Vatras's place

@frabert frabert changed the title Update audio engine [WIP] Update audio engine Sep 14, 2018
@frabert
Copy link
Contributor Author

frabert commented Sep 14, 2018

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


using namespace Audio;

static std::vector<std::string> enumeratedDevices;
Copy link

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?

Copy link
Contributor Author

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 🤔

Copy link
Contributor Author

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...!

// can be accessed in a thread-safe manner
static std::mutex mtx_enumeratedDevices;

constexpr std::size_t OPENAL_SOURCES_POOL = 256;
Copy link

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.

Copy link
Contributor Author

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.

}
else
{
enumeratedDevices.push_back("");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can emplace_back()


OpenALAudioEngine::~OpenALAudioEngine()
{
m_bufferedSounds.clear();
Copy link

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.

{
protected:
OpenALSound(OpenALAudioEngine* engine)
: m_buffer(0)
Copy link

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.

@@ -218,7 +218,7 @@ namespace Engine
*/
EngineArgs m_Args;

Audio::AudioEngine* m_AudioEngine = nullptr;
std::shared_ptr<Audio::AudioEngine> m_AudioEngine;
Copy link

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?

Copy link
Contributor Author

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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using?

Copy link
Contributor Author

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

@frabert
Copy link
Contributor Author

frabert commented Sep 14, 2018

@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 maximumDistance. More investigation is needed

@frabert
Copy link
Contributor Author

frabert commented Sep 15, 2018

Last commit should fix the issue, marking as ready for review & merge

@frabert frabert changed the title [WIP] Update audio engine Update audio engine Sep 15, 2018
{
protected:
OpenALSound(OpenALAudioEngine* engine)
: m_hasBuffer(false)
Copy link

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)
Copy link

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.

src/engine/BaseEngine.cpp Show resolved Hide resolved
"_NGT_STD",
"_NGT_THR",
"_NGT_FGT",
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also unwanted spaces?

Copy link
Contributor Author

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 😨

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);
Copy link

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.

Copy link
Contributor Author

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_ptrs are never going to get deleted if they stay inside the array, this allows them to be freed

Copy link

@ghost ghost Sep 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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


if(!renderBuffer(dataBuffer)) return;

while(true)
Copy link

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. ;)

ALint processedBuffers;
alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processedBuffers);
if(processedBuffers <= 0) continue;
while(processedBuffers--)
Copy link

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?

Copy link
Contributor Author

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>
Copy link

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)

Copy link
Contributor Author

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 {}
Copy link

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. ;)

Copy link
Contributor Author

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 :)

Copy link

@Jurigag Jurigag Sep 22, 2018

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.

Copy link
Contributor Author

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/content/AnimationLibrary.cpp Show resolved Hide resolved
@@ -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)
Copy link

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.


// 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))
Copy link

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. ;)

@tomedi
Copy link

tomedi commented Sep 22, 2018

The game crashes when you load a savegame after you started a new game.
crashlog_music.txt

@frabert
Copy link
Contributor Author

frabert commented Sep 22, 2018

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

@tomedi
Copy link

tomedi commented Sep 22, 2018

@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.

@frabert
Copy link
Contributor Author

frabert commented Sep 22, 2018

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 btVector3::normalize apparently

virtual void stop() = 0;
};

using SoundPtr = std::shared_ptr<Sound>;
Copy link

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

Copy link
Contributor Author

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

Copy link
Contributor

@mdrost mdrost Oct 8, 2018

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?

Copy link
Contributor Author

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), Sounds are always going to die before the engine. The issue is that the Engine gives out Sounds 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

Copy link

@ghost ghost left a 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);
Copy link

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.

Copy link
Contributor Author

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;
Copy link

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.

Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants