Skip to content

Modding sound

Tutorials

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

int soundIndex = SoundEvent.GetEventIdFromString("YOUR_SOUND_ID");

Several ways to play a sound

Worked ok:

mission.MakeSound(soundIndex, Vec3.Zero, false, true, -1, -1);

Did not work:

SoundEvent.PlaySound2D(soundIndex);

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");
args.MenuContext.SetAmbientSound("event:/map/ambient/node/settlements/2d/village");

Sound Events

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

SoundEvent.PlaySound2D("event:/ui/notification/quest_fail");

soundEventPath looks like:

"event:/ui/notification/levelup"

Defined in [GAME_FOLDER]\Sounds\GUIDs.txt

soundEventPath examples

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/hideout_found
event:/ui/notification/kingdom_decision
event:/ui/notification/levelup
event:/ui/notification/marriage
event:/ui/notification/peace
event:/ui/notification/quest_fail
event:/ui/notification/quest_finished
event:/ui/notification/quest_start
event:/ui/notification/quest_update
event:/ui/notification/relation

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.

Source

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
SoundEvent eventRef = SoundEvent.CreateEvent(SoundEvent.GetEventIdFromString(SoundEffectOnUse), Scene);
eventRef.SetPosition(GameEntity.GetGlobalFrame().origin);
eventRef.Play();
DelayedStop(eventRef); // used to prevent ambient sounds from looping

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
{
    public static class YOURMODModulePath
    {
        public static string YOURMODROOTPath
        {
            get
            {
                return ModuleHelper.GetModuleFullPath("YOURMOD");
            }
        }
    }

    [HarmonyPatch]
    public static class MBMusicManagerPatches
    {
        [HarmonyPrefix]
        [HarmonyPatch(typeof(MBMusicManager), "Initialize")]
        public static void OverrideCreation()
        {
            if (!NativeConfig.DisableSound)
            {
                string corePath = YOURMODModulePath.YOURMODROOTPath + "music/soundtrack.xml";
                PsaiCore.Instance.LoadSoundtrackFromProjectFile(corePath);
            }
        }

        [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;
        }
    }
}

Custom Music in Taverns

By culture:

Sandbox\ModuleData\settlement_tracks.xml

Custom Music in Main Menu

Source here

  1. Find the music
  2. Convert to OGG
  3. In \Mount & Blade II Bannerlord\music\Soundtrack.XML find Maintheme and under it: <TotalLengthInSamples>
  4. Change the number to your sample length using:

    or multiply 44305 X {Your song length in seconds}

  5. Backup \Mount & Blade II Bannerlord\music\PC\Maintheme.ogg

  6. Rename your music ogg file to Maintheme.ogg and place it in \Mount & Blade II Bannerlord\music\PC\

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:

PsaiCore.Instance.StopMusic(true, 1.5f);

Continue playing the music:

PsaiCore.Instance.ReturnToLastBasicMood(true);

NAudio

Install NAudio

RMB on your project:

Quote

Artem: For everyone that tries to add sounds to the game and is annoyed by the games restrictions, like sounds being too short, too quiet etc.

https://github.com/naudio/NAudio

using NAudio.Wave;

private AudioFileReader audioFile;

public static async void PlayCustomSound()
{

    if (this.outputDevice == null) this.outputDevice = new WaveOutEvent();

    try
    {
        //string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        string fullName = Directory.GetParent(Directory.GetParent(Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName).FullName).FullName;
        this.audioLocation = System.IO.Path.Combine(fullName, "ModuleSounds\\");
        this.audioFile = new AudioFileReader(this.audioLocation + "encounter_river_pirates_9.wav");
        this.audioFile.Volume = NativeOptions.GetConfig(NativeOptions.NativeOptionsType.SoundVolume);
        this.outputDevice.Init(this.audioFile);

        PsaiCore.Instance.StopMusic(true, 1.5f); // stop native Music with fade-out

        this.audioFile.Seek(0L, SeekOrigin.Begin);
        this.outputDevice.Play();

        // restore native Music
        await Task.Delay(4500); // change 4500 to the length of your sound file in ms
        PsaiCore.Instance.ReturnToLastBasicMood(true);

    }
    catch {
        // "ERROR: can't play custom sound. Missing folders/files? "
    }
}

// stop the sound
if (outputDevice != null)
{
    outputDevice.Stop();
    outputDevice.Dispose();
}

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