Guides
3D Audio System
Spatial audio positioning and tracking in FMOD API
3D Audio System
FMOD API provides sophisticated 3D spatial audio capabilities that automatically integrate with Minecraft's world space.
🎯 Spatial Audio Features
Feature | Description | Performance |
---|---|---|
🌍 World Space | Position sounds anywhere in the world | Real-time updates |
👂 Auto Listener | Follows player position/rotation | <0.1ms overhead |
📏 Distance Attenuation | Realistic volume falloff | Configurable curves |
🎭 Doppler Effect | Pitch shifts for moving sources | Optional |
📍 Basic 3D Positioning
Simple Positioned Sound
// Play sound at specific world coordinates
Vec3 position = new Vec3(100, 64, 200);
FMODAPI.playEventSimple("event:/yourmod/ambient_sound",
position.x, position.y, position.z);
Entity-Based Positioning
// Play sound attached to an entity
Entity entity = player;
FMODAPI.playEventAtEntity("event:/yourmod/footstep", entity);
Block-Based Positioning
// Play sound at block position
BlockPos blockPos = new BlockPos(50, 70, 100);
FMODAPI.playEventAtBlock("event:/yourmod/machine_hum", blockPos);
🎮 Automatic Listener Tracking
Player Position Sync
The 3D listener automatically tracks:
- Position - Player's exact world coordinates
- Rotation - Camera orientation (yaw/pitch)
- Velocity - Player movement for Doppler effect
- Up Vector - Camera roll (for advanced effects)
// Happens automatically every tick
@SubscribeEvent
public static void onClientTick(ClientTickEvent event) {
Player player = Minecraft.getInstance().player;
if (player != null) {
// FMOD listener updates automatically
Vec3 pos = player.position();
Vec3 forward = player.getLookAngle();
Vec3 velocity = player.getDeltaMovement();
FMODAPI.updateListener(pos, forward, velocity);
}
}
📐 Distance Attenuation
Default Behavior
// Sound audible from 1-64 blocks away
FMODAPI.playEventSimple("event:/yourmod/sound", x, y, z);
// Min distance: 1 block (full volume)
// Max distance: 64 blocks (silent)
Custom Distance Settings
// Custom attenuation curve
int instanceId = FMODAPI.playEvent("event:/yourmod/sound", position);
FMODAPI.setEventMinMaxDistance(instanceId,
5.0f, // Min distance (full volume within 5 blocks)
128.0f // Max distance (silent beyond 128 blocks)
);
Attenuation Curves
Curve Type | Use Case | Behavior |
---|---|---|
Linear | Realistic outdoor | Volume decreases linearly |
Inverse | Indoor spaces | Slower falloff near source |
Custom | Special effects | Defined in FMOD Studio |
🔊 Advanced 3D Features
Moving Sound Sources
// Update sound position dynamically
int instanceId = FMODAPI.playEvent("event:/yourmod/projectile", position);
// In your entity's tick method
@Override
public void tick() {
super.tick();
Vec3 currentPos = this.position();
FMODAPI.updateEventPosition(instanceId,
currentPos.x, currentPos.y, currentPos.z);
}
Velocity for Doppler Effect
// Enable Doppler effect for moving sounds
Vec3 position = projectile.position();
Vec3 velocity = projectile.getDeltaMovement();
int instanceId = FMODAPI.playEvent("event:/yourmod/projectile",
position, velocity);
// Pitch shifts as projectile moves toward/away from player
Occlusion and Obstruction
// Manual occlusion control
int instanceId = FMODAPI.playEvent("event:/yourmod/sound", position);
// Calculate line-of-sight obstruction
float occlusion = calculateOcclusion(player.position(), position);
FMODAPI.setEventOcclusion(instanceId, occlusion); // 0.0 = clear, 1.0 = fully blocked
🌍 World Space Coordinates
Coordinate System Mapping
Minecraft coordinates map directly to FMOD 3D space:
- X → Left/Right (West/East)
- Y → Up/Down
- Z → Forward/Back (North/South)
Multi-Dimension Support
// Sounds don't carry across dimensions
if (soundDimension != playerDimension) {
FMODAPI.stopEvent(instanceId);
}
🎛️ Reverb and Environment
Zone-Based Reverb
// Apply reverb based on location
if (isInCave(player.position())) {
FMODAPI.setGlobalReverb(ReverbPreset.CAVE);
} else if (isUnderwater(player.position())) {
FMODAPI.setGlobalReverb(ReverbPreset.UNDERWATER);
} else {
FMODAPI.setGlobalReverb(ReverbPreset.OFF);
}
Distance-Based Effects
// Apply lowpass filter for distant sounds
float distance = position.distanceTo(listener);
if (distance > 32) {
float cutoff = Math.max(500, 22000 - (distance * 100));
FMODAPI.setEventLowpass(instanceId, cutoff);
}
📊 Performance Optimization
Distance Culling
// Automatic culling of distant sounds
FMODAPI.setMaxAudibleDistance(96.0f); // Don't process sounds beyond 96 blocks
Instance Limiting
// Limit concurrent 3D sounds
FMODAPI.setMax3DInstances(64); // Max 64 positioned sounds at once
// Oldest/quietest instances automatically culled
Update Frequency
// Reduce update rate for distant sounds
float distance = soundPosition.distanceTo(listenerPosition);
if (distance > 64) {
// Update position every 5 ticks instead of every tick
if (tickCount % 5 == 0) {
FMODAPI.updateEventPosition(instanceId, x, y, z);
}
}
🎯 Practical Examples
Ambient Zone System
public class AmbientZone {
private final Vec3 center;
private final float radius;
private int ambientInstance = -1;
public void update(Player player) {
float distance = player.position().distanceTo(center);
if (distance < radius && ambientInstance == -1) {
// Player entered zone - start ambient sound
ambientInstance = FMODAPI.playEvent(
"event:/ambient/forest", center
);
} else if (distance >= radius && ambientInstance != -1) {
// Player left zone - stop ambient sound
FMODAPI.stopEvent(ambientInstance);
ambientInstance = -1;
}
}
}
Projectile Sound
public class SoundProjectile extends AbstractArrow {
private int soundInstance = -1;
@Override
public void tick() {
super.tick();
if (level().isClientSide) {
if (soundInstance == -1) {
soundInstance = FMODAPI.playEvent(
"event:/projectile/whoosh",
position(),
getDeltaMovement()
);
} else {
FMODAPI.updateEventPosition(soundInstance,
position().x, position().y, position().z
);
FMODAPI.updateEventVelocity(soundInstance,
getDeltaMovement().x, getDeltaMovement().y, getDeltaMovement().z
);
}
}
}
@Override
public void remove(RemovalReason reason) {
if (soundInstance != -1) {
FMODAPI.stopEvent(soundInstance);
}
super.remove(reason);
}
}
Dynamic Machine Sound
public class MachineBlockEntity extends BlockEntity {
private int machineSound = -1;
public void tick() {
if (level.isClientSide && isActive()) {
if (machineSound == -1) {
machineSound = FMODAPI.playEvent(
"event:/machines/generator",
getBlockPos()
);
}
// Update intensity based on machine state
float intensity = getWorkProgress();
FMODAPI.setEventParameter(machineSound, "intensity", intensity);
}
}
@Override
public void setRemoved() {
if (machineSound != -1) {
FMODAPI.stopEvent(machineSound);
}
super.setRemoved();
}
}
🐛 Troubleshooting
Sounds Not Positioned Correctly
- Verify coordinates are in world space, not relative
- Check listener position is updating
- Ensure sound instance is still valid
No Distance Attenuation
- Verify min/max distances are set correctly in FMOD Studio
- Check 3D settings are enabled for the event
- Ensure sound is played with position, not as 2D
Performance Issues
- Reduce max audible distance
- Implement distance-based update frequency
- Limit concurrent 3D instances
📚 Related Documentation
- 🎮 Integration Overview - Core integration features
- ⏸️ Pause/Resume - Game state management
- 🔊 Volume Sync - Volume control integration
- 📝 API Reference - Complete method documentation
3D audio works automatically! Just provide coordinates and FMOD API handles positioning, attenuation, and listener tracking for you.