Created
January 5, 2022 17:53
-
-
Save NtFreX/ff4666df7c37e888cf828a47e75b40da to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using SDL2; | |
using System.Collections; | |
namespace NtFreX.BuildingBlocks | |
{ | |
public class SdlAudioRenderer : IDisposable | |
{ | |
private SDL.SDL_AudioSpec? loadedFormat = null; | |
private bool isOpen = false; | |
// keep this here so it is not garbage collected | |
private readonly SDL.SDL_AudioCallback callbackDelegate; | |
private readonly List<SdlAudioContext> audioContexts = new List<SdlAudioContext>(); | |
public SdlAudioRenderer() | |
{ | |
this.callbackDelegate = SDL_AudioCallback; | |
} | |
public void Dispose() | |
{ | |
SDL.SDL_CloseAudio(); | |
} | |
public SdlAudioContext PlayWav(string file, int volume = SDL.SDL_MIX_MAXVOLUME, bool loop = false) | |
{ | |
if (SDL.SDL_Init(SDL.SDL_INIT_AUDIO) < 0) | |
throw new Exception("Couldn't initialize sdl"); | |
if (SDL.SDL_LoadWAV(file, out var spec, out var bufferPtr, out var length) == IntPtr.Zero) | |
throw new Exception($"Couldn't load the wav file {file}"); | |
var audioContext = new SdlAudioContext(bufferPtr, length) | |
{ | |
Loop = loop, | |
Volume = volume | |
}; | |
if (loadedFormat != null && !AreEqual(spec, loadedFormat.Value)) | |
throw new Exception($"Can only play one audio format, loaded format = {loadedFormat}, audio format = {spec}"); | |
if(!isOpen) | |
{ | |
spec.callback = this.callbackDelegate; | |
spec.userdata = IntPtr.Zero; | |
if (SDL.SDL_OpenAudio(ref spec, IntPtr.Zero) < 0) | |
throw new Exception($"Couldn't open audio: {SDL.SDL_GetError()}"); | |
loadedFormat = spec; | |
isOpen = true; | |
} | |
audioContexts.Add(audioContext); | |
SDL.SDL_PauseAudio(0); | |
return audioContext; | |
} | |
private bool AreEqual(SDL.SDL_AudioSpec first, SDL.SDL_AudioSpec second) | |
{ | |
// https://wiki.libsdl.org/SDL_AudioFormat | |
var firstBits = new BitArray(first.format); | |
var secondBits = new BitArray(second.format); | |
var firstSampleRate = BitConverter.GetBytes(first.format)[0]; | |
var secondSampleRate = BitConverter.GetBytes(second.format)[0]; | |
return firstBits.Get(15) == secondBits.Get(15) /* is signed */ && | |
firstBits.Get(12) == secondBits.Get(12) /* is big endian */ && | |
firstBits.Get(8) == secondBits.Get(8) /* is float */ && | |
firstSampleRate == secondSampleRate && | |
first.channels == second.channels && | |
first.freq == second.freq; | |
} | |
private unsafe void SDL_AudioCallback(IntPtr userdata, IntPtr stream, int len) | |
{ | |
for(var i = 0; i < audioContexts.Count; i++) | |
{ | |
if (audioContexts[i].RemainingLength == 0 || audioContexts[i].IsStopped) | |
{ | |
if (audioContexts[i].Loop && !audioContexts[i].IsStopped) | |
{ | |
audioContexts[i].Reset(); | |
} | |
else | |
{ | |
audioContexts[i].Dispose(); | |
audioContexts.RemoveAt(i); | |
i--; | |
} | |
} | |
} | |
var buffer = new byte[len]; | |
fixed (byte* ptr = buffer) | |
{ | |
SDL.SDL_memcpy(stream, new IntPtr(ptr), new IntPtr(len)); | |
} | |
if (audioContexts.Count == 0) | |
{ | |
SDL.SDL_PauseAudio(1); | |
return; | |
} | |
foreach (var context in audioContexts.Where(x => !x.IsPaused)) | |
{ | |
var contextLen = len > context.RemainingLength ? context.RemainingLength : (uint) len; | |
SDL.SDL_MixAudioFormat(stream, context.AudioPtr, loadedFormat!.Value.format, contextLen, context.Volume); | |
context.AudioPtr += (int) contextLen; | |
context.RemainingLength -= contextLen; | |
} | |
} | |
} | |
public class SdlAudioContext : IDisposable | |
{ | |
private readonly IntPtr audioStartPtr; | |
private readonly uint audioLength; | |
internal IntPtr AudioPtr { get; set; } | |
internal uint RemainingLength { get; set; } | |
public bool Loop { get; set; } | |
public int Volume { get; set; } = SDL.SDL_MIX_MAXVOLUME; | |
public bool IsPaused { get; set; } | |
public bool IsStopped { get; private set; } | |
internal SdlAudioContext(IntPtr audioPtr, uint audioLength) | |
{ | |
this.audioStartPtr = audioPtr; | |
this.AudioPtr = audioPtr; | |
this.audioLength = audioLength; | |
this.RemainingLength = audioLength; | |
} | |
public void Reset() | |
{ | |
AudioPtr = audioStartPtr; | |
RemainingLength = audioLength; | |
} | |
public void Dispose() | |
{ | |
IsStopped = true; | |
if (!IsStopped) | |
{ | |
SDL.SDL_FreeWAV(AudioPtr); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment