Skip to main content

Overview

Spatial audio (3D audio) makes sounds appear to come from specific positions in your 3D scene. Atlas Engine’s Finewave system provides automatic spatialization with distance attenuation, panning, and Doppler effects.

How Spatial Audio Works

Spatial audio in Finewave:
  1. Listener Position: Automatically synced with the active camera
  2. Source Position: Set via the audio source or attached object
  3. Attenuation: Volume decreases with distance
  4. Panning: Sound moves between left/right channels based on position
  5. Doppler Effect: Pitch shifts based on relative velocity

Enabling Spatialization

Using AudioPlayer Component

The AudioPlayer component makes spatial audio simple:
class BackpackAttach : public Component {
public:
    void init() override {
        auto player = this->object->getComponent<AudioPlayer>();
        player->setSource(Workspace::get().createResource(
            "exampleMP3.mp3", "ExampleAudio", ResourceType::Audio));
        
        // Enable 3D spatial audio
        player->useSpatialization();
        
        player->source->setLooping(true);
        player->play();
    }
};
This example from test/main.cpp shows the key line: player->useSpatialization(). Once enabled, the AudioPlayer automatically:
  • Updates the source position every frame based on the object’s position
  • Syncs the listener position with the camera
  • Updates listener orientation and velocity

Using AudioSource Directly

// Enable spatialization
audioSource.useSpatialization();

// Set 3D position
audioSource.setPosition({10.0f, 2.0f, 5.0f});

// Optionally set velocity for Doppler
audioSource.setVelocity({-5.0f, 0.0f, 0.0f});

// Play the audio
audioSource.play();

Disabling Spatialization

For global sounds like UI or background music:
// Make audio play uniformly (non-spatial)
player->disableSpatialization();

Automatic Listener Updates

The AudioPlayer component’s update() method (from include/atlas/audio.h) automatically handles listener positioning:
void AudioPlayer::update(float) override {
    ensureSourceInitialized();
    Window *window = Window::mainWindow;
    Camera *mainCamera = window->getCamera();

    if (mainCamera) {
        Position3d cameraPos = mainCamera->position;
        Normal3d cameraFront = mainCamera->getFrontVector();
        
        // Update listener position and orientation
        window->audioEngine->setListenerPosition(cameraPos);
        window->audioEngine->setListenerOrientation(
            cameraFront, Normal3d{0.0f, 1.0f, 0.0f});
        window->audioEngine->setListenerVelocity(mainCamera->getVelocity());
    }

    // Update source position from object
    source->setPosition(this->object->getPosition());
}
This means you don’t need to manually update listener or source positions—it happens automatically every frame.

Manual Listener Control

For custom camera setups, you can control the listener directly:
// Set listener position
window.audioEngine->setListenerPosition({0.0f, 1.5f, 0.0f});

// Set listener orientation (forward and up vectors)
window.audioEngine->setListenerOrientation(
    Magnitude3d{0.0f, 0.0f, -1.0f},  // Forward
    Normal3d{0.0f, 1.0f, 0.0f}        // Up
);

// Set listener velocity for Doppler
window.audioEngine->setListenerVelocity({2.0f, 0.0f, 0.0f});

Position-Based Audio Example

Ambient Environment Sounds

class Waterfall : public CompoundObject {
    CoreObject waterfallModel;
    
public:
    void init() override {
        // Create visual representation
        waterfallModel = createModel("waterfall.obj");
        waterfallModel.setPosition({50.0f, 10.0f, 20.0f});
        waterfallModel.initialize();
        this->addObject(&waterfallModel);
        
        // Add spatial audio
        auto audioPlayer = waterfallModel.addComponent<AudioPlayer>();
        audioPlayer->setSource(
            Workspace::get().createResource(
                "waterfall_loop.wav",
                "WaterfallSound",
                ResourceType::Audio
            )
        );
        audioPlayer->useSpatialization();
        audioPlayer->source->setLooping(true);
        audioPlayer->source->setVolume(0.8f);
        audioPlayer->play();
    }
};

Moving Audio Source

class Drone : public Component {
    Position3d targetPosition;
    float speed = 5.0f;
    
public:
    void init() override {
        // Set up spatial audio
        auto player = object->addComponent<AudioPlayer>();
        player->setSource(
            Workspace::get().getResource("drone_engine")
        );
        player->useSpatialization();
        player->source->setLooping(true);
        player->play();
        
        targetPosition = {20.0f, 15.0f, 10.0f};
    }
    
    void update(float deltaTime) override {
        // Move towards target
        Position3d currentPos = object->getPosition();
        Position3d direction = {
            targetPosition.x - currentPos.x,
            targetPosition.y - currentPos.y,
            targetPosition.z - currentPos.z
        };
        
        object->move({
            direction.x * speed * deltaTime,
            direction.y * speed * deltaTime,
            direction.z * speed * deltaTime
        });
        
        // Audio position updates automatically via AudioPlayer
    }
};

Distance Attenuation

Finewave automatically attenuates audio based on distance from the listener. The further away a source is, the quieter it becomes.
// Close source - louder
audioSource.setPosition({2.0f, 0.0f, 0.0f});

// Distant source - quieter
audioSource.setPosition({100.0f, 0.0f, 0.0f});

Doppler Effect

The Doppler effect changes pitch based on relative velocity between source and listener:
// Moving towards listener - higher pitch
audioSource.setVelocity({10.0f, 0.0f, 0.0f});

// Moving away from listener - lower pitch
audioSource.setVelocity({-10.0f, 0.0f, 0.0f});
When using AudioPlayer, camera velocity is automatically set for the listener, enabling Doppler effects.

Query Position Information

You can query spatial audio positions at runtime:
// Get source position
Position3d sourcePos = audioSource.getPosition();

// Get listener position
Position3d listenerPos = audioSource.getListenerPosition();

// Calculate distance
float distance = std::sqrt(
    std::pow(sourcePos.x - listenerPos.x, 2) +
    std::pow(sourcePos.y - listenerPos.y, 2) +
    std::pow(sourcePos.z - listenerPos.z, 2)
);

Complete Scene Example

class SpatialAudioDemo : public Scene {
    Camera camera;
    CoreObject player;
    CoreObject campfire;
    CoreObject ambientSource;
    
public:
    void initialize(Window &window) override {
        // Set up camera
        camera = Camera();
        camera.setPosition({0.0f, 1.5f, 10.0f});
        window.setCamera(&camera);
        
        // Player object (listener follows this)
        player = CoreObject();
        player.setPosition({0.0f, 0.0f, 0.0f});
        window.addObject(&player);
        
        // Campfire with crackling sound
        campfire = createDebugSphere(0.5f);
        campfire.setPosition({5.0f, 0.5f, 0.0f});
        campfire.initialize();
        
        auto fireAudio = campfire.addComponent<AudioPlayer>();
        fireAudio->setSource(
            Workspace::get().createResource(
                "campfire_crackle.wav",
                "Campfire",
                ResourceType::Audio
            )
        );
        fireAudio->useSpatialization();
        fireAudio->source->setLooping(true);
        fireAudio->play();
        
        window.addObject(&campfire);
        
        // Ambient wind (non-spatial)
        ambientSource = CoreObject();
        auto windAudio = ambientSource.addComponent<AudioPlayer>();
        windAudio->setSource(
            Workspace::get().createResource(
                "wind_ambient.wav",
                "Wind",
                ResourceType::Audio
            )
        );
        windAudio->disableSpatialization(); // Global sound
        windAudio->source->setLooping(true);
        windAudio->source->setVolume(0.3f);
        windAudio->play();
        
        window.addObject(&ambientSource);
    }
    
    void update(Window &window) override {
        camera.update(window);
    }
};

Best Practices

Enable spatialization for sounds that exist within the game world (footsteps, object interactions, environment sounds). Disable it for UI sounds and background music.
When using AudioPlayer component, you don’t need to manually update source or listener positions. The component handles this in its update() method.
Spatial audio works best with mono audio files. Stereo files may not pan correctly in 3D space. Finewave can automatically convert stereo to mono when needed.
Spatial audio calculations happen every frame. For scenes with many audio sources, consider deactivating distant or inaudible sources.

Next Steps

Audio Effects

Add reverb, echo, and distortion to your spatial audio

Build docs developers (and LLMs) love