Skip to content

Commit

Permalink
feature: Refactor music engine to prepare for advanced transitions (#29)
Browse files Browse the repository at this point in the history
* feature: Refactor music engine to prepare for advanced transitions development

* Remove unused classes: MusicDef, etc.

* Set `Executors.IO` threads number to the number of available CPU cores

* Respect .ini options ForceDisableReverb and ForceFadeTransition with the new loader
  • Loading branch information
piotrmacha authored May 26, 2024
1 parent 3e67fe7 commit ad7c8a1
Show file tree
Hide file tree
Showing 26 changed files with 1,103 additions and 579 deletions.
2 changes: 1 addition & 1 deletion dependencies/union-api
Submodule union-api updated from 346853 to 406e3f
163 changes: 163 additions & 0 deletions src/Gothic/BassLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#include <functional>

namespace GOTHIC_NAMESPACE
{
struct GothicMusicTheme
{
zSTRING fileName;
float vol;
bool loop;
float reverbMix;
float reverbTime;
zTMus_TransType trType;
zTMus_TransSubType trSubType;
};

struct BassMusicTheme
{
zSTRING Name;
zSTRING Zones;
};

struct BassMusicThemeAudio
{
zSTRING Type;
zSTRING Filename;
};

class BassLoader
{
zCParser* m_Parser;
std::vector<GothicMusicTheme*> m_GothicThemeInstances;
std::vector<BassMusicTheme*> m_BassThemeInstances;
std::vector<BassMusicThemeAudio*> m_BassThemeAudioInstances;

public:
explicit BassLoader(zCParser* p) : m_Parser(p) {}

~BassLoader()
{
for (auto theme: m_GothicThemeInstances) { delete theme; }
for (auto theme: m_BassThemeInstances) { delete theme; }
for (auto theme: m_BassThemeAudioInstances) { delete theme; }
}

void Load()
{
LoadGothic();
LoadBass();
}

private:
void LoadGothic()
{
ForEachClass<GothicMusicTheme>(
"C_MUSICTHEME",
[&]() { return m_GothicThemeInstances.emplace_back(new GothicMusicTheme{}); },
[&](GothicMusicTheme* input, zCPar_Symbol* symbol)
{
std::shared_ptr<NH::Bass::MusicTheme> theme = std::make_shared<NH::Bass::MusicTheme>(symbol->name.ToChar());
theme->SetAudioFile(NH::Bass::AudioFile::DEFAULT, input->fileName.ToChar());
theme->SetAudioEffects(NH::Bass::AudioFile::DEFAULT, [&](NH::Bass::AudioEffects& effects)
{
effects.Loop.Active = input->loop;
effects.Volume.Active = true;
effects.Volume.Volume = input->vol;
if (!NH::Bass::Options->ForceDisableReverb)
{
effects.ReverbDX8.Active = true;
effects.ReverbDX8.Mix = input->reverbMix;
effects.ReverbDX8.Time = input->reverbTime;
}
bool forceFade = NH::Bass::Options->ForceFadeTransition;
if (forceFade || input->trType == zMUS_TR_INTRO || input->trType == zMUS_TR_ENDANDINTRO)
{
effects.FadeIn.Active = true;
effects.FadeIn.Duration = NH::Bass::Options->TransitionTime;
}
if (forceFade || input->trType == zMUS_TR_END || input->trType == zMUS_TR_ENDANDINTRO)
{
effects.FadeOut.Active = true;
effects.FadeOut.Duration = NH::Bass::Options->TransitionTime;
}
});
theme->AddZone(symbol->name.ToChar());
NH::Bass::Engine::GetInstance()->GetMusicManager().AddTheme(symbol->name.ToChar(), theme);
});
}

void LoadBass()
{
ForEachClass<BassMusicTheme>(
"C_BassMusic_Theme",
[&]() { return m_BassThemeInstances.emplace_back(new BassMusicTheme{}); },
[&](BassMusicTheme* theme, zCPar_Symbol* symbol)
{
// @todo:
});

ForEachClass<BassMusicThemeAudio>(
"C_BassMusic_ThemeAudio",
[&]() { return m_BassThemeAudioInstances.emplace_back(new BassMusicThemeAudio{}); },
[&](BassMusicThemeAudio* theme, zCPar_Symbol* symbol)
{
// @todo:
});
}

template<typename T>
void ForEachClass(const zSTRING& className, const std::function<T*()>& classFactory, const std::function<void(T*, zCPar_Symbol*)>& instanceFunc)
{
ForEachPrototype(className, [&](int index)
{
T* theme = classFactory();
if (theme)
{
m_Parser->CreatePrototype(index, theme);
}
});
ForEachInstance(className, [&](int index, zCPar_Symbol* symbol)
{
T* theme = classFactory();
if (theme)
{
m_Parser->CreateInstance(index, theme);
}
instanceFunc(theme, symbol);
});
}

void ForEachPrototype(const zSTRING& className, const std::function<void(int)>& func)
{
int classIndex = m_Parser->GetIndex(className);
if (classIndex < 0)
{
return;
}

int prototypeIndex = m_Parser->GetPrototype(classIndex, 0);
while (prototypeIndex > 0)
{
func(prototypeIndex);
prototypeIndex = m_Parser->GetPrototype(classIndex, prototypeIndex + 1);
}
}

void ForEachInstance(const zSTRING& className, const std::function<void(int, zCPar_Symbol*)>& func)
{
int classIndex = m_Parser->GetIndex(className);
if (classIndex < 0)
{
return;
}

int symbolIndex = m_Parser->GetInstance(classIndex, 0);
while (symbolIndex > 0)
{
zCPar_Symbol* symbol = m_Parser->GetSymbol(symbolIndex);
func(symbolIndex, symbol);
symbolIndex = m_Parser->GetInstance(classIndex, symbolIndex + 1);
}
}
};
}
165 changes: 28 additions & 137 deletions src/Gothic/CMusicSys_Bass.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ namespace GOTHIC_NAMESPACE
{
namespace BassEvent
{
void Event_OnEnd(const NH::Bass::MusicDef& musicDef, int data, void* userData)
void Event_OnEnd(const NH::Bass::Event& event, void* userData)
{
static NH::Logger* log = NH::CreateLogger("zBassMusic::Event_OnEnd");
log->Trace("{0}", musicDef.Filename);

zSTRING filename{ musicDef.Filename.ToChar() };
zSTRING name{ musicDef.Name.ToChar() };
NH::Bass::Event::MusicEnd data = std::get<NH::Bass::Event::MusicEnd>(event.Data);
zSTRING filename{ data.Theme->GetAudioFile(data.AudioId).Filename.ToChar() };
zSTRING name{ data.Theme->GetName() };
log->Trace("{0}, {1}", name.ToChar(), filename.ToChar());

for (int i = 0; i < Globals->Event_OnEnd_Functions.GetNumInList(); i++)
{
Expand All @@ -19,30 +20,33 @@ namespace GOTHIC_NAMESPACE
}
}

void Event_OnTransition(const NH::Bass::MusicDef& musicDef, int data, void* userData)
void Event_OnTransition(const NH::Bass::Event& event, void* userData)
{
static NH::Logger* log = NH::CreateLogger("zBassMusic::Event_OnTransition");
log->Trace("{0}, {1} ms", musicDef.Filename, data);

zSTRING filename{ musicDef.Filename.ToChar() };
zSTRING name{ musicDef.Name.ToChar() };
NH::Bass::Event::MusicTransition data = std::get<NH::Bass::Event::MusicTransition>(event.Data);
zSTRING filename{ data.Theme->GetAudioFile(data.AudioId).Filename.ToChar() };
zSTRING name{ data.Theme->GetName() };
float timeLeft = data.TimeLeft;
log->Trace("{0}, {1}", name.ToChar(), filename.ToChar());

for (int i = 0; i < Globals->Event_OnTransition_Functions.GetNumInList(); i++)
{
const int funcId = Globals->Event_OnTransition_Functions[i];
Globals->BassMusic_EventThemeFilename = filename;
Globals->BassMusic_EventThemeID = name;
parser->CallFunc(funcId, data);
parser->CallFunc(funcId, timeLeft);
}
}

void Event_OnChange(const NH::Bass::MusicDef& musicDef, int data, void* userData)
void Event_OnChange(const NH::Bass::Event& event, void* userData)
{
static NH::Logger* log = NH::CreateLogger("zBassMusic::Event_OnChange");
log->Trace("{0}", musicDef.Filename);

zSTRING filename{ musicDef.Filename.ToChar() };
zSTRING name{ musicDef.Name.ToChar() };
NH::Bass::Event::MusicChange data = std::get<NH::Bass::Event::MusicChange>(event.Data);
zSTRING filename{ data.Theme->GetAudioFile(data.AudioId).Filename.ToChar() };
zSTRING name{ data.Theme->GetName() };
log->Trace("{0}, {1}", name.ToChar(), filename.ToChar());

Globals->BassMusic_ActiveThemeFilename = filename;
Globals->BassMusic_ActiveThemeID = name;
Expand Down Expand Up @@ -114,6 +118,7 @@ namespace GOTHIC_NAMESPACE
}

zCMusicTheme* theme = new zCMusicTheme;
theme->name = identifier;

if (!(NH::Bass::Options->CreateMainParserCMusicTheme && parser->CreateInstance(identifier, &theme->fileName)))
{
Expand All @@ -125,60 +130,6 @@ namespace GOTHIC_NAMESPACE
delete theme;
theme = m_DirectMusic->LoadThemeByScript(id);
}
else
{
theme->name = identifier;

zoptions->ChangeDir(DIR_MUSIC);
std::unique_ptr<zFILE> file{ zfactory->CreateZFile(theme->fileName) };

if (file->Exists())
{
NH::Bass::MusicFile& musicFileRef = m_BassEngine->CreateMusicBuffer(theme->fileName.ToChar());
if (!musicFileRef.Ready && !musicFileRef.Loading)
{
log->Trace("Loading music: {0}", file->GetFullPath().ToChar());

const auto error = file->Open(false);

if (error == 0)
{
musicFileRef.Loading = true;

std::thread([loadingStart = std::chrono::system_clock::now(), this](std::unique_ptr<zFILE> myFile, NH::Bass::MusicFile* myMusicPtr)
{

zSTRING path = myFile->GetFullPath();
const long size = myFile->Size();

myMusicPtr->Buffer.resize(static_cast<size_t>(size));
const long read = myFile->Read(myMusicPtr->Buffer.data(), size);

if (read == size)
{
myMusicPtr->Ready = true;
log->Trace("Music ready: {0}, size = {1}", path.ToChar(), read);
}

myMusicPtr->Loading = false;
myFile->Close();

auto loadingTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - loadingStart).count();

log->Trace("Loading done: {0}, time = {1}", path.ToChar(), loadingTime);
}, std::move(file), &musicFileRef).detach();
}
else
{
log->Error("Could not open file {0}\n at {1}{2}", theme->fileName.ToChar(), __FILE__, __LINE__);
}
}
}
else
{
log->Error("Could not find file {0}\n at {1}{2}", theme->fileName.ToChar(), __FILE__, __LINE__);
}
}

return theme;
}
Expand All @@ -205,38 +156,19 @@ namespace GOTHIC_NAMESPACE
}

zCMusicTheme* theme = LoadThemeByScript(id);
if (theme)
if (theme && IsDirectMusicFormat(theme->fileName))
{
if (IsDirectMusicFormat(theme->fileName))
{
m_ActiveTheme = theme;
m_BassEngine->StopMusic();
return m_DirectMusic->PlayThemeByScript(id, manipulate, done);
}

if (done)
{
*done = true;
}

switch (manipulate)
{
case 1:
PlayTheme(theme, zMUS_THEME_VOL_DEFAULT, zMUS_TR_END, zMUS_TRSUB_DEFAULT);
break;
case 2:
PlayTheme(theme, zMUS_THEME_VOL_DEFAULT, zMUS_TR_NONE, zMUS_TRSUB_DEFAULT);
break;
default:
PlayTheme(theme);
}

return;
m_ActiveTheme = theme;
m_BassEngine->StopMusic();
return m_DirectMusic->PlayThemeByScript(id, manipulate, done);
}

identifier.Upper();
m_BassEngine->GetCommandQueue().AddCommand(std::make_shared<NH::Bass::ChangeZoneCommand>(identifier.ToChar()));

if (done)
{
*done = false;
*done = true;
}
}

Expand All @@ -256,50 +188,9 @@ namespace GOTHIC_NAMESPACE
}

m_DirectMusic->Stop();

if (transition != zMUS_TR_DEFAULT)
{
theme->trType = transition;
}

if (subTransition != zMUS_TRSUB_DEFAULT)
{
theme->trSubType = subTransition;
}

NH::Bass::MusicDef musicDef{};
musicDef.Filename = theme->fileName.ToChar();
musicDef.Name = theme->name.ToChar();
musicDef.Loop = theme->loop;
musicDef.Volume = theme->vol;

if (theme->trType == zMUS_TR_INTRO || theme->trType == zMUS_TR_ENDANDINTRO)
{
musicDef.StartTransition.Type = NH::Bass::TransitionType::FADE;
musicDef.StartTransition.Duration = NH::Bass::Options->TransitionTime;
}

if (theme->trType == zMUS_TR_END || theme->trType == zMUS_TR_ENDANDINTRO)
{
musicDef.EndTransition.Type = NH::Bass::TransitionType::FADE;
musicDef.EndTransition.Duration = NH::Bass::Options->TransitionTime;
}

if (m_DirectMusic->prefs.globalReverbEnabled)
{
musicDef.Effects.Reverb = true;
musicDef.Effects.ReverbMix = theme->reverbMix;
musicDef.Effects.ReverbTime = theme->reverbTime;
}

m_ActiveTheme = theme;

// Engine::PlayMusic() uses a mutex, so let's submit it in a deteached thread to avoid blocking
std::thread submitThread([this, musicDef]()
{
m_BassEngine->PlayMusic(musicDef);
});
submitThread.detach();
log->Warning("This path in CMusicSys_Bass::PlayTheme() shouldn't be possible");
PlayThemeByScript(theme->name, 0, nullptr);
}

zCMusicTheme* GetActiveTheme() override
Expand Down
Loading

0 comments on commit ad7c8a1

Please sign in to comment.