mirror of
https://github.com/aaru-dps/RedBookPlayer.git
synced 2025-12-16 19:24:41 +00:00
Audio to its own namespace
This commit is contained in:
48
RedBookPlayer.Models/Audio/DeEmphasisFilter.cs
Normal file
48
RedBookPlayer.Models/Audio/DeEmphasisFilter.cs
Normal 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) {}
|
||||
}
|
||||
}
|
||||
58
RedBookPlayer.Models/Audio/FilterStage.cs
Normal file
58
RedBookPlayer.Models/Audio/FilterStage.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
RedBookPlayer.Models/Audio/IAudioBackend.cs
Normal file
30
RedBookPlayer.Models/Audio/IAudioBackend.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
52
RedBookPlayer.Models/Audio/Linux/AudioBackend.cs
Normal file
52
RedBookPlayer.Models/Audio/Linux/AudioBackend.cs
Normal 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
|
||||
}
|
||||
}
|
||||
44
RedBookPlayer.Models/Audio/PlayerSource.cs
Normal file
44
RedBookPlayer.Models/Audio/PlayerSource.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
183
RedBookPlayer.Models/Audio/SoundOutput.cs
Normal file
183
RedBookPlayer.Models/Audio/SoundOutput.cs
Normal 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
|
||||
}
|
||||
}
|
||||
52
RedBookPlayer.Models/Audio/Windows/AudioBackend.cs
Normal file
52
RedBookPlayer.Models/Audio/Windows/AudioBackend.cs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user