Skip to content
This repository has been archived by the owner on Jan 5, 2024. It is now read-only.

FMOD and sound improvements - busses and effects and properties #553

Merged
merged 8 commits into from
Nov 4, 2023
Merged
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*.APS
*.user

/.idea

compile_commands.json
/.ccls-cache

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- New `SLTerrain` INI property `OrbitDirection`, which defines which direction is considered to be orbit, for the sake of brain-path-to-orbit, dropship spawn/return location, etc. Can be any of `Up`, `Down`, `Left` or `Right`. Defaults to `Up`.

- New FMOD and SoundContainer features:
The game is now divided into SFX, UI, and Music busses which all route into the Master bus.
The SFX bus has compression added for a better listening experience, and a safety volume limiter has been added to the Master bus.
Aside from volume being attenuated, sounds will now also be lowpass filtered as distance increases.
New `SoundContainer` INI and Lua (R/W) property `BusRouting`, which denotes which bus the SoundContainer routes to. Available busses: `SFX, UI, Music`. Defaults to `SFX`.
`Enum` binding for `SoundContainer.BusRouting`: `SFX = 0, UI = 1, MUSIC = 2`.
New `SoundContainer` INI and Lua (R/W) property `PanningStrengthMultiplier`, which will multiply the strength of 3D panning. This can be used to achieve for example a psuedo-Immobile effect where attenuation effects are still applied but the sound does not move from the center. Recommended to keep between 0.0 and 1.0.

</details>

<details><summary><b>Changed</b></summary>
Expand Down
32 changes: 29 additions & 3 deletions Entities/SoundContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ namespace RTE {
{"Ignore Play", SoundContainer::SoundOverlapMode::IGNORE_PLAY}
};

const std::unordered_map<std::string, SoundContainer::BusRouting> SoundContainer::c_BusRoutingMap = {
{"SFX", SoundContainer::BusRouting::SFX},
{"UI", SoundContainer::BusRouting::UI},
{"Music", SoundContainer::BusRouting::MUSIC}
};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void SoundContainer::Clear() {
Expand All @@ -19,8 +25,10 @@ namespace RTE {
m_PlayingChannels.clear();
m_SoundOverlapMode = SoundOverlapMode::OVERLAP;

m_BusRouting = BusRouting::SFX;
m_Immobile = false;
m_AttenuationStartDistance = c_DefaultAttenuationStartDistance;
m_PanningStrengthMultiplier = 1.0F;
m_Loops = 0;
m_SoundPropertiesUpToDate = false;

Expand All @@ -43,8 +51,10 @@ namespace RTE {
m_PlayingChannels.clear();
m_SoundOverlapMode = reference.m_SoundOverlapMode;

m_BusRouting = reference.m_BusRouting;
m_Immobile = reference.m_Immobile;
m_AttenuationStartDistance = reference.m_AttenuationStartDistance;
m_PanningStrengthMultiplier = reference.m_PanningStrengthMultiplier;
m_Loops = reference.m_Loops;

m_Priority = reference.m_Priority;
Expand Down Expand Up @@ -83,8 +93,21 @@ namespace RTE {
}
}
});
MatchProperty("BusRouting", {
std::string busRoutingString = reader.ReadPropValue();
if (c_BusRoutingMap.find(busRoutingString) != c_BusRoutingMap.end()) {
m_BusRouting = c_BusRoutingMap.find(busRoutingString)->second;
} else {
try {
m_BusRouting = static_cast<BusRouting>(std::stoi(busRoutingString));
} catch (const std::exception &) {
reader.ReportError("Tried to route to non-existent sound bus " + busRoutingString);
}
}
});
MatchProperty("Immobile", { reader >> m_Immobile; });
MatchProperty("AttenuationStartDistance", { reader >> m_AttenuationStartDistance; });
MatchProperty("PanningStrengthMultiplier", { reader >> m_PanningStrengthMultiplier; });
MatchProperty("LoopSetting", { reader >> m_Loops; });
MatchProperty("Priority", {
reader >> m_Priority;
Expand All @@ -100,7 +123,7 @@ namespace RTE {
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int SoundContainer::Save(Writer &writer) const {
Entity::Save(writer);

Expand All @@ -117,11 +140,14 @@ namespace RTE {
} else {
RTEAbort("Tried to write invalid SoundOverlapMode when saving SoundContainer.");
}

writer.NewProperty("BusRouting");
writer << m_BusRouting;
writer.NewProperty("Immobile");
writer << m_Immobile;
writer.NewProperty("AttenuationStartDistance");
writer << m_AttenuationStartDistance;
writer.NewProperty("PanningStrengthMultiplier");
writer << m_PanningStrengthMultiplier;
writer.NewProperty("LoopSetting");
writer << m_Loops;

Expand Down Expand Up @@ -216,7 +242,7 @@ namespace RTE {
for (SoundSet::SoundData *soundData : flattenedSoundData) {
FMOD_MODE soundMode = (m_Loops == 0) ? FMOD_LOOP_OFF : FMOD_LOOP_NORMAL;
if (m_Immobile) {
soundMode |= FMOD_3D_HEADRELATIVE;
soundMode |= FMOD_2D;
m_AttenuationStartDistance = c_SoundMaxAudibleDistance;
} else if (g_AudioMan.GetSoundPanningEffectStrength() == 1.0F) {
soundMode |= FMOD_3D_INVERSEROLLOFF;
Expand Down
51 changes: 45 additions & 6 deletions Entities/SoundContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ namespace RTE {
SerializableOverrideMethods;
ClassInfoGetters;

/// <summary>
/// The FMOD channelgroup/bus this sound routes through.
/// </summary>
enum BusRouting {
SFX = 0, // Default diegetic bus for general game SFX.
UI = 1, // Menu sounds and other things that shouldn't be affected by diegetic sound processing.
MUSIC = 2 // Self-explanatory music bus.
};

/// <summary>
/// How the SoundContainer should behave when it tries to play again while already playing.
/// </summary>
Expand Down Expand Up @@ -48,13 +57,14 @@ namespace RTE {
int Create(const SoundContainer &reference);

/// <summary>
/// Creates a SoundContainer and adds a sound, optionally setting whether it's immobile or affected by global pitch.
/// Creates a SoundContainer and adds a sound, optionally setting immobility, being affected by global pitch, and bus routing.
/// </summary>
/// <param name="soundFilePath">The path to a sound to add to the first SoundSet of this SoundContainer.</param>
/// <param name="immobile">Whether this SoundContainer's sounds will be treated as immobile, i.e. they won't be affected by 3D sound manipulation.</param>
/// <param name="affectedByGlobalPitch">Whether this SoundContainer's sounds' frequency will be affected by the global pitch.</param>
/// <param name="busRouting">Bus to route this sound to.</param>
/// <returns>An error return value signaling success or any particular failure. Anything below 0 is an error signal.</returns>
int Create(const std::string &soundFilePath, bool immobile = false, bool affectedByGlobalPitch = true) { m_TopLevelSoundSet.AddSound(soundFilePath, true); SetImmobile(immobile); SetAffectedByGlobalPitch(affectedByGlobalPitch); return 0; }
int Create(const std::string &soundFilePath, bool immobile = false, bool affectedByGlobalPitch = true, BusRouting busRouting = BusRouting::SFX) { m_TopLevelSoundSet.AddSound(soundFilePath, true); SetImmobile(immobile); SetAffectedByGlobalPitch(affectedByGlobalPitch); SetBusRouting(busRouting); return 0; }
#pragma endregion

#pragma region Destruction
Expand Down Expand Up @@ -157,6 +167,19 @@ namespace RTE {
#pragma endregion

#pragma region Sound Property Getters and Setters

/// <summary>
/// Gets the bus this sound routes to.
/// </summary>
/// <returns>The bus this sound routes to.</returns>
BusRouting GetBusRouting() const { return m_BusRouting; }

/// <summary>
/// Sets the bus this sound routes to.
/// </summary>
/// <param name="newBusRoute">The new bus for this sound to route to.</param>
void SetBusRouting(BusRouting newBusRoute) { m_BusRouting = newBusRoute; }

/// <summary>
/// Gets whether the sounds in this SoundContainer should be considered immobile, i.e. always play at the listener's position.
/// </summary>
Expand All @@ -181,6 +204,18 @@ namespace RTE {
/// <param name="attenuationStartDistance">The new attenuation start distance.</param>
void SetAttenuationStartDistance(float attenuationStartDistance) { m_AttenuationStartDistance = (attenuationStartDistance < 0) ? c_DefaultAttenuationStartDistance : attenuationStartDistance; m_SoundPropertiesUpToDate = false; }

/// <summary>
/// Gets the panning strength multiplier of this SoundContainer.
/// </summary>
/// <returns>A float with the panning strength multiplier.</returns>
float GetPanningStrengthMultiplier() const { return m_PanningStrengthMultiplier; }

/// <summary>
/// Sets the panning strength multiplier of this SoundContainer.
/// </summary>
/// <param name="panningStrengthMultiplier">The new panning strength multiplier.</param>
void SetPanningStrengthMultiplier(float panningStrengthMultiplier) { m_PanningStrengthMultiplier = panningStrengthMultiplier; m_SoundPropertiesUpToDate = false; }

/// <summary>
/// Gets the looping setting of this SoundContainer.
/// </summary>
Expand Down Expand Up @@ -349,14 +384,18 @@ namespace RTE {

static Entity::ClassInfo m_sClass; //!< ClassInfo for this class.
static const std::unordered_map<std::string, SoundOverlapMode> c_SoundOverlapModeMap; //!< A map of strings to SoundOverlapModes to support string parsing for the SoundOverlapMode enum. Populated in the implementing cpp file.

static const std::unordered_map<std::string, BusRouting> c_BusRoutingMap; //!< A map of strings to BusRoutings to support string parsing for the BusRouting enum. Populated in the implementing cpp file.

SoundSet m_TopLevelSoundSet; //The top level SoundSet that handles all SoundData and sub SoundSets in this SoundContainer.

std::unordered_set<int> m_PlayingChannels; //!< The channels this SoundContainer is currently using.
SoundOverlapMode m_SoundOverlapMode; //!< The SoundOverlapMode for this SoundContainer, used to determine how it should handle overlapping play calls.

bool m_Immobile; //!< Whether this SoundContainer's sounds should be treated as immobile, i.e. not affected by 3D sound effects. Mostly used for GUI sounds and the like.

BusRouting m_BusRouting; //!< What bus this sound routes to.

bool m_Immobile; //!< Whether this SoundContainer's sounds should be treated as immobile, i.e. not affected by 3D sound effects.
float m_AttenuationStartDistance; //!< The distance away from the AudioSystem listener to start attenuating this sound. Attenuation follows FMOD 3D Inverse roll-off model.
float m_PanningStrengthMultiplier; //!< Multiplier for panning strength.
int m_Loops; //!< Number of loops (repeats) the SoundContainer's sounds should play when played. 0 means it plays once, -1 means it plays until stopped.
bool m_SoundPropertiesUpToDate = false; //!< Whether this SoundContainer's sounds' modes and properties are up to date. Used primarily to handle discrepancies that can occur when loading from ini if the line ordering isn't ideal.

Expand All @@ -374,4 +413,4 @@ namespace RTE {
void Clear();
};
}
#endif
#endif
54 changes: 27 additions & 27 deletions GUI/GUISound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,74 +39,74 @@ namespace RTE {
void GUISound::Initialize() {
// Interface sounds should not be pitched to reinforce the appearance of time decoupling between simulation and UI.

m_SplashSound.Create("Base.rte/Sounds/GUIs/MetaStart.flac", true, false);
m_SplashSound.Create("Base.rte/Sounds/GUIs/MetaStart.flac", true, false, SoundContainer::BusRouting::UI);

m_EnterMenuSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false);
m_EnterMenuSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false, SoundContainer::BusRouting::UI);

m_ExitMenuSound.Create("Base.rte/Sounds/GUIs/MenuExit1.flac", true, false);
m_ExitMenuSound.Create("Base.rte/Sounds/GUIs/MenuExit1.flac", true, false, SoundContainer::BusRouting::UI);
m_ExitMenuSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/MenuExit2.flac", true);

m_FocusChangeSound.Create("Base.rte/Sounds/GUIs/FocusChange.flac", true, false);
m_FocusChangeSound.Create("Base.rte/Sounds/GUIs/FocusChange.flac", true, false, SoundContainer::BusRouting::UI);

m_SelectionChangeSound.Create("Base.rte/Sounds/GUIs/SelectionChange.flac", true, false);
m_SelectionChangeSound.Create("Base.rte/Sounds/GUIs/SelectionChange.flac", true, false, SoundContainer::BusRouting::UI);

m_ItemChangeSound.Create("Base.rte/Sounds/GUIs/ItemChange.flac", true, false);
m_ItemChangeSound.Create("Base.rte/Sounds/GUIs/ItemChange.flac", true, false, SoundContainer::BusRouting::UI);

m_ButtonPressSound.Create("Base.rte/Sounds/GUIs/ButtonPress.flac", true, false);
m_ButtonPressSound.Create("Base.rte/Sounds/GUIs/ButtonPress.flac", true, false, SoundContainer::BusRouting::UI);

m_BackButtonPressSound.Create("Base.rte/Sounds/GUIs/BackButtonPress.flac", true, false);
m_BackButtonPressSound.Create("Base.rte/Sounds/GUIs/BackButtonPress.flac", true, false, SoundContainer::BusRouting::UI);

m_ConfirmSound.Create("Base.rte/Sounds/GUIs/MenuExit1.flac", true, false);
m_ConfirmSound.Create("Base.rte/Sounds/GUIs/MenuExit1.flac", true, false, SoundContainer::BusRouting::UI);

m_UserErrorSound.Create("Base.rte/Sounds/GUIs/UserError.flac", true, false);
m_UserErrorSound.Create("Base.rte/Sounds/GUIs/UserError.flac", true, false, SoundContainer::BusRouting::UI);

m_TestSound.Create("Base.rte/Sounds/GUIs/Test.flac", true, false);
m_TestSound.Create("Base.rte/Sounds/GUIs/Test.flac", true, false, SoundContainer::BusRouting::UI);

m_PieMenuEnterSound.Create("Base.rte/Sounds/GUIs/PieMenuEnter.flac", true, false);
m_PieMenuEnterSound.Create("Base.rte/Sounds/GUIs/PieMenuEnter.flac", true, false, SoundContainer::BusRouting::UI);

m_PieMenuExitSound.Create("Base.rte/Sounds/GUIs/PieMenuExit.flac", true, false);
m_PieMenuExitSound.Create("Base.rte/Sounds/GUIs/PieMenuExit.flac", true, false, SoundContainer::BusRouting::UI);

// m_HoverChangeSound.Create("Base.rte/Sounds/GUIs/SelectionChange.flac", true, false);
// m_HoverChangeSound.Create("Base.rte/Sounds/GUIs/SelectionChange.flac", true, false, SoundContainer::BusRouting::UI);
m_HoverChangeSound = m_SelectionChangeSound;

m_HoverDisabledSound.Create("Base.rte/Sounds/GUIs/PlacementBlip.flac", true, false);
m_HoverDisabledSound.Create("Base.rte/Sounds/GUIs/PlacementBlip.flac", true, false, SoundContainer::BusRouting::UI);

m_SlicePickedSound.Create("Base.rte/Sounds/GUIs/SlicePicked.flac", true, false);
m_SlicePickedSound.Create("Base.rte/Sounds/GUIs/SlicePicked.flac", true, false, SoundContainer::BusRouting::UI);

// m_DisabledPickedSound.Create("Base.rte/Sounds/GUIs/PieMenuExit.flac", true, false);
// m_DisabledPickedSound.Create("Base.rte/Sounds/GUIs/PieMenuExit.flac", true, false, SoundContainer::BusRouting::UI);
m_DisabledPickedSound = m_PieMenuExitSound;

m_FundsChangedSound.Create("Base.rte/Sounds/GUIs/FundsChanged1.flac", true, false);
m_FundsChangedSound.Create("Base.rte/Sounds/GUIs/FundsChanged1.flac", true, false, SoundContainer::BusRouting::UI);
m_FundsChangedSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/FundsChanged2.flac", true);
m_FundsChangedSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/FundsChanged3.flac", true);
m_FundsChangedSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/FundsChanged4.flac", true);
m_FundsChangedSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/FundsChanged5.flac", true);
m_FundsChangedSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/FundsChanged6.flac", true);
m_FundsChangedSound.SetSoundOverlapMode(SoundContainer::SoundOverlapMode::RESTART);

m_ActorSwitchSound.Create("Base.rte/Sounds/GUIs/ActorSwitch.flac", true, false);
m_ActorSwitchSound.Create("Base.rte/Sounds/GUIs/ActorSwitch.flac", true, false, SoundContainer::BusRouting::UI);

m_BrainSwitchSound.Create("Base.rte/Sounds/GUIs/BrainSwitch.flac", true, false);
m_BrainSwitchSound.Create("Base.rte/Sounds/GUIs/BrainSwitch.flac", true, false, SoundContainer::BusRouting::UI);

m_CameraTravelSound.Create("Base.rte/Sounds/GUIs/CameraTravel1.flac", true, false);
m_CameraTravelSound.Create("Base.rte/Sounds/GUIs/CameraTravel1.flac", true, false, SoundContainer::BusRouting::UI);
m_CameraTravelSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/CameraTravel2.flac", true);
m_CameraTravelSound.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/CameraTravel3.flac", true);

// m_AreaPickedSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false);
// m_AreaPickedSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false, SoundContainer::BusRouting::UI);
m_AreaPickedSound = m_ConfirmSound;

// m_ObjectPickedSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false);
// m_ObjectPickedSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false, SoundContainer::BusRouting::UI);
m_ObjectPickedSound = m_ConfirmSound;

// m_PurchaseMadeSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false);
// m_PurchaseMadeSound.Create("Base.rte/Sounds/GUIs/MenuEnter.flac", true, false, SoundContainer::BusRouting::UI);
m_PurchaseMadeSound = m_ConfirmSound;

m_PlacementBlip.Create("Base.rte/Sounds/GUIs/PlacementBlip.flac", true, false);
m_PlacementBlip.Create("Base.rte/Sounds/GUIs/PlacementBlip.flac", true, false, SoundContainer::BusRouting::UI);

m_PlacementThud.Create("Base.rte/Sounds/GUIs/PlacementThud1.flac", true, false);
m_PlacementThud.Create("Base.rte/Sounds/GUIs/PlacementThud1.flac", true, false, SoundContainer::BusRouting::UI);
m_PlacementThud.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/PlacementThud2.flac", true);

m_PlacementGravel.Create("Base.rte/Sounds/GUIs/PlacementGravel1.flac", true, false);
m_PlacementGravel.Create("Base.rte/Sounds/GUIs/PlacementGravel1.flac", true, false, SoundContainer::BusRouting::UI);
m_PlacementGravel.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/PlacementGravel2.flac", true);
m_PlacementGravel.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/PlacementGravel3.flac", true);
m_PlacementGravel.GetTopLevelSoundSet().AddSound("Base.rte/Sounds/GUIs/PlacementGravel4.flac", true);
Expand Down
10 changes: 9 additions & 1 deletion Lua/LuaBindingsEntities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1366,8 +1366,10 @@ namespace RTE {
.def(luabind::constructor<>())

.property("SoundOverlapMode", &SoundContainer::GetSoundOverlapMode, &SoundContainer::SetSoundOverlapMode)
.property("BusRouting", &SoundContainer::GetBusRouting, &SoundContainer::SetBusRouting)
.property("Immobile", &SoundContainer::IsImmobile, &SoundContainer::SetImmobile)
.property("AttenuationStartDistance", &SoundContainer::GetAttenuationStartDistance, &SoundContainer::SetAttenuationStartDistance)
.property("PanningStrengthMultiplier", &SoundContainer::GetPanningStrengthMultiplier, &SoundContainer::SetPanningStrengthMultiplier)
.property("Loops", &SoundContainer::GetLoopSetting, &SoundContainer::SetLoopSetting)
.property("Priority", &SoundContainer::GetPriority, &SoundContainer::SetPriority)
.property("AffectedByGlobalPitch", &SoundContainer::IsAffectedByGlobalPitch, &SoundContainer::SetAffectedByGlobalPitch)
Expand All @@ -1389,7 +1391,13 @@ namespace RTE {
.def("Restart", (bool (SoundContainer:: *)()) &SoundContainer::Restart)
.def("Restart", (bool (SoundContainer:: *)(int player)) &SoundContainer::Restart)
.def("FadeOut", &SoundContainer::FadeOut)


.enum_("BusRouting")[
luabind::value("SFX", SoundContainer::BusRouting::SFX),
luabind::value("UI", SoundContainer::BusRouting::UI),
luabind::value("MUSIC", SoundContainer::BusRouting::MUSIC)
]

.enum_("SoundOverlapMode")[
luabind::value("OVERLAP", SoundContainer::SoundOverlapMode::OVERLAP),
luabind::value("RESTART", SoundContainer::SoundOverlapMode::RESTART),
Expand Down
Loading