A wrapper for a modified version of miniaudio.
$ dotnet add package JAJ.Packages.MiniAudioExA .NET wrapper for MiniAudioEx. MiniAudioEx is a modified version of MiniAudio, see this repository. The goal of MiniAudioExNET is to make it easy to add audio playback to .NET applications. I've tried several libraries in the past and none of them could offer all of the things I was looking for:
dotnet add package JAJ.Packages.MiniAudioEx --version 3.1.0
AudioSource.ChorusEffect.AudioSource causing unmanaged exception.DelayEffect.PhaserEffect.AudioSource.GetOutputBuffer and AudioContext.GetOutputBuffer methods.DistortionEffectAudioSource ended.AudioSource.MaDataSource.MaProceduralDataSource.MaEffectNode.Reverb and ReverbEffect.MaSound.Play to MaSound.Start.AudioBuffer to NativeArray.AudioProcessEvent.Process method in IAudioEffect.MiniAudioEx.Core.StandardAPI and MiniAudioEx.Core.AdvancedAPI.MiniAudioEx.Core.StandardAPI.PlayOneShot which is suitable in scenarios where you need to rapidly play sounds (for example think of gun shots) without having to stop an already playing sound and thus cutting it off. An additional benefit is that all these sounds are processed in the same FX chain.AudioDecoder and added DecodeFromMemory method.Pointer property to AudioBuffer.IsDefault property in DeviceInfo.AudioClip constructor if file or data does not exist.AudioClip constructor and use Marshal.Copy instead.Name property to AudioClip class.Filter constructor.MiniAudioExNET namespace to MiniAudioEx.MiniAudioEx class to AudioContext.Span<T> in favor of AudioBuffer<T>.End callback will never be able to run.Process and Read event run on a separate thread as well. You should not call any MiniAudioEx API functions from these callbacks.Initializing MiniAudioEx and playing audio from a file on disk.
using System;
using System.Threading;
using MiniAudioEx.Core.StandardAPI;
namespace MiniAudioExExample
{
class Program
{
static readonly uint SAMPLE_RATE = 44100;
static readonly uint CHANNELS = 2;
static void Main(string[] args)
{
Console.CancelKeyPress += OnCancelKeyPress;
AudioContext.Initialize(SAMPLE_RATE, CHANNELS);
AudioSource source = new AudioSource();
AudioClip clip = new AudioClip("some_audio.mp3");
source.Play(clip);
while(true)
{
AudioContext.Update();
Thread.Sleep(10);
}
}
static void OnCancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
AudioContext.Deinitialize();
}
}
}
Note that you must compile with AllowUnsafeBlocks.
using System;
using MiniAudioEx.Native;
using MiniAudioEx.Core.AdvancedAPI;
namespace MiniAudioExExample
{
class Program
{
static unsafe void Main(string[] args)
{
MaLog log = new MaLog();
log.Message += OnLog;
if (log.Initialize() != ma_result.success)
{
Console.WriteLine("Failed to initialize log");
log.Dispose();
return;
}
MaContext context = new MaContext();
ma_context_config contextConfig = context.GetConfig();
contextConfig.pLog = log.Handle;
if (context.Initialize(null, contextConfig) != ma_result.success)
{
Console.WriteLine("Failed to initialize context");
context.Dispose();
log.Dispose();
return;
}
ma_decoding_backend_vtable_ptr[] vtables = {
MiniAudioNative.ma_libvorbis_get_decoding_backend_ptr()
};
MaResourceManager resourceManager = new MaResourceManager();
ma_resource_manager_config resourceManagerConfig = resourceManager.GetConfig();
resourceManagerConfig.SetCustomDecodingBackendVTables(vtables);
if (resourceManager.Initialize(resourceManagerConfig) != ma_result.success)
{
Console.WriteLine("Failed to initialize resource manager");
resourceManager.Dispose();
context.Dispose();
log.Dispose();
resourceManagerConfig.FreeCustomDecodingBackendVTables();
return;
}
resourceManagerConfig.FreeCustomDecodingBackendVTables();
MaDeviceInfo deviceInfo = context.GetDefaultPlaybackDevice();
ma_device_data_proc deviceDataCallback = OnDeviceData;
MaDevice device = new MaDevice();
ma_device_config deviceConfig = device.GetConfig(ma_device_type.playback);
deviceConfig.sampleRate = 44100;
deviceConfig.periodSizeInFrames = 2048;
deviceConfig.playback.format = ma_format.f32;
deviceConfig.playback.channels = 2;
deviceConfig.playback.pDeviceID = deviceInfo.pDeviceId;
deviceConfig.SetDataCallback(deviceDataCallback);
if (device.Initialize(deviceConfig) != ma_result.success)
{
Console.WriteLine("Failed to initialize device");
device.Dispose();
resourceManager.Dispose();
context.Dispose();
log.Dispose();
return;
}
MaEngine engine = new MaEngine();
ma_engine_config engineConfig = engine.GetConfig();
engineConfig.pDevice = device.Handle;
engineConfig.pResourceManager = resourceManager.Handle;
if (engine.Initialize(engineConfig) != ma_result.success)
{
Console.WriteLine("Failed to initialize engine");
engine.Dispose();
device.Dispose();
resourceManager.Dispose();
context.Dispose();
log.Dispose();
return;
}
MaSound sound = new MaSound();
if (sound.InitializeFromFile(engine, "some_file.ogg", ma_sound_flags.stream | ma_sound_flags.decode) != ma_result.success)
{
Console.WriteLine("Failed to initialize sound");
sound.Dispose();
engine.Dispose();
device.Dispose();
resourceManager.Dispose();
context.Dispose();
log.Dispose();
return;
}
sound.SetLooping(true);
// Set the user data before starting the device, so the device callback can use it.
device.Handle.Get()->pUserData = engine.Handle.pointer;
device.Start();
sound.Start();
Console.WriteLine("Press enter to exit");
Console.ReadLine();
sound.Stop();
device.Stop();
// Dispose in the reverse order of initialization.
sound.Dispose();
engine.Dispose();
resourceManager.Dispose();
device.Dispose();
context.Dispose();
log.Dispose();
}
private static unsafe void OnDeviceData(ma_device_ptr pDevice, IntPtr pOutput, IntPtr pInput, UInt32 frameCount)
{
ma_device* device = pDevice.Get();
if (device == null)
return;
ma_engine_ptr pEngine = new ma_engine_ptr(device->pUserData);
MiniAudioNative.ma_engine_read_pcm_frames(pEngine, pOutput, frameCount);
}
private static void OnLog(UInt32 level, string message)
{
Console.Write("Log [{0}] {1}", level, message);
}
}
}
See links below for more examples. Most of these use the AudioApp class which is suitable for simple console based applications.
Playing audio from a file on disk
Using the 'Read' callback to generate sound