Pepper.SFX
All Guidance pages

Guidance / Manual

Multi-Position Audio

8.1 Concept

What: Multiple speakers playing synchronized audio at different 3D positions.

Real-World Example: Nightclub with 8 speakers playing the same music.

Challenge: Without synchronization, speakers have phasing issues (sounds canceling out).

Solution: DSP-scheduled playback ensures sample-accurate synchronization.

8.2 Architecture

AudioMultiPositionEmitterParent

Component: Manages child emitters

Properties:

int maxEmitters = 16;                    // Limit (1-16)
bool autoFindChildren = true;            // Auto-detect children
bool autoUpdateChildren = true;          // Update when hierarchy changes

Methods:

Vector3[] GetActivePositions();          // Positions of active emitters
Transform[] GetActiveTransforms();       // Transforms of active emitters
int ActiveEmitterCount { get; }          // Number active
void EnableAllEmitters();
void DisableAllEmitters();
void SetAllVolumes(float volume);
void RefreshChildren();

AudioMultiPositionEmitterChild

Component: Individual emitter point

Properties:

bool isActive = true;                    // Enable/disable
float volumeMultiplier = 1f;             // Per-emitter volume (0-1)
bool useDirectionality = false;          // Draws a cone gizmo in the Scene view (no effect on sound)
float coneAngle = 90f;                   // That gizmo's cone angle (0-360)

Properties (Read-Only):

Vector3 Position { get; }                // World position
Quaternion Rotation { get; }             // World rotation
Vector3 Forward { get; }                 // Forward direction

8.3 DSP Synchronization

Problem Without Sync:

Speaker A starts: Frame 1, Sample 0
Speaker B starts: Frame 1, Sample 512  ← Delayed by 512 samples
Speaker C starts: Frame 2, Sample 0    ← Delayed by ~2000 samples

Result: Phasing, echoing, muddy sound

Solution With DSP Scheduling:

double dspTime = AudioSettings.dspTime + 0.1; // Schedule 100ms ahead

foreach (var position in emitterPositions)
{
    var voice = GetVoice();
    voice.source.clip = clip;
    voice.source.transform.position = position;
    voice.scheduledStartTime = dspTime;
    voice.source.PlayScheduled(dspTime);
}

Result: All speakers start at exact same DSP sample, perfect synchronization.

8.4 Setup Tutorial

Step 1: Create Parent

1. Create empty GameObject: "NightclubSpeakers"
2. Add component: AudioMultiPositionEmitterParent
3. Configure:
   - maxEmitters: 8
   - autoFindChildren: ✓

Step 2: Create Children (Manual)

1. Create child GameObject: "Speaker_01"
2. Position at (10, 2, 0)
3. Add component: AudioMultiPositionEmitterChild
4. Repeat for 8 speakers in circle pattern

Step 2: Create Children (Auto - Inspector Button)

1. Select parent
2. In Inspector, click "Create 8 Child Emitters (Circle Pattern)"
3. Automatically creates 8 speakers in circle

Step 3: Create Event

1. Create AudioEvent
2. Add action: Play music container
   (No multi-position setting is needed on the event: Step 4 posts it with PostMultiPosition.)

Step 4: Script

using UnityEngine;
using AudioSystem;

public class NightclubAudio : MonoBehaviour
{
    [SerializeField] private AudioEvent musicEvent;
    private AudioMultiHandle musicHandle;

    void Start()
    {
        var parent = GetComponent<AudioMultiPositionEmitterParent>();
        musicHandle = musicEvent.PostMultiPosition(parent);

        Debug.Log($"Playing on {musicHandle.VoiceCount} speakers");
    }

    void OnDestroy()
    {
        musicHandle?.Stop(2f); // Fade out over 2s
    }
}

8.5 Per-Emitter Control

Volume:

// Individually
emitterChild.VolumeMultiplier = 0.75f;

// All at once
emitterParent.SetAllVolumes(0.5f);

Active State:

// Individual
emitterChild.IsActive = false; // Disable this speaker

// All
emitterParent.DisableAllEmitters();
emitterParent.EnableAllEmitters();

Directionality (Scene view only):

emitterChild.UseDirectionality = true;
emitterChild.ConeAngle = 90f;  // draws a 90° cone along transform.forward

These two only draw a cone gizmo to help you aim an emitter. Playback doesn’t read them: every active emitter is heard the same in every direction. To make one quieter, use its Volume Multiplier.

8.6 Use Cases

Nightclub Speakers:

Setup: 8 speakers in circle
Audio: Looping music
Result: Immersive club environment

Stadium Announcer:

Setup: 12 speakers around stadium
Audio: Announcer voice
Result: Realistic PA system

Vehicle Multi-Speaker:

Setup: 4 speakers (FL, FR, RL, RR)
Audio: Radio/music
Result: Realistic car audio

Ambient Sound Array:

Setup: 6 speakers in outdoor environment
Audio: Wind/nature ambience
Result: Enveloping soundscape

8.7 Performance Considerations

Voice Count:

  • 1 multi-position event with 8 emitters = 8 voices
  • Monitor total voice count: AudioManager.Instance.GetStatistics()

Instance Limiting:

Event: Multi-position music
MaxInstances: 1  ← Only one instance of entire array

Occlusion:

  • Each emitter can be individually occluded
  • Can be expensive with many emitters
  • Consider disabling occlusion for some emitters

Distance Culling:

  • Virtualization works per-voice
  • Under voice pressure, distant emitters are virtualized first (importance falls with distance)
  • No special handling needed

From the Pepper.SFX 3.1 guides (USER_GUIDE.md). Something wrong or unclear? Tell me.