mirror of
https://github.com/Ale32bit/Capy64.git
synced 2025-01-18 18:46:43 +00:00
Finish audio library
This commit is contained in:
parent
bf6424c0d1
commit
3e3693bc2e
5 changed files with 195 additions and 24 deletions
|
@ -3,6 +3,7 @@ local timer = require("timer")
|
|||
local gpu = require("gpu")
|
||||
local fs = require("fs")
|
||||
local machine = require("machine")
|
||||
local audio = require("audio")
|
||||
|
||||
local bootSleep = 2000
|
||||
local bg = 0x0
|
||||
|
@ -145,6 +146,8 @@ local function bootScreen()
|
|||
end
|
||||
end
|
||||
|
||||
audio.beep(1000, 0.4, 0.2)
|
||||
|
||||
bootScreen()
|
||||
|
||||
term.clear()
|
|
@ -18,7 +18,7 @@ namespace Capy64;
|
|||
|
||||
public class Capy64 : Game, IGame
|
||||
{
|
||||
public const string Version = "0.0.6-alpha";
|
||||
public const string Version = "0.0.7-alpha";
|
||||
public static string AppDataPath = Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.ApplicationData,
|
||||
|
@ -32,6 +32,7 @@ public class Capy64 : Game, IGame
|
|||
public int Height { get; set; } = 300;
|
||||
public float Scale { get; set; } = 2f;
|
||||
public Drawing Drawing { get; private set; }
|
||||
public Audio Audio { get; private set; }
|
||||
public LuaState LuaRuntime { get; set; }
|
||||
public Eventing.EventEmitter EventEmitter { get; private set; }
|
||||
public DiscordIntegration Discord { get; set; }
|
||||
|
@ -131,6 +132,8 @@ public class Capy64 : Game, IGame
|
|||
Window.AllowUserResizing = true;
|
||||
Window.ClientSizeChanged += OnWindowSizeChange;
|
||||
|
||||
Audio = new Audio();
|
||||
|
||||
NativePlugins = GetNativePlugins();
|
||||
Plugins = PluginLoader.LoadAllPlugins("plugins", _serviceProvider);
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Xna.Framework.Audio;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -8,15 +9,59 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace Capy64.Core;
|
||||
|
||||
public class Audio
|
||||
public class Audio : IDisposable
|
||||
{
|
||||
public const int SampleRate = 48000;
|
||||
public static TimeSpan Play(byte[] buffer, out SoundEffect soundEffect)
|
||||
public const AudioChannels Channels = AudioChannels.Mono;
|
||||
public readonly DynamicSoundEffectInstance Sound;
|
||||
public Audio()
|
||||
{
|
||||
soundEffect = new SoundEffect(buffer, SampleRate, AudioChannels.Stereo);
|
||||
Sound = new DynamicSoundEffectInstance(SampleRate, Channels);
|
||||
}
|
||||
|
||||
soundEffect.Play(1, 0, 0);
|
||||
public TimeSpan Submit(byte[] buffer)
|
||||
{
|
||||
Sound.SubmitBuffer(buffer);
|
||||
return Sound.GetSampleDuration(buffer.Length);
|
||||
}
|
||||
|
||||
return soundEffect.Duration;
|
||||
public static byte[] GenerateSineWave(double frequency, double time, double volume = 1d)
|
||||
{
|
||||
var amplitude = 128 * volume;
|
||||
var timeStep = 1d / SampleRate;
|
||||
|
||||
var buffer = new byte[(int)(SampleRate * time)];
|
||||
var ctime = 0d;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
double angle = (Math.PI * frequency) * ctime;
|
||||
double factor = 0.5 * (Math.Sin(angle) + 1.0);
|
||||
buffer[i] = (byte)(amplitude * factor);
|
||||
ctime += timeStep;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static byte[] GenerateSquareWave(double frequency, double time, double volume = 1d)
|
||||
{
|
||||
var amplitude = 128 * volume;
|
||||
var timeStep = 1d / SampleRate;
|
||||
|
||||
var buffer = new byte[(int)(SampleRate * time)];
|
||||
var ctime = 0d;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
double angle = (Math.PI * frequency) * ctime;
|
||||
double factor = Math.Sin(angle);
|
||||
buffer[i] = (byte)(factor >= 0 ? amplitude : 0);
|
||||
ctime += timeStep;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Sound.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ public interface IGame
|
|||
IList<IPlugin> Plugins { get; }
|
||||
GameWindow Window { get; }
|
||||
Drawing Drawing { get; }
|
||||
Audio Audio { get; }
|
||||
LuaState LuaRuntime { get; set; }
|
||||
Eventing.EventEmitter EventEmitter { get; }
|
||||
void ConfigureServices(IServiceProvider serviceProvider);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using Capy64.API;
|
||||
using Capy64.Core;
|
||||
using KeraLua;
|
||||
using Microsoft.Xna.Framework.Audio;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -29,6 +31,41 @@ public class Audio : IPlugin
|
|||
name = "play",
|
||||
function = L_Play,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "beep",
|
||||
function = L_Beep,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "resume",
|
||||
function = L_Resume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "pause",
|
||||
function = L_Pause,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "stop",
|
||||
function = L_Stop,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "getVolume",
|
||||
function = L_GetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "setVolume",
|
||||
function = L_SetVolume,
|
||||
},
|
||||
new()
|
||||
{
|
||||
name = "status",
|
||||
function = L_Status,
|
||||
},
|
||||
new(),
|
||||
};
|
||||
|
||||
|
@ -48,13 +85,19 @@ public class Audio : IPlugin
|
|||
{
|
||||
try
|
||||
{
|
||||
var time = Core.Audio.Play(buffer, out var soundEffect);
|
||||
await Task.Delay(time - TimeSpan.FromMilliseconds(1000 / 60));
|
||||
var time = _game.Audio.Submit(buffer);
|
||||
if (_game.Audio.Sound.State != Microsoft.Xna.Framework.Audio.SoundState.Playing)
|
||||
{
|
||||
_game.Audio.Sound.Play();
|
||||
}
|
||||
var waitTime = time - TimeSpan.FromMilliseconds(1000 / 60);
|
||||
if (waitTime.TotalMilliseconds < 0)
|
||||
waitTime = time;
|
||||
await Task.Delay(waitTime);
|
||||
_game.LuaRuntime.QueueEvent("audio_end", LK =>
|
||||
{
|
||||
soundEffect.Dispose();
|
||||
|
||||
return 0;
|
||||
LK.PushInteger(_game.Audio.Sound.PendingBufferCount);
|
||||
return 1;
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -64,13 +107,29 @@ public class Audio : IPlugin
|
|||
}
|
||||
}
|
||||
|
||||
private static void ProcessAudio()
|
||||
{
|
||||
if (isProcessing)
|
||||
return;
|
||||
|
||||
isProcessing = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (queue.TryDequeue(out var buffer))
|
||||
{
|
||||
await PlayAudio(buffer);
|
||||
}
|
||||
isProcessing = false;
|
||||
});
|
||||
}
|
||||
|
||||
private static int L_Play(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var buffer = L.CheckBuffer(1);
|
||||
|
||||
if (queue.Count > queueLimit)
|
||||
if (_game.Audio.Sound.PendingBufferCount > queueLimit)
|
||||
{
|
||||
L.PushBoolean(false);
|
||||
L.PushString("queue is full");
|
||||
|
@ -80,19 +139,79 @@ public class Audio : IPlugin
|
|||
|
||||
queue.Enqueue(buffer);
|
||||
|
||||
if (!isProcessing)
|
||||
{
|
||||
isProcessing = true;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (queue.TryDequeue(out var buffer))
|
||||
{
|
||||
await PlayAudio(buffer);
|
||||
}
|
||||
isProcessing = false;
|
||||
});
|
||||
}
|
||||
ProcessAudio();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Beep(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var freq = L.CheckNumber(1);
|
||||
var time = L.OptNumber(2, 1);
|
||||
var volume = L.OptNumber(3, 1);
|
||||
Math.Clamp(volume, 0, 1);
|
||||
|
||||
var sine = Core.Audio.GenerateSquareWave(freq, time, volume);
|
||||
|
||||
queue.Enqueue(sine);
|
||||
|
||||
ProcessAudio();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Resume(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Resume();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Pause(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Pause();
|
||||
return 0;
|
||||
}
|
||||
private static int L_Stop(IntPtr state)
|
||||
{
|
||||
_game.Audio.Sound.Stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_GetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
L.PushNumber(_game.Audio.Sound.Volume);
|
||||
|
||||
return 1;
|
||||
}
|
||||
private static int L_SetVolume(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var volume = (float)L.CheckNumber(1);
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
_game.Audio.Sound.Volume = volume;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int L_Status(IntPtr state)
|
||||
{
|
||||
var L = Lua.FromIntPtr(state);
|
||||
|
||||
var status = _game.Audio.Sound.State switch
|
||||
{
|
||||
SoundState.Playing => "playing",
|
||||
SoundState.Paused => "paused",
|
||||
SoundState.Stopped => "stopped",
|
||||
_ => "unknown",
|
||||
};
|
||||
|
||||
L.PushString(status);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue