Skip to content

Commit

Permalink
WIP: Audio subsystem.
Browse files Browse the repository at this point in the history
  • Loading branch information
Spectere committed Apr 8, 2024
1 parent 7d4bf29 commit 11a4593
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Spectere.SdlKit.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=Keymod/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Keysym/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=libc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=memset/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=msvcrt/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nanosleep/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=nsec/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nunchuk/@EntryIndexedValue">True</s:Boolean>
Expand Down
64 changes: 64 additions & 0 deletions Spectere.SdlKit/Audio.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Spectere.SdlKit.Interop;
using Spectere.SdlKit.Interop.LibC;

namespace Spectere.SdlKit;

/// <summary>
/// Provides a means for an application to play audio via SDL.
/// </summary>
public class Audio {
/// <summary>
/// The audio buffer that the application should fill in for us.
/// </summary>
private float[] _buffer = Array.Empty<float>();

/// <summary>
/// A field that will point to the audio callback function in this class.
/// </summary>
private Interop.Sdl.Audio.SdlAudioCallback _callback;

/// <summary>
/// Raised when the audio subsystem is ready to receive more audio.
/// </summary>
public delegate void AudioRequestedEventHandler(object sender, int samplesRequested, ref float[] buffer);

/// <summary>
/// Raised when the audio subsystem is ready to receive more audio.
/// </summary>
public event AudioRequestedEventHandler? AudioRequested;

/// <summary>
/// If this is set to <c>true</c>, the audio buffer will be initialized to 0 when passed to the application. If
/// this is <c>false</c>, the buffer will not be initialized and may contain stale data. This defaults to
/// <c>false</c>.
/// </summary>
public bool ClearBufferOnCallback { get; set; }

/// <summary>
///
/// </summary>
public Audio() {
LibraryResolver.SetSdlKitLibraryResolver(); // Required for memset.

_callback = SdlCallback;
}

/// <summary>
/// The SDL audio callback function.
/// </summary>
/// <param name="userdata">Custom user data. This is not used by SdlKit.</param>
/// <param name="stream">A pointer to the audio stream that should be filled by the application.</param>
/// <param name="len">The amount of data to fill the stream with, in bytes.</param>
private unsafe void SdlCallback(IntPtr userdata, IntPtr stream, int len) {
if(AudioRequested is null) {
// No subscriber. Fill the stream with silence.
CMemory.Memset(stream.ToPointer(), 0, (nuint)len);
return;
}

var samples = 0; // TODO: Get the number of samples that need filled. Resize the buffer array if necessary.
AudioRequested(this, samples, ref _buffer);

// TODO: Convert the audio stream from float to the desired format, then copy to 'stream'.
}
}
19 changes: 19 additions & 0 deletions Spectere.SdlKit/Interop/LibC/CMemory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Runtime.InteropServices;

namespace Spectere.SdlKit.Interop.LibC;

/// <summary>
/// Contains C library functions related to memory.
/// </summary>
internal class CMemory {
/// <summary>
/// Fill memory with a constant byte.
/// </summary>
/// <param name="s">A pointer to the memory area to fill.</param>
/// <param name="c">The character to fill the block of memory with.</param>
/// <param name="n">The number of bytes to fill.</param>
/// <returns>A pointer to the memory area <paramref name="s"/>.</returns>
// Possible caveat: nuint isn't always equal to size_t.
[DllImport(Lib.LibC, EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl)]
internal static extern unsafe void* Memset(void* s, int c, nuint n);
}
49 changes: 49 additions & 0 deletions Spectere.SdlKit/Interop/LibraryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Reflection;
using System.Runtime.InteropServices;

namespace Spectere.SdlKit.Interop;

/// <summary>
/// Contains helper methods related to native library resolution.
/// </summary>
internal static class LibraryResolver {
/// <summary>
/// If this is <c>true</c>, the custom resolver has already been set. Future calls to
/// <see cref="SetSdlKitLibraryResolver"/> will immediately return.
/// </summary>
private static bool _resolverSet;

/// <summary>
/// Configures the .NET runtime to use SDLKit's native library resolver. This must be called prior to calling any
/// native functions that are marked as requiring this resolver.
/// </summary>
internal static void SetSdlKitLibraryResolver() {
if(_resolverSet) {
return;
}

NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), SdlKitDllImportResolver);
_resolverSet = true;
}

/// <summary>
/// SDLKit's custom DllImport resolver. This is required to handle cases where required libraries are named
/// differently on different operating systems (for example, the C runtime being called "msvcrt" on Windows and
/// "libc" on Linux/Unix systems.
/// </summary>
/// <param name="libraryName">The name of the native library to load.</param>
/// <param name="assembly">The assembly loading the native library.</param>
/// <param name="searchPath">The search path.</param>
/// <returns>The OS handle for the loaded library.</returns>
private static IntPtr SdlKitDllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) {
if(libraryName == "c") {
// libc is "msvcrt" on Windows.
if(OperatingSystem.IsWindows()) {
return NativeLibrary.Load("msvcrt", assembly, searchPath);
}
}

// Fall back to the default resolver.
return IntPtr.Zero;
}
}

0 comments on commit 11a4593

Please sign in to comment.