Add multiple audio channels (WIP)

This commit is contained in:
Alessandro Proto 2023-02-16 20:14:39 +01:00
parent 9907232af7
commit 32d5299a3f
2 changed files with 157 additions and 33 deletions

View file

@ -30,24 +30,78 @@ public class Audio : IDisposable
} }
public const int SampleRate = 48000; public const int SampleRate = 48000;
public const AudioChannels Channels = AudioChannels.Mono; public const AudioChannels AudioChannel = AudioChannels.Mono;
public readonly DynamicSoundEffectInstance Sound; public const int ChannelsCount = 5;
public readonly DynamicSoundEffectInstance[] Channels = new DynamicSoundEffectInstance[ChannelsCount];
private bool[] freeChannels = new bool[ChannelsCount];
private static readonly Random rng = new(); private static readonly Random rng = new();
public Audio() public Audio()
{ {
Sound = new DynamicSoundEffectInstance(SampleRate, Channels); for (int i = 0; i < ChannelsCount; i++)
{
Channels[i] = new DynamicSoundEffectInstance(SampleRate, AudioChannel);
freeChannels[i] = true;
Channels[i].BufferNeeded += Audio_BufferNeeded;
}
} }
public TimeSpan Submit(byte[] buffer) private void Audio_BufferNeeded(object sender, EventArgs e)
{ {
Sound.SubmitBuffer(buffer); for (int i = 0; i < ChannelsCount; i++)
return Sound.GetSampleDuration(buffer.Length); {
if (Channels[i] == sender)
{
freeChannels[i] = true;
}
}
} }
public byte[] GenerateWave(Waveform form, double frequency, TimeSpan time, float volume = 1f) public int GetChannelId(int inp)
{ {
var size = Sound.GetSampleSizeInBytes(time); if (inp >= 0)
return inp;
if (inp == -1)
{
for (int i = 0; i < ChannelsCount; i++)
{
if (freeChannels[i])
return i;
}
}
// -2 is all?
return -3;
}
public bool TryGetChannel(int id, out DynamicSoundEffectInstance channel, out int resolvedId)
{
resolvedId = GetChannelId(id);
if (resolvedId >= 0)
channel = Channels[resolvedId];
else
channel = null;
return channel != null;
}
public TimeSpan Submit(int id, byte[] buffer)
{
if (!TryGetChannel(id, out var channel, out var rId))
return TimeSpan.Zero;
channel.SubmitBuffer(buffer);
freeChannels[rId] = false;
return channel.GetSampleDuration(buffer.Length);
}
public byte[] GenerateWave(DynamicSoundEffectInstance channel, Waveform form, double frequency, TimeSpan time, float volume = 1f)
{
var size = channel.GetSampleSizeInBytes(time);
var buffer = new byte[size]; var buffer = new byte[size];
var step = 1d / SampleRate; var step = 1d / SampleRate;
@ -63,7 +117,7 @@ public class Audio : IDisposable
Waveform.Noise => rng.NextDouble() * 2 - 1, Waveform.Noise => rng.NextDouble() * 2 - 1,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
value = Math.Clamp(value, -1, 1); value = Math.Clamp(value, -1, 1);
var sample = (short)((value >= 0.0f ? value * short.MaxValue : value * short.MinValue * -1) * volume); var sample = (short)((value >= 0.0f ? value * short.MaxValue : value * short.MinValue * -1) * volume);
if (!BitConverter.IsLittleEndian) if (!BitConverter.IsLittleEndian)
@ -117,6 +171,9 @@ public class Audio : IDisposable
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
Sound.Dispose(); for (int i = 0; i < ChannelsCount; i++)
{
Channels[i]?.Dispose();
}
} }
} }

View file

@ -90,7 +90,7 @@ public class Audio : IComponent
return 1; return 1;
} }
private static async Task DelayEmit(TimeSpan time) private static async Task DelayEmit(TimeSpan time, DynamicSoundEffectInstance channel)
{ {
var waitTime = time - TimeSpan.FromMilliseconds(1000 / 60); var waitTime = time - TimeSpan.FromMilliseconds(1000 / 60);
if (waitTime.TotalMilliseconds < 0) if (waitTime.TotalMilliseconds < 0)
@ -98,7 +98,7 @@ public class Audio : IComponent
await Task.Delay(waitTime); await Task.Delay(waitTime);
_game.LuaRuntime.QueueEvent("audio_end", LK => _game.LuaRuntime.QueueEvent("audio_end", LK =>
{ {
LK.PushInteger(_game.Audio.Sound.PendingBufferCount); LK.PushInteger(channel.PendingBufferCount);
return 1; return 1;
}); });
} }
@ -127,7 +127,16 @@ public class Audio : IComponent
} }
} }
if (_game.Audio.Sound.PendingBufferCount > queueLimit) var id = (int)L.OptInteger(2, -1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushBoolean(false);
L.PushString("Channel not available");
return 2;
}
if (channel.PendingBufferCount > queueLimit)
{ {
L.PushBoolean(false); L.PushBoolean(false);
L.PushString("queue is full"); L.PushString("queue is full");
@ -137,11 +146,11 @@ public class Audio : IComponent
try try
{ {
var ts = _game.Audio.Submit(buffer); var ts = _game.Audio.Submit(rid, buffer);
DelayEmit(ts); DelayEmit(ts, channel);
if (_game.Audio.Sound.State != SoundState.Playing) if (channel.State != SoundState.Playing)
_game.Audio.Sound.Play(); channel.Play();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -157,11 +166,11 @@ public class Audio : IComponent
var freq = L.OptNumber(1, 440); var freq = L.OptNumber(1, 440);
var time = L.OptNumber(2, 1); var time = L.OptNumber(2, 1);
var timespan = TimeSpan.FromSeconds(time);
var volume = (float)L.OptNumber(3, 1); var volume = (float)L.OptNumber(3, 1);
volume = Math.Clamp(volume, 0, 1); volume = Math.Clamp(volume, 0, 1);
var timespan = TimeSpan.FromSeconds(time);
var form = L.CheckOption(4, "sine", new string[] var form = L.CheckOption(4, "sine", new string[]
{ {
"sine", "sine",
@ -172,37 +181,71 @@ public class Audio : IComponent
null, null,
}); });
var buffer = _game.Audio.GenerateWave((Waveform)form, freq, timespan, volume); var id = (int)L.OptInteger(5, -1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushBoolean(false);
return 1;
}
var buffer = _game.Audio.GenerateWave(channel, (Waveform)form, freq, timespan, volume);
try try
{ {
var ts = _game.Audio.Submit(buffer); var ts = _game.Audio.Submit(rid, buffer);
DelayEmit(ts); DelayEmit(ts, channel);
if (_game.Audio.Sound.State != SoundState.Playing) if (channel.State != SoundState.Playing)
_game.Audio.Sound.Play(); channel.Play();
L.PushBoolean(true);
} }
catch (Exception ex) catch (Exception ex)
{ {
L.Error(ex.Message); L.Error(ex.Message);
} }
return 0; return 1;
} }
private static int L_Resume(IntPtr state) private static int L_Resume(IntPtr state)
{ {
_game.Audio.Sound.Resume(); var L = Lua.FromIntPtr(state);
var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushBoolean(false);
return 1;
}
channel.Resume();
return 0; return 0;
} }
private static int L_Pause(IntPtr state) private static int L_Pause(IntPtr state)
{ {
_game.Audio.Sound.Pause(); var L = Lua.FromIntPtr(state);
var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushBoolean(false);
return 1;
}
channel.Pause();
return 0; return 0;
} }
private static int L_Stop(IntPtr state) private static int L_Stop(IntPtr state)
{ {
_game.Audio.Sound.Stop(); var L = Lua.FromIntPtr(state);
var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushBoolean(false);
return 1;
}
channel.Stop();
return 0; return 0;
} }
@ -211,7 +254,14 @@ public class Audio : IComponent
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
L.PushNumber(_game.Audio.Sound.Volume); var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushNil();
return 1;
}
L.PushNumber(channel.Volume);
return 1; return 1;
} }
@ -219,10 +269,17 @@ public class Audio : IComponent
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
var volume = (float)L.CheckNumber(1); var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushNil();
return 1;
}
var volume = (float)L.CheckNumber(2);
volume = Math.Clamp(volume, 0, 1); volume = Math.Clamp(volume, 0, 1);
_game.Audio.Sound.Volume = volume; channel.Volume = volume;
return 0; return 0;
} }
@ -231,7 +288,14 @@ public class Audio : IComponent
{ {
var L = Lua.FromIntPtr(state); var L = Lua.FromIntPtr(state);
var status = _game.Audio.Sound.State switch var id = (int)L.CheckInteger(1);
if (!_game.Audio.TryGetChannel(id, out var channel, out var rid))
{
L.PushNil();
return 1;
}
var status = channel.State switch
{ {
SoundState.Playing => "playing", SoundState.Playing => "playing",
SoundState.Paused => "paused", SoundState.Paused => "paused",
@ -246,6 +310,9 @@ public class Audio : IComponent
private void OnClose(object sender, EventArgs e) private void OnClose(object sender, EventArgs e)
{ {
_game.Audio.Sound.Stop(true); foreach (var channel in _game.Audio.Channels)
{
channel.Stop();
}
} }
} }