diff --git a/RedBookPlayer/GUI/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs index d6e9f3b..5c11353 100644 --- a/RedBookPlayer/GUI/PlayerView.xaml.cs +++ b/RedBookPlayer/GUI/PlayerView.xaml.cs @@ -11,6 +11,7 @@ using Avalonia.Markup.Xaml; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; +using RedBookPlayer.Hardware; namespace RedBookPlayer.GUI { diff --git a/RedBookPlayer/DeEmphasisFilter.cs b/RedBookPlayer/Hardware/DeEmphasisFilter.cs similarity index 97% rename from RedBookPlayer/DeEmphasisFilter.cs rename to RedBookPlayer/Hardware/DeEmphasisFilter.cs index 87d3f66..546cbb3 100644 --- a/RedBookPlayer/DeEmphasisFilter.cs +++ b/RedBookPlayer/Hardware/DeEmphasisFilter.cs @@ -1,7 +1,7 @@ using System; using NWaves.Filters.BiQuad; -namespace RedBookPlayer +namespace RedBookPlayer.Hardware { /// /// Filter for applying de-emphasis to audio diff --git a/RedBookPlayer/Hardware/Player.cs b/RedBookPlayer/Hardware/Player.cs new file mode 100644 index 0000000..4e60b65 --- /dev/null +++ b/RedBookPlayer/Hardware/Player.cs @@ -0,0 +1,293 @@ +using System; +using System.IO; +using System.Linq; +using Aaru.CommonTypes.Enums; +using Aaru.DiscImages; +using Aaru.Filters; +using RedBookPlayer.Discs; +using RedBookPlayer.GUI; + +namespace RedBookPlayer.Hardware +{ + public class Player + { + #region Public Fields + + /// + /// Indicate if the player is ready to be used + /// + public bool Initialized { get; private set; } = false; + + /// + /// Indicate if the disc is playing + /// + public bool Playing => _soundOutput?.Playing ?? false; + + #endregion + + #region Private State Variables + + /// + /// OpticalDisc object + /// + private OpticalDisc _opticalDisc; + + /// + /// Sound output handling class + /// + public SoundOutput _soundOutput; + + #endregion + + /// + /// Initialize the player with a given image path + /// + /// Path to the disc image + /// True if playback should begin immediately, false otherwise + public void Init(string path, bool autoPlay = false) + { + // Reset the internal state for initialization + Initialized = false; + _soundOutput = new SoundOutput(); + _soundOutput.ApplyDeEmphasis = false; + _opticalDisc = null; + + try + { + // Validate the image exists + if(string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + return; + + // Load the disc image to memory + var image = new AaruFormat(); + var filter = new ZZZNoFilter(); + filter.Open(path); + image.Open(filter); + + // Generate and instantiate the disc + _opticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay); + } + catch + { + // All errors mean an invalid image in some way + return; + } + + // Initialize the sound output + _soundOutput.Init(_opticalDisc, autoPlay); + if(_soundOutput == null || !_soundOutput.Initialized) + return; + + // Mark the player as ready + Initialized = true; + } + + #region Playback + + /// + /// Toggle audio playback + /// + /// True to start playback, false to pause + public void TogglePlayPause(bool start) + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + if(start) + { + _soundOutput.Play(); + _opticalDisc.SetTotalIndexes(); + } + else + { + _soundOutput.Stop(); + } + } + + /// + /// Stop the current audio playback + /// + public void Stop() + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + _soundOutput.Stop(); + _opticalDisc.LoadFirstTrack(); + } + + /// + /// Move to the next playable track + /// + public void NextTrack() + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + bool wasPlaying = Playing; + if(wasPlaying) TogglePlayPause(false); + + _opticalDisc.NextTrack(); + if(_opticalDisc is CompactDisc compactDisc) + _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + + if(wasPlaying) TogglePlayPause(true); + } + + /// + /// Move to the previous playable track + /// + public void PreviousTrack() + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + bool wasPlaying = Playing; + if(wasPlaying) TogglePlayPause(false); + + _opticalDisc.PreviousTrack(); + if(_opticalDisc is CompactDisc compactDisc) + _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + + if(wasPlaying) TogglePlayPause(true); + } + + /// + /// Move to the next index + /// + /// True if index changes can trigger a track change, false otherwise + public void NextIndex(bool changeTrack) + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + bool wasPlaying = Playing; + if(wasPlaying) TogglePlayPause(false); + + _opticalDisc.NextIndex(changeTrack); + if(_opticalDisc is CompactDisc compactDisc) + _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + + if(wasPlaying) TogglePlayPause(true); + } + + /// + /// Move to the previous index + /// + /// True if index changes can trigger a track change, false otherwise + public void PreviousIndex(bool changeTrack) + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + bool wasPlaying = Playing; + if(wasPlaying) TogglePlayPause(false); + + _opticalDisc.PreviousIndex(changeTrack); + if(_opticalDisc is CompactDisc compactDisc) + _soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + + if(wasPlaying) TogglePlayPause(true); + } + + /// + /// Fast-forward playback by 75 sectors, if possible + /// + public void FastForward() + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + _opticalDisc.CurrentSector = Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75); + } + + /// + /// Rewind playback by 75 sectors, if possible + /// + public void Rewind() + { + if(_opticalDisc == null || !_opticalDisc.Initialized) + return; + + if(_opticalDisc.CurrentSector >= 75) + _opticalDisc.CurrentSector -= 75; + } + + #endregion + + #region Helpers + + /// + /// Generate the digit string to be interpreted by the frontend + /// + /// String representing the digits for the frontend + public string GenerateDigitString() + { + // If the disc isn't initialized, return all '-' characters + if(_opticalDisc == null || !_opticalDisc.Initialized) + return string.Empty.PadLeft(20, '-'); + + // Otherwise, take the current time into account + ulong sectorTime = _opticalDisc.CurrentSector; + if(_opticalDisc.SectionStartSector != 0) + sectorTime -= _opticalDisc.SectionStartSector; + else + sectorTime += _opticalDisc.TimeOffset; + + int[] numbers = new int[] + { + _opticalDisc.CurrentTrackNumber + 1, + _opticalDisc.CurrentTrackIndex, + + (int)(sectorTime / (75 * 60)), + (int)(sectorTime / 75 % 60), + (int)(sectorTime % 75), + + _opticalDisc.TotalTracks, + _opticalDisc.TotalIndexes, + + (int)(_opticalDisc.TotalTime / (75 * 60)), + (int)(_opticalDisc.TotalTime / 75 % 60), + (int)(_opticalDisc.TotalTime % 75), + }; + + return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); + } + + /// + /// Toggle de-emphasis processing + /// + /// True to apply de-emphasis, false otherwise + public void ToggleDeEmphasis(bool enable) => _soundOutput.ToggleDeEmphasis(enable); + + /// + /// Update the data context for the frontend + /// + /// Data context to be updated + public void UpdateDataContext(PlayerViewModel dataContext) + { + if(!Initialized || dataContext == null) + return; + + dataContext.HiddenTrack = _opticalDisc.TimeOffset > 150; + dataContext.ApplyDeEmphasis = _soundOutput.ApplyDeEmphasis; + + if(_opticalDisc is CompactDisc compactDisc) + { + dataContext.QuadChannel = compactDisc.QuadChannel; + dataContext.IsDataTrack = compactDisc.IsDataTrack; + dataContext.CopyAllowed = compactDisc.CopyAllowed; + dataContext.TrackHasEmphasis = compactDisc.TrackHasEmphasis; + } + else + { + dataContext.QuadChannel = false; + dataContext.IsDataTrack = _opticalDisc.TrackType != TrackType.Audio; + dataContext.CopyAllowed = false; + dataContext.TrackHasEmphasis = false; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/RedBookPlayer/PlayerSource.cs b/RedBookPlayer/Hardware/PlayerSource.cs similarity index 96% rename from RedBookPlayer/PlayerSource.cs rename to RedBookPlayer/Hardware/PlayerSource.cs index 3b73f5a..17b4a5c 100644 --- a/RedBookPlayer/PlayerSource.cs +++ b/RedBookPlayer/Hardware/PlayerSource.cs @@ -2,7 +2,7 @@ using System; using CSCore; using WaveFormat = CSCore.WaveFormat; -namespace RedBookPlayer +namespace RedBookPlayer.Hardware { public class PlayerSource : IWaveSource { diff --git a/RedBookPlayer/Player.cs b/RedBookPlayer/Hardware/SoundOutput.cs similarity index 51% rename from RedBookPlayer/Player.cs rename to RedBookPlayer/Hardware/SoundOutput.cs index ba49032..ef951de 100644 --- a/RedBookPlayer/Player.cs +++ b/RedBookPlayer/Hardware/SoundOutput.cs @@ -1,31 +1,26 @@ using System; -using System.IO; using System.Linq; using System.Threading.Tasks; -using Aaru.CommonTypes.Enums; -using Aaru.DiscImages; -using Aaru.Filters; using CSCore.SoundOut; using NWaves.Audio; using NWaves.Filters.BiQuad; using RedBookPlayer.Discs; -using RedBookPlayer.GUI; -namespace RedBookPlayer +namespace RedBookPlayer.Hardware { - public class Player + public class SoundOutput { #region Public Fields /// - /// Indicate if the player is ready to be used + /// Indicate if the disc is ready to be used /// public bool Initialized { get; private set; } = false; /// /// Indicates if de-emphasis should be applied /// - public bool ApplyDeEmphasis { get; private set; } = false; + public bool ApplyDeEmphasis { get; set; } = false; /// /// Indicate if the disc is playing @@ -36,16 +31,16 @@ namespace RedBookPlayer #region Private State Variables - /// - /// OpticalDisc object - /// - private OpticalDisc _opticalDisc; - /// /// Current position in the sector /// private int _currentSectorReadPosition = 0; + /// + /// OpticalDisc from the parent player for easy access + /// + private OpticalDisc _opticalDisc; + /// /// Data provider for sound output /// @@ -74,44 +69,21 @@ namespace RedBookPlayer #endregion /// - /// Initialize the player with a given image path + /// Initialize the output with a given image /// - /// Path to the disc image + /// OpticalDisc to load from /// True if playback should begin immediately, false otherwise - public void Init(string path, bool autoPlay = false) + public void Init(OpticalDisc opticalDisc, bool autoPlay = false) { - // Reset the internal state for initialization - Initialized = false; - ApplyDeEmphasis = false; - _opticalDisc = null; - - try - { - // Validate the image exists - if(string.IsNullOrWhiteSpace(path) || !File.Exists(path)) - return; - - // Load the disc image to memory - var image = new AaruFormat(); - var filter = new ZZZNoFilter(); - filter.Open(path); - image.Open(filter); - - // Generate and instantiate the disc - _opticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay); - } - catch - { - // All errors mean an invalid image in some way - return; - } - // If we have an unusable disc, just return - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(opticalDisc == null || !opticalDisc.Initialized) return; + // Save a reference to the disc + _opticalDisc = opticalDisc; + // Enable de-emphasis for CDs, if necessary - if(_opticalDisc is CompactDisc compactDisc) + if(opticalDisc is CompactDisc compactDisc) ApplyDeEmphasis = compactDisc.TrackHasEmphasis; // Setup de-emphasis filters @@ -124,7 +96,7 @@ namespace RedBookPlayer if(autoPlay) _soundOut.Play(); - // Mark the player as ready + // Mark the output as ready Initialized = true; // Begin loading data @@ -138,6 +110,9 @@ namespace RedBookPlayer /// Offset in the buffer to load at /// Number of bytes to load /// Number of bytes read + /// + /// TODO: Can we remove the need for a local reference to OpticalDisc? + /// public int ProviderRead(byte[] buffer, int offset, int count) { // Set the current volume @@ -179,7 +154,7 @@ namespace RedBookPlayer { lock(_readingImage) { - for (int i = 0; i < 4; i++) + for(int i = 0; i < 4; i++) { try { @@ -251,209 +226,25 @@ namespace RedBookPlayer #region Playback /// - /// Toggle audio playback + /// Start audio playback /// - /// True to start playback, false to pause - public void TogglePlayPause(bool start) - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - if(start) - { - _soundOut.Play(); - _opticalDisc.SetTotalIndexes(); - } - else - { - _soundOut.Stop(); - } - } + public void Play() => _soundOut.Play(); /// - /// Stop the current audio playback + /// Stop audio playback /// - public void Stop() - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - _soundOut.Stop(); - _opticalDisc.LoadFirstTrack(); - } - - /// - /// Move to the next playable track - /// - public void NextTrack() - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - bool wasPlaying = Playing; - if(wasPlaying) TogglePlayPause(false); - - _opticalDisc.NextTrack(); - if(_opticalDisc is CompactDisc compactDisc) - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - - if(wasPlaying) TogglePlayPause(true); - } - - /// - /// Move to the previous playable track - /// - public void PreviousTrack() - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - bool wasPlaying = Playing; - if(wasPlaying) TogglePlayPause(false); - - _opticalDisc.PreviousTrack(); - if(_opticalDisc is CompactDisc compactDisc) - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - - if(wasPlaying) TogglePlayPause(true); - } - - /// - /// Move to the next index - /// - /// True if index changes can trigger a track change, false otherwise - public void NextIndex(bool changeTrack) - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - bool wasPlaying = Playing; - if(wasPlaying) TogglePlayPause(false); - - _opticalDisc.NextIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - - if(wasPlaying) TogglePlayPause(true); - } - - /// - /// Move to the previous index - /// - /// True if index changes can trigger a track change, false otherwise - public void PreviousIndex(bool changeTrack) - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - bool wasPlaying = Playing; - if(wasPlaying) TogglePlayPause(false); - - _opticalDisc.PreviousIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - - if(wasPlaying) TogglePlayPause(true); - } - - /// - /// Fast-forward playback by 75 sectors, if possible - /// - public void FastForward() - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - _opticalDisc.CurrentSector = Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75); - } - - /// - /// Rewind playback by 75 sectors, if possible - /// - public void Rewind() - { - if(_opticalDisc == null || !_opticalDisc.Initialized) - return; - - if(_opticalDisc.CurrentSector >= 75) - _opticalDisc.CurrentSector -= 75; - } + public void Stop() => _soundOut.Stop(); #endregion #region Helpers - /// - /// Generate the digit string to be interpreted by the frontend - /// - /// String representing the digits for the frontend - public string GenerateDigitString() - { - // If the disc isn't initialized, return all '-' characters - if(_opticalDisc == null || !_opticalDisc.Initialized) - return string.Empty.PadLeft(20, '-'); - - // Otherwise, take the current time into account - ulong sectorTime = _opticalDisc.CurrentSector; - if(_opticalDisc.SectionStartSector != 0) - sectorTime -= _opticalDisc.SectionStartSector; - else - sectorTime += _opticalDisc.TimeOffset; - - int[] numbers = new int[] - { - _opticalDisc.CurrentTrackNumber + 1, - _opticalDisc.CurrentTrackIndex, - - (int)(sectorTime / (75 * 60)), - (int)(sectorTime / 75 % 60), - (int)(sectorTime % 75), - - _opticalDisc.TotalTracks, - _opticalDisc.TotalIndexes, - - (int)(_opticalDisc.TotalTime / (75 * 60)), - (int)(_opticalDisc.TotalTime / 75 % 60), - (int)(_opticalDisc.TotalTime % 75), - }; - - return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); - } - /// /// Toggle de-emphasis processing /// /// True to apply de-emphasis, false otherwise public void ToggleDeEmphasis(bool enable) => ApplyDeEmphasis = enable; - /// - /// Update the data context for the frontend - /// - /// Data context to be updated - public void UpdateDataContext(PlayerViewModel dataContext) - { - if(!Initialized || dataContext == null) - return; - - dataContext.HiddenTrack = _opticalDisc.TimeOffset > 150; - dataContext.ApplyDeEmphasis = ApplyDeEmphasis; - - if(_opticalDisc is CompactDisc compactDisc) - { - dataContext.QuadChannel = compactDisc.QuadChannel; - dataContext.IsDataTrack = compactDisc.IsDataTrack; - dataContext.CopyAllowed = compactDisc.CopyAllowed; - dataContext.TrackHasEmphasis = compactDisc.TrackHasEmphasis; - } - else - { - dataContext.QuadChannel = false; - dataContext.IsDataTrack = _opticalDisc.TrackType != TrackType.Audio; - dataContext.CopyAllowed = false; - dataContext.TrackHasEmphasis = false; - } - } - /// /// Sets or resets the de-emphasis filters /// @@ -490,4 +281,4 @@ namespace RedBookPlayer #endregion } -} \ No newline at end of file +}