From 14ce896567656a083f59ad74c21b4b72b2e4ed68 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 12:04:26 -0700 Subject: [PATCH 01/19] Add Windows and Linux build scripts --- build.bat | 1 + build.sh | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 build.bat create mode 100644 build.sh diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..8ef6c32 --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +dotnet publish -f netcoreapp3.1 -r win-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..748729d --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +dotnet publish -f netcoreapp3.1 -r linux-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true \ No newline at end of file From 6f7095a13fd4d87964cf96e3ce2129929aac7fd3 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 12:08:08 -0700 Subject: [PATCH 02/19] Further separation of code - Create a new OpticalDisc base class for future use - Create a new CompactDisc class for most current usage - Separate out some player logic from the UI - Remove all direct references to the disc from the UI - Player initializtion starts from path --- .../{PlayableDisc.cs => Discs/CompactDisc.cs} | 221 +++----------- RedBookPlayer/Discs/OpticalDisc.cs | 166 ++++++++++ RedBookPlayer/Discs/OpticalDiscFactory.cs | 80 +++++ RedBookPlayer/Player.cs | 288 +++++++++++++++--- RedBookPlayer/PlayerView.xaml | 4 +- RedBookPlayer/PlayerView.xaml.cs | 148 +-------- RedBookPlayer/PlayerViewModel.cs | 49 ++- 7 files changed, 569 insertions(+), 387 deletions(-) rename RedBookPlayer/{PlayableDisc.cs => Discs/CompactDisc.cs} (76%) create mode 100644 RedBookPlayer/Discs/OpticalDisc.cs create mode 100644 RedBookPlayer/Discs/OpticalDiscFactory.cs diff --git a/RedBookPlayer/PlayableDisc.cs b/RedBookPlayer/Discs/CompactDisc.cs similarity index 76% rename from RedBookPlayer/PlayableDisc.cs rename to RedBookPlayer/Discs/CompactDisc.cs index f030fa9..ce714a3 100644 --- a/RedBookPlayer/PlayableDisc.cs +++ b/RedBookPlayer/Discs/CompactDisc.cs @@ -8,24 +8,17 @@ using Aaru.Decoders.CD; using Aaru.Helpers; using static Aaru.Decoders.CD.FullTOC; -namespace RedBookPlayer +namespace RedBookPlayer.Discs { - public class PlayableDisc + public class CompactDisc : OpticalDisc { #region Public Fields - /// - /// Indicate if the disc is ready to be used - /// - public bool Initialized { get; private set; } = false; - - /// - /// Current track number - /// - public int CurrentTrackNumber + /// + public override int CurrentTrackNumber { get => _currentTrackNumber; - set + protected set { // Unset image means we can't do anything if(_image == null) @@ -54,8 +47,6 @@ namespace RedBookPlayer // Set track flags from subchannel data, if possible SetTrackFlags(track); - ApplyDeEmphasis = TrackHasEmphasis; - TotalIndexes = track.Indexes.Keys.Max(); CurrentTrackIndex = track.Indexes.Keys.Min(); @@ -73,13 +64,11 @@ namespace RedBookPlayer } } - /// - /// Current track index - /// - public ushort CurrentTrackIndex + /// + public override ushort CurrentTrackIndex { get => _currentTrackIndex; - set + protected set { // Unset image means we can't do anything if(_image == null) @@ -102,10 +91,8 @@ namespace RedBookPlayer } } - /// - /// Current sector number - /// - public ulong CurrentSector + /// + public override ulong CurrentSector { get => _currentSector; set @@ -146,14 +133,14 @@ namespace RedBookPlayer } /// - /// Represents the PRE flag + /// Represents the 4CH flag /// - public bool TrackHasEmphasis { get; private set; } = false; + public bool QuadChannel { get; private set; } = false; /// - /// Indicates if de-emphasis should be applied + /// Represents the DATA flag /// - public bool ApplyDeEmphasis { get; private set; } = false; + public bool IsDataTrack => TrackType != TrackType.Audio; /// /// Represents the DCP flag @@ -161,54 +148,14 @@ namespace RedBookPlayer public bool CopyAllowed { get; private set; } = false; /// - /// Represents the track type + /// Represents the PRE flag /// - public TrackType TrackType { get; private set; } - - /// - /// Represents the 4CH flag - /// - public bool QuadChannel { get; private set; } = false; - - /// - /// Represents the sector starting the section - /// - public ulong SectionStartSector { get; private set; } - - /// - /// Represents the total tracks on the disc - /// - public int TotalTracks { get; private set; } = 0; - - /// - /// Represents the total indices on the disc - /// - public int TotalIndexes { get; private set; } = 0; - - /// - /// Total sectors in the image - /// - public ulong TotalSectors => _image.Info.Sectors; - - /// - /// Represents the time adjustment offset for the disc - /// - public ulong TimeOffset { get; private set; } = 0; - - /// - /// Represents the total playing time for the disc - /// - public ulong TotalTime { get; private set; } = 0; + public bool TrackHasEmphasis { get; private set; } = false; #endregion #region Private State Variables - /// - /// Currently loaded disc image - /// - private IOpticalMediaImage _image; - /// /// Current track number /// @@ -231,12 +178,8 @@ namespace RedBookPlayer #endregion - /// - /// Initialize the disc with a given image - /// - /// Aaruformat image to load - /// True if playback should begin immediately, false otherwise - public void Init(IOpticalMediaImage image, bool autoPlay = false) + /// + public override void Init(IOpticalMediaImage image, bool autoPlay = false) { // If the image is null, we can't do anything if(image == null) @@ -268,45 +211,8 @@ namespace RedBookPlayer #region Seeking - /// - /// Try to move to the next track, wrapping around if necessary - /// - public void NextTrack() - { - if(_image == null) - return; - - CurrentTrackNumber++; - LoadTrack(CurrentTrackNumber); - } - - /// - /// Try to move to the previous track, wrapping around if necessary - /// - public void PreviousTrack() - { - if(_image == null) - return; - - if(CurrentSector < (ulong)_image.Tracks[CurrentTrackNumber].Indexes[1] + 75) - { - if(App.Settings.AllowSkipHiddenTrack && CurrentTrackNumber == 0 && CurrentSector >= 75) - CurrentSector = 0; - else - CurrentTrackNumber--; - } - else - CurrentTrackNumber--; - - LoadTrack(CurrentTrackNumber); - } - - /// - /// Try to move to the next track index - /// - /// True if index changes can trigger a track change, false otherwise - /// True if the track was changed, false otherwise - public bool NextIndex(bool changeTrack) + /// + public override bool NextIndex(bool changeTrack) { if(_image == null) return false; @@ -328,12 +234,8 @@ namespace RedBookPlayer return false; } - /// - /// Try to move to the previous track index - /// - /// True if index changes can trigger a track change, false otherwise - /// True if the track was changed, false otherwise - public bool PreviousIndex(bool changeTrack) + /// + public override bool PreviousIndex(bool changeTrack) { if(_image == null) return false; @@ -355,59 +257,19 @@ namespace RedBookPlayer return false; } - /// - /// Fast-forward playback by 75 sectors, if possible - /// - public void FastForward() - { - if(_image == null) - return; - - CurrentSector = Math.Min(_image.Info.Sectors - 1, CurrentSector + 75); - } - - /// - /// Rewind playback by 75 sectors, if possible - /// - public void Rewind() - { - if(_image == null) - return; - - if(CurrentSector >= 75) - CurrentSector -= 75; - } - - /// - /// Toggle de-emphasis processing - /// - /// True to apply de-emphasis, false otherwise - public void ToggleDeEmphasis(bool enable) => ApplyDeEmphasis = enable; - #endregion #region Helpers - /// - /// Load the first valid track in the image - /// - public void LoadFirstTrack() + /// + public override void LoadFirstTrack() { CurrentTrackNumber = 0; LoadTrack(CurrentTrackNumber); } - /// - /// Read sector data from the base image starting from the current sector - /// - /// Current number of sectors to read - /// Byte array representing the read sectors, if possible - public byte[] ReadSectors(uint sectorsToRead) => _image.ReadSectors(CurrentSector, sectorsToRead); - - /// - /// Set the total indexes from the current track - /// - public void SetTotalIndexes() + /// + public override void SetTotalIndexes() { if(_image == null) return; @@ -415,6 +277,20 @@ namespace RedBookPlayer TotalIndexes = _image.Tracks[CurrentTrackNumber].Indexes.Keys.Max(); } + /// + protected override void LoadTrack(int track) + { + if(_image == null) + return; + + if(track < 0 || track >= _image.Tracks.Count) + return; + + ushort firstIndex = _image.Tracks[track].Indexes.Keys.Min(); + int firstSector = _image.Tracks[track].Indexes[firstIndex]; + CurrentSector = (ulong)(firstSector >= 0 ? firstSector : _image.Tracks[track].Indexes[1]); + } + /// /// Generate a CDFullTOC object from the current image /// @@ -639,23 +515,6 @@ namespace RedBookPlayer return true; } - /// - /// Load the desired track, if possible - /// - /// Track number to load - private void LoadTrack(int track) - { - if(_image == null) - return; - - if(track < 0 || track >= _image.Tracks.Count) - return; - - ushort firstIndex = _image.Tracks[track].Indexes.Keys.Min(); - int firstSector = _image.Tracks[track].Indexes[firstIndex]; - CurrentSector = (ulong)(firstSector >= 0 ? firstSector : _image.Tracks[track].Indexes[1]); - } - /// /// Set default track flags for the current track /// diff --git a/RedBookPlayer/Discs/OpticalDisc.cs b/RedBookPlayer/Discs/OpticalDisc.cs new file mode 100644 index 0000000..817c626 --- /dev/null +++ b/RedBookPlayer/Discs/OpticalDisc.cs @@ -0,0 +1,166 @@ +using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Interfaces; + +namespace RedBookPlayer.Discs +{ + public abstract class OpticalDisc + { + #region Public Fields + + /// + /// Indicate if the disc is ready to be used + /// + public bool Initialized { get; protected set; } = false; + + /// + /// Current track number + /// + public abstract int CurrentTrackNumber { get; protected set; } + + /// + /// Current track index + /// + public abstract ushort CurrentTrackIndex { get; protected set; } + + /// + /// Current sector number + /// + public abstract ulong CurrentSector { get; set; } + + /// + /// Represents the sector starting the section + /// + public ulong SectionStartSector { get; protected set; } + + /// + /// Number of bytes per sector for the current track + /// + public int BytesPerSector => _image.Tracks[CurrentTrackNumber].TrackBytesPerSector; + + /// + /// Represents the track type + /// + public TrackType TrackType { get; protected set; } + + /// + /// Represents the total tracks on the disc + /// + public int TotalTracks { get; protected set; } = 0; + + /// + /// Represents the total indices on the disc + /// + public int TotalIndexes { get; protected set; } = 0; + + /// + /// Total sectors in the image + /// + public ulong TotalSectors => _image.Info.Sectors; + + /// + /// Represents the time adjustment offset for the disc + /// + public ulong TimeOffset { get; protected set; } = 0; + + /// + /// Represents the total playing time for the disc + /// + public ulong TotalTime { get; protected set; } = 0; + + #endregion + + #region Protected State Variables + + /// + /// Currently loaded disc image + /// + protected IOpticalMediaImage _image; + + #endregion + + /// + /// Initialize the disc with a given image + /// + /// Aaruformat image to load + /// True if playback should begin immediately, false otherwise + public abstract void Init(IOpticalMediaImage image, bool autoPlay = false); + + #region Seeking + + /// + /// Try to move to the next track, wrapping around if necessary + /// + public void NextTrack() + { + if(_image == null) + return; + + CurrentTrackNumber++; + LoadTrack(CurrentTrackNumber); + } + + /// + /// Try to move to the previous track, wrapping around if necessary + /// + public void PreviousTrack() + { + if(_image == null) + return; + + if(CurrentSector < (ulong)_image.Tracks[CurrentTrackNumber].Indexes[1] + 75) + { + if(App.Settings.AllowSkipHiddenTrack && CurrentTrackNumber == 0 && CurrentSector >= 75) + CurrentSector = 0; + else + CurrentTrackNumber--; + } + else + CurrentTrackNumber--; + + LoadTrack(CurrentTrackNumber); + } + + /// + /// Try to move to the next track index + /// + /// True if index changes can trigger a track change, false otherwise + /// True if the track was changed, false otherwise + public abstract bool NextIndex(bool changeTrack); + + /// + /// Try to move to the previous track index + /// + /// True if index changes can trigger a track change, false otherwise + /// True if the track was changed, false otherwise + public abstract bool PreviousIndex(bool changeTrack); + + #endregion + + #region Helpers + + /// + /// Load the first valid track in the image + /// + public abstract void LoadFirstTrack(); + + /// + /// Read sector data from the base image starting from the current sector + /// + /// Current number of sectors to read + /// Byte array representing the read sectors, if possible + public byte[] ReadSectors(uint sectorsToRead) => _image.ReadSectors(CurrentSector, sectorsToRead); + + /// + /// Set the total indexes from the current track + /// + public abstract void SetTotalIndexes(); + + /// + /// Load the desired track, if possible + /// + /// Track number to load + protected abstract void LoadTrack(int track); + + #endregion + } +} diff --git a/RedBookPlayer/Discs/OpticalDiscFactory.cs b/RedBookPlayer/Discs/OpticalDiscFactory.cs new file mode 100644 index 0000000..99d9257 --- /dev/null +++ b/RedBookPlayer/Discs/OpticalDiscFactory.cs @@ -0,0 +1,80 @@ +using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Metadata; + +namespace RedBookPlayer.Discs +{ + public static class OpticalDiscFactory + { + /// + /// Generate an OpticalDisc from an input IOpticalMediaImage + /// + /// IOpticalMediaImage to create from + /// True if the image should be playable immediately, false otherwise + /// Instantiated OpticalDisc, if possible + public static OpticalDisc GenerateFromImage(IOpticalMediaImage image, bool autoPlay) + { + // If the image is not usable, we don't do anything + if(!IsUsableImage(image)) + return null; + + // Create the output object + OpticalDisc opticalDisc; + + // Create the proper disc type + switch(GetMediaType(image)) + { + case "Compact Disc": + case "GD": + opticalDisc = new CompactDisc(); + break; + default: + opticalDisc = null; + break; + } + + // Null image means we don't do anything + if(opticalDisc == null) + return opticalDisc; + + // Instantiate the disc and return + opticalDisc.Init(image, autoPlay); + return opticalDisc; + } + + /// + /// Gets the human-readable media type from an image + /// + /// Media image to check + /// Type from the image, empty string on error + /// TODO: Can we be more granular with sub types? + private static string GetMediaType(IOpticalMediaImage image) + { + // Null image means we don't do anything + if(image == null) + return string.Empty; + + (string type, string _) = MediaType.MediaTypeToString(image.Info.MediaType); + return type; + } + + /// + /// Indicates if the image is considered "usable" or not + /// + /// Aaruformat image file + /// True if the image is playble, false otherwise + private static bool IsUsableImage(IOpticalMediaImage image) + { + // Invalid images can't be used + if(image == null) + return false; + + // Determine based on media type + return GetMediaType(image) switch + { + "Compact Disc" => true, + "GD" => true, // Requires TOC generation + _ => false, + }; + } + } +} diff --git a/RedBookPlayer/Player.cs b/RedBookPlayer/Player.cs index 6ef92d4..a471d79 100644 --- a/RedBookPlayer/Player.cs +++ b/RedBookPlayer/Player.cs @@ -1,9 +1,14 @@ 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; namespace RedBookPlayer { @@ -16,6 +21,11 @@ namespace RedBookPlayer /// public bool Initialized { get; private set; } = false; + /// + /// Indicates if de-emphasis should be applied + /// + public bool ApplyDeEmphasis { get; private set; } = false; + /// /// Indicate if the disc is playing /// @@ -25,16 +35,16 @@ namespace RedBookPlayer #region Private State Variables + /// + /// OpticalDisc object + /// + private OpticalDisc _opticalDisc; + /// /// Current position in the sector /// private int _currentSectorReadPosition = 0; - /// - /// PlaybableDisc object - /// - private PlayableDisc _playableDisc; - /// /// Data provider for sound output /// @@ -63,20 +73,47 @@ namespace RedBookPlayer #endregion /// - /// Initialize the player with a given image + /// Initialize the player with a given image path /// - /// Initialized disc image + /// Path to the disc image /// True if playback should begin immediately, false otherwise - public void Init(PlayableDisc disc, bool autoPlay = false) + public void Init(string path, bool autoPlay = false) { - // If the disc is not initalized, we can't do anything - if(!disc.Initialized) + // 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) return; - // Set the internal reference to the disc - _playableDisc = disc; + // Enable de-emphasis for CDs, if necessary + if(_opticalDisc is CompactDisc compactDisc) + ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - // Setup the de-emphasis filters + // Setup de-emphasis filters SetupFilters(); // Setup the audio output @@ -111,27 +148,27 @@ namespace RedBookPlayer do { // Attempt to read 2 more sectors than requested - sectorsToRead = ((ulong)count / 2352) + 2; + sectorsToRead = ((ulong)(count / _opticalDisc.BytesPerSector)) + 2; zeroSectorsAmount = 0; // Avoid overreads by padding with 0-byte data at the end - if(_playableDisc.CurrentSector + sectorsToRead > _playableDisc.TotalSectors) + if(_opticalDisc.CurrentSector + sectorsToRead > _opticalDisc.TotalSectors) { ulong oldSectorsToRead = sectorsToRead; - sectorsToRead = _playableDisc.TotalSectors - _playableDisc.CurrentSector; + sectorsToRead = _opticalDisc.TotalSectors - _opticalDisc.CurrentSector; zeroSectorsAmount = oldSectorsToRead - sectorsToRead; } // TODO: Figure out when this value could be negative if(sectorsToRead <= 0) { - _playableDisc.LoadFirstTrack(); + _opticalDisc.LoadFirstTrack(); _currentSectorReadPosition = 0; } } while(sectorsToRead <= 0); // Create padding data for overreads - byte[] zeroSectors = new byte[zeroSectorsAmount * 2352]; + byte[] zeroSectors = new byte[(int)zeroSectorsAmount * _opticalDisc.BytesPerSector]; byte[] audioData; // Attempt to read the required number of sectors @@ -141,12 +178,12 @@ namespace RedBookPlayer { try { - return _playableDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); } catch(ArgumentOutOfRangeException) { - _playableDisc.LoadFirstTrack(); - return _playableDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + _opticalDisc.LoadFirstTrack(); + return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); } } }); @@ -167,7 +204,7 @@ namespace RedBookPlayer Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, Math.Min(count, audioData.Length - _currentSectorReadPosition)); // Apply de-emphasis filtering, only if enabled - if(_playableDisc.ApplyDeEmphasis) + if(ApplyDeEmphasis) { float[][] floatAudioData = new float[2][]; floatAudioData[0] = new float[audioDataSegment.Length / 4]; @@ -188,10 +225,10 @@ namespace RedBookPlayer // Set the read position in the sector for easier access _currentSectorReadPosition += count; - if(_currentSectorReadPosition >= 2352) + if(_currentSectorReadPosition >= _opticalDisc.BytesPerSector) { - _playableDisc.CurrentSector += (ulong)_currentSectorReadPosition / 2352; - _currentSectorReadPosition %= 2352; + _opticalDisc.CurrentSector += (ulong)(_currentSectorReadPosition / _opticalDisc.BytesPerSector); + _currentSectorReadPosition %= _opticalDisc.BytesPerSector; } return count; @@ -200,26 +237,23 @@ namespace RedBookPlayer #region Playback /// - /// Start audio playback + /// Toggle audio playback /// - public void Play() + /// True to start playback, false to pause + public void TogglePlayPause(bool start) { - if(!_playableDisc.Initialized) + if(_opticalDisc == null || !_opticalDisc.Initialized) return; - _soundOut.Play(); - _playableDisc.SetTotalIndexes(); - } - - /// - /// Pause the current audio playback - /// - public void Pause() - { - if(!_playableDisc.Initialized) - return; - - _soundOut.Stop(); + if(start) + { + _soundOut.Play(); + _opticalDisc.SetTotalIndexes(); + } + else + { + _soundOut.Stop(); + } } /// @@ -227,17 +261,185 @@ namespace RedBookPlayer /// public void Stop() { - if(!_playableDisc.Initialized) + if(_opticalDisc == null || !_opticalDisc.Initialized) return; _soundOut.Stop(); - _playableDisc.LoadFirstTrack(); + _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; } #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 /// diff --git a/RedBookPlayer/PlayerView.xaml b/RedBookPlayer/PlayerView.xaml index 2213eba..ad79b76 100644 --- a/RedBookPlayer/PlayerView.xaml +++ b/RedBookPlayer/PlayerView.xaml @@ -86,8 +86,8 @@ - AUDIO - AUDIO + AUDIO + AUDIO DATA DATA EMPHASIS diff --git a/RedBookPlayer/PlayerView.xaml.cs b/RedBookPlayer/PlayerView.xaml.cs index 72dc4b3..2f1a092 100644 --- a/RedBookPlayer/PlayerView.xaml.cs +++ b/RedBookPlayer/PlayerView.xaml.cs @@ -4,10 +4,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using System.Timers; -using Aaru.CommonTypes.Enums; -using Aaru.CommonTypes.Interfaces; -using Aaru.DiscImages; -using Aaru.Filters; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; @@ -25,11 +21,6 @@ namespace RedBookPlayer /// public static Player Player = new Player(); - /// - /// Disc representing the loaded image - /// - public static PlayableDisc PlayableDisc = new PlayableDisc(); - /// /// Set of images representing the digits for the UI /// @@ -56,7 +47,7 @@ namespace RedBookPlayer public async Task GetPath() { var dialog = new OpenFileDialog { AllowMultiple = false }; - List knownExtensions = new AaruFormat().KnownExtensions.ToList(); + List knownExtensions = new Aaru.DiscImages.AaruFormat().KnownExtensions.ToList(); dialog.Filters.Add(new FileDialogFilter() { Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")", @@ -66,43 +57,6 @@ namespace RedBookPlayer return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault(); } - /// - /// Generate the digit string to be interpreted by the UI - /// - /// String representing the digits for the player - private string GenerateDigitString() - { - // If the disc or player aren't initialized, return all '-' characters - if (!PlayableDisc.Initialized) - return string.Empty.PadLeft(20, '-'); - - // Otherwise, take the current time into account - ulong sectorTime = PlayableDisc.CurrentSector; - if (PlayableDisc.SectionStartSector != 0) - sectorTime -= PlayableDisc.SectionStartSector; - else - sectorTime += PlayableDisc.TimeOffset; - - int[] numbers = new int[] - { - PlayableDisc.CurrentTrackNumber + 1, - PlayableDisc.CurrentTrackIndex, - - (int)(sectorTime / (75 * 60)), - (int)(sectorTime / 75 % 60), - (int)(sectorTime % 75), - - PlayableDisc.TotalTracks, - PlayableDisc.TotalIndexes, - - (int)(PlayableDisc.TotalTime / (75 * 60)), - (int)(PlayableDisc.TotalTime / 75 % 60), - (int)(PlayableDisc.TotalTime % 75), - }; - - return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2))); - } - /// /// Load the png image for a given character based on the theme /// @@ -195,28 +149,6 @@ namespace RedBookPlayer _updateTimer.Start(); } - /// - /// Indicates if the image is considered "playable" or not - /// - /// Aaruformat image file - /// True if the image is playble, false otherwise - private bool IsPlayableImage(IOpticalMediaImage image) - { - // Invalid images can't be played - if (image == null) - return false; - - // Determine based on media type - // TODO: Can we be more granular with sub types? - (string type, string _) = Aaru.CommonTypes.Metadata.MediaType.MediaTypeToString(image.Info.MediaType); - return type switch - { - "Compact Disc" => true, - "GD" => true, // Requires TOC generation - _ => false, - }; - } - /// /// Load an image from the path /// @@ -225,24 +157,8 @@ namespace RedBookPlayer { bool result = await Task.Run(() => { - var image = new AaruFormat(); - var filter = new ZZZNoFilter(); - filter.Open(path); - image.Open(filter); - - if(IsPlayableImage(image)) - { - PlayableDisc.Init(image, App.Settings.AutoPlay); - if(PlayableDisc.Initialized) - { - Player.Init(PlayableDisc, App.Settings.AutoPlay); - return true; - } - - return false; - } - else - return false; + Player.Init(path, App.Settings.AutoPlay); + return Player.Initialized; }); if(result) @@ -261,24 +177,14 @@ namespace RedBookPlayer { Dispatcher.UIThread.InvokeAsync(() => { - string digitString = GenerateDigitString(); + string digitString = Player.GenerateDigitString(); for (int i = 0; i < _digits.Length; i++) { if (_digits[i] != null) _digits[i].Source = GetBitmap(digitString[i]); } - if (Player.Initialized) - { - PlayerViewModel dataContext = (PlayerViewModel)DataContext; - dataContext.HiddenTrack = PlayableDisc.TimeOffset > 150; - dataContext.ApplyDeEmphasis = PlayableDisc.ApplyDeEmphasis; - dataContext.TrackHasEmphasis = PlayableDisc.TrackHasEmphasis; - dataContext.CopyAllowed = PlayableDisc.CopyAllowed; - dataContext.QuadChannel = PlayableDisc.QuadChannel; - dataContext.IsAudioTrack = PlayableDisc.TrackType == TrackType.Audio; - dataContext.IsDataTrack = PlayableDisc.TrackType != TrackType.Audio; - } + Player.UpdateDataContext(DataContext as PlayerViewModel); }); } @@ -295,51 +201,27 @@ namespace RedBookPlayer LoadImage(path); } - public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.Play(); + public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(true); - public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.Pause(); + public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(false); public void StopButton_Click(object sender, RoutedEventArgs e) => Player.Stop(); - public void NextTrackButton_Click(object sender, RoutedEventArgs e) - { - bool wasPlaying = Player.Playing; - if(wasPlaying) Player.Pause(); - PlayableDisc.NextTrack(); - if(wasPlaying) Player.Play(); - } + public void NextTrackButton_Click(object sender, RoutedEventArgs e) => Player.NextTrack(); - public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) - { - bool wasPlaying = Player.Playing; - if(wasPlaying) Player.Pause(); - PlayableDisc.PreviousTrack(); - if(wasPlaying) Player.Play(); - } + public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => Player.PreviousTrack(); - public void NextIndexButton_Click(object sender, RoutedEventArgs e) - { - bool wasPlaying = Player.Playing; - if(wasPlaying) Player.Pause(); - PlayableDisc.NextIndex(App.Settings.IndexButtonChangeTrack); - if(wasPlaying) Player.Play(); - } + public void NextIndexButton_Click(object sender, RoutedEventArgs e) => Player.NextIndex(App.Settings.IndexButtonChangeTrack); - public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) - { - bool wasPlaying = Player.Playing; - if(wasPlaying) Player.Pause(); - PlayableDisc.PreviousIndex(App.Settings.IndexButtonChangeTrack); - if(wasPlaying) Player.Play(); - } + public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => Player.PreviousIndex(App.Settings.IndexButtonChangeTrack); - public void FastForwardButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.FastForward(); + public void FastForwardButton_Click(object sender, RoutedEventArgs e) => Player.FastForward(); - public void RewindButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.Rewind(); + public void RewindButton_Click(object sender, RoutedEventArgs e) => Player.Rewind(); - public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.ToggleDeEmphasis(true); + public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(true); - public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.ToggleDeEmphasis(false); + public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(false); #endregion } diff --git a/RedBookPlayer/PlayerViewModel.cs b/RedBookPlayer/PlayerViewModel.cs index 17e058f..f87664b 100644 --- a/RedBookPlayer/PlayerViewModel.cs +++ b/RedBookPlayer/PlayerViewModel.cs @@ -11,6 +11,27 @@ namespace RedBookPlayer set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); } + private bool _quadChannel; + public bool QuadChannel + { + get => _quadChannel; + set => this.RaiseAndSetIfChanged(ref _quadChannel, value); + } + + private bool _isDataTrack; + public bool IsDataTrack + { + get => _isDataTrack; + set => this.RaiseAndSetIfChanged(ref _isDataTrack, value); + } + + private bool _copyAllowed; + public bool CopyAllowed + { + get => _copyAllowed; + set => this.RaiseAndSetIfChanged(ref _copyAllowed, value); + } + private bool _trackHasEmphasis; public bool TrackHasEmphasis { @@ -24,33 +45,5 @@ namespace RedBookPlayer get => _hiddenTrack; set => this.RaiseAndSetIfChanged(ref _hiddenTrack, value); } - - private bool _copyAllowed; - public bool CopyAllowed - { - get => _copyAllowed; - set => this.RaiseAndSetIfChanged(ref _copyAllowed, value); - } - - private bool _quadChannel; - public bool QuadChannel - { - get => _quadChannel; - set => this.RaiseAndSetIfChanged(ref _quadChannel, value); - } - - private bool _isAudioTrack; - public bool IsAudioTrack - { - get => _isAudioTrack; - set => this.RaiseAndSetIfChanged(ref _isAudioTrack, value); - } - - private bool _isDataTrack; - public bool IsDataTrack - { - get => _isDataTrack; - set => this.RaiseAndSetIfChanged(ref _isDataTrack, value); - } } } \ No newline at end of file From 258b3e9feea7d0521fd9e3f3cb7f98122b1c5204 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 13:49:12 -0700 Subject: [PATCH 03/19] Fix boundary issues on read --- RedBookPlayer/Player.cs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/RedBookPlayer/Player.cs b/RedBookPlayer/Player.cs index a471d79..a496e26 100644 --- a/RedBookPlayer/Player.cs +++ b/RedBookPlayer/Player.cs @@ -148,7 +148,7 @@ namespace RedBookPlayer do { // Attempt to read 2 more sectors than requested - sectorsToRead = ((ulong)(count / _opticalDisc.BytesPerSector)) + 2; + sectorsToRead = ((ulong)count / (ulong)_opticalDisc.BytesPerSector) + 2; zeroSectorsAmount = 0; // Avoid overreads by padding with 0-byte data at the end @@ -156,7 +156,9 @@ namespace RedBookPlayer { ulong oldSectorsToRead = sectorsToRead; sectorsToRead = _opticalDisc.TotalSectors - _opticalDisc.CurrentSector; - zeroSectorsAmount = oldSectorsToRead - sectorsToRead; + + int tempZeroSectorCount = (int)(oldSectorsToRead - sectorsToRead); + zeroSectorsAmount = (ulong)(tempZeroSectorCount < 0 ? 0 : tempZeroSectorCount); } // TODO: Figure out when this value could be negative @@ -176,15 +178,19 @@ namespace RedBookPlayer { lock(_readingImage) { - try + for (int i = 0; i < 4; i++) { - return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); - } - catch(ArgumentOutOfRangeException) - { - _opticalDisc.LoadFirstTrack(); - return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + try + { + return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + } + catch(ArgumentOutOfRangeException) + { + _opticalDisc.LoadFirstTrack(); + } } + + return zeroSectors; } }); @@ -201,7 +207,14 @@ namespace RedBookPlayer // Load only the requested audio segment byte[] audioDataSegment = new byte[count]; - Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, Math.Min(count, audioData.Length - _currentSectorReadPosition)); + int copyAmount = Math.Min(count, audioData.Length - _currentSectorReadPosition); + if(Math.Max(0, copyAmount) == 0) + { + Array.Clear(buffer, offset, count); + return count; + } + + Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, copyAmount); // Apply de-emphasis filtering, only if enabled if(ApplyDeEmphasis) From ef7cdcb2e14e9ee45ce88d99d9e6d5115d1b3060 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 13:49:37 -0700 Subject: [PATCH 04/19] Make console opening Windows + Debug only --- RedBookPlayer/Program.cs | 10 +++++----- RedBookPlayer/RedBookPlayer.csproj | 10 +++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/RedBookPlayer/Program.cs b/RedBookPlayer/Program.cs index 111558d..532003a 100644 --- a/RedBookPlayer/Program.cs +++ b/RedBookPlayer/Program.cs @@ -1,4 +1,4 @@ -#if Windows +#if WindowsDebug using System.Runtime.InteropServices; #endif using Avalonia; @@ -10,17 +10,17 @@ namespace RedBookPlayer { public static void Main(string[] args) { - #if Windows +#if WindowsDebug AllocConsole(); - #endif +#endif BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); } - #if Windows +#if WindowsDebug [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] static extern bool AllocConsole(); - #endif +#endif public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure().UsePlatformDetect().LogToDebug(); } diff --git a/RedBookPlayer/RedBookPlayer.csproj b/RedBookPlayer/RedBookPlayer.csproj index 0494bd3..c313adc 100644 --- a/RedBookPlayer/RedBookPlayer.csproj +++ b/RedBookPlayer/RedBookPlayer.csproj @@ -1,17 +1,13 @@ - + WinExe netcoreapp3.1 true win-x64;linux-x64 - linux-x64 embedded - true - true - true - - Windows + + WindowsDebug From 06dcf0e0e9070bb0ea9a1d9a7c40cc8eb886bd83 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 13:57:11 -0700 Subject: [PATCH 05/19] Simplify condition check --- RedBookPlayer/RedBookPlayer.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedBookPlayer/RedBookPlayer.csproj b/RedBookPlayer/RedBookPlayer.csproj index c313adc..bc7ee1e 100644 --- a/RedBookPlayer/RedBookPlayer.csproj +++ b/RedBookPlayer/RedBookPlayer.csproj @@ -6,7 +6,7 @@ win-x64;linux-x64 embedded - + WindowsDebug From 3755b168ff75155bc674ee50e05fa98e86b9bba1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 13:58:02 -0700 Subject: [PATCH 06/19] Add build files to solution --- RedBookPlayer.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/RedBookPlayer.sln b/RedBookPlayer.sln index a4fee71..a622fe1 100644 --- a/RedBookPlayer.sln +++ b/RedBookPlayer.sln @@ -33,6 +33,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUETools.Codecs", "Aaru\cue EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUETools.Codecs.Flake", "Aaru\cuetools.net\CUETools.Codecs.Flake\CUETools.Codecs.Flake.csproj", "{ED8E11B7-786F-4EFF-9E4C-B937B7A2DE89}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0531C157-8111-4BC9-8C65-A2FDDB0C96FD}" + ProjectSection(SolutionItems) = preProject + build.bat = build.bat + build.sh = build.sh + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 8c2a74c1005c00926df33f2ae619fb8ff730778a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 13:59:20 -0700 Subject: [PATCH 07/19] Build both debug and release in scripts --- build.bat | 1 + build.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/build.bat b/build.bat index 8ef6c32..fe840c6 100644 --- a/build.bat +++ b/build.bat @@ -1 +1,2 @@ +dotnet publish -f netcoreapp3.1 -r win-x64 -c Debug -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true dotnet publish -f netcoreapp3.1 -r win-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true \ No newline at end of file diff --git a/build.sh b/build.sh index 748729d..67af1ef 100644 --- a/build.sh +++ b/build.sh @@ -1,2 +1,3 @@ #!/usr/bin/env bash +dotnet publish -f netcoreapp3.1 -r linux-x64 -c Debug -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true dotnet publish -f netcoreapp3.1 -r linux-x64 -c Release -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true \ No newline at end of file From 0207448d7629036f7a4ebd38083e3d62d1f5fd8a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 14:23:33 -0700 Subject: [PATCH 08/19] Move GUI code to own namespace --- RedBookPlayer/App.xaml.cs | 1 + RedBookPlayer/{ => GUI}/MainWindow.xaml | 2 +- RedBookPlayer/{ => GUI}/MainWindow.xaml.cs | 2 +- RedBookPlayer/{ => GUI}/PlayerView.xaml | 2 +- RedBookPlayer/{ => GUI}/PlayerView.xaml.cs | 2 +- RedBookPlayer/{ => GUI}/PlayerViewModel.cs | 2 +- RedBookPlayer/{ => GUI}/SettingsWindow.xaml | 2 +- RedBookPlayer/{ => GUI}/SettingsWindow.xaml.cs | 2 +- RedBookPlayer/Player.cs | 1 + 9 files changed, 9 insertions(+), 7 deletions(-) rename RedBookPlayer/{ => GUI}/MainWindow.xaml (75%) rename RedBookPlayer/{ => GUI}/MainWindow.xaml.cs (99%) rename RedBookPlayer/{ => GUI}/PlayerView.xaml (98%) rename RedBookPlayer/{ => GUI}/PlayerView.xaml.cs (99%) rename RedBookPlayer/{ => GUI}/PlayerViewModel.cs (97%) rename RedBookPlayer/{ => GUI}/SettingsWindow.xaml (95%) rename RedBookPlayer/{ => GUI}/SettingsWindow.xaml.cs (98%) diff --git a/RedBookPlayer/App.xaml.cs b/RedBookPlayer/App.xaml.cs index 43a5898..03c77fd 100644 --- a/RedBookPlayer/App.xaml.cs +++ b/RedBookPlayer/App.xaml.cs @@ -5,6 +5,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using RedBookPlayer.GUI; namespace RedBookPlayer { diff --git a/RedBookPlayer/MainWindow.xaml b/RedBookPlayer/GUI/MainWindow.xaml similarity index 75% rename from RedBookPlayer/MainWindow.xaml rename to RedBookPlayer/GUI/MainWindow.xaml index eeab898..89d5f64 100644 --- a/RedBookPlayer/MainWindow.xaml +++ b/RedBookPlayer/GUI/MainWindow.xaml @@ -1,6 +1,6 @@ + x:Class="RedBookPlayer.GUI.MainWindow" Title="RedBookPlayer" SizeToContent="WidthAndHeight"> \ No newline at end of file diff --git a/RedBookPlayer/MainWindow.xaml.cs b/RedBookPlayer/GUI/MainWindow.xaml.cs similarity index 99% rename from RedBookPlayer/MainWindow.xaml.cs rename to RedBookPlayer/GUI/MainWindow.xaml.cs index f6166cc..70fae17 100644 --- a/RedBookPlayer/MainWindow.xaml.cs +++ b/RedBookPlayer/GUI/MainWindow.xaml.cs @@ -5,7 +5,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Markup.Xaml; -namespace RedBookPlayer +namespace RedBookPlayer.GUI { public class MainWindow : Window { diff --git a/RedBookPlayer/PlayerView.xaml b/RedBookPlayer/GUI/PlayerView.xaml similarity index 98% rename from RedBookPlayer/PlayerView.xaml rename to RedBookPlayer/GUI/PlayerView.xaml index ad79b76..9221ecd 100644 --- a/RedBookPlayer/PlayerView.xaml +++ b/RedBookPlayer/GUI/PlayerView.xaml @@ -1,7 +1,7 @@ + x:Class="RedBookPlayer.GUI.PlayerView" Width="900" Height="400"> diff --git a/RedBookPlayer/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs similarity index 99% rename from RedBookPlayer/PlayerView.xaml.cs rename to RedBookPlayer/GUI/PlayerView.xaml.cs index 2f1a092..d6e9f3b 100644 --- a/RedBookPlayer/PlayerView.xaml.cs +++ b/RedBookPlayer/GUI/PlayerView.xaml.cs @@ -12,7 +12,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Threading; -namespace RedBookPlayer +namespace RedBookPlayer.GUI { public class PlayerView : UserControl { diff --git a/RedBookPlayer/PlayerViewModel.cs b/RedBookPlayer/GUI/PlayerViewModel.cs similarity index 97% rename from RedBookPlayer/PlayerViewModel.cs rename to RedBookPlayer/GUI/PlayerViewModel.cs index f87664b..5090377 100644 --- a/RedBookPlayer/PlayerViewModel.cs +++ b/RedBookPlayer/GUI/PlayerViewModel.cs @@ -1,6 +1,6 @@ using ReactiveUI; -namespace RedBookPlayer +namespace RedBookPlayer.GUI { public class PlayerViewModel : ReactiveObject { diff --git a/RedBookPlayer/SettingsWindow.xaml b/RedBookPlayer/GUI/SettingsWindow.xaml similarity index 95% rename from RedBookPlayer/SettingsWindow.xaml rename to RedBookPlayer/GUI/SettingsWindow.xaml index 3f2ce4c..7f5c54a 100644 --- a/RedBookPlayer/SettingsWindow.xaml +++ b/RedBookPlayer/GUI/SettingsWindow.xaml @@ -1,7 +1,7 @@ + d:DesignHeight="450" x:Class="RedBookPlayer.GUI.SettingsWindow" Title="Settings" Width="450" Height="600"> Themes diff --git a/RedBookPlayer/SettingsWindow.xaml.cs b/RedBookPlayer/GUI/SettingsWindow.xaml.cs similarity index 98% rename from RedBookPlayer/SettingsWindow.xaml.cs rename to RedBookPlayer/GUI/SettingsWindow.xaml.cs index ae39c0f..f88a3ac 100644 --- a/RedBookPlayer/SettingsWindow.xaml.cs +++ b/RedBookPlayer/GUI/SettingsWindow.xaml.cs @@ -4,7 +4,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -namespace RedBookPlayer +namespace RedBookPlayer.GUI { public class SettingsWindow : Window { diff --git a/RedBookPlayer/Player.cs b/RedBookPlayer/Player.cs index a496e26..ba49032 100644 --- a/RedBookPlayer/Player.cs +++ b/RedBookPlayer/Player.cs @@ -9,6 +9,7 @@ using CSCore.SoundOut; using NWaves.Audio; using NWaves.Filters.BiQuad; using RedBookPlayer.Discs; +using RedBookPlayer.GUI; namespace RedBookPlayer { From 3018b374ab533f83667c6a269851740edc3754fd Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 14:24:13 -0700 Subject: [PATCH 09/19] Remove unneeded code --- RedBookPlayer/HiResTimer.cs | 116 -------------------- RedBookPlayer/HiResTimerElapsedEventArgs.cs | 11 -- 2 files changed, 127 deletions(-) delete mode 100644 RedBookPlayer/HiResTimer.cs delete mode 100644 RedBookPlayer/HiResTimerElapsedEventArgs.cs diff --git a/RedBookPlayer/HiResTimer.cs b/RedBookPlayer/HiResTimer.cs deleted file mode 100644 index d5c8307..0000000 --- a/RedBookPlayer/HiResTimer.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; - -namespace RedBookPlayer -{ - /// - /// Recurring timer wrapper with a high degree of accuracy - /// - public class HiResTimer - { - static readonly float tickFrequency = 1000f / Stopwatch.Frequency; - - volatile float _interval; - volatile bool _isRunning; - - public HiResTimer() : this(1f) {} - - public HiResTimer(float interval) - { - if(interval < 0f || - float.IsNaN(interval)) - throw new ArgumentOutOfRangeException(nameof(interval)); - - _interval = interval; - } - - public float Interval - { - get => _interval; - set - { - if(value < 0f || - float.IsNaN(value)) - throw new ArgumentOutOfRangeException(nameof(value)); - - _interval = value; - } - } - - public bool Enabled - { - get => _isRunning; - set - { - if(value) - Start(); - else - Stop(); - } - } - - public event EventHandler Elapsed; - - public void Start() - { - if(_isRunning) - return; - - _isRunning = true; - var thread = new Thread(ExecuteTimer); - thread.Priority = ThreadPriority.Highest; - thread.Start(); - } - - public void Stop() => _isRunning = false; - - void ExecuteTimer() - { - float nextTrigger = 0f; - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - while(_isRunning) - { - nextTrigger += _interval; - float elapsed; - - while(true) - { - elapsed = ElapsedHiRes(stopwatch); - float diff = nextTrigger - elapsed; - - if(diff <= 0f) - break; - - if(diff < 1f) - Thread.SpinWait(10); - else if(diff < 5f) - Thread.SpinWait(100); - else if(diff < 15f) - Thread.Sleep(1); - else - Thread.Sleep(10); - - if(!_isRunning) - return; - } - - float delay = elapsed - nextTrigger; - Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay)); - - if(!(stopwatch.Elapsed.TotalHours >= 1d)) - continue; - - stopwatch.Restart(); - nextTrigger = 0f; - } - - stopwatch.Stop(); - } - - static float ElapsedHiRes(Stopwatch stopwatch) => stopwatch.ElapsedTicks * tickFrequency; - } -} \ No newline at end of file diff --git a/RedBookPlayer/HiResTimerElapsedEventArgs.cs b/RedBookPlayer/HiResTimerElapsedEventArgs.cs deleted file mode 100644 index 1abbc91..0000000 --- a/RedBookPlayer/HiResTimerElapsedEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace RedBookPlayer -{ - public class HiResTimerElapsedEventArgs : EventArgs - { - internal HiResTimerElapsedEventArgs(float delay) => Delay = delay; - - public float Delay { get; } - } -} \ No newline at end of file From 93709b8d286c835d04f673bd890a2e147106bd29 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 15:43:58 -0700 Subject: [PATCH 10/19] Fix Settings --- RedBookPlayer/Settings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/RedBookPlayer/Settings.cs b/RedBookPlayer/Settings.cs index 8640e43..fe144a0 100644 --- a/RedBookPlayer/Settings.cs +++ b/RedBookPlayer/Settings.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text.Json; +using RedBookPlayer.GUI; namespace RedBookPlayer { From 5f0e2f03dd716591ae08b04f18d2fd4a51388a0a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 15:45:13 -0700 Subject: [PATCH 11/19] Create Hardware namespace for components --- RedBookPlayer/GUI/PlayerView.xaml.cs | 1 + .../{ => Hardware}/DeEmphasisFilter.cs | 2 +- RedBookPlayer/Hardware/Player.cs | 293 ++++++++++++++++++ RedBookPlayer/{ => Hardware}/PlayerSource.cs | 2 +- .../{Player.cs => Hardware/SoundOutput.cs} | 263 ++-------------- 5 files changed, 323 insertions(+), 238 deletions(-) rename RedBookPlayer/{ => Hardware}/DeEmphasisFilter.cs (97%) create mode 100644 RedBookPlayer/Hardware/Player.cs rename RedBookPlayer/{ => Hardware}/PlayerSource.cs (96%) rename RedBookPlayer/{Player.cs => Hardware/SoundOutput.cs} (51%) 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 +} From c1827ff6a365ab0333b79ab377175187cf31f490 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 15:51:24 -0700 Subject: [PATCH 12/19] Rename helper method to be more accurate --- RedBookPlayer/GUI/PlayerView.xaml.cs | 68 ++++++++++++++-------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/RedBookPlayer/GUI/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs index 5c11353..fa4e8ae 100644 --- a/RedBookPlayer/GUI/PlayerView.xaml.cs +++ b/RedBookPlayer/GUI/PlayerView.xaml.cs @@ -82,10 +82,43 @@ namespace RedBookPlayer.GUI } } + /// + /// Initialize the UI based on the currently selected theme + /// + /// XAML data representing the theme, null for default + private void InitializeComponent(string xaml) + { + DataContext = new PlayerViewModel(); + + if (xaml != null) + new AvaloniaXamlLoader().Load(xaml, null, this); + else + AvaloniaXamlLoader.Load(this); + + InitializeDigits(); + + _updateTimer = new Timer(1000 / 60); + + _updateTimer.Elapsed += (sender, e) => + { + try + { + UpdateView(sender, e); + } + catch(Exception ex) + { + Console.WriteLine(ex); + } + }; + + _updateTimer.AutoReset = true; + _updateTimer.Start(); + } + /// /// Initialize the displayed digits array /// - private void Initialize() + private void InitializeDigits() { _digits = new Image[] { @@ -117,39 +150,6 @@ namespace RedBookPlayer.GUI }; } - /// - /// Initialize the UI based on the currently selected theme - /// - /// XAML data representing the theme, null for default - private void InitializeComponent(string xaml) - { - DataContext = new PlayerViewModel(); - - if (xaml != null) - new AvaloniaXamlLoader().Load(xaml, null, this); - else - AvaloniaXamlLoader.Load(this); - - Initialize(); - - _updateTimer = new Timer(1000 / 60); - - _updateTimer.Elapsed += (sender, e) => - { - try - { - UpdateView(sender, e); - } - catch(Exception ex) - { - Console.WriteLine(ex); - } - }; - - _updateTimer.AutoReset = true; - _updateTimer.Start(); - } - /// /// Load an image from the path /// From 2f0f2d36054d3e0b4fcca114ee7b9c420203deb2 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 15:53:48 -0700 Subject: [PATCH 13/19] Move comment to a more relevant location --- RedBookPlayer/Hardware/SoundOutput.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RedBookPlayer/Hardware/SoundOutput.cs b/RedBookPlayer/Hardware/SoundOutput.cs index ef951de..ca89391 100644 --- a/RedBookPlayer/Hardware/SoundOutput.cs +++ b/RedBookPlayer/Hardware/SoundOutput.cs @@ -39,6 +39,9 @@ namespace RedBookPlayer.Hardware /// /// OpticalDisc from the parent player for easy access /// + /// + /// TODO: Can we remove the need for a local reference to OpticalDisc? + /// private OpticalDisc _opticalDisc; /// @@ -110,9 +113,6 @@ namespace RedBookPlayer.Hardware /// 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 From 6130a52c2f5097ccaf0672873d0902ead8608094 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 16:33:15 -0700 Subject: [PATCH 14/19] Update Aaru to 2a6903f8 --- Aaru | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Aaru b/Aaru index b41b167..2a6903f 160000 --- a/Aaru +++ b/Aaru @@ -1 +1 @@ -Subproject commit b41b1679117927df188b2f14bfaa5c2190af05d1 +Subproject commit 2a6903f866a29b0c858d37120cfb1c725ff17980 From 9147fe7da7ec15161223c58e6d84fda2176b35b4 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 16:42:28 -0700 Subject: [PATCH 15/19] Use Aaru.Decoders TOC creation --- RedBookPlayer/Discs/CompactDisc.cs | 188 +++-------------------------- 1 file changed, 18 insertions(+), 170 deletions(-) diff --git a/RedBookPlayer/Discs/CompactDisc.cs b/RedBookPlayer/Discs/CompactDisc.cs index ce714a3..87b670a 100644 --- a/RedBookPlayer/Discs/CompactDisc.cs +++ b/RedBookPlayer/Discs/CompactDisc.cs @@ -291,180 +291,16 @@ namespace RedBookPlayer.Discs CurrentSector = (ulong)(firstSector >= 0 ? firstSector : _image.Tracks[track].Indexes[1]); } - /// - /// Generate a CDFullTOC object from the current image - /// - /// CDFullTOC object, if possible - /// Copied from - private bool GenerateTOC() - { - // Invalid image means we can't generate anything - if(_image == null) - return false; - - _toc = new CDFullTOC(); - Dictionary _trackFlags = new Dictionary(); - Dictionary sessionEndingTrack = new Dictionary(); - _toc.FirstCompleteSession = byte.MaxValue; - _toc.LastCompleteSession = byte.MinValue; - List trackDescriptors = new List(); - byte currentTrack = 0; - - foreach(Track track in _image.Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) - { - byte[] trackFlags = _image.ReadSectorTag(track.TrackStartSector + 1, SectorTagType.CdTrackFlags); - if(trackFlags != null) - _trackFlags.Add((byte)track.TrackStartSector, trackFlags[0]); - } - - foreach(Track track in _image.Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) - { - if(track.TrackSession < _toc.FirstCompleteSession) - _toc.FirstCompleteSession = (byte)track.TrackSession; - - if(track.TrackSession <= _toc.LastCompleteSession) - { - currentTrack = (byte)track.TrackSequence; - - continue; - } - - if(_toc.LastCompleteSession > 0) - sessionEndingTrack.Add(_toc.LastCompleteSession, currentTrack); - - _toc.LastCompleteSession = (byte)track.TrackSession; - } - - byte currentSession = 0; - - foreach(Track track in _image.Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence)) - { - _trackFlags.TryGetValue((byte)track.TrackSequence, out byte trackControl); - - if(trackControl == 0 && - track.TrackType != Aaru.CommonTypes.Enums.TrackType.Audio) - trackControl = (byte)CdFlags.DataTrack; - - // Lead-Out - if(track.TrackSession > currentSession && - currentSession != 0) - { - (byte minute, byte second, byte frame) leadoutAmsf = LbaToMsf(track.TrackStartSector - 150); - - (byte minute, byte second, byte frame) leadoutPmsf = - LbaToMsf(_image.Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).Last(). - TrackStartSector); - - // Lead-out - trackDescriptors.Add(new TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xB0, - ADR = 5, - CONTROL = 0, - HOUR = 0, - Min = leadoutAmsf.minute, - Sec = leadoutAmsf.second, - Frame = leadoutAmsf.frame, - PHOUR = 2, - PMIN = leadoutPmsf.minute, - PSEC = leadoutPmsf.second, - PFRAME = leadoutPmsf.frame - }); - - // This seems to be constant? It should not exist on CD-ROM but CloneCD creates them anyway - // Format seems like ATIP, but ATIP should not be as 0xC0 in TOC... - //trackDescriptors.Add(new TrackDataDescriptor - //{ - // SessionNumber = currentSession, - // POINT = 0xC0, - // ADR = 5, - // CONTROL = 0, - // Min = 128, - // PMIN = 97, - // PSEC = 25 - //}); - } - - // Lead-in - if(track.TrackSession > currentSession) - { - currentSession = (byte)track.TrackSession; - sessionEndingTrack.TryGetValue(currentSession, out byte endingTrackNumber); - - (byte minute, byte second, byte frame) leadinPmsf = - LbaToMsf(_image.Tracks.FirstOrDefault(t => t.TrackSequence == endingTrackNumber)?.TrackEndSector ?? - 0 + 1); - - // Starting track - trackDescriptors.Add(new TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA0, - ADR = 1, - CONTROL = trackControl, - PMIN = (byte)track.TrackSequence - }); - - // Ending track - trackDescriptors.Add(new TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA1, - ADR = 1, - CONTROL = trackControl, - PMIN = endingTrackNumber - }); - - // Lead-out start - trackDescriptors.Add(new TrackDataDescriptor - { - SessionNumber = currentSession, - POINT = 0xA2, - ADR = 1, - CONTROL = trackControl, - PHOUR = 0, - PMIN = leadinPmsf.minute, - PSEC = leadinPmsf.second, - PFRAME = leadinPmsf.frame - }); - } - - (byte minute, byte second, byte frame) pmsf = LbaToMsf(track.TrackStartSector); - - // Track - trackDescriptors.Add(new TrackDataDescriptor - { - SessionNumber = (byte)track.TrackSession, - POINT = (byte)track.TrackSequence, - ADR = 1, - CONTROL = trackControl, - PHOUR = 0, - PMIN = pmsf.minute, - PSEC = pmsf.second, - PFRAME = pmsf.frame - }); - } - - _toc.TrackDescriptors = trackDescriptors.ToArray(); - return true; - } - - /// - /// Convert the sector to LBA values - /// - /// Sector to convert - /// LBA values for the sector number - /// Copied from - private (byte minute, byte second, byte frame) LbaToMsf(ulong sector) => - ((byte)((sector + 150) / 75 / 60), (byte)((sector + 150) / 75 % 60), (byte)((sector + 150) % 75)); - /// /// Load TOC for the current disc image /// /// True if the TOC could be loaded, false otherwise private bool LoadTOC() { + // If the image is invalide, we can't load or generate a TOC + if(_image == null) + return false; + if(_image.Info.ReadableMediaTags?.Contains(MediaTagType.CD_FullTOC) != true) { // Only generate the TOC if we have it set @@ -475,12 +311,24 @@ namespace RedBookPlayer.Discs } Console.WriteLine("Attempting to generate TOC"); - if(GenerateTOC()) + + // Get the list of tracks and flags to create the TOC with + List tracks = _image.Tracks.OrderBy(t => t.TrackSession).ThenBy(t => t.TrackSequence).ToList(); + Dictionary trackFlags = new Dictionary(); + foreach(Track track in tracks) { + byte[] singleTrackFlags = _image.ReadSectorTag(track.TrackStartSector + 1, SectorTagType.CdTrackFlags); + if(singleTrackFlags != null) + trackFlags.Add((byte)track.TrackStartSector, singleTrackFlags[0]); + } + + try + { + _toc = Create(tracks, trackFlags); Console.WriteLine(Prettify(_toc)); return true; } - else + catch { Console.WriteLine("Full TOC not found or generated"); return false; From c6f1523dd11c30f8dbe62d60e5201661a5982392 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 21:00:34 -0700 Subject: [PATCH 16/19] Enable drag and drop support --- RedBookPlayer/GUI/MainWindow.xaml | 3 +- RedBookPlayer/GUI/MainWindow.xaml.cs | 43 ++++++++++++++++++++------ RedBookPlayer/GUI/PlayerView.xaml.cs | 45 +++++++++++++++------------- RedBookPlayer/Program.cs | 2 ++ 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/RedBookPlayer/GUI/MainWindow.xaml b/RedBookPlayer/GUI/MainWindow.xaml index 89d5f64..56dbf79 100644 --- a/RedBookPlayer/GUI/MainWindow.xaml +++ b/RedBookPlayer/GUI/MainWindow.xaml @@ -1,6 +1,7 @@ + x:Class="RedBookPlayer.GUI.MainWindow" Title="RedBookPlayer" SizeToContent="WidthAndHeight" + DragDrop.AllowDrop="True"> \ No newline at end of file diff --git a/RedBookPlayer/GUI/MainWindow.xaml.cs b/RedBookPlayer/GUI/MainWindow.xaml.cs index 70fae17..474037b 100644 --- a/RedBookPlayer/GUI/MainWindow.xaml.cs +++ b/RedBookPlayer/GUI/MainWindow.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Xml; using Avalonia.Controls; @@ -62,15 +63,9 @@ namespace RedBookPlayer.GUI Instance.Height = ((PlayerView)Instance.ContentControl.Content).Height; } - public void OnKeyDown(object sender, KeyEventArgs e) - { - if(e.Key == Key.F1) - { - settingsWindow = new SettingsWindow(App.Settings); - settingsWindow.Show(); - } - } - + /// + /// Initialize the main window + /// void InitializeComponent() { AvaloniaXamlLoader.Load(this); @@ -97,6 +92,36 @@ namespace RedBookPlayer.GUI { PlayerView.Player.Stop(); }; + + AddHandler(DragDrop.DropEvent, MainWindow_Drop); } + + #region Event Handlers + + public async void MainWindow_Drop(object sender, DragEventArgs e) + { + PlayerView playerView = ContentControl.Content as PlayerView; + if(playerView == null) + return; + + IEnumerable fileNames = e.Data.GetFileNames(); + foreach(string filename in fileNames) + { + bool loaded = await playerView.LoadImage(filename); + if(loaded) + break; + } + } + + public void OnKeyDown(object sender, KeyEventArgs e) + { + if(e.Key == Key.F1) + { + settingsWindow = new SettingsWindow(App.Settings); + settingsWindow.Show(); + } + } + + #endregion } } \ No newline at end of file diff --git a/RedBookPlayer/GUI/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs index fa4e8ae..ef733d3 100644 --- a/RedBookPlayer/GUI/PlayerView.xaml.cs +++ b/RedBookPlayer/GUI/PlayerView.xaml.cs @@ -58,6 +58,29 @@ namespace RedBookPlayer.GUI return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault(); } + /// + /// Load an image from the path + /// + /// Path to the image to load + public async Task LoadImage(string path) + { + bool result = await Task.Run(() => + { + Player.Init(path, App.Settings.AutoPlay); + return Player.Initialized; + }); + + if(result) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last(); + }); + } + + return result; + } + /// /// Load the png image for a given character based on the theme /// @@ -90,6 +113,7 @@ namespace RedBookPlayer.GUI { DataContext = new PlayerViewModel(); + // Load the theme if (xaml != null) new AvaloniaXamlLoader().Load(xaml, null, this); else @@ -150,27 +174,6 @@ namespace RedBookPlayer.GUI }; } - /// - /// Load an image from the path - /// - /// Path to the image to load - private async void LoadImage(string path) - { - bool result = await Task.Run(() => - { - Player.Init(path, App.Settings.AutoPlay); - return Player.Initialized; - }); - - if(result) - { - await Dispatcher.UIThread.InvokeAsync(() => - { - MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last(); - }); - } - } - /// /// Update the UI with the most recent information from the Player /// diff --git a/RedBookPlayer/Program.cs b/RedBookPlayer/Program.cs index 532003a..731908b 100644 --- a/RedBookPlayer/Program.cs +++ b/RedBookPlayer/Program.cs @@ -1,3 +1,4 @@ +using System; #if WindowsDebug using System.Runtime.InteropServices; #endif @@ -8,6 +9,7 @@ namespace RedBookPlayer { internal class Program { + [STAThread] public static void Main(string[] args) { #if WindowsDebug From cd6414c0324c5394e2944515e44fabef719e7085 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 21:16:43 -0700 Subject: [PATCH 17/19] Fix summaries --- RedBookPlayer/Hardware/SoundOutput.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RedBookPlayer/Hardware/SoundOutput.cs b/RedBookPlayer/Hardware/SoundOutput.cs index ca89391..a88dab7 100644 --- a/RedBookPlayer/Hardware/SoundOutput.cs +++ b/RedBookPlayer/Hardware/SoundOutput.cs @@ -13,7 +13,7 @@ namespace RedBookPlayer.Hardware #region Public Fields /// - /// Indicate if the disc is ready to be used + /// Indicate if the output is ready to be used /// public bool Initialized { get; private set; } = false; @@ -23,7 +23,7 @@ namespace RedBookPlayer.Hardware public bool ApplyDeEmphasis { get; set; } = false; /// - /// Indicate if the disc is playing + /// Indicate if the output is playing /// public bool Playing => _soundOut.PlaybackState == PlaybackState.Playing; From 55fc8d5e4019b7ff24099b3e4deeb0a6672ac523 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 29 Jun 2021 22:54:50 -0700 Subject: [PATCH 18/19] Populate themes to its own helper method --- RedBookPlayer/GUI/SettingsWindow.xaml.cs | 54 +++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/RedBookPlayer/GUI/SettingsWindow.xaml.cs b/RedBookPlayer/GUI/SettingsWindow.xaml.cs index f88a3ac..cfe293e 100644 --- a/RedBookPlayer/GUI/SettingsWindow.xaml.cs +++ b/RedBookPlayer/GUI/SettingsWindow.xaml.cs @@ -44,30 +44,42 @@ namespace RedBookPlayer.GUI void InitializeComponent() { AvaloniaXamlLoader.Load(this); - - _themeList = this.FindControl("ThemeList"); - _themeList.SelectionChanged += ThemeList_SelectionChanged; - - List items = new List(); - items.Add("default"); - - if(Directory.Exists("themes/")) - { - foreach(string dir in Directory.EnumerateDirectories("themes/")) - { - string themeName = dir.Split('/')[1]; - - if (!File.Exists($"themes/{themeName}/view.xaml")) - continue; - - items.Add(themeName); - } - } - - _themeList.Items = items; + + PopulateThemes(); this.FindControl