Audio to its own namespace

This commit is contained in:
Matt Nadareski
2021-10-06 20:52:16 -07:00
parent 4eecc68cae
commit e6222352e9
8 changed files with 8 additions and 7 deletions

View File

@@ -0,0 +1,48 @@
using System;
using NWaves.Filters.BiQuad;
namespace RedBookPlayer.Models.Audio
{
/// <summary>
/// Filter for applying de-emphasis to audio
/// </summary>
public class DeEmphasisFilter : BiQuadFilter
{
private static readonly double B0;
private static readonly double B1;
private static readonly double B2;
private static readonly double A0;
private static readonly double A1;
private static readonly double A2;
static DeEmphasisFilter()
{
double fc = 5277;
double slope = 0.4850;
double gain = -9.465;
double w0 = 2 * Math.PI * fc / 44100;
double A = Math.Exp(gain / 40 * Math.Log(10));
double alpha = Math.Sin(w0) / 2 * Math.Sqrt(((A + (1 / A)) * ((1 / slope) - 1)) + 2);
double cs = Math.Cos(w0);
double v = 2 * Math.Sqrt(A) * alpha;
B0 = A * (A + 1 + ((A - 1) * cs) + v);
B1 = -2 * A * (A - 1 + ((A + 1) * cs));
B2 = A * (A + 1 + ((A - 1) * cs) - v);
A0 = A + 1 - ((A - 1) * cs) + v;
A1 = 2 * (A - 1 - ((A + 1) * cs));
A2 = A + 1 - ((A - 1) * cs) - v;
B2 /= A0;
B1 /= A0;
B0 /= A0;
A2 /= A0;
A1 /= A0;
A0 = 1;
}
public DeEmphasisFilter() : base(B0, B1, B2, A0, A1, A2) {}
}
}

View File

@@ -0,0 +1,58 @@
using NWaves.Audio;
using NWaves.Filters.BiQuad;
namespace RedBookPlayer.Models.Audio
{
/// <summary>
/// Output stage that represents all filters on the audio
/// </summary>
public class FilterStage
{
/// <summary>
/// Left channel de-emphasis filter
/// </summary>
private BiQuadFilter _deEmphasisFilterLeft;
/// <summary>
/// Right channel de-emphasis filter
/// </summary>
private BiQuadFilter _deEmphasisFilterRight;
/// <summary>
/// Process audio data with internal filters
/// </summary>
/// <param name="audioData">Audio data to process</param>
public void ProcessAudioData(byte[] audioData)
{
float[][] floatAudioData = new float[2][];
floatAudioData[0] = new float[audioData.Length / 4];
floatAudioData[1] = new float[audioData.Length / 4];
ByteConverter.ToFloats16Bit(audioData, floatAudioData);
for(int i = 0; i < floatAudioData[0].Length; i++)
{
floatAudioData[0][i] = _deEmphasisFilterLeft.Process(floatAudioData[0][i]);
floatAudioData[1][i] = _deEmphasisFilterRight.Process(floatAudioData[1][i]);
}
ByteConverter.FromFloats16Bit(floatAudioData, audioData);
}
/// <summary>
/// Sets or resets the output filters
/// </summary>
public void SetupFilters()
{
if(_deEmphasisFilterLeft == null)
{
_deEmphasisFilterLeft = new DeEmphasisFilter();
_deEmphasisFilterRight = new DeEmphasisFilter();
}
else
{
_deEmphasisFilterLeft.Reset();
_deEmphasisFilterRight.Reset();
}
}
}
}

View File

@@ -0,0 +1,30 @@
namespace RedBookPlayer.Models.Audio
{
public interface IAudioBackend
{
/// <summary>
/// Pauses the audio playback
/// </summary>
void Pause();
/// <summary>
/// Starts the playback.
/// </summary>
void Play();
/// <summary>
/// Stops the audio playback
/// </summary>
void Stop();
/// <summary>
/// Get the current playback state
/// </summary>
PlayerState GetPlayerState();
/// <summary>
/// Set the new volume value
/// </summary>
void SetVolume(float volume);
}
}

View File

@@ -0,0 +1,52 @@
using CSCore.SoundOut;
namespace RedBookPlayer.Models.Audio.Linux
{
public class AudioBackend : IAudioBackend
{
/// <summary>
/// Sound output instance
/// </summary>
private readonly ALSoundOut _soundOut;
public AudioBackend() { }
public AudioBackend(PlayerSource source)
{
_soundOut = new ALSoundOut(100);
_soundOut.Initialize(source);
}
#region IAudioBackend Implementation
/// <inheritdoc/>
public void Pause() => _soundOut.Pause();
/// <inheritdoc/>
public void Play() => _soundOut.Play();
/// <inheritdoc/>
public void Stop() => _soundOut.Stop();
/// <inheritdoc/>
public PlayerState GetPlayerState()
{
return (_soundOut?.PlaybackState) switch
{
PlaybackState.Paused => PlayerState.Paused,
PlaybackState.Playing => PlayerState.Playing,
PlaybackState.Stopped => PlayerState.Stopped,
_ => PlayerState.NoDisc,
};
}
/// <inheritdoc/>
public void SetVolume(float volume)
{
if (_soundOut != null)
_soundOut.Volume = volume;
}
#endregion
}
}

View File

@@ -0,0 +1,44 @@
using System;
using CSCore;
using WaveFormat = CSCore.WaveFormat;
namespace RedBookPlayer.Models.Audio
{
public class PlayerSource : IWaveSource
{
public delegate int ReadFunction(byte[] buffer, int offset, int count);
readonly ReadFunction _read;
public bool Run = true;
public PlayerSource(ReadFunction read) => _read = read;
public WaveFormat WaveFormat => new WaveFormat();
bool IAudioSource.CanSeek => throw new NotImplementedException();
public long Position
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public long Length => throw new NotImplementedException();
public int Read(byte[] buffer, int offset, int count)
{
if(Run)
return _read(buffer, offset, count);
Array.Clear(buffer, offset, count);
return count;
}
public void Dispose() {}
public void Start() => Run = true;
public void Stop() => Run = false;
}
}

View File

@@ -0,0 +1,183 @@
using System.Runtime.InteropServices;
using ReactiveUI;
namespace RedBookPlayer.Models.Audio
{
public class SoundOutput : ReactiveObject
{
#region Public Fields
/// <summary>
/// Indicate if the output is ready to be used
/// </summary>
public bool Initialized
{
get => _initialized;
private set => this.RaiseAndSetIfChanged(ref _initialized, value);
}
/// <summary>
/// Indicates the current player state
/// </summary>
public PlayerState PlayerState
{
get => _playerState;
private set => this.RaiseAndSetIfChanged(ref _playerState, value);
}
/// <summary>
/// Current playback volume
/// </summary>
public int Volume
{
get => _volume;
private set
{
int tempVolume = value;
if(value > 100)
tempVolume = 100;
else if(value < 0)
tempVolume = 0;
this.RaiseAndSetIfChanged(ref _volume, tempVolume);
}
}
private bool _initialized;
private PlayerState _playerState;
private int _volume;
#endregion
#region Private State Variables
/// <summary>
/// Data provider for sound output
/// </summary>
private PlayerSource _source;
/// <summary>
/// Sound output instance
/// </summary>
private IAudioBackend _soundOut;
#endregion
/// <summary>
/// Constructor
/// </summary>
/// <param name="defaultVolume">Default volume between 0 and 100 to use when starting playback</param>
public SoundOutput(int defaultVolume = 100) => Volume = defaultVolume;
/// <summary>
/// Initialize the output with a given image
/// </summary>
/// <param name="read">ReadFunction to use during decoding</param>
/// <param name="autoPlay">True if playback should begin immediately, false otherwise</param>
public void Init(PlayerSource.ReadFunction read, bool autoPlay)
{
// Reset initialization
Initialized = false;
// Setup the audio output
SetupAudio(read);
// Initialize playback, if necessary
if(autoPlay)
_soundOut.Play();
// Mark the output as ready
Initialized = true;
PlayerState = PlayerState.Stopped;
// Begin loading data
_source.Start();
}
/// <summary>
/// Reset the current internal state
/// </summary>
public void Reset()
{
_soundOut.Stop();
Initialized = false;
PlayerState = PlayerState.NoDisc;
}
#region Playback
/// <summary>
/// Start audio playback
/// </summary>
public void Play()
{
if(_soundOut.GetPlayerState() != PlayerState.Playing)
_soundOut.Play();
PlayerState = PlayerState.Playing;
}
/// <summary>
/// Pause audio playback
/// </summary>
public void Pause()
{
if(_soundOut.GetPlayerState() != PlayerState.Paused)
_soundOut.Pause();
PlayerState = PlayerState.Paused;
}
/// <summary>
/// Stop audio playback
/// </summary>
public void Stop()
{
if(_soundOut.GetPlayerState() != PlayerState.Stopped)
_soundOut.Stop();
PlayerState = PlayerState.Stopped;
}
/// <summary>
/// Eject the currently loaded disc
/// </summary>
public void Eject() => Reset();
#endregion
#region Helpers
/// <summary>
/// Set the value for the volume
/// </summary>
/// <param name="volume">New volume value</param>
public void SetVolume(int volume)
{
Volume = volume;
_soundOut.SetVolume((float)Volume / 100);
}
/// <summary>
/// Sets or resets the audio playback objects
/// </summary>
/// <param name="read">ReadFunction to use during decoding</param>
private void SetupAudio(PlayerSource.ReadFunction read)
{
if(_source == null)
{
_source = new PlayerSource(read);
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
_soundOut = new Linux.AudioBackend(_source);
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
_soundOut = new Windows.AudioBackend(_source);
}
else
{
_soundOut.Stop();
}
}
#endregion
}
}

View File

@@ -0,0 +1,52 @@
using CSCore.SoundOut;
namespace RedBookPlayer.Models.Audio.Windows
{
public class AudioBackend : IAudioBackend
{
/// <summary>
/// Sound output instance
/// </summary>
private readonly ALSoundOut _soundOut;
public AudioBackend() { }
public AudioBackend(PlayerSource source)
{
_soundOut = new ALSoundOut(100);
_soundOut.Initialize(source);
}
#region IAudioBackend Implementation
/// <inheritdoc/>
public void Pause() => _soundOut.Pause();
/// <inheritdoc/>
public void Play() => _soundOut.Play();
/// <inheritdoc/>
public void Stop() => _soundOut.Stop();
/// <inheritdoc/>
public PlayerState GetPlayerState()
{
return (_soundOut?.PlaybackState) switch
{
PlaybackState.Paused => PlayerState.Paused,
PlaybackState.Playing => PlayerState.Playing,
PlaybackState.Stopped => PlayerState.Stopped,
_ => PlayerState.NoDisc,
};
}
/// <inheritdoc/>
public void SetVolume(float volume)
{
if (_soundOut != null)
_soundOut.Volume = volume;
}
#endregion
}
}