Skip to content

Commit

Permalink
First pass at low level audio API.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomspilman committed Sep 8, 2024
1 parent 871a61f commit 87dd501
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 67 deletions.
26 changes: 12 additions & 14 deletions MonoGame.Framework/Audio/SoundEffectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ public partial class SoundEffectInstance : IDisposable
internal SoundEffect _effect;
private float _pan;
private float _volume;
private float _pitch;
private float _pitch;
private bool _isLooped;

/// <summary>Enables or Disables whether the SoundEffectInstance should repeat after playback.</summary>
/// <remarks>This value has no effect on an already playing sound.</remarks>
public virtual bool IsLooped
{
get { return PlatformGetIsLooped(); }
set { PlatformSetIsLooped(value); }
{
get { return _isLooped; }
set { _isLooped = value; }
}

/// <summary>Gets or sets the pan, or speaker balance..</summary>
Expand Down Expand Up @@ -94,13 +95,8 @@ internal SoundEffectInstance()
{
_pan = 0.0f;
_volume = 1.0f;
_pitch = 0.0f;
}

internal SoundEffectInstance(byte[] buffer, int sampleRate, int channels)
: this()
{
PlatformInitialize(buffer, sampleRate, channels);
_pitch = 0.0f;
_isLooped = false;
}

/// <summary>
Expand Down Expand Up @@ -142,19 +138,21 @@ public virtual void Play()
{
if (_isDisposed)
throw new ObjectDisposedException("SoundEffectInstance");

var state = State;

if (State == SoundState.Playing)
if (state == SoundState.Playing)
return;

if (State == SoundState.Paused)
if (state == SoundState.Paused)
{
Resume();
return;
}

// We don't need to check if we're at the instance play limit
// if we're resuming from a paused state.
if (State != SoundState.Paused)
if (state != SoundState.Paused)
{
if (!SoundEffectInstancePool.SoundsAvailable)
throw new InstancePlayLimitException();
Expand Down
111 changes: 111 additions & 0 deletions MonoGame.Framework/Platform/Native/Audio.Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,130 @@
// This file is subject to the terms and conditions defined in
// file 'LICENSE.txt', which is part of this source code package.

using Microsoft.Xna.Framework.Audio;
using System;
using System.Runtime.InteropServices;


namespace MonoGame.Interop;



[MGHandle]
internal readonly struct MGA_System { }

[MGHandle]
internal readonly struct MGA_Buffer { }

[MGHandle]
internal readonly struct MGA_Voice { }


/// <summary>
/// MonoGame native calls for platform audio features.
/// </summary>
internal static unsafe partial class MGA
{
const string MonoGameNativeDLL = "monogame.native";

#region System

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void NativeFinishedCallback(nint callbackData);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_System_Create", StringMarshalling = StringMarshalling.Utf8)]
public static partial MGA_System* System_Create();

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_System_Destroy", StringMarshalling = StringMarshalling.Utf8)]
public static partial void System_Destroy(MGA_System* system);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_System_GetMaxInstances", StringMarshalling = StringMarshalling.Utf8)]
public static partial int System_GetMaxInstances();

#endregion

#region Buffer

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_Create", StringMarshalling = StringMarshalling.Utf8)]
public static partial MGA_Buffer* Buffer_Create();

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_Destroy", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Buffer_Destroy(MGA_Buffer* buffer);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_InitializeFormat", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Buffer_InitializeFormat(
MGA_Buffer* buffer,
byte[] waveHeader,
byte[] waveData,
int length,
int loopStart,
int loopLength);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_InitializePCM", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Buffer_InitializePCM(
MGA_Buffer* buffer,
byte[] waveData,
int offset,
int length,
int sampleBits,
int sampleRate,
int channels,
int loopStart,
int loopLength);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_InitializeXact", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Buffer_InitializeXact(
MGA_Buffer* buffer,
uint codec,
byte[] waveData,
int length,
int sampleRate,
int blockAlignment,
int channels,
int loopStart,
int loopLength);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Buffer_GetDuration", StringMarshalling = StringMarshalling.Utf8)]
public static partial ulong Buffer_GetDuration(MGA_Buffer* buffer);

#endregion

#region Voice

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Create", StringMarshalling = StringMarshalling.Utf8)]
public static partial MGA_Voice* Voice_Create();

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Destroy", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_Destroy(MGA_Voice* voice);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_AppendBuffer", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_AppendBuffer(MGA_Voice* voice, MGA_Buffer* buffer, [MarshalAs(UnmanagedType.U1)] bool clear);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Play", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_Play(MGA_Voice* voice, [MarshalAs(UnmanagedType.U1)] bool looped);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Pause", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_Pause(MGA_Voice* voice);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Resume", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_Resume(MGA_Voice* voice);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_Stop", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_Stop(MGA_Voice* voice, [MarshalAs(UnmanagedType.U1)] bool immediate);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_GetState", StringMarshalling = StringMarshalling.Utf8)]
public static partial SoundState Voice_GetState(MGA_Voice* voice);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_SetPan", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_SetPan(MGA_Voice* voice, float pan);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_SetPitch", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_SetPitch(MGA_Voice* voice, float pitch);

[LibraryImport(MonoGameNativeDLL, EntryPoint = "MGA_Voice_SetVolume", StringMarshalling = StringMarshalling.Utf8)]
public static partial void Voice_SetVolume(MGA_Voice* voice, float volume);

#endregion
}


108 changes: 91 additions & 17 deletions MonoGame.Framework/Platform/Native/SoundEffect.Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,129 @@

using System;
using System.IO;
using MonoGame.Interop;


namespace Microsoft.Xna.Framework.Audio;

public sealed partial class SoundEffect
{
internal const int MAX_PLAYING_INSTANCES = int.MaxValue;
internal static readonly int MAX_PLAYING_INSTANCES = MGA.System_GetMaxInstances();

private void PlatformLoadAudioStream(Stream s, out TimeSpan duration)
internal static unsafe MGA_System* System;

internal unsafe MGA_Buffer* Buffer;

private unsafe static void PlatformInitialize()
{
duration = TimeSpan.Zero;
System = MGA.System_Create();
}

private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sampleBits, int sampleRate, AudioChannels channels, int loopStart, int loopLength)
internal unsafe static void PlatformShutdown()
{

if (System != null)
{
MGA.System_Destroy(System);
System = null;
}
}

private void PlatformInitializeFormat(byte[] header, byte[] buffer, int bufferSize, int loopStart, int loopLength)
private void PlatformLoadAudioStream(Stream s, out TimeSpan duration)
{

using (var reader = new BinaryReader(s))
{
var riff = reader.ReadBytes(4);
reader.ReadBytes(8);

byte[] waveData = null;
byte[] headerData = null;
byte[] dpdsData = null;

// Read chunks.
for (;;)
{
var name = reader.ReadBytes(4);
var len = reader.ReadInt32();
if (len == -1)
break;

var isData = name[0] == 'd' && name[1] == 'a' && name[2] == 't' && name[3] == 'a';
var isFormat = name[0] == 'f' && name[1] == 'm' && name[2] == 't' && name[3] == ' ';
var isDpDs = name[0] == 'd' && name[1] == 'p' && name[2] == 'd' && name[3] == 's';

if (isData)
waveData = reader.ReadBytes(len);
else if (isFormat)
headerData = reader.ReadBytes(len);
else if (isDpDs)
dpdsData = reader.ReadBytes(len);
else
reader.ReadBytes(len);

if (waveData != null && headerData != null)
break;
}

unsafe
{
Buffer = MGA.Buffer_Create();
MGA.Buffer_InitializeFormat(Buffer, headerData, waveData, waveData.Length, 0, 0);

var milliseconds = MGA.Buffer_GetDuration(Buffer);
duration = TimeSpan.FromMilliseconds(milliseconds);
}
}
}

private void PlatformInitializeXact(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength, out TimeSpan duration)
private unsafe void PlatformInitializePcm(byte[] buffer, int offset, int count, int sampleBits, int sampleRate, AudioChannels channels, int loopStart, int loopLength)
{
duration = TimeSpan.Zero;
Buffer = MGA.Buffer_Create();
MGA.Buffer_InitializePCM(Buffer, buffer, offset, count, sampleBits, sampleRate, (int)channels, loopStart, loopLength);
}

private void PlatformSetupInstance(SoundEffectInstance instance)
private unsafe void PlatformInitializeFormat(byte[] header, byte[] buffer, int bufferSize, int loopStart, int loopLength)
{

Buffer = MGA.Buffer_Create();
MGA.Buffer_InitializeFormat(Buffer, header, buffer, bufferSize, loopStart, loopLength);
}

private void PlatformDispose(bool disposing)
private unsafe void PlatformInitializeXact(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength, out TimeSpan duration)
{
// This is only the platform specific non-streaming
// Xact sound handling as PCM is already handled.

Buffer = MGA.Buffer_Create();
MGA.Buffer_InitializeXact(Buffer, (uint)codec, buffer, buffer.Length, sampleRate, blockAlignment, channels, loopStart, loopLength);

var milliseconds = MGA.Buffer_GetDuration(Buffer);
duration = TimeSpan.FromMilliseconds(milliseconds);
}

internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings)
private unsafe void PlatformSetupInstance(SoundEffectInstance instance)
{
// If the instance came from the pool then it could
// already have a valid voice assigned.

if (instance.Voice == null)
instance.Voice = MGA.Voice_Create();

MGA.Voice_AppendBuffer(instance.Voice, Buffer, true);
}

private static void PlatformInitialize()
private unsafe void PlatformDispose(bool disposing)
{

if (disposing)
{
if (Buffer != null)
{
MGA.Buffer_Destroy(Buffer);
Buffer = null;
}
}
}

internal static void PlatformShutdown()
internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings)
{

// TODO!
}
}
Loading

0 comments on commit 87dd501

Please sign in to comment.