Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Core/Audio/Audio.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.IO;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Handles Audio related operations.
/// </summary>
public static class Audio
{
/// <summary>
/// Plays an audio file if it is in the WAV format.
/// </summary>
/// <param name="filePath">Specifies the location of the audio file to be played.</param>
/// <exception cref="NotSupportedException">Thrown when the file format is not supported, such as when it is not a WAV file.</exception>
public static void Play(string filePath)
{
switch (Path.GetExtension(filePath))
{
case ".wav":
new WavPlayer().Play(filePath);
break;

default:
throw new NotSupportedException("Unsupported file format.");
}
}
}
43 changes: 43 additions & 0 deletions Core/Audio/AudioBuffer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Silk.NET.OpenAL;
using System;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Represents an audio buffer with a unique identifier.
/// </summary>
public class AudioBuffer
{
/// <summary>Gets the identifier to the audio buffer.</summary>
public uint BufferId => _buffer;

private readonly uint _buffer;
private readonly AL _al;

/// <summary>
/// Initializes a new instance of <see cref="AudioBuffer"/>.
/// </summary>
/// <param name="al">The OpenAL API context.</param>
public AudioBuffer(AL al)
{
_al = al;
_buffer = _al.GenBuffer();
}

/// <summary>
/// Loads the given <paramref name="data"/> into the buffer.
/// </summary>
/// <param name="data">The data to be loaded into the buffer.</param>
/// <param name="wavData">Details about the data.</param>
public void LoadData(ReadOnlySpan<byte> data, WavData wavData)
{
unsafe
{
fixed (byte* pData = data)
{
_al.BufferData(_buffer, wavData.Format, pData, data.Length, wavData.SampleRate);
}
}
}

}
46 changes: 46 additions & 0 deletions Core/Audio/AudioDevice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Silk.NET.OpenAL;
using System;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Manages an audio device and its context for audio operations.
/// </summary>
public unsafe class AudioDevice : IDisposable
{
/// <summary>Gets the OpenAL context.</summary>
public AL Al { get; }

private readonly ALContext _alc;
private readonly Device* _device;
private readonly Context* _context;

/// <summary>
/// Initializes a new instance of <see cref="AudioDevice"/>.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when unable to create the audio device.</exception>
public AudioDevice()
{
_alc = ALContext.GetApi();
Al = AL.GetApi();
_device = _alc.OpenDevice("");

if (_device == null)
throw new InvalidOperationException("Could not create device.");

_context = _alc.CreateContext(_device, null);
_alc.MakeContextCurrent(_context);
Al.GetError();
}

/// <inheritdoc />
public void Dispose()
{
_alc.DestroyContext(_context);
_alc.CloseDevice(_device);
Al.Dispose();
_alc.Dispose();

GC.SuppressFinalize(this);
}
}
16 changes: 16 additions & 0 deletions Core/Audio/AudioFileExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace SharpEngine.Core.Audio;

/// <summary>
/// Contains all the supported audio file extensions.
/// </summary>
public static class AudioFileExtensions
{
/// <summary>The file extension of the Waveform audio file.</summary>
public const string WavExtension = ".wav";

/// <summary>The file extension of the MPEG-1 Audio Layer III or MPEG-2 Audio Layer III (MP3) file.</summary>
public const string Mp3Extension = ".mp3";

/// <summary>The file extension of the MPEG-4 (MP4) file.</summary>
public const string Mp4Extension = ".mp4";
}
67 changes: 67 additions & 0 deletions Core/Audio/AudioPlayerBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using SharpEngine.Core.Scenes;

using System;
using System.IO;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Represents a base class for all audio players.
/// </summary>
public abstract class AudioPlayerBase : SceneNode
{
/// <summary>Gets the extension accepted by the audio player.</summary>
protected abstract string FileExtension { get; }

/// <summary>Gets or sets the properties for the player.</summary>
public AudioProperties AudioProperties { get; set; }

/// <summary>Contains the audio data of the media file.</summary>
protected readonly WavData Data;

/// <summary>Represents a container for the <see cref="Data"/>.</summary>
protected readonly AudioBuffer AudioBuffer;

/// <summary>The source of the audio.</summary>
protected readonly AudioSource AudioSource;

/// <summary>
/// Initializes a new instance of <see cref="AudioPlayerBase"/>.
/// </summary>
protected AudioPlayerBase()
{
Data = new WavData();

var audioDevice = new AudioDevice();
AudioBuffer = new AudioBuffer(audioDevice.Al);
AudioSource = new AudioSource(audioDevice.Al);

AudioProperties = new AudioProperties(audioDevice.Al, AudioSource);
}

/// <summary>
/// Plays a media file specified by the given path.
/// </summary>
/// <param name="filePath">The location of the media file to be played.</param>
public abstract void Play(string filePath);

/// <summary>
/// Validates the specified file by checking its extension and existence.
/// </summary>
/// <param name="filePath">The path to the file that needs to be validated for its type and existence.</param>
/// <exception cref="ArgumentException">Thrown when the file does not have the allowed extension.</exception>
/// <exception cref="FileNotFoundException">Thrown when the specified file does not exist in the file system.</exception>
protected virtual void ValidateFile(string filePath)
{
if (Path.GetExtension(filePath) != FileExtension)
throw new ArgumentException($"Given file is not a {FileExtension} file", nameof(filePath));

if (!File.Exists(filePath))
throw new FileNotFoundException("Given file does not exist", filePath);
}

/// <summary>
/// Stops the audio playback of the associated AudioSource.
/// </summary>
public virtual void Stop() => AudioSource.Stop();
}
36 changes: 36 additions & 0 deletions Core/Audio/AudioProperties.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Silk.NET.OpenAL;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Contains properties used to control how the audio should be played.
/// </summary>
public class AudioProperties
{
private readonly AL _al;
private readonly uint _audioSourceId;

/// <summary>
/// Initializes a new instance of <see cref="AudioProperties"/>.
/// </summary>
/// <param name="al">The context where the audio should be played.</param>
/// <param name="audioSource">The source at which the audio should be played.</param>
public AudioProperties(AL al, AudioSource audioSource)
{
_al = al;
_audioSourceId = audioSource.Get();
}

/// <summary>Gets or sets whether the audio should be looping.</summary>
public bool IsLooping
{
get => _isLooping;
set
{
_isLooping = value;
_al.SetSourceProperty(_audioSourceId, SourceBoolean.Looping, value);
}
}

private bool _isLooping;
}
47 changes: 47 additions & 0 deletions Core/Audio/AudioSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Silk.NET.OpenAL;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Represents a source for audio
/// </summary>
public class AudioSource
{
private readonly uint _source;
private readonly AL _al;

/// <summary>
/// Initializes a new instance of <see cref="AudioSource"/>.
/// </summary>
/// <param name="al">An accessor to the OpenAL context.</param>
public AudioSource(AL al)
{
_al = al;
_source = _al.GenSource();
}

/// <summary>
/// Gets the source identifier.
/// </summary>
/// <returns>The identifier to the source.</returns>
public uint Get() => _source;

/// <summary>
/// Plays the given audio from the <paramref name="buffer"/>.
/// </summary>
/// <param name="buffer">A buffer containing the audio data.</param>
public void Play(AudioBuffer buffer)
{
_al.SetSourceProperty(_source, SourceInteger.Buffer, buffer.BufferId);
_al.SourcePlay(_source);
}

/// <summary>
/// Stops playing the audio.
/// </summary>
public void Stop()
{
_al.SourceStop(_source);
_al.DeleteSource(_source);
}
}
22 changes: 22 additions & 0 deletions Core/Audio/WavData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Silk.NET.OpenAL;

namespace SharpEngine.Core.Audio;

/// <summary>
/// Represents audio data in WAV format.
/// </summary>
public class WavData
{
/// <summary>Gets or sets the number of channels used in the data.</summary>
public short NumChannels { get; set; }

/// <summary>Gets or sets the sample rate of the data.</summary>
public int SampleRate { get; set; }

/// <summary>Gets or sets the number of bits per sample.</summary>
public short BitsPerSample { get; set; }

/// <summary>Gets or sets the format of the buffer.</summary>
/// <remarks>Defines how data is organized in the buffer.</remarks>
public BufferFormat Format { get; set; }
}
Loading