Modding sound
Tutorials
- Forum post: Modding Sound & Music
- Audio Modding Guide for Bannerlord
- Oficial guide - Adding Custom Sounds
- Lesser Scholar Youtube Tutorial Ep 12: Sound Effects
- Bannerlord Music Modding Tutorial : Pt. 1 by SixxTailsHD
Resources
Various
Folder structure
In ...\Modules\*YOUR_MOD*\
\ModuleData\module_sounds.xml - custom definitions for our own sounds
\ModuleData\project.mbproj - tells engine to include our module_sounds.xml
\ModuleSounds - our audio files (.ogg, .wav)
File format
module_sound.xml
<?xml version="1.0" encoding="utf-8"?>
<base xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="module_sound">
<module_sounds>
<module_sound name="YOUR_SOUND_ID" is_2d="true" sound_category="ui" path="SOUND_FILE_NAME.WAV|OGG" />
</module_sounds>
</base>
Pitch
Can set pitch for the sound like this: min_pitch_multiplier="0.9" max_pitch_multiplier="1.1"
If values are different - random value between min-max is selected. If you need constant pitch - make both values equal.
project.mbproj
<?xml version="1.0" encoding="utf-8"?>
<base xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" type="solution">
<file id="soln_module_sound" name="ModuleData/module_sounds.xml" type="module_sound" />
</base>
Sound Categories
From: \Native\ModuleData\module_sounds.xml
Category | Duration | Comment |
---|---|---|
mission_ambient_bed | persistent | General scene ambient |
mission_ambient_3d_big | persistent | Loud ambient sounds e.g. barn fire |
mission_ambient_3d_medium | persistent | Common ambient sounds e.g. fireplace |
mission_ambient_3d_small | persistent | Quiet ambient sounds e.g. torch |
mission_material_impact | max 4 sec. | Weapon clash kind of sounds |
mission_combat_trivial | max 2 sec. | Damage extra foley like armor layer |
mission_combat | max 8 sec. | Damage or bow release sounds |
mission_foley | max 4 sec. | Other kind of motion fillers |
mission_voice_shout | max 8 sec. | War cries, shouted orders, horse whinnies etc. |
mission_voice | max 4 sec. | Grunts, exertions etc. |
mission_voice_trivial | max 4 sec. | Small exertions like jumping |
mission_siege_loud | max 8 sec. | Big explosions, destructions |
mission_footstep | max 1 sec. | Human footsteps, foley |
mission_footstep_run | max 1 sec. | Human running (contributes to BASS) |
mission_horse_gallop | max 2 sec. | Loud mount gallop footsteps (contributes to BASS) |
mission_horse_walk | max 1 sec. | Quiet mount footsteps, walk |
ui | max 4 sec. | All UI sounds |
alert | max 10 sec. | Pseudo-3D in-mission alerts like warning bells |
campaign_node | persistent | Campaign map point sound like rivers etc. |
campaign_bed | persistent | Campaign amboent bed |
- Sounds that dont have valid categories wont be played!
How to play a sound
Read sound index
Several ways to play a sound
Worked ok:
Did not work:
Works:
SoundEvent eventRef = SoundEvent.CreateEvent(soundIndex, mission.Scene);
eventRef.SetPosition(_mission.MainAgent.Position);
eventRef.Play();
is2D option
Setting is2D to true makes it play at a constant volume throughout the entire scene.
is_2d="true" in XML
Sound in menus
args.MenuContext.SetPanelSound("event:/ui/panels/settlement_village"); // one-time sound on opening
args.MenuContext.SetAmbientSound("event:/map/ambient/node/settlements/2d/village");
Why does not work on wait-type menus?
Sound Events
hunharibo: You don't need the event prefix. Only vanilla sounds use 'event:' to identify sounds in the fmod middleware. Since fmod is a licensed proprietary library, modders cant use that
Possible to play sound (events) with InformationManager.AddQuickInformation(text, 0, null, string SoundEventPath)
You need to null the SoundEvent when you leave the mission.
Same with MultiSelectionInquiryData and in the menus
In GauntletUI: HandleQueryCreated played with SoundEvent.PlaySound2D(soundEventPath);
soundEventPath looks like:
"event:/ui/notification/levelup"
Defined in [GAME_FOLDER]\Sounds\GUIDs.txt
event:/ui list
event:/ui/campaign/autobattle_defeat
event:/ui/campaign/autobattle_large
event:/ui/campaign/autobattle_small
event:/ui/campaign/click_party
event:/ui/campaign/click_party_enemy
event:/ui/campaign/click_settlement
event:/ui/campaign/click_settlement_enemy
event:/ui/campaign/focus
event:/ui/checkbox
event:/ui/choice
event:/ui/conversation
event:/ui/crafting/craft_success
event:/ui/crafting/craft_tab
event:/ui/crafting/randomize
event:/ui/crafting/refine_success
event:/ui/crafting/refine_tab
event:/ui/crafting/smelt_success
event:/ui/crafting/smelt_tab
event:/ui/cutscenes/child_coming_of_age
event:/ui/cutscenes/child_newborn
event:/ui/cutscenes/child_newborn_mother
event:/ui/cutscenes/child_newborn_orphan
event:/ui/cutscenes/death_clan
event:/ui/cutscenes/death_clan_war
event:/ui/cutscenes/death_old_age
event:/ui/cutscenes/death_war_defeat
event:/ui/cutscenes/death_war_victory
event:/ui/cutscenes/execution
event:/ui/cutscenes/execution_female
event:/ui/cutscenes/kingdom_created
event:/ui/cutscenes/kingdom_destroyed
event:/ui/cutscenes/kingdom_join
event:/ui/cutscenes/marriage
event:/ui/cutscenes/story_banner_assemble_part_01
event:/ui/cutscenes/story_banner_assemble_part_02
event:/ui/cutscenes/story_banner_find_01
event:/ui/cutscenes/story_banner_find_02
event:/ui/cutscenes/story_become_king
event:/ui/cutscenes/story_claim
event:/ui/cutscenes/story_conspiracy_activate
event:/ui/cutscenes/story_conspiracy_start
event:/ui/cutscenes/story_defeat
event:/ui/cutscenes/story_empire_destroyed
event:/ui/cutscenes/story_empire_united
event:/ui/cutscenes/story_pledge_support
event:/ui/cutscenes/story_pledge_support_nonempire
event:/ui/default
event:/ui/dropdown
event:/ui/empty
event:/ui/endgame/end_clan_destroyed
event:/ui/endgame/end_retirement
event:/ui/endgame/end_victory
event:/ui/endgame/tab_battles
event:/ui/endgame/tab_companions
event:/ui/endgame/tab_crafting
event:/ui/endgame/tab_finances
event:/ui/endgame/tab_general
event:/ui/filter
event:/ui/inspect
event:/ui/inventory/animal
event:/ui/inventory/animal_butcher
event:/ui/inventory/bow
event:/ui/inventory/crossbow
event:/ui/inventory/helmet
event:/ui/inventory/horse
event:/ui/inventory/horsearmor
event:/ui/inventory/leather
event:/ui/inventory/leather_lite
event:/ui/inventory/onehanded
event:/ui/inventory/perk
event:/ui/inventory/pickup
event:/ui/inventory/polearm
event:/ui/inventory/quiver
event:/ui/inventory/sack
event:/ui/inventory/shield
event:/ui/inventory/take_all
event:/ui/inventory/throwing
event:/ui/inventory/twohanded
event:/ui/inventory/unit
event:/ui/item_close
event:/ui/item_open
event:/ui/mini_popup
event:/ui/mission/arena_victory
event:/ui/mission/ballista
event:/ui/mission/batteringram
event:/ui/mission/catapult
event:/ui/mission/deploy
event:/ui/mission/drinks
event:/ui/mission/emptyslot
event:/ui/mission/horns/attack
event:/ui/mission/horns/move
event:/ui/mission/horns/retreat
event:/ui/mission/multiplayer/changeclass
event:/ui/mission/multiplayer/defeat
event:/ui/mission/multiplayer/gamestart
event:/ui/mission/multiplayer/lastmanstanding
event:/ui/mission/multiplayer/pointcapture
event:/ui/mission/multiplayer/pointlost
event:/ui/mission/multiplayer/pointsremoved
event:/ui/mission/multiplayer/pointwarning
event:/ui/mission/multiplayer/roundstart
event:/ui/mission/multiplayer/victory
event:/ui/mission/siege_engine
event:/ui/mission/siegetower
event:/ui/multiplayer/click_class
event:/ui/multiplayer/click_culture
event:/ui/multiplayer/click_item
event:/ui/multiplayer/click_purchase
event:/ui/multiplayer/coin_add
event:/ui/multiplayer/levelup
event:/ui/multiplayer/match_ready
event:/ui/multiplayer/pick_sigil
event:/ui/multiplayer/shop_purchase
event:/ui/multiplayer/shop_purchase_complete
event:/ui/multiplayer/shop_purchase_proceed
event:/ui/multiplayer/wear_armor_big
event:/ui/multiplayer/wear_armor_small
event:/ui/multiplayer/wear_generic
event:/ui/multiplayer/wear_helmet
event:/ui/multiplayer/xpbar
event:/ui/multiplayer/xpbar_stop
event:/ui/notification/alert
event:/ui/notification/army_created
event:/ui/notification/army_dispersion
event:/ui/notification/child_born
event:/ui/notification/coins_negative
event:/ui/notification/coins_positive
event:/ui/notification/death
event:/ui/notification/education
event:/ui/notification/hideout_found
event:/ui/notification/kingdom_decision
event:/ui/notification/levelup
event:/ui/notification/marriage
event:/ui/notification/peace
event:/ui/notification/peace_offer
event:/ui/notification/quest_fail
event:/ui/notification/quest_finished
event:/ui/notification/quest_start
event:/ui/notification/quest_update
event:/ui/notification/ransom_offer
event:/ui/notification/relation
event:/ui/notification/settlement_owner_change
event:/ui/notification/settlement_rebellion
event:/ui/notification/settlement_under_siege
event:/ui/notification/tournament_end
event:/ui/notification/trait_change
event:/ui/notification/truce
event:/ui/notification/unlock
event:/ui/notification/war_declared
event:/ui/oob/dropdown
event:/ui/oob/formation_archers
event:/ui/oob/formation_cavalry
event:/ui/oob/formation_cavalry_horse_archers
event:/ui/oob/formation_horse_archers
event:/ui/oob/formation_infantry
event:/ui/oob/formation_infantry_archers
event:/ui/oob/officer_drag
event:/ui/oob/officer_pick
event:/ui/panels/battle/attack_large
event:/ui/panels/battle/attack_medium
event:/ui/panels/battle/attack_small
event:/ui/panels/battle/encounter_attacker
event:/ui/panels/battle/encounter_defender
event:/ui/panels/battle/retreat
event:/ui/panels/battle/slide_in
event:/ui/panels/encyclopedia_open
event:/ui/panels/encyclopedia_page
event:/ui/panels/next
event:/ui/panels/panel_character_open
event:/ui/panels/panel_clan_open
event:/ui/panels/panel_inventory_open
event:/ui/panels/panel_kingdom_open
event:/ui/panels/panel_party_open
event:/ui/panels/panel_quest_open
event:/ui/panels/panel_trading_loop
event:/ui/panels/perk
event:/ui/panels/previous
event:/ui/panels/recruit
event:/ui/panels/scoreboard_flags
event:/ui/panels/scoreboard_skill
event:/ui/panels/scoreboard_tournament
event:/ui/panels/settlement_city
event:/ui/panels/settlement_hideout
event:/ui/panels/settlement_keep
event:/ui/panels/settlement_village
event:/ui/panels/siege/besiege
event:/ui/panels/siege/engine_build
event:/ui/panels/siege/engine_build_complete
event:/ui/panels/siege/engine_click
event:/ui/panels/siege/lead_assault
event:/ui/panels/siege/raid
event:/ui/panels/siege/sally_out
event:/ui/panels/skill_add
event:/ui/panels/skill_new
event:/ui/panels/tutorial
event:/ui/panels/twopanel_open
event:/ui/panels/upgrade
event:/ui/party/recruit prisoner
event:/ui/party/recruit_all
event:/ui/party/upgrade
event:/ui/persuasion/critical_fail
event:/ui/persuasion/critical_success
event:/ui/persuasion/ineffective
event:/ui/persuasion/success
event:/ui/popup
event:/ui/reign/decision
event:/ui/reign/vote
event:/ui/sort
event:/ui/stealth/stealth_notification
event:/ui/tab
event:/ui/transfer
Some info about events
- the event:/ derives from the FMOD event paths that the game uses to play sound events
- however when we mod the game’s audio we don’t have access to the FMOD project and their events, so we have to replace entire events with singular oneshots
- the event:/ is part of an event path
- for example, “event:/Ambience/Rain” would be an event called Rain located in a folder called Ambience
Sound examples
ui/mission sounds
ui/multiplayer sounds
ui/notification sounds
alerts sounds
Issues with sound volume using SoundEvent
Windwhistle: My findings so far:
When using SoundEvent to play a custom sound in module_sounds.xml from the sound category "mission_ambient_3d_small" and "mission_ambient_3d_medium", if the attribute is_2d is set to false, the volume of the sound at max volume (close to the sound source) is directly related to the distance at which it was spawned to the player.
So, if you set the position of the SoundEvent FARTHER from the player, it will be FAINTER at MAX volume. If you set the position of the SoundEvent CLOSER to the player, it will be LOUDER at MAX volume.
It draws confusion, as I do not mean that the sound becomes fainter as you travel farther away from it. That's normal. I am not talking about that. There's some videos if you scroll up that depict what I am talking about, because it's pretty hard to describe.
To fix this: I just set is_2d to true instead of false, and the sound plays how I wanted it to play.
Why: I don't know. I'm going to test it out in a regular scene/mission to see if it's my code somehow, or if it's a genuine bug. It might not even be a bug but intentional, but it'd be weird if it were.
EDIT: it also suffers from the same problem in a regular scene. Setting the position repeatedly on tick also does not alleviate the issue. Making the file format .wav also did not help.
Delayed stop to prevent ambient sounds from looping
private async void DelayedStop(SoundEvent eventRef)
{
await Task.Delay(soundDuration);
eventRef.Stop();
}
How to get sound duration?
Stop looping example
Short sounds for agents in Mission
agent.MakeVoice(SkinVoiceManager.VoiceType.Grunt, SkinVoiceManager.CombatVoiceNetworkPredictionType.NoPrediction);
SkinVoiceManager.VoiceType examples
- Grunt - eeh
- Jump - yah
- Yell - eyah, hey, yeee
- Pain - uh, mhmph
- Death - uuuuhmmm
- Stun - yyy, aaaa, eeee
- Fear - yaaaaah, aaaah
- Climb - ?
- Focus - (silence?)
- Debacle - aaaaaaah, shriek/screech
- and many others...
Custom Music
Create your own soundtrack.xml in YOURMOD/music/soundtrack.xml
using System;
using HarmonyLib;
using psai.net;
using TaleWorlds.Engine;
using TaleWorlds.MountAndBlade;
using TaleWorlds.ModuleManager;
namespace Patches
{
[HarmonyPatch]
public static class MBMusicManagerPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(MBMusicManager), "Initialize")]
public static void OverrideCreation()
{
if (!NativeConfig.DisableSound)
{
string corePath = ModuleHelper.GetModuleFullPath("YOURMOD") + "music/soundtrack.xml";
PsaiCore.Instance.LoadSoundtrackFromProjectFile(corePath);
}
}
// this will only work if you have custom audio? with ID 401. By default comment this out to play default menu music
[HarmonyPrefix]
[HarmonyPatch(typeof(MBMusicManager), "ActivateMenuMode")]
public static bool UseTowMenuMusicId(ref MBMusicManager __instance)
{
Random rnd = new Random();
int index = rnd.Next(401, 401);
typeof(MBMusicManager).GetProperty("CurrentMode").SetValue(__instance, MusicMode.Menu);
PsaiCore.Instance.MenuModeEnter(index, 0.5f);
return false;
}
}
}
In your MOD/music/soundtrack.xml you can reference native audio by adding ..\..\..\..\music\PC\ in their <Path>
Original:
<Path>MBII_Campaign_Maintheme_Variation.wav</Path>
After the patch:
<Path>..\..\..\..\music\PC\MBII_Campaign_Maintheme_Variation.wav</Path>
Custom Music in Taverns
By culture:
Sandbox\ModuleData\settlement_tracks.xml
Custom Music in Main Menu
Source here
- Find the music
- Convert to OGG
- In \Mount & Blade II Bannerlord\music\Soundtrack.XML find Maintheme and under it: <TotalLengthInSamples>
-
Change the number to your sample length using:
or multiply 44305 X {Your song length in seconds}
-
Backup \Mount & Blade II Bannerlord\music\PC\Maintheme.ogg
- Rename your music ogg file to Maintheme.ogg and place it in \Mount & Blade II Bannerlord\music\PC\
Custom Music in Culture selection menu
Requires patching:
public class CharacterCreationCultureStageView : CharacterCreationStageViewBase
{
.....
private void OnCultureSelected(CultureObject culture)
{
MissionSoundParametersView.SoundParameterMissionCulture soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.None;
if (culture.StringId == "aserai")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Aserai;
}
else if (culture.StringId == "khuzait")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Khuzait;
}
else if (culture.StringId == "vlandia")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Vlandia;
}
else if (culture.StringId == "sturgia")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Sturgia;
}
else if (culture.StringId == "battania")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Battania;
}
else if (culture.StringId == "empire")
{
soundParameterMissionCulture = MissionSoundParametersView.SoundParameterMissionCulture.Empire;
}
SoundManager.SetGlobalParameter("MissionCulture",(float)soundParameterMissionCulture);
}
...
}
More info by -Mr. E-
Game Sound Settings
Get/set Sound/Music Volume:
NativeOptions.GetConfig(NativeOptions.NativeOptionsType.SoundVolume)
NativeOptions.GetConfig(NativeOptions.NativeOptionsType.MusicVolume)
NativeOptions.SetConfig(NativeOptions.NativeOptionsType.SoundVolume, _soundVolume);
NativeOptions.SetConfig(NativeOptions.NativeOptionsType.MusicVolume, _musicVolume);
Manage Game Music
Stop the music with fade-out:
Continue playing the music:
Extract native sounds
Use Fmod Bank Tools
NAudio
Artem:
This is for everyone who tries to add sounds to the game and is annoyed by the game restrictions, like sounds being too short, too quiet, etc.
https://github.com/naudio/NAudio
Install NAudio
RMB on your project:
Example Play Audio method
using NAudio.Wave;
public static async void PlayNAudioSound(string audioFileName, double soundLevel = 1.0, double musicLevel = 1.0, bool soundLevelByNativeMusic = false, bool fadeOutNativeMusic = false)
{
if (audioFileName.Length == 0) return;
string _audioLocation;
AudioFileReader _audioFile;
WaveOutEvent _outputDevice = _outputDevice = new WaveOutEvent();
string fullName = Directory.GetParent(Directory.GetParent(Directory.GetParent("YOUR_MODULE").FullName).FullName).FullName;
_audioLocation = System.IO.Path.Combine(fullName, "Modules\\YOUR_MODULE\\ModuleSounds\\General\\");
try
{
_audioFile = new AudioFileReader(_audioLocation + audioFileName + ".wav");
// set sound level by native sound or music level
if (soundLevelByNativeMusic)
{
_audioFile.Volume = NativeOptions.GetConfig(NativeOptions.NativeOptionsType.MusicVolume) * (float)musicLevel;
}
else
{
_audioFile.Volume = NativeOptions.GetConfig(NativeOptions.NativeOptionsType.SoundVolume) * (float)soundLevel;
}
// take into account Master Volume level
_audioFile.Volume = _audioFile.Volume * NativeOptions.GetConfig(NativeOptions.NativeOptionsType.MasterVolume);
_outputDevice.Init(_audioFile);
if (fadeOutNativeMusic) PsaiCore.Instance.StopMusic(true, 1.5f); // stop native Music with fade-out
_audioFile.Seek(0L, SeekOrigin.Begin);
_outputDevice.Play();
// calculate the duration of the audio
// Get the total number of samples in the file
long totalSamples = _audioFile.Length / (_audioFile.WaveFormat.BitsPerSample / 8 * _audioFile.WaveFormat.Channels);
// Calculate the total duration in milliseconds
double durationMs = (double)totalSamples / _audioFile.WaveFormat.SampleRate * 1000;
await Task.Delay((int)durationMs); // wait till audio stops playing
if (fadeOutNativeMusic) PsaiCore.Instance.ReturnToLastBasicMood(true); // restore native Music
// cleanup
if (_outputDevice != null)
{
_outputDevice.Stop();
_outputDevice.Dispose();
}
}
catch
{
// ERROR ($"ERROR: can't play NAudio sound: {audioFileName}. Missing folders/files? " + _audioLocation);
}
}
Implementation example: Artem's Assassination Mod
hunharibo: I expanded on the NAudio integration idea a lot in TOR. Made a MixingSampleProvider so you can actually play multiple sound sources at the same time if you wanted to. It only takes 48khz stereo vorbis files though. LINK. Also has rudimentary 3d spatial sound with stereo panning and distance attenuation
OGG for me worked only with NAudio v2.0.0 (crash otherwise)
(VS) Tools - NuGet Package Manager - Package Manager Console
Install-Package NAudio -Version 2.0.0
CRASH:
Type: System.IO.FileNotFoundException
Message: Could not load file or assembly 'NAudio.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41' or one of its dependencies. The system cannot find the file specified.