SCPSL-AudioManagerAPI
1.2.0
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
<PackageReference Include="SCPSL-AudioManagerAPI" Version="1.2.0" />
<PackageVersion Include="SCPSL-AudioManagerAPI" Version="1.2.0" />
<PackageReference Include="SCPSL-AudioManagerAPI" />
paket add SCPSL-AudioManagerAPI --version 1.2.0
#r "nuget: SCPSL-AudioManagerAPI, 1.2.0"
#:package SCPSL-AudioManagerAPI@1.2.0
#addin nuget:?package=SCPSL-AudioManagerAPI&version=1.2.0
#tool nuget:?package=SCPSL-AudioManagerAPI&version=1.2.0
SCPSL-AudioManagerAPI
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
, andISpeakerFactory
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
1. Using DefaultAudioManager (Recommended)
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 usesVector3.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
andFadeOut
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
, andMEC
. 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’sLog
) for additional context. - Spatial Audio: Use
isSpatial: true
for positional effects (e.g., screams) andisSpatial: false
for ambient sounds (e.g., background music). Non-spatial audio defaults toVector3.zero
for global playback. - Fading and Queuing: Use
FadeIn
/FadeOut
for smooth transitions andqueue: 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 | Versions Compatible and additional computed target framework versions. |
---|---|
.NET Framework | net48 is compatible. net481 was computed. |
-
.NETFramework 4.8
- Northwood.LabAPI (>= 1.1.0)
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 |