diff --git a/DaggerFramework.csproj b/DaggerFramework.csproj index 4bd54b8..0cef74e 100644 --- a/DaggerFramework.csproj +++ b/DaggerFramework.csproj @@ -10,5 +10,6 @@ + \ No newline at end of file diff --git a/DaggerFramework.sln b/DaggerFramework.sln deleted file mode 100644 index bb32f59..0000000 --- a/DaggerFramework.sln +++ /dev/null @@ -1,14 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Source/Audio/AudioBackend.cs b/Source/Audio/AudioBackend.cs index 38714dd..9c520e0 100644 --- a/Source/Audio/AudioBackend.cs +++ b/Source/Audio/AudioBackend.cs @@ -1,29 +1,35 @@ namespace DaggerFramework.Audio { - public abstract class AudioBackend + public abstract class AudioBackend : IDisposable { public abstract void Initialize(); public abstract void Update(); + public abstract void Shutdown(); // BUS public abstract void CreateBus(string busName); public abstract void SetBusVolume(string busName, float volume); public abstract float GetBusVolume(string busName); // SOUND - protected abstract void PlaySound(Sound sound, string bus = "Master", float pitch = 1.0f, float volume = 1.0f); - public void PlaySound(Sound sound, string bus = "Master") => PlaySound(sound, bus, sound.PitchScale, sound.Volume); - public void PlaySoundVariation(Sound sound, string bus = "Master", float pitchVariation = 0.1f) + public abstract void PlaySound(Sound sound, string bus = "Master", float pitch = 1.0f, float volume = 1.0f); + public void PlaySound(Sound sound, string bus = "Master") => PlaySound(sound, bus, default, default); + public void PlaySoundVariation(Sound sound, string bus = "Master", float pitchVariation = 0.1f, float volume = 1.0f) { - var maxPitch = sound.PitchScale + pitchVariation; - var minPitch = sound.PitchScale - pitchVariation; + var maxPitch = 1.0f + pitchVariation; + var minPitch = 1.0f - pitchVariation; var pitch = (float)_random.NextDouble() * (maxPitch - minPitch) + minPitch; - PlaySound(sound, bus, pitch, sound.Volume); + PlaySound(sound, bus, pitch, volume); } // EFFECTS public abstract void AddBusEffect(T effect, string bus = "Master") where T : AudioEffect; + public void Dispose() + { + Shutdown(); + } + private LehmerRandom _random = new LehmerRandom(); } } \ No newline at end of file diff --git a/Source/Audio/DummyAudioBackend.cs b/Source/Audio/DummyAudioBackend.cs index 11c7410..fdff592 100644 --- a/Source/Audio/DummyAudioBackend.cs +++ b/Source/Audio/DummyAudioBackend.cs @@ -27,12 +27,17 @@ namespace DaggerFramework.Audio return; } + public override void Shutdown() + { + return; + } + public override void Update() { return; } - protected override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1) + public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1) { return; } diff --git a/Source/Audio/OpenALAudioBackend.cs b/Source/Audio/OpenALAudioBackend.cs new file mode 100644 index 0000000..d7744e9 --- /dev/null +++ b/Source/Audio/OpenALAudioBackend.cs @@ -0,0 +1,125 @@ +using Silk.NET.OpenAL; + +namespace DaggerFramework.Audio +{ + public class OpenALAudioBackend : AudioBackend + { + public unsafe override void Initialize() + { + _alc = ALContext.GetApi(); + _al = AL.GetApi(); + _alDevice = _alc.OpenDevice(""); + + _context = _alc.CreateContext(_alDevice, null); + _alc.MakeContextCurrent(_context); + } + + public override void Update() + { + } + + public unsafe override void Shutdown() + { + _alc.CloseDevice(_alDevice); + _al.Dispose(); + _alc.Dispose(); + } + + public override void AddBusEffect(T effect, string bus = "Master") + { + throw new NotImplementedException(); + } + + public override void CreateBus(string busName) + { + throw new NotImplementedException(); + } + + public override float GetBusVolume(string busName) + { + throw new NotImplementedException(); + } + + public override void SetBusVolume(string busName, float volume) + { + throw new NotImplementedException(); + } + + public override void PlaySound(Sound sound, string bus = "Master", float pitch = 1, float volume = 1) + { + ALSound alSound; + + if (_alSoundsForDaggerSounds.ContainsKey(sound)) + { + alSound = _alSoundsForDaggerSounds[sound]; + PlayALSound(alSound, pitch); + return; + } + + alSound = CreateALSound(sound); + _alSoundsForDaggerSounds.Add(sound, alSound); + + PlayALSound(alSound, pitch); + } + + private void PlayALSound(ALSound sound, float pitch, float volume = 1.0f) + { + _al.SetSourceProperty(sound.SourceHandle, SourceFloat.Pitch, pitch); + // TODO: Add gain. + // _al.SetSourceProperty(sound.SourceHandle, SourceFloat.Gain, 0.0f); + _al.SourcePlay(sound.SourceHandle); + } + + private unsafe ALSound CreateALSound(Sound sound) + { + ALSound result; + uint source = _al.GenSource(); + uint buffer = _al.GenBuffer(); + + fixed (byte* pData = sound.Buffer) + { + BufferFormat bufferFormat = BufferFormat.Stereo16; + + if (sound.Format == SoundFormat.Mono) + { + bufferFormat = BufferFormat.Mono16; + } + else if (sound.Format == SoundFormat.Stereo) + { + bufferFormat = BufferFormat.Stereo16; + } + + _al.BufferData(buffer, bufferFormat, pData, sound.BufferSize, sound.BufferSize); + } + + result = new ALSound(buffer, source); + + _al.SetSourceProperty(source, SourceInteger.Buffer, buffer); + return result; + } + + private void DeleteALSound(ALSound sound) + { + _al.DeleteSource(sound.SourceHandle); + _al.DeleteBuffer(sound.BufferHandle); + } + + private Dictionary _alSoundsForDaggerSounds = new(); + + private ALContext _alc; + private AL _al; + private unsafe Device* _alDevice; + private unsafe Context* _context; + + private struct ALSound + { + public uint BufferHandle { get; set; } + public uint SourceHandle { get; set; } + public ALSound(uint bufferHandle, uint sourceHandle) + { + BufferHandle = bufferHandle; + SourceHandle = sourceHandle; + } + } + } +} \ No newline at end of file diff --git a/Source/Resources/Loaders/SoundLoader.cs b/Source/Resources/Loaders/SoundLoader.cs index f5d564b..109ab50 100644 --- a/Source/Resources/Loaders/SoundLoader.cs +++ b/Source/Resources/Loaders/SoundLoader.cs @@ -1,15 +1,47 @@ +using StbVorbisSharp; + namespace DaggerFramework { public class SoundLoader : ResourceLoader { public override Sound Load(string path) { - // TODO - // var data = File.ReadAllBytes(path); - var sound = new Sound(path, new byte[] { }); - sound.Path = path; + Vorbis vorbis; + Sound result; - return sound; + var fileBuffer = File.ReadAllBytes(path); + vorbis = Vorbis.FromMemory(fileBuffer); + + vorbis.SubmitBuffer(); + + if (vorbis.Decoded == 0) + { + vorbis.Restart(); + vorbis.SubmitBuffer(); + } + + var audioShort = vorbis.SongBuffer; + int length = vorbis.Decoded * vorbis.Channels; + byte[] audioData = new byte[length * 2]; + + for (int i = 0; i < length; i++) + { + if (i * 2 >= audioData.Length) break; + + var b1 = (byte)(audioShort[i] >> 8); + var b2 = (byte)(audioShort[i] & 256); + + audioData[i * 2] = b2; + audioData[i * 2 + 1] = b1; + } + + result = new Sound(path, audioData); + result.Format = (SoundFormat)vorbis.Channels - 1; + result.SampleRate = vorbis.SampleRate; + result.BufferSize = length; + + vorbis.Dispose(); + return result; } } } \ No newline at end of file diff --git a/Source/Resources/Sound.cs b/Source/Resources/Sound.cs index 725f3bf..766d369 100644 --- a/Source/Resources/Sound.cs +++ b/Source/Resources/Sound.cs @@ -2,11 +2,18 @@ namespace DaggerFramework { public class Sound : Resource { - public float PitchScale { get; set; } = 1.0f; - public float Volume { get; set; } = 1.0f; + public SoundFormat Format { get; set; } + public int SampleRate { get; set; } + public int BufferSize { get; set; } public Sound(string path, byte[] buffer) : base(path, buffer) { } } + + public enum SoundFormat + { + Mono, + Stereo + } } \ No newline at end of file diff --git a/TestGame/Resources/sounds/test_sound.ogg b/TestGame/Resources/sounds/test_sound.ogg new file mode 100644 index 0000000..e2d1727 Binary files /dev/null and b/TestGame/Resources/sounds/test_sound.ogg differ diff --git a/TestGame/TestGame.cs b/TestGame/TestGame.cs index f98c7a7..9d46673 100644 --- a/TestGame/TestGame.cs +++ b/TestGame/TestGame.cs @@ -1,6 +1,7 @@ using DaggerFramework; using System.Numerics; using DaggerFramework.Rendering; +using DaggerFramework.Audio; public class TestGame : Game { @@ -8,18 +9,26 @@ public class TestGame : Game protected override void OnStart() { _renderer = new RaylibRenderer(); + _audioBackend = new OpenALAudioBackend(); + _inputHandler = new RaylibInputHandler(); _renderer.CreateAndInitialize(new WindowSettings() { Title = "Test Game", Size = new Vector2(1280, 720) }, RendererSettings.Default); + _renderer.SetTargetFps(60); + + _audioBackend.Initialize(); + + _inputHandler.AddInputMapping("play", new InputAction[] { new KeyInputAction(KeyboardKey.Spacebar) }); } protected override void LoadResources() { - + _soundLoader = new SoundLoader(); + _testSound = _soundLoader.Load($"{ResourceRoot}sounds/test_sound.ogg"); } protected override void MainLoop() @@ -28,6 +37,12 @@ public class TestGame : Game { _renderer.BeginFrame(); _renderer.ClearBackground(Color.Black); + + if (_inputHandler.IsActionJustPressed("play")) + { + _audioBackend.PlaySoundVariation(_testSound, default, 0.1f); + } + _renderer.SetTransform(new Vector2(640, 480)); _renderer.DrawCircle(16f, Color.Chocolate); _renderer.EndFrame(); @@ -39,4 +54,8 @@ public class TestGame : Game _renderer.Shutdown(); } private Renderer _renderer; + private SoundLoader _soundLoader; + private Sound _testSound; + private OpenALAudioBackend _audioBackend; + private InputHandler _inputHandler; } \ No newline at end of file