Skip to content

Commit

Permalink
feature: invert dependencies and move scheduling code directly to the…
Browse files Browse the repository at this point in the history
… `MusicTheme`; Engine became only an aggregate of objects, Channel became a command processor
  • Loading branch information
piotrmacha committed May 27, 2024
1 parent e06994a commit cc2b62b
Show file tree
Hide file tree
Showing 22 changed files with 591 additions and 627 deletions.
22 changes: 10 additions & 12 deletions src/Gothic/BassLoader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ namespace GOTHIC_NAMESPACE
zTMus_TransSubType trSubType;
};

enum class BassMusicThemeType : size_t { NORMAL = 0 };

struct BassMusicTheme
{
zSTRING Name;
zSTRING Zones;
BassMusicThemeType Type = BassMusicThemeType::NORMAL;
};

struct BassMusicThemeAudio
{
zSTRING Theme;
zSTRING Type;
zSTRING Filename;
zSTRING MidiFile;
Expand Down Expand Up @@ -56,12 +60,10 @@ namespace GOTHIC_NAMESPACE
ForEachClass<GothicMusicTheme>(
"C_MUSICTHEME",
[&]() { return m_GothicThemeInstances.emplace_back(new GothicMusicTheme{}); },
[&](GothicMusicTheme* input, zCPar_Symbol* symbol)
{
[&](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)
{
theme->SetAudioEffects(NH::Bass::AudioFile::DEFAULT, [&](NH::Bass::AudioEffects& effects) {
effects.Loop.Active = input->loop;
effects.Volume.Active = true;
effects.Volume.Volume = input->vol;
Expand Down Expand Up @@ -93,33 +95,29 @@ namespace GOTHIC_NAMESPACE
ForEachClass<BassMusicTheme>(
"C_BassMusic_Theme",
[&]() { return m_BassThemeInstances.emplace_back(new BassMusicTheme{}); },
[&](BassMusicTheme* theme, zCPar_Symbol* symbol)
{
[&](BassMusicTheme* theme, zCPar_Symbol* symbol) {
// @todo:
});

ForEachClass<BassMusicThemeAudio>(
"C_BassMusic_ThemeAudio",
[&]() { return m_BassThemeAudioInstances.emplace_back(new BassMusicThemeAudio{}); },
[&](BassMusicThemeAudio* theme, zCPar_Symbol* symbol)
{
[&](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)
{
ForEachPrototype(className, [&](int index) {
T* theme = classFactory();
if (theme)
{
m_Parser->CreatePrototype(index, theme);
}
});
ForEachInstance(className, [&](int index, zCPar_Symbol* symbol)
{
ForEachInstance(className, [&](int index, zCPar_Symbol* symbol) {
T* theme = classFactory();
if (theme)
{
Expand Down
6 changes: 4 additions & 2 deletions src/Gothic/CMusicSys_Bass.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,12 @@ namespace GOTHIC_NAMESPACE
return;
}

// Called from an external
m_DirectMusic->Stop();
m_ActiveTheme = theme;
log->Warning("This path in CMusicSys_Bass::PlayTheme() shouldn't be possible");
PlayThemeByScript(theme->name, 0, nullptr);
zSTRING identifier = theme->name;
identifier.Upper();
m_BassEngine->GetCommandQueue().AddCommand(std::make_shared<NH::Bass::ScheduleThemeChangeCommand>(identifier.ToChar()));
}

zCMusicTheme* GetActiveTheme() override
Expand Down
3 changes: 2 additions & 1 deletion src/Gothic/Externals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ namespace GOTHIC_NAMESPACE
timePoints.push_back(point);
}

NH::Bass::Engine::GetInstance()->GetTransitionScheduler().AddRuleOnBeat(name.ToChar(), interval, timePoints);
// @todo: think about Daedalus API
//NH::Bass::Engine::GetInstance()->GetTransitionScheduler().AddRuleOnBeat(name.ToChar(), interval, timePoints);
}
catch (const std::invalid_argument& e)
{
Expand Down
200 changes: 90 additions & 110 deletions src/NH/Bass/Channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,164 +4,144 @@

namespace NH::Bass
{
void Channel::Play(const std::shared_ptr<MusicTheme>& theme, HashString id)
struct OnSyncWhenAudioEndsData
{
HSTREAM Channel;
const std::function<void()>& Function;
};

struct OnSyncBeforeAudioEndsData
{
HSTREAM Channel;
const std::function<void(double)>& Function;
};

Channel::Result<void> Channel::PlayInstant(const AudioFile& audioFile)
{
if (m_Stream > 0)
{
BASS_ChannelFree(m_Stream);
}

m_Theme = theme;
m_AudioId = id;
const auto& file = theme->GetAudioFile(id);
const auto& effects = theme->GetAudioEffects(id);

m_Stream = BASS_StreamCreateFile(true, file.Buffer.data(), 0, file.Buffer.size(), 0);
m_Stream = BASS_StreamCreateFile(true, audioFile.Buffer.data(), 0, audioFile.Buffer.size(), 0);
if (!m_Stream)
{
log->Error("Could not create stream: {0}\n error: {1}\n at {2}:{3}",
m_Theme->GetName(), Engine::ErrorCodeToString(BASS_ErrorGetCode()),
__FILE__, __LINE__);
return;
int code = BASS_ErrorGetCode();
log->Error("Could not create stream: {0}\n error: {1}\n at {2}:{3}", audioFile.Filename, Engine::ErrorCodeToString(code), __FILE__, __LINE__);
return std::unexpected(Error{ IChannel::ErrorType::INVALID_BUFFER, code, Engine::ErrorCodeToString(code) });
}

BASS_ChannelStart(m_Stream);
log->Info("Channel started: {0}", m_Theme->GetName());
return {};
}

float targetVolume = effects.Volume.Active ? effects.Volume.Volume : 1.0f;
BASS_ChannelSetAttribute(m_Stream, BASS_ATTRIB_VOL, targetVolume);
void Channel::StopInstant()
{
BASS_ChannelStop(m_Stream);
}

if (effects.FadeIn.Active)
{
BASS_ChannelSetAttribute(m_Stream, BASS_ATTRIB_VOL, 0.0f);
BASS_ChannelSlideAttribute(m_Stream, BASS_ATTRIB_VOL, targetVolume, effects.FadeIn.Duration);
}
void Channel::SetVolume(float volume)
{
if (volume < 0.0f) { log->Warning("Clamping invalid volume {0} to 0.0f", volume); volume = 0.0f; }
if (volume > 1.0f) { log->Warning("Clamping invalid volume {0} to 1.0f", volume); volume = 1.0f; }
BASS_ChannelSetAttribute(m_Stream, BASS_ATTRIB_VOL, volume);
}

if (effects.Loop.Active)
{
BASS_ChannelFlags(m_Stream, BASS_SAMPLE_LOOP, BASS_SAMPLE_LOOP);
log->Trace("Loop set {0}", m_Theme->GetName());
}
void Channel::SlideVolume(float targetVolume, uint32_t time)
{
BASS_ChannelSlideAttribute(m_Stream, BASS_ATTRIB_VOL, targetVolume, time);
}

if (!Options->ForceDisableReverb && effects.ReverbDX8.Active)
{
HFX fx = BASS_ChannelSetFX(m_Stream, BASS_FX_DX8_REVERB, 1);
BASS_DX8_REVERB params{ 0, effects.ReverbDX8.Mix, effects.ReverbDX8.Time, 0.001f };
if (!BASS_FXSetParameters(fx, (void*)&params))
{
log->Error("Could not set reverb FX: {0}\n error: {1}\n at {2}:{3}",
m_Theme->GetName(), Engine::ErrorCodeToString(BASS_ErrorGetCode()),
__FILE__, __LINE__);
}
log->Trace("Reverb set: {0}", m_Theme->GetName());
}
void Channel::SlideVolume(float targetVolume, uint32_t time, const std::function<void()>& onFinish)
{
SlideVolume(targetVolume, time);
BASS_ChannelSetSync(m_Stream, BASS_SYNC_SLIDE, 0, OnSlideVolumeSyncCallFunction, (void*)&onFinish);
}

if (effects.FadeOut.Active)
void Channel::SetDX8ReverbEffect(float reverbMix, float reverbTime, float inputGain, float highFreqRTRatio)
{
HFX effect = BASS_ChannelSetFX(m_Stream, BASS_FX_DX8_REVERB, 0);
if (reverbMix < -96) { log->Warning("Clamping DX8Reverb reverbMix({0}) to -96", reverbMix); reverbMix = -96; }
if (reverbMix > 0) { log->Warning("Clamping DX8Reverb reverbMix({0}) to 0", reverbMix); reverbMix = 0; }
if (reverbTime < 0.001f) { log->Warning("Clamping DX8Reverb reverbTime({0}) to 0.001f", reverbTime); reverbTime = 0.001f; }
if (reverbTime > 3000.0f) { log->Warning("Clamping DX8Reverb reverbTime({0}) to 3000.0f", reverbTime); reverbTime = 3000.0f; }
BASS_DX8_REVERB reverb{
inputGain, reverbMix, reverbTime, highFreqRTRatio
};
if (!BASS_FXSetParameters(effect, &reverb))
{
const QWORD length = BASS_ChannelGetLength(m_Stream, BASS_POS_BYTE);
const QWORD transitionBytes = BASS_ChannelSeconds2Bytes(m_Stream, effects.FadeOut.Duration / 1000.0f);
const QWORD offset = length - transitionBytes;
BASS_ChannelSetSync(m_Stream, BASS_SYNC_END, offset, OnVolumeSlideSync, this);
log->Trace("SyncEnd set: {0}", m_Theme->GetName());
int code = BASS_ErrorGetCode();
log->Error("Could not set DX8Reverb effect: {0}\n error: {1}\n at {2}:{3}", m_Stream, Engine::ErrorCodeToString(code), __FILE__, __LINE__);
}
}

BASS_ChannelSetSync(m_Stream, BASS_SYNC_END, 0, OnEndSync, this);
log->Trace("SyncEnd set: {0}", m_Theme->GetName());
void Channel::WhenAudioEnds(const std::function<void()>& onFinish)
{
BASS_ChannelSetSync(m_Stream, BASS_SYNC_END, 0, OnAudioEndSyncCallFunction, (void*)new OnSyncWhenAudioEndsData{ m_Stream, onFinish });
}

m_Status = ChannelStatus::PLAYING;
m_EventManager.DispatchEvent(MusicChangeEvent(m_Theme, m_AudioId));
void Channel::BeforeAudioEnds(double aheadSeconds, const std::function<void(double)>& onFinish)
{
double position = Length() - aheadSeconds;
size_t positionBytes = BASS_ChannelSeconds2Bytes(m_Stream, position);
BASS_ChannelSetSync(m_Stream, BASS_SYNC_POS, positionBytes, BeforeAudioEndsSyncCallFunction, (void*)new OnSyncBeforeAudioEndsData{ m_Stream, onFinish });
}

double Channel::CurrentPosition() const
void Channel::Acquire()
{
if (m_Stream > 0)
if (m_Status != ChannelStatus::AVAILABLE)
{
return BASS_ChannelBytes2Seconds(m_Stream, BASS_ChannelGetPosition(m_Stream, BASS_POS_BYTE));
log->Error("Trying to acquire a non-available channel. We will allow that to not crash the game but the music may be funky.");
}
return -1;
m_Status = ChannelStatus::PLAYING;
}

void Channel::Release()
{
m_Status = ChannelStatus::AVAILABLE;
}

double Channel::CurrentLength() const
double Channel::Position() const
{
if (m_Stream > 0)
{
return BASS_ChannelBytes2Seconds(m_Stream, BASS_ChannelGetLength(m_Stream, BASS_POS_BYTE));
return BASS_ChannelBytes2Seconds(m_Stream, BASS_ChannelGetPosition(m_Stream, BASS_POS_BYTE));
}
return -1;
}

void Channel::Stop()
double Channel::Length() const
{
if (m_Stream > 0)
{
const auto& effects = m_Theme->GetAudioEffects(m_AudioId);
if (effects.FadeOut.Active)
{
BASS_ChannelSlideAttribute(m_Stream, BASS_ATTRIB_VOL, 0.0f, effects.FadeOut.Duration);
BASS_ChannelSetSync(m_Stream, BASS_SYNC_SLIDE, 0, OnVolumeSlideSync, this);
m_Status = ChannelStatus::FADING_OUT;
}
else
{
BASS_ChannelFree(m_Stream);
m_Stream = 0;
m_Status = ChannelStatus::AVAILABLE;
}
return BASS_ChannelBytes2Seconds(m_Stream, BASS_ChannelGetLength(m_Stream, BASS_POS_BYTE));
}
return -1;
}

std::shared_ptr<MusicTheme> Channel::CurrentTheme() const
{
return m_Theme;
}

const AudioFile& Channel::CurrentAudioFile() const
void Channel::OnSlideVolumeSyncCallFunction(HSYNC, DWORD channel, DWORD data, void* userData)
{
return m_Theme->GetAudioFile(m_AudioId);
auto* onFinish = static_cast<std::function<void()>*>(userData);
if (onFinish) { (*onFinish)(); }
else { CreateLogger("HSYNC::OnSlideVolumeSyncCallFunction")->Error("onFinish is nullptr"); }
}

const AudioEffects& Channel::CurrentAudioEffects() const
void Channel::OnAudioEndSyncCallFunction(HSYNC, DWORD channel, DWORD data, void* userData)
{
return m_Theme->GetAudioEffects(m_AudioId);
}
auto* payload = static_cast<OnSyncWhenAudioEndsData*>(userData);
if (channel != payload->Channel) return;

void Channel::OnTransitionSync(HSYNC, DWORD channel, DWORD data, void* userData)
{
auto* _this = static_cast<Channel*>(userData);
if (_this->m_Stream == channel)
{
_this->m_EventManager.DispatchEvent(MusicTransitionEvent(_this->m_Theme, _this->m_AudioId, _this->CurrentAudioEffects().FadeOut.Duration));
}
if (payload->Function) { payload->Function(); }
else { CreateLogger("HSYNC::OnAudioEndSyncCallFunction")->Error("onFinish is nullptr"); }
}

void Channel::OnEndSync(HSYNC, DWORD channel, DWORD data, void* userData)
void Channel::BeforeAudioEndsSyncCallFunction(HSYNC, DWORD channel, DWORD data, void* userData)
{
auto* _this = static_cast<Channel*>(userData);
if (_this->m_Stream == channel)
{
const auto& effects = _this->m_Theme->GetAudioEffects(_this->m_AudioId);
if (!effects.Loop.Active)
{
_this->m_Status = ChannelStatus::AVAILABLE;
}
auto* payload = static_cast<OnSyncBeforeAudioEndsData*>(userData);
if (channel != payload->Channel) return;

_this->m_EventManager.DispatchEvent(MusicEndEvent(_this->m_Theme, _this->m_AudioId));
}
}

void Channel::OnVolumeSlideSync(HSYNC, DWORD channel, DWORD data, void* userData)
{
auto* _this = static_cast<Channel*>(userData);
if (_this->m_Stream == channel && _this->m_Status == ChannelStatus::FADING_OUT)
{
float volume;
BASS_ChannelGetAttribute(channel, BASS_ATTRIB_VOL, &volume);
if (volume < DBL_EPSILON)
{
BASS_ChannelFree(channel);
_this->m_Stream = 0;
_this->m_Status = ChannelStatus::AVAILABLE;
}
}
double aheadSeconds = BASS_ChannelBytes2Seconds(channel, BASS_ChannelGetLength(channel, BASS_POS_BYTE) - BASS_ChannelGetPosition(channel, BASS_POS_BYTE));
if (payload->Function) { payload->Function(aheadSeconds); }
else { CreateLogger("HSYNC::BeforeAudioEndsSyncCallFunction")->Error("onFinish is nullptr"); }
}
}
Loading

0 comments on commit cc2b62b

Please sign in to comment.