SCPSL-AudioManagerAPI 1.2.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package SCPSL-AudioManagerAPI --version 1.2.0
                    
NuGet\Install-Package SCPSL-AudioManagerAPI -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SCPSL-AudioManagerAPI" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SCPSL-AudioManagerAPI" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="SCPSL-AudioManagerAPI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SCPSL-AudioManagerAPI --version 1.2.0
                    
#r "nuget: SCPSL-AudioManagerAPI, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SCPSL-AudioManagerAPI@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SCPSL-AudioManagerAPI&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=SCPSL-AudioManagerAPI&version=1.2.0
                    
Install as a Cake Tool

SCPSL-AudioManagerAPI

NuGet Version
A lightweight, reusable C# library for managing audio playback in SCP: Secret Laboratory (SCP:SL) plugins using LabAPI. It provides a robust system for loading, caching, and playing audio through SpeakerToy instances, with centralized controller ID management, advanced audio control (volume, range, spatialization, fading, queuing), and prioritization for enhanced gameplay.

⚠️ Warning
This plugin is currently in active development. New features are being added regularly, and not all functionality has been fully tested in live gameplay.
If you encounter any issues or bugs, please report them on the official GitHub repository.

Features

  • Centralized Controller ID Management: Uses ControllerIdManager to ensure unique speaker IDs (1-255) across plugins, with priority-based eviction and queuing for high-priority audio.
  • LRU Audio Caching: Efficiently manages audio samples with lazy loading and least-recently-used (LRU) eviction via AudioCache.
  • Flexible Speaker Abstraction: Supports custom speaker implementations through ISpeaker, ISpeakerWithPlayerFilter, and ISpeakerFactory interfaces.
  • Advanced Audio Control: Configurable volume (0.0 to 1.0), minimum/maximum distance, spatialization (3D audio), and fade-in/fade-out for precise audio tuning.
  • Audio Queuing: Supports queuing multiple audio clips with optional looping and smooth transitions via fade-in/fade-out.
  • Audio Prioritization: Supports Low, Medium, and High priorities, with queuing for high-priority audio when IDs are limited.
  • Pause/Resume/Skip: Pause, resume, or skip audio clips, including queued clips, for dynamic control.
  • Thread-Safe Operations: Handles concurrent audio playback, caching, and ID allocation safely.
  • LabAPI Compatibility: Optimized for SCP:SL, integrating seamlessly with SpeakerToy for spatial and global audio playback.
  • Default System: Simplifies usage with DefaultAudioManager, offering plug-and-play methods for common audio tasks.

Installation

Install the SCPSL-AudioManagerAPI package via NuGet:

dotnet add package SCPSL-AudioManagerAPI --version 1.2.0

Or, in Visual Studio, use the NuGet Package Manager to search for SCPSL-AudioManagerAPI.

Project Setup

Add the SCPSL-AudioManagerAPI package to your SCP:SL plugin project. Ensure you reference UnityEngine.CoreModule for Vector3, LabApi for SpeakerToy, and MEC for coroutines (used for fading).

Example .csproj snippet:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net48</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="SCPSL-AudioManagerAPI" Version="1.2.0" />
    <Reference Include="LabApi">
      <HintPath>path\to\LabApi.dll</HintPath>
    </Reference>
    <Reference Include="UnityEngine.CoreModule">
      <HintPath>path\to\UnityEngine.CoreModule.dll</HintPath>
    </Reference>
    <Reference Include="MEC">
      <HintPath>path\to\MEC.dll</HintPath>
    </Reference>
  </ItemGroup>
</Project>

Usage

The DefaultAudioManager provides a plug-and-play interface for common audio tasks, ideal for most SCP:SL plugins.

using AudioManagerAPI.Defaults;
using UnityEngine;

// At plugin startup
DefaultAudioManager.RegisterDefaults(cacheSize: 50);

// Register audio
DefaultAudioManager.RegisterAudio("explosionSound", () => Assembly.GetExecutingAssembly().GetManifestResourceStream("MyPlugin.Audio.explosion.wav"));

// Play audio globally with default settings (non-spatial, full volume, no looping)
byte id = DefaultAudioManager.Play("explosionSound");

// Play and queue another clip
DefaultAudioManager.Play("screamSound", queue: true);

// Control playback
DefaultAudioManager.FadeIn(id, 2f); // Fade in over 2 seconds
DefaultAudioManager.Pause(id);
DefaultAudioManager.Resume(id);
DefaultAudioManager.Skip(id, 1); // Skip current clip
DefaultAudioManager.FadeOut(id, 2f); // Fade out and stop
DefaultAudioManager.Stop(id); // Stop and destroy speaker

2. Extension Methods

The SpeakerExtensions class provides extension methods for ISpeaker instances, allowing for easy configuration and lifecycle management.

Configure

The Configure method allows you to set the volume, minimum distance, maximum distance, and spatialization of a speaker, as well as apply custom configurations.

speaker.Configure(volume: 0.8f, minDistance: 5f, maxDistance: 50f, isSpatial: true, configureSpeaker: customConfig);
StartAutoStop

The StartAutoStop method initiates a coroutine that automatically stops and fades out the speaker after a specified lifespan.

speaker.StartAutoStop(controllerId: 1, lifespan: 10f, autoCleanup: true, fadeOutAction: id => audioManager.FadeOutAudio(id, 2f));

These methods can be used in conjunction with the AudioManager or DefaultAudioManager to manage speakers more effectively.

3. Implement ISpeaker and ISpeakerFactory (Advanced)

For custom speaker implementations, create a class compatible with LabAPI's SpeakerToy.

using AudioManagerAPI.Features.Speakers;
using LabApi.Features.Wrappers;
using UnityEngine;

public class LabApiSpeaker : ISpeakerWithPlayerFilter
{
    private readonly SpeakerToy speakerToy;
    private float targetVolume;

    public LabApiSpeaker(SpeakerToy speakerToy)
    {
        this.speakerToy = speakerToy ?? throw new ArgumentNullException(nameof(speakerToy));
        targetVolume = 1f;
    }

    public void Play(float[] samples, bool loop, float playbackPosition = 0f)
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Play(samples, queue: false, loop: loop);
        SetVolume(targetVolume);
    }

    public void Queue(float[] samples, bool loop)
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Play(samples, queue: true, loop: loop);
    }

    public void Stop()
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Stop();
    }

    public void Destroy()
    {
        speakerToy.Destroy();
    }

    public void Pause()
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Pause();
    }

    public void Resume()
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Resume();
    }

    public void Skip(int count)
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        transmitter?.Skip(count);
    }

    public void FadeIn(float duration)
    {
        if (duration > 0)
        {
            Timing.RunCoroutine(FadeVolume(0f, targetVolume, duration));
        }
    }

    public void FadeOut(float duration, Action onComplete = null)
    {
        if (duration > 0)
        {
            Timing.RunCoroutine(FadeVolume(speakerToy.Volume, 0f, duration, stopOnComplete: true, onComplete));
        }
        else
        {
            Stop();
            onComplete?.Invoke();
        }
    }

    public void SetValidPlayers(Func<Player, bool> playerFilter)
    {
        var transmitter = SpeakerToy.GetTransmitter(speakerToy.ControllerId);
        if (transmitter != null)
        {
            transmitter.ValidPlayers = playerFilter;
        }
    }

    public void SetVolume(float volume)
    {
        speakerToy.Volume = Mathf.Clamp01(volume);
        targetVolume = speakerToy.Volume;
    }

    public void SetMinDistance(float minDistance)
    {
        speakerToy.MinDistance = Mathf.Max(0, minDistance);
    }

    public void SetMaxDistance(float maxDistance)
    {
        speakerToy.MaxDistance = Mathf.Max(0, maxDistance);
    }

    public void SetSpatialization(bool isSpatial)
    {
        speakerToy.IsSpatial = isSpatial;
    }

    private IEnumerator<float> FadeVolume(float startVolume, float endVolume, float duration, bool stopOnComplete = false, Action onComplete = null)
    {
        float elapsed = 0f;
        while (elapsed < duration)
        {
            elapsed += Timing.DeltaTime;
            float t = Mathf.Clamp01(elapsed / duration);
            SetVolume(Mathf.Lerp(startVolume, endVolume, t));
            yield return Timing.WaitForOneFrame;
        }
        SetVolume(endVolume);
        if (stopOnComplete)
        {
            Stop();
        }
        onComplete?.Invoke();
    }
}

public class LabApiSpeakerFactory : ISpeakerFactory
{
    public ISpeaker CreateSpeaker(Vector3 position, byte controllerId)
    {
        SpeakerToy speaker = SpeakerToy.Create(position, networkSpawn: true);
        if (speaker == null) return null;
        speaker.ControllerId = controllerId;
        return new LabApiSpeaker(speaker);
    }
}

4. Initialize AudioManager (Advanced)

Create an instance of AudioManager for advanced control.

using AudioManagerAPI;
using AudioManagerAPI.Features.Management;
using System;
using System.Reflection;

public class MyPluginAudioManager
{
    private readonly IAudioManager audioManager;

    public MyPluginAudioManager()
    {
        audioManager = new AudioManager(new LabApiSpeakerFactory(), cacheSize: 20);
        RegisterAudioResources();
    }

    private void RegisterAudioResources()
    {
        var assembly = Assembly.GetExecutingAssembly();
        audioManager.RegisterAudio("myplugin.scream", () => 
            assembly.GetManifestResourceStream("MyPlugin.Audio.scream.wav"));
    }

    public void PlayScream(Vector3 position, Player targetPlayer)
    {
        byte controllerId = audioManager.PlayAudio("myplugin.scream", position, false, 
            volume: 0.8f, minDistance: 5f, maxDistance: 50f, isSpatial: true, priority: AudioPriority.High, speaker =>
            {
                if (speaker is ISpeakerWithPlayerFilter filterSpeaker)
                {
                    filterSpeaker.SetValidPlayers(p => p == targetPlayer);
                }
            });
        if (audioManager.IsValidController(controllerId))
        {
            audioManager.FadeInAudio(controllerId, 1f); // Fade in over 1 second
            Log.Info($"Played scream with controller ID {controllerId}.");
        }
    }

    public void PlayGlobalScream(Vector3 position)
    {
        byte controllerId = audioManager.PlayGlobalAudio("myplugin.scream", false, 
            volume: 0.8f, priority: AudioPriority.High, queue: true);
        if (audioManager.IsValidController(controllerId))
        {
            audioManager.FadeInAudio(controllerId, 1f);
            Log.Info($"Played global scream with controller ID {controllerId}.");
        }
    }
}

5. Manage Speakers

Control audio playback with advanced features.

// Pause and resume
audioManager.PauseAudio(controllerId);
audioManager.ResumeAudio(controllerId);

// Skip clips
audioManager.SkipAudio(controllerId, 1); // Skip current clip

// Fade in/out
audioManager.FadeInAudio(controllerId, 2f); // Fade in over 2 seconds
audioManager.FadeOutAudio(controllerId, 2f); // Fade out and stop over 2 seconds

// Stop and destroy
audioManager.DestroySpeaker(controllerId);

// Cleanup all speakers
audioManager.CleanupAllSpeakers();

Audio Requirements

The AudioCache class processes WAV files with the following specifications:

  • Format: 48kHz, Mono, Signed 16-bit PCM.
  • Header: Expects a standard WAV header; skips the first 44 bytes during loading.
  • Recommendation: Prefix audio keys with your plugin’s namespace (e.g., myplugin.scream) to avoid conflicts with other plugins.

Note: The API has been tested with this configuration, and audio files play correctly. Ensure your WAV files adhere to these specifications for compatibility.

Audio Control and Prioritization

  • Volume: Set between 0.0 (mute) and 1.0 (full volume) to control loudness (SetVolume).
  • MinDistance/MaxDistance: Define the range where audio starts to fall off and drops to zero, in Unity units (SetMinDistance, SetMaxDistance).
  • Spatialization: Enable/disable 3D audio (SetSpatialization) for positional or ambient effects. Non-spatial audio uses Vector3.zero for global playback.
  • Priority: Use AudioPriority (Low, Medium, High) to prioritize critical sounds. High-priority audio can evict lower-priority speakers or be queued if IDs are unavailable.
  • Fading: Smoothly transition volume with FadeIn and FadeOut for immersive effects.
  • Queuing: Queue multiple clips to play sequentially, with optional looping and fade transitions.

API Reference

Key Classes and Interfaces

Name Namespace Description
IAudioManager AudioManagerAPI.Features.Management Defines the contract for audio playback and speaker lifecycle management.
AudioManager AudioManagerAPI.Features.Management Implements audio management with caching and shared controller IDs.
ISpeaker AudioManagerAPI.Features.Speakers Represents a speaker for playing, queuing, pausing, resuming, skipping, and fading audio.
ISpeakerWithPlayerFilter AudioManagerAPI.Features.Speakers Extends ISpeaker to support player-specific audibility, volume, range, and spatialization.
ISpeakerFactory AudioManagerAPI.Features.Speakers Defines a factory for creating speaker instances.
AudioCache AudioManagerAPI.Cache Manages audio samples with LRU eviction and lazy loading.
ControllerIdManager AudioManagerAPI.Controllers Static class for managing unique controller IDs with priority-based eviction and queuing.
AudioPriority AudioManagerAPI.Features.Enums Enum defining audio priority levels (Low, Medium, High).
DefaultAudioManager AudioManagerAPI.Defaults Simplifies audio management with default settings and convenience methods.
DefaultSpeakerToyAdapter AudioManagerAPI.Defaults Default LabAPI SpeakerToy adapter with full feature support.
DefaultSpeakerFactory AudioManagerAPI.Defaults Creates DefaultSpeakerToyAdapter instances for default usage.

Important Methods

  • IAudioManager.RegisterAudio(string key, Func<Stream> streamProvider): Registers a WAV stream for lazy loading.
  • IAudioManager.PlayAudio(string key, Vector3 position, bool loop, float volume, float minDistance, float maxDistance, bool isSpatial, AudioPriority priority, Action<ISpeaker> configureSpeaker, bool queue, bool persistent, float? lifespan, bool autoCleanup): Plays or queues audio with optional configuration.
  • IAudioManager.PlayGlobalAudio(string key, bool loop, float volume, AudioPriority priority, bool queue, float fadeInDuration, bool persistent, float? lifespan, bool autoCleanup): Plays or queues audio globally, audible to all players.
  • IAudioManager.RecoverSpeaker(byte controllerId, bool resetPlayback): Recovers a persistent speaker with saved state.
  • IAudioManager.PauseAudio(byte controllerId): Pauses audio playback.
  • IAudioManager.ResumeAudio(byte controllerId): Resumes paused audio.
  • IAudioManager.SkipAudio(byte controllerId, int count): Skips the current or queued clips.
  • IAudioManager.FadeInAudio(byte controllerId, float duration): Fades in audio volume.
  • IAudioManager.FadeOutAudio(byte controllerId, float duration): Fades out and stops audio.
  • IAudioManager.StopAudio(byte controllerId): Stops audio playback.
  • IAudioManager.DestroySpeaker(byte controllerId, bool forceRemoveState): Destroys a speaker and releases its ID.
  • IAudioManager.CleanupAllSpeakers(): Cleans up all active speakers and releases their IDs.
  • IAudioManager.GetSpeaker(byte controllerId): Retrieves a speaker instance for further configuration.
  • SpeakerExtensions.Configure(ISpeaker, float volume, float minDistance, float maxDistance, bool isSpatial, Action<ISpeaker> configureSpeaker): Configures speaker settings.
  • SpeakerExtensions.StartAutoStop(ISpeaker, byte controllerId, float lifespan, bool autoCleanup, Action<byte> fadeOutAction): Initiates automatic speaker cleanup.

Events

The IAudioManager interface defines several events for tracking audio state changes:

  • OnPlaybackStarted: Invoked when a speaker begins playback.
  • OnPaused: Raised when playback is paused for a given controller ID.
  • OnResumed: Raised when previously paused audio resumes playback.
  • OnStop: Raised when audio playback is explicitly stopped for a given controller ID.
  • OnSkipped: Raised when audio skip logic is invoked for a specified controller ID.
  • OnQueueEmpty: Triggered when the audio queue for a speaker becomes empty.

These events can be used to synchronize UI elements, manage state persistence, or trigger custom logic based on audio events.

Notes

  • Controller ID Synchronization: ControllerIdManager ensures no ID conflicts by maintaining a shared pool of IDs (1-255). High-priority audio can evict lower-priority speakers or be queued for later allocation.
  • Thread Safety: All operations (ID allocation, caching, speaker management) are thread-safe using locks.
  • Dependencies: Requires UnityEngine.CoreModule, LabApi, and MEC. Ensure these are available in your SCP:SL environment.
  • Logging: Uses LabApi.Features.Console.Logger for debugging. Integrate with your plugin’s logging system (e.g., Exiled’s Log) for additional context.
  • Spatial Audio: Use isSpatial: true for positional effects (e.g., screams) and isSpatial: false for ambient sounds (e.g., background music). Non-spatial audio defaults to Vector3.zero for global playback.
  • Fading and Queuing: Use FadeIn/FadeOut for smooth transitions and queue: true to play multiple clips sequentially.
  • Persistent Speakers: Use persistent: true to retain speaker state for recovery after eviction or scene reloads.

Contributing

Contributions are welcome! Please submit issues or pull requests to the GitHub repository.

License

This project is licensed under the GNU Lesser General Public License v3.0 (LGPL3). See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET Framework net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.7.0 86 7/28/2025
1.6.0 58 7/27/2025
1.5.2 205 7/26/2025
1.5.1 208 7/26/2025
1.5.0 435 7/24/2025
1.4.2 438 7/24/2025
1.4.1 432 7/24/2025
1.4.0 435 7/24/2025
1.3.0 437 7/23/2025
1.2.3 483 7/22/2025
1.2.2 483 7/22/2025
1.2.1 488 7/22/2025
1.2.0 487 7/22/2025
1.0.3 484 7/22/2025
1.0.2 433 7/21/2025
1.0.1 430 7/21/2025
1.0.0 434 7/21/2025