Skip to content

These docs were made completely by AI, so they might be right, or wrong, you'll need to test them yourself. This was made for a easier understanding of everything. So use at your own risk. If anything is wrong, please don't hurt to make a PR on the page you have a problem with. ON GITHUB

Custom Sounds & Music

LCE uses the Miles Sound System (MSS) for all audio on console platforms. Sounds are stored in a compiled soundbank (Minecraft.msscmp), and music tracks are streamed from .binka files on disk. The engine supports full 3D positional audio, distance-based falloff, splitscreen listener handling, and per-domain music selection.

This guide covers the whole audio pipeline, from how the sound system boots up to how you add your own custom sounds and music tracks.

There are three layers to understand:

  1. ConsoleSoundEngine (Minecraft.Client/Common/Audio/Consoles_SoundEngine.h) is the abstract base class. It defines the interface that the rest of the game talks to.
  2. SoundEngine (Minecraft.Client/Common/Audio/SoundEngine.h) is the concrete implementation. It owns the Miles driver, the soundbank handle, and the streaming state machine.
  3. SoundTypes.h (Minecraft.World/SoundTypes.h) defines the eSOUND_TYPE enum that the world/entity code uses to refer to sounds by integer ID.

The game code never touches Miles directly. It calls Level::playSound() or Entity::playSound(), which go through LevelListener and eventually reach SoundEngine::play() or SoundEngine::playUI().

Entity/Level code
|
v
LevelListener::playSound()
|
v
ServerLevelListener --> LevelSoundPacket (network)
|
v
SoundEngine::play() -- 3D positioned sound
SoundEngine::playUI() -- non-positional UI sound
SoundEngine::playStreaming() -- music / jukebox

Every sound in the game has an entry in the eSOUND_TYPE enum in SoundTypes.h. The enum values map 1:1 to event names in the Miles soundbank via the wchSoundNames[] array in SoundNames.cpp.

For example:

SoundTypes.h
enum eSOUND_TYPE
{
eSoundType_MOB_ZOMBIE_AMBIENT, // enum value = some integer
eSoundType_MOB_ZOMBIE_HURT,
eSoundType_MOB_ZOMBIE_DEATH,
// ...
};
SoundNames.cpp
const WCHAR *ConsoleSoundEngine::wchSoundNames[eSoundType_MAX] = {
// ...
L"mob.zombie", // eSoundType_MOB_ZOMBIE_AMBIENT
L"mob.zombiehurt", // eSoundType_MOB_ZOMBIE_HURT
L"mob.zombiedeath", // eSoundType_MOB_ZOMBIE_DEATH
// ...
};

When SoundEngine::play() fires, it builds a Miles event name like "Minecraft/mob.zombie" from the string and enqueues it to the event system.

Sounds are grouped by naming prefix. Here’s every category:

Ambient, hurt, death, and step sounds for every mob. Examples:

EnumString nameUsed by
eSoundType_MOB_ZOMBIE_AMBIENTmob.zombieZombie idle
eSoundType_MOB_ZOMBIE_HURTmob.zombiehurtZombie taking damage
eSoundType_MOB_ZOMBIE_DEATHmob.zombiedeathZombie dying
eSoundType_MOB_CREEPER_HURTmob.creeperCreeper hurt
eSoundType_MOB_WOLF_BARKmob.wolf.barkWolf barking
eSoundType_MOB_ENDERDRAGON_GROWLmob.enderdragon.growlEnder Dragon
eSoundType_MOB_HORSE_GALLOPmob.horse.gallopHorse running

Step sounds play when walking on blocks. Dig sounds play when breaking them. Tile sounds are for specific block interactions like pistons.

EnumString nameUsed for
eSoundType_STEP_STONEstep.stoneWalking on stone
eSoundType_STEP_WOODstep.woodWalking on wood
eSoundType_DIG_GRASSdig.grassBreaking grass blocks
eSoundType_DIG_STONEdig.stoneBreaking stone
eSoundType_TILE_PISTON_INtile.piston.inPiston retracting
eSoundType_TILE_PISTON_OUTtile.piston.outPiston extending

General gameplay sounds like explosions, UI clicks, chests, doors:

EnumString nameUsed for
eSoundType_RANDOM_EXPLODErandom.explodeExplosions
eSoundType_RANDOM_BOWrandom.bowFiring a bow
eSoundType_RANDOM_CHEST_OPENrandom.chestopenOpening a chest
eSoundType_RANDOM_DOOR_OPENrandom.door_openOpening a door
eSoundType_RANDOM_CLICKrandom.clickButtons, levers
eSoundType_RANDOM_GLASSrandom.glassBreaking glass
eSoundType_RANDOM_ANVIL_USErandom.anvil_useUsing an anvil

Weather and cave ambience:

EnumString name
eSoundType_AMBIENT_WEATHER_RAINambient.weather.rain
eSoundType_AMBIENT_WEATHER_THUNDERambient.weather.thunder
eSoundType_AMBIENT_CAVE_CAVEambient.cave.cave
PrefixExamplesPurpose
portal.*portal.portal, portal.trigger, portal.travelPortal effects
fire.*fire.ignite, fire.fireFire sounds
damage.*damage.hurtflesh, damage.fallsmall, damage.fallbigPlayer damage
note.*note.harp, note.bd, note.snare, note.hat, note.bassattackNote blocks
liquid.*liquid.water, liquid.lava, liquid.lavapopLiquid sounds
fireworks.*fireworks.launch, fireworks.blast, fireworks.twinkleFirework effects

UI sounds are a separate, smaller enum:

enum ESoundEffect
{
eSFX_Back, // "back"
eSFX_Craft, // "craft"
eSFX_CraftFail, // "craftfail"
eSFX_Focus, // "focus"
eSFX_Press, // "press"
eSFX_Scroll, // "scroll"
eSFX_MAX
};

These get played through SoundEngine::playUI() and are routed to the "Minecraft/UI/" event path in the soundbank.

To add a brand new sound, you need to touch three files. Here’s the full process.

In Minecraft.World/SoundTypes.h, add your new entry. Put it right before eSoundType_MAX:

SoundTypes.h
enum eSOUND_TYPE
{
// ... existing sounds ...
eSoundType_RANDOM_LEVELUP,
eSoundType_FIRE_NEWIGNITE,
// your new sound
eSoundType_MOB_CUSTOM_AMBIENT,
eSoundType_MAX
};

In Minecraft.Client/Common/Audio/SoundNames.cpp, add the matching string at the same position:

const WCHAR *ConsoleSoundEngine::wchSoundNames[eSoundType_MAX] = {
// ... must match enum order exactly ...
L"random.levelup",
L"fire.newignite",
// your new sound
L"mob.custom.ambient",
};

The audio data itself lives in Minecraft.msscmp, which is a compiled Miles soundbank. You need to add a new event named Minecraft/mob.custom.ambient to the bank using the Miles Sound Studio tools. The event name must match the string you put in wchSoundNames[] (with dots converted to slashes).

The ConvertSoundPathToName() function handles the conversion from the dot-separated wide string to the slash-separated event name that Miles expects.

Every mob inherits from Mob, which has three virtual methods for its core sounds:

class Mob : public LivingEntity {
protected:
virtual int getAmbientSound(); // returns -1 by default (no sound)
virtual int getHurtSound(); // returns eSoundType_DAMAGE_HURT
virtual int getDeathSound(); // returns eSoundType_DAMAGE_HURT
virtual float getSoundVolume(); // how loud this mob is
};

Override them in your mob class to give it custom sounds:

Zombie.cpp
int Zombie::getAmbientSound()
{
return eSoundType_MOB_ZOMBIE_AMBIENT;
}
int Zombie::getHurtSound()
{
return eSoundType_MOB_ZOMBIE_HURT;
}
int Zombie::getDeathSound()
{
return eSoundType_MOB_ZOMBIE_DEATH;
}

The engine calls Mob::playAmbientSound() automatically on a timer. Every getAmbientSoundInterval() ticks (default: 80 ticks, or 4 seconds), it does this:

void Mob::playAmbientSound()
{
int ambient = getAmbientSound();
if (ambient != -1)
{
level->playSound(shared_from_this(), ambient, getSoundVolume(), getVoicePitch());
}
}

Hurt and death sounds are played automatically in Mob::hurt():

// inside Mob::hurt()
if (health <= 0)
{
level->playSound(shared_from_this(), getDeathSound(), getSoundVolume(), getVoicePitch());
die(source);
}
else
{
level->playSound(shared_from_this(), getHurtSound(), getSoundVolume(), getVoicePitch());
}

The pitch gets randomized by getVoicePitch(), and baby mobs automatically get a higher pitch.

Each block type has a SoundType that determines what plays when you walk on it, break it, or place it:

class Tile {
public:
class SoundType {
public:
eMATERIALSOUND_TYPE eMaterialSound;
int iBreakSound, iStepSound, iPlaceSound;
float volume;
float pitch;
int getBreakSound() const;
int getStepSound() const;
int getPlaceSound() const;
};
const SoundType *soundType; // every tile has one
// predefined sound types
static SoundType *SOUND_STONE;
static SoundType *SOUND_WOOD;
static SoundType *SOUND_GRAVEL;
static SoundType *SOUND_GRASS;
static SoundType *SOUND_METAL;
static SoundType *SOUND_GLASS;
static SoundType *SOUND_CLOTH;
static SoundType *SOUND_SAND;
static SoundType *SOUND_SNOW;
static SoundType *SOUND_LADDER;
static SoundType *SOUND_ANVIL;
};

You assign a sound type when creating a block using the builder chain:

// In Tile::initTiles()
Tile::goldBlock = (new MetalTile(41))
->setDestroyTime(3.0f)
->setExplodeable(10)
->setSoundType(Tile::SOUND_METAL)
->setTextureName(L"blockGold");

The SoundType constructor auto-maps the material to the correct step, dig, and place sounds. For example, eMaterialSoundType_STONE maps step to eSoundType_STEP_STONE and break to eSoundType_DIG_STONE. You can override the break and place sounds:

// Glass has a custom break sound but normal stone step sound
Tile::SOUND_GLASS = new Tile::SoundType(
eMaterialSoundType_STONE, // material type
1, // volume
1, // pitch
eSoundType_RANDOM_GLASS, // custom break sound
eSoundType_STEP_STONE // custom place sound
);

To play a sound at a specific position in the world:

// Play an explosion sound at coordinates (x, y, z)
level->playSound(100.0, 64.0, 200.0, eSoundType_RANDOM_EXPLODE, 1.0f, 1.0f);

The full signature:

void Level::playSound(double x, double y, double z, int iSound, float volume, float pitch, float fClipSoundDist = 16.0f);

The fClipSoundDist parameter controls how far away a player can be and still hear the sound. Default is 16 blocks.

To play a sound attached to an entity (it follows the entity’s position):

level->playSound(shared_from_this(), eSoundType_MOB_WOLF_BARK, 1.0f, 1.0f);

UI sounds don’t have a 3D position. Call playUI() directly:

soundEngine->playUI(eSFX_Press, 1.0f, 1.0f);

If you need to play a regular game sound as a UI sound (no 3D positioning), you can pass a game sound enum to playUI() too. The engine detects this by checking if the ID is >= eSFX_MAX and routes it through the right path.

Music is streamed from .binka files (a Bink Audio compressed format). The tracks are defined in the eMUSICFILES enum:

enum eMUSICFILES
{
// Overworld
eStream_Overworld_Calm1 = 0, // "calm1"
eStream_Overworld_Calm2, // "calm2"
eStream_Overworld_Calm3, // "calm3"
eStream_Overworld_hal1, // "hal1"
eStream_Overworld_hal2, // "hal2"
eStream_Overworld_hal3, // "hal3"
eStream_Overworld_hal4, // "hal4"
eStream_Overworld_nuance1, // "nuance1"
eStream_Overworld_nuance2, // "nuance2"
eStream_Overworld_piano1, // "piano1"
eStream_Overworld_piano2, // "piano2"
eStream_Overworld_piano3, // "piano3"
// Nether
eStream_Nether1, // "nether1"
eStream_Nether2, // "nether2"
eStream_Nether3, // "nether3"
eStream_Nether4, // "nether4"
// The End
eStream_end_dragon, // "the_end_dragon_alive"
eStream_end_end, // "the_end_end"
// Music discs
eStream_CD_1, // "11"
// ... through eStream_CD_12 // "where_are_we_now"
eStream_Max,
};

Non-Xbox platforms also have creative mode and menu tracks (creative1 through creative6, menu1 through menu4) inserted between nuance2 and piano1.

File paths follow this pattern:

  • Background music: music/<trackname>.binka
  • Music discs: cds/<discname>.binka

The SoundEngine uses a state machine (MUSIC_STREAMSTATE) to manage streaming:

Idle --> Opening --> Playing --> Completed --> Stop --> Idle

When it’s time to play music, playStreaming() is called with an empty name for background music or a named track for jukebox/CD music.

For background music, there’s a random delay of up to 3 minutes before the next track starts. Then the engine picks a track based on the player’s current dimension:

int SoundEngine::getMusicID(int iDomain)
{
switch (iDomain)
{
case LevelData::DIMENSION_END:
// The End always plays the dragon-alive track
return m_iStream_End_Min;
case LevelData::DIMENSION_NETHER:
return GetRandomishTrack(m_iStream_Nether_Min, m_iStream_Nether_Max);
default:
// Overworld picks randomly
return GetRandomishTrack(m_iStream_Overworld_Min, m_iStream_Overworld_Max);
}
}

The GetRandomishTrack() function tries to avoid repeating recently-played tracks. It keeps a boolean array of heard tracks, picks randomly, and prefers unheard ones. Once all tracks have been heard, the array resets. It doesn’t try too hard though, so occasionally you will hear the same track twice.

For music discs (jukebox), the track name gets matched against the m_szStreamFileA array to find the right file.

The engine tracks two types of streaming music:

enum eMUSICTYPE
{
eMusicType_None,
eMusicType_Game, // background music (not 3D positioned)
eMusicType_CD, // jukebox music (3D positioned, attenuates with distance)
};

Background music plays globally with no 3D positioning. Jukebox music is 3D-positioned at the jukebox block with a distance scaler of 64 blocks (4x the normal sound range).

Texture packs (mash-up packs) can include custom music. When TexturePack::hasAudio() returns true, the engine loads music from the DLC pack path instead of the default music/ folder. The SetStreamingSounds() function lets mash-up packs define their own range of overworld, nether, and end tracks.

All 3D sounds use a custom linear falloff function instead of the default Miles rolloff:

F32 AILCALLBACK custom_falloff_function(
HSAMPLE S, F32 distance, F32 rolloff_factor, F32 min_dist, F32 max_dist)
{
// Thunder has no attenuation at all
if (max_dist == 10000.0f)
{
return 1.0f;
}
// Linear falloff: full volume at distance 0, silent at max_dist
F32 result = 1.0f - (distance / max_dist);
if (result < 0.0f) result = 0.0f;
if (result > 1.0f) result = 1.0f;
return result;
}

The max_dist parameter (called distanceScaler in the code) controls how far a sound carries. Default is 16 blocks. Special cases:

Sound typeDistance (blocks)Notes
Most sounds16Default
Ghast sounds30So you can hear them from further away
Ender Dragon100Boss sounds carry far
Thunder10,000Effectively infinite, always full volume
Music discs64Jukebox carries 4x further than normal

In splitscreen, the engine can’t just set one listener position. Instead, it calculates the Manhattan distance from each sound to every active player, picks the closest one, and uses that distance for the 3D falloff. The listener is placed at the origin and the sound is placed along the Z axis at the calculated distance. This gives reasonable spatial audio across all screen splits.

In SoundEngine.h, add your new track to eMUSICFILES:

enum eMUSICFILES
{
// ... existing tracks ...
eStream_Overworld_piano3,
// your new track
eStream_Overworld_MyTrack,
// Nether
eStream_Nether1,
// ...
};

Where you put it matters. The engine uses ranges to pick tracks for each dimension. If it’s an overworld track, put it between the last overworld entry and the first nether entry.

In SoundEngine.cpp, add the filename string in m_szStreamFileA at the matching position:

char *SoundEngine::m_szStreamFileA[eStream_Max] = {
// ... existing entries ...
"piano3",
// your new track
"mytrack",
// Nether
"nether1",
// ...
};

In the SoundEngine constructor, the ranges are set by SetStreamingSounds(). If you added an overworld track, the max overworld value needs to include it:

// Before: piano3 was the last overworld track
SetStreamingSounds(
eStream_Overworld_Calm1, eStream_Overworld_MyTrack, // overworld range (was piano3)
eStream_Nether1, eStream_Nether4, // nether range
eStream_end_dragon, eStream_end_end, // end range
eStream_CD_1 // first CD
);

Put your .binka file in the music/ folder:

  • music/mytrack.binka

The file must be in Bink Audio format. You can convert WAV files to .binka using the RAD Game Tools Bink Audio encoder.

Same process, just put the enum and filename in the nether or end section and adjust the corresponding range in SetStreamingSounds().

Here’s a complete example of adding a custom mob with its own sounds.

In SoundTypes.h:

// Add before eSoundType_MAX
eSoundType_MOB_WARDEN_AMBIENT,
eSoundType_MOB_WARDEN_HURT,
eSoundType_MOB_WARDEN_DEATH,
eSoundType_MOB_WARDEN_ROAR,

In SoundNames.cpp:

// Add at the matching position
L"mob.warden.ambient",
L"mob.warden.hurt",
L"mob.warden.death",
L"mob.warden.roar",

In your mob class:

int Warden::getAmbientSound()
{
return eSoundType_MOB_WARDEN_AMBIENT;
}
int Warden::getHurtSound()
{
return eSoundType_MOB_WARDEN_HURT;
}
int Warden::getDeathSound()
{
return eSoundType_MOB_WARDEN_DEATH;
}
// Custom attack with a roar sound
void Warden::performRoarAttack()
{
level->playSound(shared_from_this(), eSoundType_MOB_WARDEN_ROAR, 2.0f, 1.0f);
// ... attack logic
}

Add the corresponding events to the Miles soundbank with paths like Minecraft/mob.warden.ambient, and you’re good to go.

If the built-in sound types (stone, wood, grass, etc.) don’t fit your block, you can create a custom one:

// In Tile.cpp, alongside the other static SoundType definitions:
Tile::SOUND_CRYSTAL = new Tile::SoundType(
eMaterialSoundType_STONE, // base material (for step sound fallback)
0.8f, // volume
1.5f, // pitch
eSoundType_RANDOM_GLASS, // custom break sound
eSoundType_STEP_STONE // custom place sound
);

Then use it on your tile:

Tile::crystalBlock = (new CrystalTile(200))
->setDestroyTime(1.0f)
->setSoundType(Tile::SOUND_CRYSTAL)
->setTextureName(L"crystal");

The eMATERIALSOUND_TYPE controls which step and dig sounds the base class uses. The last two parameters let you override the break and place sounds independently.

Both volume and pitch are floats. Volume ranges from 0.0 (silent) to around 4.0 (very loud; default is 1.0). Pitch ranges from about 0.5 (deep) to 2.0 (high; default is 1.0).

Baby mobs automatically get a higher pitch through getVoicePitch(), which returns a randomized value between about 1.3 and 1.7 for young entities.

The randomization in getVoicePitch() prevents sounds from being monotonous:

float Mob::getVoicePitch()
{
return (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f;
}

The engine tracks how many copies of each sound are currently playing in CurrentSoundsPlaying[]. The cap is 8. If you try to play a 9th copy of the same sound, it gets dropped silently.

This matters most for ambient sounds in dense mob farms. If you have 20 zombies in a small area, only 8 zombie ambient sounds will play at once. The rest get culled.

When game code plays a sound through Level::playSound(), the default clip distance is 16 blocks. But LevelRenderer::playSound() lets you override it:

// Normal sound: audible within 16 blocks
level->playSound(x, y, z, eSoundType_RANDOM_CLICK, 1.0f, 1.0f);
// Long-range sound: audible within 100 blocks
levelRenderer->playSound(eSoundType_MOB_ENDERDRAGON_GROWL,
x, y, z, 1.0f, 1.0f, 100.0f);

Thunder uses the special value of 10000 blocks, which the custom falloff function treats as “no attenuation at all.”

FilePurpose
Minecraft.World/SoundTypes.heSOUND_TYPE enum, ESoundEffect enum, eMATERIALSOUND_TYPE enum
Minecraft.Client/Common/Audio/SoundNames.cppwchSoundNames[] and wchUISoundNames[] string arrays
Minecraft.Client/Common/Audio/SoundEngine.hSoundEngine class, eMUSICFILES enum, AUDIO_INFO struct
Minecraft.Client/Common/Audio/SoundEngine.cppFull Miles integration, playback, streaming, falloff
Minecraft.Client/Common/Audio/Consoles_SoundEngine.hConsoleSoundEngine abstract base class
Minecraft.World/Tile.hTile::SoundType class for block sounds
Minecraft.World/Tile.cppSoundType initialization and material-to-sound mapping
Minecraft.World/Level.hLevel::playSound() and Level::playStreamingMusic()
Minecraft.World/Mob.hgetAmbientSound(), getHurtSound(), getDeathSound() virtuals
Minecraft.World/LevelSoundPacket.hNetwork packet for syncing sounds in multiplayer