From 87dd5019b3a56f52cf980211a9436a45260ea167 Mon Sep 17 00:00:00 2001 From: Tom Spilman Date: Sun, 8 Sep 2024 13:07:19 -0500 Subject: [PATCH] First pass at low level audio API. --- .../Audio/SoundEffectInstance.cs | 26 ++- .../Platform/Native/Audio.Interop.cs | 111 +++++++++++++ .../Platform/Native/SoundEffect.Native.cs | 108 ++++++++++-- .../Native/SoundEffectInstance.Native.cs | 79 +++++---- src/monogame/faudio/MGA_faudio.cpp | 156 ++++++++++++++++++ src/monogame/include/csharp_MGA.h | 40 +++++ src/monogame/include/csharp_enums.h | 7 + src/monogame/premake5.lua | 15 ++ 8 files changed, 475 insertions(+), 67 deletions(-) create mode 100644 src/monogame/faudio/MGA_faudio.cpp create mode 100644 src/monogame/include/csharp_MGA.h diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.cs b/MonoGame.Framework/Audio/SoundEffectInstance.cs index 2983daae51a..3985a154458 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.cs @@ -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; /// Enables or Disables whether the SoundEffectInstance should repeat after playback. /// This value has no effect on an already playing sound. public virtual bool IsLooped - { - get { return PlatformGetIsLooped(); } - set { PlatformSetIsLooped(value); } + { + get { return _isLooped; } + set { _isLooped = value; } } /// Gets or sets the pan, or speaker balance.. @@ -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; } /// @@ -142,11 +138,13 @@ 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; @@ -154,7 +152,7 @@ public virtual void Play() // 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(); diff --git a/MonoGame.Framework/Platform/Native/Audio.Interop.cs b/MonoGame.Framework/Platform/Native/Audio.Interop.cs index 02610291c34..a50c7dc8a4b 100644 --- a/MonoGame.Framework/Platform/Native/Audio.Interop.cs +++ b/MonoGame.Framework/Platform/Native/Audio.Interop.cs @@ -2,6 +2,7 @@ // 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; @@ -9,12 +10,122 @@ namespace MonoGame.Interop; + +[MGHandle] +internal readonly struct MGA_System { } + +[MGHandle] +internal readonly struct MGA_Buffer { } + +[MGHandle] +internal readonly struct MGA_Voice { } + + /// /// MonoGame native calls for platform audio features. /// 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 } diff --git a/MonoGame.Framework/Platform/Native/SoundEffect.Native.cs b/MonoGame.Framework/Platform/Native/SoundEffect.Native.cs index dc021d124d7..68e67334f2a 100644 --- a/MonoGame.Framework/Platform/Native/SoundEffect.Native.cs +++ b/MonoGame.Framework/Platform/Native/SoundEffect.Native.cs @@ -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! } } diff --git a/MonoGame.Framework/Platform/Native/SoundEffectInstance.Native.cs b/MonoGame.Framework/Platform/Native/SoundEffectInstance.Native.cs index 337f2033a22..8123735ff6a 100644 --- a/MonoGame.Framework/Platform/Native/SoundEffectInstance.Native.cs +++ b/MonoGame.Framework/Platform/Native/SoundEffectInstance.Native.cs @@ -3,88 +3,95 @@ // file 'LICENSE.txt', which is part of this source code package. using System; +using MonoGame.Interop; + namespace Microsoft.Xna.Framework.Audio; public partial class SoundEffectInstance : IDisposable { - private void PlatformInitialize(byte[] buffer, int sampleRate, int channels) - { + internal unsafe MGA_Voice* Voice; - } private void PlatformApply3D(AudioListener listener, AudioEmitter emitter) { - + // TODO: Implement me. } - private void PlatformPause() + private unsafe void PlatformPause() { - + if (Voice != null) + MGA.Voice_Pause(Voice); } - private void PlatformPlay() + private unsafe void PlatformPlay() { - + if (Voice != null) + MGA.Voice_Play(Voice, _isLooped); } - private void PlatformResume() + private unsafe void PlatformResume() { - + if (Voice != null) + MGA.Voice_Resume(Voice); } - private void PlatformStop(bool immediate) + private unsafe void PlatformStop(bool immediate) { - + if (Voice != null) + MGA.Voice_Stop(Voice, immediate); } - private void PlatformSetIsLooped(bool value) + private unsafe void PlatformSetPan(float pan) { - + if (Voice != null) + MGA.Voice_SetPan(Voice, pan); } - private bool PlatformGetIsLooped() + private unsafe void PlatformSetPitch(float pitch) { - return false; + if (Voice != null) + MGA.Voice_SetPitch(Voice, pitch); } - private void PlatformSetPan(float value) + private unsafe SoundState PlatformGetState() { + if (Voice != null) + return MGA.Voice_GetState(Voice); - } - - private void PlatformSetPitch(float value) - { - - } - - private SoundState PlatformGetState() - { return SoundState.Stopped; } - private void PlatformSetVolume(float value) + private unsafe void PlatformSetVolume(float volume) { - + if (Voice != null) + MGA.Voice_SetVolume(Voice, volume); } - internal void PlatformSetReverbMix(float mix) + internal unsafe void PlatformSetReverbMix(float mix) { - + // TODO: Implement me. } - internal void PlatformSetFilter(FilterMode mode, float filterQ, float frequency) + internal unsafe void PlatformSetFilter(FilterMode mode, float filterQ, float frequency) { - + // TODO: Implement me. } - internal void PlatformClearFilter() + internal unsafe void PlatformClearFilter() { - + // TODO: Implement me. } - private void PlatformDispose(bool disposing) + private unsafe void PlatformDispose(bool disposing) { - + if (disposing) + { + if (Voice != null) + { + MGA.Voice_Destroy(Voice); + Voice = null; + } + } } } diff --git a/src/monogame/faudio/MGA_faudio.cpp b/src/monogame/faudio/MGA_faudio.cpp new file mode 100644 index 00000000000..730e31c8a6a --- /dev/null +++ b/src/monogame/faudio/MGA_faudio.cpp @@ -0,0 +1,156 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +#include "csharp_MGA.h" + +#include "stl_common.h" + + +// TODO: Implement against the C++ API for FAudio. + +struct MGA_System +{ +}; + +struct MGA_Buffer +{ +}; + +struct MGA_Voice +{ +}; + + +MGA_System* MGA_System_Create() +{ + auto system = new MGA_System(); + return system; +} + +void MGA_System_Destroy(MGA_System* system) +{ + assert(system != nullptr); + + // TODO: We're assuming here the C# side is cleaning up + // buffers/voices, but if we want this to be a good C++ + // API as well, we likely should cleanup ourselves too. + + delete system; +} + +mgint MGA_System_GetMaxInstances() +{ + return INT_MAX; +} + +MGA_Buffer* MGA_Buffer_Create() +{ + auto buffer = new MGA_Buffer(); + return buffer; +} + +void MGA_Buffer_Destroy(MGA_Buffer* buffer) +{ + assert(buffer != nullptr); + delete buffer; +} + +void MGA_Buffer_InitializeFormat(MGA_Buffer* buffer, mgbyte* waveHeader, mgbyte* waveData, mgint length, mgint loopStart, mgint loopLength) +{ + assert(buffer != nullptr); + assert(waveHeader != nullptr); + assert(waveData != nullptr); + assert(length > 0); +} + +void MGA_Buffer_InitializePCM(MGA_Buffer* buffer, mgbyte* waveData, mgint offset, mgint length, mgint sampleBits, mgint sampleRate, mgint channels, mgint loopStart, mgint loopLength) +{ + assert(buffer != nullptr); + assert(waveData != nullptr); + assert(offset >=0); + assert(length > 0); +} + +void MGA_Buffer_InitializeXact(MGA_Buffer* buffer, mguint codec, mgbyte* waveData, mgint length, mgint sampleRate, mgint blockAlignment, mgint channels, mgint loopStart, mgint loopLength) +{ + assert(buffer != nullptr); + assert(waveData != nullptr); + assert(length > 0); +} + +mgulong MGA_Buffer_GetDuration(MGA_Buffer* buffer) +{ + assert(buffer != nullptr); + return 0; +} + +MGA_Voice* MGA_Voice_Create() +{ + auto voice = new MGA_Voice(); + return voice; +} + +void MGA_Voice_Destroy(MGA_Voice* voice) +{ + assert(voice != nullptr); + delete voice; +} + +void MGA_Voice_AppendBuffer(MGA_Voice* voice, MGA_Buffer* buffer, mgbool clear) +{ + assert(voice != nullptr); + + if (clear) + { + // Stop and remove any pending buffers first. + } + + // Append a buffer if we got one. + if (buffer) + { + + } +} + +void MGA_Voice_Play(MGA_Voice* voice, mgbool looped) +{ + assert(voice != nullptr); +} + +void MGA_Voice_Pause(MGA_Voice* voice) +{ + assert(voice != nullptr); +} + +void MGA_Voice_Resume(MGA_Voice* voice) +{ + assert(voice != nullptr); +} + +void MGA_Voice_Stop(MGA_Voice* voice, mgbool immediate) +{ + assert(voice != nullptr); +} + +MGSoundState MGA_Voice_GetState(MGA_Voice* voice) +{ + assert(voice != nullptr); + return MGSoundState::Stopped; +} + +void MGA_Voice_SetPan(MGA_Voice* voice, mgfloat pan) +{ + assert(voice != nullptr); +} + +void MGA_Voice_SetPitch(MGA_Voice* voice, mgfloat pitch) +{ + assert(voice != nullptr); +} + +void MGA_Voice_SetVolume(MGA_Voice* voice, mgfloat volume) +{ + assert(voice != nullptr); +} + diff --git a/src/monogame/include/csharp_MGA.h b/src/monogame/include/csharp_MGA.h new file mode 100644 index 00000000000..69218c7b703 --- /dev/null +++ b/src/monogame/include/csharp_MGA.h @@ -0,0 +1,40 @@ +// MonoGame - Copyright (C) The MonoGame Team +// This file is subject to the terms and conditions defined in +// file 'LICENSE.txt', which is part of this source code package. + +// +// This code is auto generated, don't modify it by hand. +// To regenerate it run: Tools/MonoGame.Generator.CTypes +// + +#pragma once + +#include "csharp_common.h" +#include "csharp_enums.h" +#include "csharp_structs.h" + + +struct MGA_System; +struct MGA_Buffer; +struct MGA_Voice; + +MG_EXPORT MGA_System* MGA_System_Create(); +MG_EXPORT void MGA_System_Destroy(MGA_System* system); +MG_EXPORT mgint MGA_System_GetMaxInstances(); +MG_EXPORT MGA_Buffer* MGA_Buffer_Create(); +MG_EXPORT void MGA_Buffer_Destroy(MGA_Buffer* buffer); +MG_EXPORT void MGA_Buffer_InitializeFormat(MGA_Buffer* buffer, mgbyte* waveHeader, mgbyte* waveData, mgint length, mgint loopStart, mgint loopLength); +MG_EXPORT void MGA_Buffer_InitializePCM(MGA_Buffer* buffer, mgbyte* waveData, mgint offset, mgint length, mgint sampleBits, mgint sampleRate, mgint channels, mgint loopStart, mgint loopLength); +MG_EXPORT void MGA_Buffer_InitializeXact(MGA_Buffer* buffer, mguint codec, mgbyte* waveData, mgint length, mgint sampleRate, mgint blockAlignment, mgint channels, mgint loopStart, mgint loopLength); +MG_EXPORT mgulong MGA_Buffer_GetDuration(MGA_Buffer* buffer); +MG_EXPORT MGA_Voice* MGA_Voice_Create(); +MG_EXPORT void MGA_Voice_Destroy(MGA_Voice* voice); +MG_EXPORT void MGA_Voice_AppendBuffer(MGA_Voice* voice, MGA_Buffer* buffer, mgbool clear); +MG_EXPORT void MGA_Voice_Play(MGA_Voice* voice, mgbool looped); +MG_EXPORT void MGA_Voice_Pause(MGA_Voice* voice); +MG_EXPORT void MGA_Voice_Resume(MGA_Voice* voice); +MG_EXPORT void MGA_Voice_Stop(MGA_Voice* voice, mgbool immediate); +MG_EXPORT MGSoundState MGA_Voice_GetState(MGA_Voice* voice); +MG_EXPORT void MGA_Voice_SetPan(MGA_Voice* voice, mgfloat pan); +MG_EXPORT void MGA_Voice_SetPitch(MGA_Voice* voice, mgfloat pitch); +MG_EXPORT void MGA_Voice_SetVolume(MGA_Voice* voice, mgfloat volume); diff --git a/src/monogame/include/csharp_enums.h b/src/monogame/include/csharp_enums.h index a8a30e36f9f..4af45c3f92f 100644 --- a/src/monogame/include/csharp_enums.h +++ b/src/monogame/include/csharp_enums.h @@ -7,6 +7,13 @@ #include "csharp_common.h" +enum class MGSoundState : mgint +{ + Playing = 0, + Paused = 1, + Stopped = 2, +}; + enum class MGSurfaceFormat : mgint { Color = 0, diff --git a/src/monogame/premake5.lua b/src/monogame/premake5.lua index fd485e88f7d..4d914128cbf 100644 --- a/src/monogame/premake5.lua +++ b/src/monogame/premake5.lua @@ -83,6 +83,20 @@ function vulkan() end + +-- FAudio is supported for all desktop platforms. +function faudio() + + defines { "MG_FAUDIO" } + + files + { + "faudio/**.h", + "faudio/**.cpp", + } + +end + function configs() filter "configurations:Debug" @@ -102,6 +116,7 @@ project "desktopvk" common("desktopvk") sdl2() vulkan() + faudio() configs()