> ## Documentation Index
> Fetch the complete documentation index at: https://docs.liv.tv/llms.txt
> Use this file to discover all available pages before exploring further.

# ILCKAudioSource Interface

> Modular audio source interface for extensible audio capture in Unreal Engine.

## What Problem Does This Solve?

LCK needs to capture audio from multiple sources:

* Game audio (Unreal, FMOD, Wwise)
* Microphone input
* Voice chat (Vivox)

`ILCKAudioSource` provides a unified interface so all audio plugins work the same way. This lets you:

* Mix multiple audio sources together
* Switch audio middleware without changing code
* Create custom audio sources
* Route audio to the encoder

## When to Use This

**Read this if:**

* Building a custom audio source plugin
* Integrating new audio middleware
* Understanding how audio flows through LCK
* Debugging audio capture issues

**Skip this if:** You're just using the default audio plugins (UnrealAudio, FMOD, Wwise). They already implement this interface.

***

## Critical: Single Delegate Warning

<Warning>
  **IMPORTANT:** `OnAudioDataDelegate` is a **SINGLE delegate**, not multicast.

  Use `BindLambda()`, NOT `AddLambda()`.

  Each bind **replaces** the previous binding. This is by design—audio data goes to one destination (the encoder).
</Warning>

### Correct Usage ✅

```cpp theme={null}
// CORRECT: Use BindLambda (replaces any existing binding)
Source->OnAudioDataDelegate.BindLambda([](
    TArrayView<const float> PCM,
    int32 Channels,
    int32 SampleRate,
    ELCKAudioChannel SourceChannel)
{
    // Process audio data
});
```

### Incorrect Usage ❌

```cpp theme={null}
// WRONG: AddLambda doesn't exist on single delegates
Source->OnAudioDataDelegate.AddLambda(...); // Compilation error

// WRONG: AddDynamic doesn't exist on single delegates
Source->OnAudioDataDelegate.AddDynamic(...); // Compilation error
```

***

## Interface Definition

```cpp theme={null}
class ILCKAudioSource : public IModularFeature,
                        public TSharedFromThis<ILCKAudioSource>
{
public:
    static FName GetModularFeatureName()
    {
        return TEXT("LCKAudioSource");
    }
    
    // Audio data callback (single delegate - use BindLambda)
    FOnRenderAudioDelegate OnAudioDataDelegate;
    
    // Control methods
    virtual bool StartCapture() noexcept = 0;
    virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept = 0;
    virtual void StopCapture() noexcept = 0;
    
    // Query methods
    virtual float GetVolume() const noexcept = 0;
    virtual const FString& GetSourceName() const noexcept = 0;
    TLCKAudioChannelsMask GetSupportedChannels() const noexcept;
    
protected:
    TLCKAudioChannelsMask SupportedChannels;
};
```

| Method                   | Purpose                                      | When to Call          |
| ------------------------ | -------------------------------------------- | --------------------- |
| `StartCapture()`         | Begin audio capture (all supported channels) | Before recording      |
| `StartCapture(Channels)` | Begin capture for specific channels          | Before recording      |
| `StopCapture()`          | Stop audio capture                           | After recording       |
| `GetVolume()`            | Get current audio level (0.0-1.0)            | For volume indicators |
| `GetSourceName()`        | Get source identifier                        | Debugging, UI labels  |
| `GetSupportedChannels()` | Get channel bitmask                          | Capability detection  |

***

## Audio Delegate Signature

```cpp theme={null}
DECLARE_MULTICAST_DELEGATE_FourParams(
    FDelegateRenderAudio,
    TArrayView<const float>,  // PCM samples (interleaved)
    int32,                    // Number of channels
    int32,                    // Sample rate (Hz)
    ELCKAudioChannel          // Source channel type
);

typedef FDelegateRenderAudio::FDelegate FOnRenderAudioDelegate;
```

### Parameters Explained

| Parameter         | Type                      | Description                                                  |
| ----------------- | ------------------------- | ------------------------------------------------------------ |
| **PCM**           | `TArrayView<const float>` | Interleaved audio samples, 32-bit float format (-1.0 to 1.0) |
| **Channels**      | `int32`                   | Number of audio channels (1 = mono, 2 = stereo)              |
| **SampleRate**    | `int32`                   | Audio sample rate in Hz (typically 48000)                    |
| **SourceChannel** | `ELCKAudioChannel`        | Audio type: Game, Microphone, or VoiceChat                   |

### Audio Data Format

| Property         | Value                                              |
| ---------------- | -------------------------------------------------- |
| Sample format    | 32-bit float                                       |
| Range            | -1.0 to 1.0                                        |
| Layout           | Interleaved (e.g., `[L, R, L, R, ...]` for stereo) |
| Typical channels | 2 (stereo)                                         |
| Sample rate      | Source-dependent (usually 48000 Hz)                |

**Example PCM data (stereo):**

```cpp theme={null}
float PCM[] = {
    0.5f, -0.3f,  // Frame 0: Left = 0.5, Right = -0.3
    0.2f,  0.1f,  // Frame 1: Left = 0.2, Right = 0.1
    -0.4f, 0.6f   // Frame 2: Left = -0.4, Right = 0.6
};
```

***

## Supported Channels

Each audio source declares which channels it can capture:

| Source             | Game | Microphone | VoiceChat | Notes                                          |
| ------------------ | ---- | ---------- | --------- | ---------------------------------------------- |
| **LCKUnrealAudio** | ✅    | ✅          | ❌         | Built-in Unreal audio                          |
| **LCKFMOD**        | ✅    | ❌          | ❌         | FMOD Studio game audio                         |
| **LCKWwise**       | ✅    | ❌          | ❌         | Wwise game audio                               |
| **LCKOboe**        | ❌    | ✅          | ❌         | Android low-latency mic                        |
| **LCKVivox**       | ✅    | ✅          | ❌         | Vivox voice chat (incoming→Game, outgoing→Mic) |

**Check supported channels:**

```cpp theme={null}
TLCKAudioChannelsMask SupportedChannels = Source->GetSupportedChannels();

// Check if source supports microphone
if ((SupportedChannels & ELCKAudioChannel::Microphone) != 0)
{
    UE_LOG(LogLCK, Log, TEXT("Source can capture microphone"));
}

// Check multiple channels
if ((SupportedChannels & (ELCKAudioChannel::Game | ELCKAudioChannel::Microphone)) ==
    (ELCKAudioChannel::Game | ELCKAudioChannel::Microphone))
{
    UE_LOG(LogLCK, Log, TEXT("Source can capture both game audio and mic"));
}
```

***

## Finding Audio Sources

Audio sources are discovered via Unreal's modular features system:

```cpp theme={null}
// Get all registered audio sources
TArray<ILCKAudioSource*> GetAllAudioSources()
{
    return IModularFeatures::Get()
        .GetModularFeatureImplementations<ILCKAudioSource>(
            ILCKAudioSource::GetModularFeatureName()
        );
}

// Find sources that support a specific channel
TArray<ILCKAudioSource*> GetSourcesForChannel(ELCKAudioChannel Channel)
{
    TArray<ILCKAudioSource*> Result;
    
    for (ILCKAudioSource* Source : GetAllAudioSources())
    {
        if ((Source->GetSupportedChannels() & Channel) != 0)
        {
            Result.Add(Source);
        }
    }
    
    return Result;
}

// Example: Find all game audio sources
TArray<ILCKAudioSource*> GameAudioSources = GetSourcesForChannel(ELCKAudioChannel::Game);

for (ILCKAudioSource* Source : GameAudioSources)
{
    UE_LOG(LogLCK, Log, TEXT("Game audio source: %s"), *Source->GetSourceName());
}
```

***

## Audio Mixing with FLCKAudioMix

`FLCKAudioMix` combines multiple audio sources into a single stereo output:

```cpp theme={null}
class FLCKAudioMix
{
public:
    void AddSource(TWeakPtr<ILCKAudioSource> AudioSource) noexcept;

    // Get mixed stereo audio for specified channels
    TArray<float> StereoMix(TLCKAudioChannelsMask Channels);

private:
    TArray<TWeakPtr<ILCKAudioSource>> Sources;
    FCriticalSection Mutex;
};
```

### Usage Example

```cpp theme={null}
// Create mixer
FLCKAudioMix AudioMix;

// Add game audio source
TSharedPtr<ILCKAudioSource> GameAudio = GetUnrealAudioSource();
AudioMix.AddSource(GameAudio);

// Add microphone source
TSharedPtr<ILCKAudioSource> Microphone = GetMicrophoneSource();
AudioMix.AddSource(Microphone);

// Get mixed audio (Game + Microphone)
TLCKAudioChannelsMask Channels = 
    ELCKAudioChannel::Game | ELCKAudioChannel::Microphone;

TArray<float> MixedAudio = AudioMix.StereoMix(Channels);

// Pass to encoder
Encoder->EncodeAudio(MixedAudio);
```

***

## Implementing a Custom Audio Source

### Step 1: Create Source Class

```cpp theme={null}
class FMyAudioSource : public ILCKAudioSource
{
public:
    FMyAudioSource()
    {
        // Declare which channels this source supports
        SupportedChannels = ELCKAudioChannel::Game;
        SourceName = TEXT("MyAudioSource");
    }
    
    // ILCKAudioSource interface
    virtual bool StartCapture() noexcept override
    {
        return StartCapture(SupportedChannels);
    }
    
    virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override
    {
        // Only start if requested channels are supported
        if ((SupportedChannels & Channels) != Channels)
        {
            UE_LOG(LogLCK, Warning, TEXT("Requested channels not supported"));
            return false;
        }
        
        // Initialize audio capture
        bIsCapturing = true;
        
        // Start your audio callback thread/system here
        StartAudioThread();
        
        return true;
    }
    
    virtual void StopCapture() noexcept override
    {
        bIsCapturing = false;
        StopAudioThread();
    }
    
    virtual float GetVolume() const noexcept override
    {
        return CurrentVolume;
    }
    
    virtual const FString& GetSourceName() const noexcept override
    {
        return SourceName;
    }
    
private:
    FString SourceName;
    float CurrentVolume = 0.0f;
    bool bIsCapturing = false;
    
    void StartAudioThread() { /* Your implementation */ }
    void StopAudioThread() { /* Your implementation */ }
};
```

***

### Step 2: Fire Audio Delegate

When you have audio data, fire the delegate:

```cpp theme={null}
void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples, int32 NumChannels)
{
    if (!bIsCapturing || !OnAudioDataDelegate.IsBound())
    {
        return;
    }
    
    // Calculate volume (RMS)
    float Sum = 0.0f;
    for (int32 i = 0; i < NumSamples; ++i)
    {
        Sum += Buffer[i] * Buffer[i];
    }
    CurrentVolume = FMath::Sqrt(Sum / NumSamples);
    
    // Fire delegate with audio data
    TArrayView<const float> PCMData(Buffer, NumSamples);
    OnAudioDataDelegate.ExecuteIfBound(
        PCMData,
        NumChannels,
        48000,
        ELCKAudioChannel::Game
    );
}
```

***

### Step 3: Register as Modular Feature

```cpp theme={null}
class FMyAudioModule : public IModuleInterface
{
public:
    virtual void StartupModule() override
    {
        // Create audio source
        AudioSource = MakeShared<FMyAudioSource>();
        
        // Register with modular features
        IModularFeatures::Get().RegisterModularFeature(
            ILCKAudioSource::GetModularFeatureName(),
            AudioSource.Get()
        );
        
        UE_LOG(LogLCK, Log, TEXT("MyAudioSource registered"));
    }
    
    virtual void ShutdownModule() override
    {
        // Unregister
        if (AudioSource.IsValid())
        {
            IModularFeatures::Get().UnregisterModularFeature(
                ILCKAudioSource::GetModularFeatureName(),
                AudioSource.Get()
            );
        }
    }
    
private:
    TSharedPtr<FMyAudioSource> AudioSource;
};

IMPLEMENT_MODULE(FMyAudioModule, MyAudioPlugin)
```

***

## Thread Safety

<Warning>
  Audio callbacks often come from different threads (audio thread, render thread). If you need to access game thread objects (UObject, UI), use `AsyncTask`.
</Warning>

### Thread-Safe Audio Callback

```cpp theme={null}
void FMyAudioSource::OnAudioCallback(float* Buffer, int32 NumSamples)
{
    // This may be called from audio thread, render thread, or any thread
    
    // Option 1: Fire delegate immediately (receiver must be thread-safe)
    if (OnAudioDataDelegate.IsBound())
    {
        TArrayView<const float> PCMData(Buffer, NumSamples);
        OnAudioDataDelegate.Execute(PCMData, 2, 48000, ELCKAudioChannel::Game);
    }
    
    // Option 2: Marshal to game thread (safer for UI updates)
    TArray<float> AudioCopy(Buffer, NumSamples);
    
    AsyncTask(ENamedThreads::GameThread, [this, AudioCopy = MoveTemp(AudioCopy)]()
    {
        if (OnAudioDataDelegate.IsBound())
        {
            OnAudioDataDelegate.Execute(AudioCopy, 2, 48000, ELCKAudioChannel::Game);
        }
    });
}
```

***

### When to Use Each Approach

| Approach                   | Use When                       | Notes                      |
| -------------------------- | ------------------------------ | -------------------------- |
| **Fire immediately**       | Encoder is the only listener   | LCK encoder is thread-safe |
| **Marshal to game thread** | Multiple listeners, UI updates | Safer but adds latency     |

***

## Complete Example: Custom Microphone Source

```cpp theme={null}
class FCustomMicSource : public ILCKAudioSource
{
public:
    FCustomMicSource()
    {
        SupportedChannels = ELCKAudioChannel::Microphone;
        SourceName = TEXT("CustomMicrophone");
    }
    
    virtual bool StartCapture(TLCKAudioChannelsMask Channels) noexcept override
    {
        if ((Channels & ELCKAudioChannel::Microphone) == 0)
        {
            return false;
        }
        
        // Initialize microphone capture (platform-specific)
#if PLATFORM_WINDOWS
        InitializeWASAPI();
#elif PLATFORM_ANDROID
        InitializeOboe();
#endif
        
        bIsCapturing = true;
        return true;
    }
    
    virtual void StopCapture() noexcept override
    {
        bIsCapturing = false;
        ShutdownCapture();
    }
    
    virtual float GetVolume() const noexcept override
    {
        return CurrentVolume;
    }
    
    virtual const FString& GetSourceName() const noexcept override
    {
        return SourceName;
    }
    
private:
    FString SourceName;
    float CurrentVolume = 0.0f;
    bool bIsCapturing = false;
    
    void OnMicrophoneData(float* Buffer, int32 NumSamples)
    {
        if (!bIsCapturing)
        {
            return;
        }
        
        // Calculate RMS volume
        float Sum = 0.0f;
        for (int32 i = 0; i < NumSamples; ++i)
        {
            Sum += Buffer[i] * Buffer[i];
        }
        CurrentVolume = FMath::Sqrt(Sum / NumSamples);
        
        // Fire delegate
        if (OnAudioDataDelegate.IsBound())
        {
            TArrayView<const float> PCM(Buffer, NumSamples);
            OnAudioDataDelegate.Execute(PCM, 1, 48000, ELCKAudioChannel::Microphone);
        }
    }
    
#if PLATFORM_WINDOWS
    void InitializeWASAPI() { /* WASAPI setup */ }
#elif PLATFORM_ANDROID
    void InitializeOboe() { /* Oboe setup */ }
#endif
    
    void ShutdownCapture() { /* Cleanup */ }
};
```

***

## Debugging Audio Sources

### List All Sources

```cpp theme={null}
void ListAudioSources()
{
    TArray<ILCKAudioSource*> Sources = IModularFeatures::Get()
        .GetModularFeatureImplementations<ILCKAudioSource>(
            ILCKAudioSource::GetModularFeatureName()
        );
    
    UE_LOG(LogLCK, Log, TEXT("Found %d audio sources:"), Sources.Num());
    
    for (ILCKAudioSource* Source : Sources)
    {
        TLCKAudioChannelsMask Channels = Source->GetSupportedChannels();
        
        UE_LOG(LogLCK, Log, TEXT("  - %s"), *Source->GetSourceName());
        UE_LOG(LogLCK, Log, TEXT("    Supports Game: %s"), 
            (Channels & ELCKAudioChannel::Game) ? TEXT("Yes") : TEXT("No"));
        UE_LOG(LogLCK, Log, TEXT("    Supports Mic: %s"), 
            (Channels & ELCKAudioChannel::Microphone) ? TEXT("Yes") : TEXT("No"));
        UE_LOG(LogLCK, Log, TEXT("    Supports VoiceChat: %s"), 
            (Channels & ELCKAudioChannel::VoiceChat) ? TEXT("Yes") : TEXT("No"));
    }
}
```

***

### Monitor Audio Levels

```cpp theme={null}
void MonitorAudioLevels()
{
    TArray<ILCKAudioSource*> Sources = GetAllAudioSources();
    
    for (ILCKAudioSource* Source : Sources)
    {
        float Volume = Source->GetVolume();
        UE_LOG(LogLCK, Log, TEXT("%s volume: %.2f"), 
            *Source->GetSourceName(), Volume);
    }
}
```

***

## Key Takeaways

<Check>
  **Single delegate** — Use BindLambda, not AddLambda
</Check>

<Check>
  **Modular features** — Audio sources register at module startup
</Check>

<Check>
  **Channel bitmask** — Sources declare what they support (Game, Mic, VoiceChat)
</Check>

<Check>
  **Thread safety** — Audio callbacks may come from any thread
</Check>

<Check>
  **Audio format** — 32-bit float, interleaved, -1.0 to 1.0 range
</Check>

<Check>
  **FLCKAudioMix** — Combines multiple sources into one output
</Check>

***

## Related

* [Architecture](/api-reference/unreal/architecture) — How audio flows through the system
* [Delegates Reference](/api-reference/unreal/delegates) — FOnRenderAudioDelegate details
* [Module Loading](/api-reference/unreal/module-loading) — Audio plugin priorities
* [Best Practices](/api-reference/unreal/best-practices) — Threading and performance tips
