Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
db5c262
bump versions
Axwabo Nov 22, 2025
16fe6da
obsolete ConditionalOneTimeDisposable
Axwabo Nov 22, 2025
ae41788
move PackageVersion
Axwabo Nov 22, 2025
34abf68
begin IAudioProcessor system
Axwabo Nov 22, 2025
3a69fe1
ProcessorChain
Axwabo Nov 22, 2025
3a22dd3
obsolete some methods + rename some stuff + dispose in Pop
Axwabo Nov 22, 2025
f169bb2
more ProcessorChainExtensions + fix docs
Axwabo Nov 22, 2025
ee3c333
use null propagation
Axwabo Nov 22, 2025
b959caf
use file + CreateAudioProcessor + stream properties
Axwabo Nov 22, 2025
901ff22
more extensions
Axwabo Nov 22, 2025
9d3847b
ISeekable + ILoopable
Axwabo Nov 22, 2025
9c3aecb
implement ILoopable & ISeekable in raw providers
Axwabo Nov 22, 2025
567ebdf
improve docs + automatically set OwnsProcessor
Axwabo Nov 22, 2025
18b2865
volume + small fixes
Axwabo Nov 22, 2025
b2c3573
rename Root + fixes + Queue to processor + short clip extensions
Axwabo Nov 22, 2025
fd45231
fix double dispose
Axwabo Nov 22, 2025
7e95cc5
properly dispose in SampleProviderQueue
Axwabo Nov 22, 2025
9071f80
reformat
Axwabo Nov 24, 2025
514d526
simplify release workflow
Axwabo Nov 24, 2025
f3213d0
Extensions.Processors namespace + more mixer methods
Axwabo Nov 24, 2025
fecf37d
move some obsolete stuff
Axwabo Nov 24, 2025
fdfced2
unprocessor things in Providers
Axwabo Nov 25, 2025
62d6ef1
StreamAudioProcessor isOwned
Axwabo Nov 25, 2025
7851a5d
move some extensions + mixer add file + WaveFormat.Time extension + S…
Axwabo Nov 25, 2025
1978ae0
remove Private="false"
Axwabo Nov 25, 2025
7351563
extension block everywhere + fix Mixer
Axwabo Nov 25, 2025
812ff12
don't unwrap MixingSampleProvider + unparent when returning to pool +…
Axwabo Nov 26, 2025
9119200
order AudioPlayer members
Axwabo Nov 26, 2025
7b6743a
TryCreateAudioProcessor + fix AudioQueue
Axwabo Nov 26, 2025
174af8e
fix docs + more global usings
Axwabo Nov 26, 2025
73ffa3d
refactor demo + more extensions
Axwabo Nov 26, 2025
5e08e5b
cleanup
Axwabo Nov 26, 2025
9251b23
group genreic extension blocks in ProcessorChainExtensions
Axwabo Nov 26, 2025
fefc8c5
remove ConvertAndRetrieve
Axwabo Nov 26, 2025
b19232a
fix mixer
Axwabo Nov 26, 2025
9031f13
rename DiscJockeyProcessor + move DJ board check priority
Axwabo Nov 26, 2025
eb2b1ba
fix IsDestroyed check
Axwabo Nov 26, 2025
d2dee3b
re-add DiscJockeySampleProvider + demo readme
Axwabo Nov 26, 2025
7ee9e8a
maxDuration in ShortClipCache + improve provider ownership
Axwabo Nov 27, 2025
7d762fa
move some methods
Axwabo Nov 27, 2025
e1b464a
mix short clips + process delegate
Axwabo Nov 27, 2025
b7065d4
begin ProcessDelegate
Axwabo Nov 27, 2025
a4718a2
rename & move Process delegate
Axwabo Nov 27, 2025
088eb4e
optimize UseMixer + volume support in use methods
Axwabo Dec 1, 2025
40b7b28
begin docs
Axwabo Dec 2, 2025
8453788
more docs
Axwabo Dec 5, 2025
afa27ba
even more docs
Axwabo Dec 9, 2025
516b8d5
obsolete LoopingRawSampleProvider
Axwabo Dec 9, 2025
164ff18
slightly optimize RawSourceSampleProvider read
Axwabo Dec 9, 2025
5fd3580
Revert "slightly optimize RawSourceSampleProvider read"
Axwabo Dec 9, 2025
a1ff6f5
fix RSSP read length check
Axwabo Dec 9, 2025
175f222
fix Swap + more docs
Axwabo Dec 16, 2025
be27470
docs progress
Axwabo Dec 18, 2025
1148b61
remove float extensions
Axwabo Dec 18, 2025
2874ce1
rename ProcessorInputExtensions + docs
Axwabo Dec 20, 2025
e750398
docs progress numero idk
Axwabo Dec 21, 2025
8919a6c
remove IncludeBuildOutput override
Axwabo Dec 24, 2025
457b9bd
use searchOption in ShortClipCache
Axwabo Dec 26, 2025
b3e77dd
docs progress + owned WaveProviderToProcessor
Axwabo Dec 26, 2025
5623fb4
complete ProviderToProcessor docs + SampleProviderToProcessor ownership
Axwabo Dec 27, 2025
dbcbf5b
docs progress + more ShortClipCache methods
Axwabo Jan 2, 2026
0143c0b
complete obsolete messages + player mixer remove extensions
Axwabo Jan 2, 2026
e455a5f
personalization ClearAll + SendSyncVars safeguard
Axwabo Jan 2, 2026
05799f5
fix XmlDocs formatting
Axwabo Jan 2, 2026
b430184
more docs number idk
Axwabo Jan 2, 2026
0237d1e
GloballyAudible SpeakerSettings + remove stage position setter in demo
Axwabo Jan 2, 2026
b5c1786
remove AddProcess paramref
Axwabo Jan 2, 2026
753281a
optimize ProcessorChain.Layers
Axwabo Jan 2, 2026
8fc1e08
include System.ValueTuple in builds
Axwabo Jan 2, 2026
ac74612
final turn before finish line (docs)
Axwabo Jan 3, 2026
a7d2529
finish docs
Axwabo Jan 3, 2026
cd80f20
UseFileSafe
Axwabo Jan 3, 2026
62a7fe7
Enumerable.Repeat
Axwabo Jan 3, 2026
b16c2e1
don't set OwnsProvider to true in OnDisable
Axwabo Jan 3, 2026
77cf17a
implement ISeekable in LoopingWaveProvider
Axwabo Jan 3, 2026
52ce590
Props in Directory.Build.targets
Axwabo Jan 3, 2026
51f9161
rename Inputs.cs
Axwabo Jan 3, 2026
4b59b32
LogError in AudioPlayer::SampleProvider
Axwabo Jan 4, 2026
0166857
WitLivePersonalizedSendEngine overload without personalization component
Axwabo Jan 4, 2026
f6dabb0
rename Play & Stop
Axwabo Jan 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 8 additions & 37 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Release

on:
release:
types: [ created ]
types: [ published ]

permissions:
contents: write
Expand All @@ -11,53 +11,24 @@ jobs:

release:
runs-on: ubuntu-latest

env:
ReferenceUrl: https://exmod-team.github.io/SL-References/Dev.zip
ReferencePath: ${{ github.workspace }}/References

steps:

- uses: actions/checkout@v4

- name: Set Up .NET
uses: actions/setup-dotnet@v4.0.1
with:
dotnet-version: 9.0.x

- name: Download References
run: |
wget -O "References.zip" ${{ env.ReferenceUrl }}
unzip -d ${{ env.ReferencePath }} "References.zip"
- uses: actions/checkout@v6

- name: Rename Assembly-CSharp
working-directory: ${{ env.ReferencePath }}
run: mv Assembly-CSharp-Publicized.dll Assembly-CSharp.dll
- uses: Axwabo/scpsl-references-downloader@v1

- name: Build
run: dotnet build -c:Release -o Output
run: dotnet build -c Release -o Out

- name: Create Bundle
working-directory: Output
run: |
mkdir SecretLabNAudio
mkdir SecretLabNAudio/bin
mkdir SecretLabNAudio/LICENSES

mv SecretLabNAudio.dll SecretLabNAudio/bin
ls | grep -E "^(NAudio|NLayer|NVorbis)" | xargs -I {0} mv {0} SecretLabNAudio/bin

cp -r ../THIRD_PARTY_LICENSES SecretLabNAudio/LICENSES
cp ../LICENSE SecretLabNAudio/LICENSES/SecretLabNAudio.txt

cd SecretLabNAudio
zip -r ../SecretLabNAudio.zip .
working-directory: Out
run: ${{ github.workspace }}/bundle.sh

- name: Upload Assets
uses: softprops/action-gh-release@v2
with:
files: Output/SecretLabNAudio*.dll,Output/SecretLabNAudio*.xml,Output/SecretLabNAudio.zip
files: Out/SecretLabNAudio*.dll,Out/SecretLabNAudio*.xml,Out/SecretLabNAudio.zip

- name: Push to NuGet
working-directory: Output
working-directory: Out
run: dotnet nuget push SecretLabNAudio.*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json
23 changes: 13 additions & 10 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>13</LangVersion>
<LangVersion>14</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<Version>1.0.2</Version>
<Version>2.0.0</Version>
<PackageVersion>2.0.0-alpha1</PackageVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -14,24 +15,26 @@
</ItemGroup>

<ItemGroup>
<Reference Include="mscorlib" Private="false" />
<Reference Include="System" Private="false" />
<Reference Include="System.Core" Private="false" />
<Reference Include="System.Data" Private="false" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
</ItemGroup>

<ItemGroup>
<Reference Include="Assembly-CSharp" Private="false" />
<Reference Include="Mirror" Private="false" />
<Reference Include="UnityEngine.CoreModule" Private="false" />
<Reference Include="Assembly-CSharp" />
<Reference Include="Mirror" />
<Reference Include="UnityEngine.CoreModule" />
</ItemGroup>

<ItemGroup>
<Using Include="System" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.IO" />
<Using Include="LabApi.Features.Wrappers" />
<Using Include="Mirror" />
<Using Include="NAudio.Wave" />
<Using Include="SecretLabNAudio.Core.Extensions" />
<Using Include="UnityEngine" />
<Using Include="UnityEngine.Object" Alias="Object" />
</ItemGroup>
Expand Down
21 changes: 11 additions & 10 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
<Project>
<Import Project="ReferencePath.props.user" Condition="Exists('ReferencePath.props.user')"/>
<Import Project="ReferencePath.props.user" Condition="Exists('ReferencePath.props.user')" />
<PropertyGroup>
<ReferencePath Condition="$(ReferencePath) == ''">$(SL_REFERENCES)</ReferencePath>
<AssemblySearchPaths>$(ReferencePath);$(AssemblySearchPaths)</AssemblySearchPaths>
</PropertyGroup>

<Target Name="NotifyIncompleteSetup" BeforeTargets="PrepareForBuild" Condition="$(ReferencePath) == ''">
<PropertyGroup>
<Template>
&lt;Project&gt;
&lt;PropertyGroup&gt;
&lt;ReferencePath&gt;&lt;/ReferencePath&gt;
&lt;/PropertyGroup&gt;
&lt;/Project&gt;
</Template>
<Props>$(MSBuildThisFileDirectory)ReferencePath.props.user</Props>
<Template><![CDATA[
<Project>
<PropertyGroup>
<ReferencePath></ReferencePath>
</PropertyGroup>
</Project>
]]></Template>
</PropertyGroup>
<WriteLinesToFile File="$(MSBuildThisFileDirectory)ReferencePath.props.user" Lines="$(Template)" Condition="!Exists('$(MSBuildThisFileDirectory)ReferencePath.props.user')" />
<WriteLinesToFile File="$(Props)" Lines="$(Template)" Condition="!Exists($(Props))" />
<Error Text="The ReferencePath property is not specified! Make sure to set the property in the ReferencePath.props.user file." />
</Target>
</Project>
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<ItemGroup>
<PackageVersion Include="NAudio.Core" Version="2.2.1" />
<PackageVersion Include="Northwood.LabAPI" Version="1.1.3" />
<PackageVersion Include="Northwood.LabAPI" Version="1.1.4" />

<PackageVersion Include="NAudio.Wasapi" Version="2.2.1" />

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ This library has a number of open-source dependencies. See [Attributions](ATTRIB
4. Extract the necessary files from the `bin/` directory of the archive as **dependencies**
- `NAudio.Core.dll` is **always required**
- `NLayer` and `NLayer.NAudioSpport.dll` for `.mp3` support (optional)
- `NAudio.Vorbis.dll` and `NVorbis.dll` for `.ogg` support (optional)
- `NAudio.Wasapi.dll` for Media Foundation support (optional, WIndows-only)
- `NAudio.Vorbis.dll`, `NVorbis.dll` and `System.ValueTuple.dll` for `.ogg` support (optional)
- `NAudio.Wasapi.dll` for Media Foundation support (optional, Windows-only)
5. Optionally download the necessary **plugin(s)** from the releases page
- `SecretLabNAudio.NLayer.dll` for `.mp3` support
- `SecretLabNAudio.NVorbis.dll` for `.ogg` support
Expand Down
4 changes: 3 additions & 1 deletion SecretLabNAudio.Core/AudioPlayer.Create.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace SecretLabNAudio.Core;
using SecretLabNAudio.Core.Extensions;

namespace SecretLabNAudio.Core;

public partial class AudioPlayer
{
Expand Down
98 changes: 65 additions & 33 deletions SecretLabNAudio.Core/AudioPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using SecretLabNAudio.Core.Providers;
using SecretLabNAudio.Core.Extensions;
using SecretLabNAudio.Core.Providers;
using SecretLabNAudio.Core.SendEngines;
using VoiceChat.Codec;
using VoiceChat.Codec.Enums;
using VoiceChat.Networking;

namespace SecretLabNAudio.Core;

Expand All @@ -14,23 +14,60 @@ public sealed partial class AudioPlayer : MonoBehaviour

private static readonly byte[] EncoderBuffer = new byte[1024];

private ISampleProvider? _sampleProvider;

/// <summary>The provider this player will read from. Set to null to skip updates.</summary>
/// <exception cref="ArgumentException"><inheritdoc cref="ThrowIfIncompatible" path="exception"/></exception>
/// <exception cref="ArgumentException">
/// Thrown when the given sample provider is not null and does not match the following criteria:
/// <para>
/// Encoding = <see cref="WaveFormatEncoding.IeeeFloat"/><br/>
/// Sample Rate = <see cref="SampleRate"/><br/>
/// Channels = <see cref="Channels"/>
/// </para>
/// </exception>
/// <remarks>Setting the provider changes the value of <see cref="OwnsProvider"/> to whether the given provider is an <see cref="IAudioProcessor"/>.</remarks>
/// <seealso cref="AudioPlayerExtensions.Use"/>
/// <seealso cref="AudioPlayerExtensions.UseFile(AudioPlayer,string,bool,float)"/>
/// <seealso cref="AudioPlayerExtensions.UseShortClip(AudioPlayer,string,bool,float)"/>
/// <seealso cref="AudioPlayerExtensions.UseMixer(AudioPlayer,bool)"/>
/// <seealso cref="AudioPlayerExtensions.UseQueue(AudioPlayer)"/>
/// <seealso cref="AudioPlayerExtensions.WithUnmanagedProvider(AudioPlayer,ISampleProvider)"/>
public ISampleProvider? SampleProvider
{
get => _sampleProvider;
get;
set
{
if (value == field)
return;
ThrowIfIncompatible(value);
_sampleProvider = value;
try
{
if (OwnsProvider)
(field as IDisposable)?.Dispose();
}
catch (Exception e)
{
Debug.LogError(e);
}

field = value;
OwnsProvider = value is IAudioProcessor;
}
}

/// <summary>Whether to dispose of the <see cref="SampleProvider"/> when the provider is changed or this component is pooled/destroyed.</summary>
/// <remarks>This property is automatically set when the <see cref="SampleProvider"/> changes.</remarks>
public bool OwnsProvider { get; set; }

/// <summary>The <see cref="SpeakerToy"/> this player is attached to.</summary>
public SpeakerToy Speaker { get; private set; } = null!;

/// <summary>The controller ID of this player.</summary>
/// <seealso cref="SpeakerToy.ControllerId"/>
public byte Id
{
get => Speaker.ControllerId;
set => Speaker.ControllerId = value;
}

/// <summary>
/// The <see cref="SendEngine"/> used to broadcast audio messages.
/// If null, encoding and broadcasting is skipped.
Expand All @@ -53,17 +90,15 @@ public ISampleProvider? SampleProvider
/// <summary>True if playback has finished on the previous frame (fewer samples were read than requested).</summary>
public bool HasEnded { get; private set; }

/// <summary>The controller ID of this player.</summary>
/// <seealso cref="SpeakerToy.ControllerId"/>
public byte Id
{
get => Speaker.ControllerId;
set => Speaker.ControllerId = value;
}
/// <summary>
/// If true, the <see cref="SampleProvider"/> will be read from continuously.
/// If false, the <see cref="SampleProvider"/> will be set to null upon reaching its end.
/// </summary>
public bool AlwaysRead { get; set; } = true;

/// <summary>Invoked every frame when no samples were read from the <see cref="SampleProvider"/>.</summary>
/// <remarks>The provider is not set to null by default.</remarks>
/// <seealso cref="AudioPlayerExtensions.UnsetProviderOnEnd"/>
/// <seealso cref="AlwaysRead"/>
/// <seealso cref="HasEnded"/>
public event Action? NoSamplesRead;

Expand Down Expand Up @@ -102,19 +137,11 @@ private void OnDisable()
SampleProvider = null;
SendEngine = SendEngine.DefaultEngine;
OutputMonitor = null;
AlwaysRead = true;
_remainingTime = 0;
}

private void OnDestroy()
{
SampleProvider = null;
SendEngine = null;
OutputMonitor = null;
_encoder.Dispose();
Destroyed?.Invoke();
NoSamplesRead = null;
Destroyed = null;
}
private void OnDestroy() => _encoder.Dispose();

private void ProcessPacket()
{
Expand All @@ -131,10 +158,7 @@ private void ProcessPacket()

if (read == 0)
{
HasEnded = true;
ClearBuffer();
OutputMonitor?.OnEmpty();
NoSamplesRead?.Invoke();
End();
return;
}

Expand All @@ -150,20 +174,28 @@ private void ProcessPacket()
OutputMonitor?.OnRead(ReadBuffer.AsSpan()[..read]);
if (SendEngine == null)
return;
#pragma warning disable CS0618
if (MasterAmplification is not 1f)
for (var i = 0; i < read; i++)
ReadBuffer[i] *= MasterAmplification;
#pragma warning restore CS0618
var encoded = _encoder.Encode(ReadBuffer, EncoderBuffer);
SendEngine.Broadcast(new AudioMessage(Id, EncoderBuffer, encoded));
}

/// <summary>Resets the amount of samples to send and clears the <see cref="SampleProvider"/>'s buffer if it's a <see cref="BufferedSampleProvider"/>.</summary>
private void End()
{
HasEnded = true;
ClearBuffer();
OutputMonitor?.OnEmpty();
NoSamplesRead?.Invoke();
if (!AlwaysRead)
SampleProvider = null;
}

/// <summary>Resets the amount of samples to send and clears the buffer of the single <see cref="BufferedSampleProvider"/> input (if present).</summary>
public void ClearBuffer()
{
_remainingTime = 0;
(SampleProvider as BufferedSampleProvider)?.Clear();
this.SingleInputAs<BufferedSampleProvider>()?.Clear();
}

/// <summary>Destroys the player and its <see cref="Speaker"/>.</summary>
Expand Down
Loading