From 93a38ec9e98086a7211103269684226733611066 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 21:50:44 -0700 Subject: [PATCH 01/46] Add multiple audio backend framework --- .../Hardware/IAudioBackend.cs | 30 +++++++++++ .../Hardware/Linux/AudioBackend.cs | 52 ++++++++++++++++++ .../Hardware/Mac/AudioBackend.cs | 52 ++++++++++++++++++ RedBookPlayer.Models/Hardware/SoundOutput.cs | 22 ++++---- .../Hardware/Windows/AudioBackend.cs | 53 +++++++++++++++++++ .../RedBookPlayer.Models.csproj | 10 ++++ 6 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 RedBookPlayer.Models/Hardware/IAudioBackend.cs create mode 100644 RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs create mode 100644 RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs create mode 100644 RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs diff --git a/RedBookPlayer.Models/Hardware/IAudioBackend.cs b/RedBookPlayer.Models/Hardware/IAudioBackend.cs new file mode 100644 index 0000000..b461c2a --- /dev/null +++ b/RedBookPlayer.Models/Hardware/IAudioBackend.cs @@ -0,0 +1,30 @@ +namespace RedBookPlayer.Models.Hardware +{ + public interface IAudioBackend + { + /// + /// Pauses the audio playback + /// + void Pause(); + + /// + /// Starts the playback. + /// + void Play(); + + /// + /// Stops the audio playback + /// + void Stop(); + + /// + /// Get the current playback state + /// + PlayerState GetPlayerState(); + + /// + /// Set the new volume value + /// + void SetVolume(float volume); + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs new file mode 100644 index 0000000..b84ffa2 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs @@ -0,0 +1,52 @@ +using CSCore.SoundOut; + +namespace RedBookPlayer.Models.Hardware.Linux +{ + public class AudioBackend : IAudioBackend + { + /// + /// Sound output instance + /// + private ALSoundOut _soundOut; + + public AudioBackend() { } + + public AudioBackend(PlayerSource source) + { + _soundOut = new ALSoundOut(100); + _soundOut.Initialize(source); + } + + #region IAudioBackend Implementation + + /// + public void Pause() => _soundOut.Pause(); + + /// + public void Play() => _soundOut.Play(); + + /// + public void Stop() => _soundOut.Stop(); + + /// + public PlayerState GetPlayerState() + { + return (_soundOut?.PlaybackState) switch + { + PlaybackState.Paused => PlayerState.Paused, + PlaybackState.Playing => PlayerState.Playing, + PlaybackState.Stopped => PlayerState.Stopped, + _ => PlayerState.NoDisc, + }; + } + + /// + public void SetVolume(float volume) + { + if (_soundOut != null) + _soundOut.Volume = volume; + } + + #endregion + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs new file mode 100644 index 0000000..6f7949d --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs @@ -0,0 +1,52 @@ +using CSCore.SoundOut; + +namespace RedBookPlayer.Models.Hardware.Mac +{ + public class AudioBackend : IAudioBackend + { + /// + /// Sound output instance + /// + private ALSoundOut _soundOut; + + public AudioBackend() { } + + public AudioBackend(PlayerSource source) + { + _soundOut = new ALSoundOut(100); + _soundOut.Initialize(source); + } + + #region IAudioBackend Implementation + + /// + public void Pause() => _soundOut.Pause(); + + /// + public void Play() => _soundOut.Play(); + + /// + public void Stop() => _soundOut.Stop(); + + /// + public PlayerState GetPlayerState() + { + return (_soundOut?.PlaybackState) switch + { + PlaybackState.Paused => PlayerState.Paused, + PlaybackState.Playing => PlayerState.Playing, + PlaybackState.Stopped => PlayerState.Stopped, + _ => PlayerState.NoDisc, + }; + } + + /// + public void SetVolume(float volume) + { + if (_soundOut != null) + _soundOut.Volume = volume; + } + + #endregion + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/SoundOutput.cs b/RedBookPlayer.Models/Hardware/SoundOutput.cs index c89a223..ed6c71d 100644 --- a/RedBookPlayer.Models/Hardware/SoundOutput.cs +++ b/RedBookPlayer.Models/Hardware/SoundOutput.cs @@ -1,8 +1,6 @@ using System; using System.Linq; using System.Threading.Tasks; -using CSCore.SoundOut; -using NWaves.Audio; using ReactiveUI; using RedBookPlayer.Models.Discs; @@ -92,7 +90,7 @@ namespace RedBookPlayer.Models.Hardware /// /// Sound output instance /// - private ALSoundOut _soundOut; + private IAudioBackend _soundOut; /// /// Filtering stage for audio output @@ -183,7 +181,7 @@ namespace RedBookPlayer.Models.Hardware public int ProviderRead(byte[] buffer, int offset, int count) { // Set the current volume - _soundOut.Volume = (float)Volume / 100; + _soundOut.SetVolume((float)Volume / 100); // If we have an unreadable track, just return if(_opticalDisc.BytesPerSector <= 0) @@ -230,7 +228,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Play() { - if(_soundOut.PlaybackState != PlaybackState.Playing) + if(_soundOut.GetPlayerState() != PlayerState.Playing) _soundOut.Play(); PlayerState = PlayerState.Playing; @@ -241,7 +239,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Pause() { - if(_soundOut.PlaybackState != PlaybackState.Paused) + if(_soundOut.GetPlayerState() != PlayerState.Paused) _soundOut.Pause(); PlayerState = PlayerState.Paused; @@ -252,7 +250,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Stop() { - if(_soundOut.PlaybackState != PlaybackState.Stopped) + if(_soundOut.GetPlayerState() != PlayerState.Stopped) _soundOut.Stop(); PlayerState = PlayerState.Stopped; @@ -368,8 +366,14 @@ namespace RedBookPlayer.Models.Hardware if(_source == null) { _source = new PlayerSource(ProviderRead); - _soundOut = new ALSoundOut(100); - _soundOut.Initialize(_source); + +#if LINUX + _soundOut = new Linux.AudioBackend(_source); +#elif MACOS + _soundOut = new Mac.AudioBackend(_source); +#elif WINDOWS + _soundOut = new Windows.AudioBackend(_source); +#endif } else { diff --git a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs new file mode 100644 index 0000000..da3cc6c --- /dev/null +++ b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs @@ -0,0 +1,53 @@ +using CSCore.SoundOut; +using PortAudioSharp; + +namespace RedBookPlayer.Models.Hardware.Windows +{ + public class AudioBackend : IAudioBackend + { + /// + /// Sound output instance + /// + private ALSoundOut _soundOut; + + public AudioBackend() { } + + public AudioBackend(PlayerSource source) + { + _soundOut = new ALSoundOut(100); + _soundOut.Initialize(source); + } + + #region IAudioBackend Implementation + + /// + public void Pause() => _soundOut.Pause(); + + /// + public void Play() => _soundOut.Play(); + + /// + public void Stop() => _soundOut.Stop(); + + /// + public PlayerState GetPlayerState() + { + return (_soundOut?.PlaybackState) switch + { + PlaybackState.Paused => PlayerState.Paused, + PlaybackState.Playing => PlayerState.Playing, + PlaybackState.Stopped => PlayerState.Stopped, + _ => PlayerState.NoDisc, + }; + } + + /// + public void SetVolume(float volume) + { + if (_soundOut != null) + _soundOut.Volume = volume; + } + + #endregion + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/RedBookPlayer.Models.csproj b/RedBookPlayer.Models/RedBookPlayer.Models.csproj index 7ed4a85..4b505f2 100644 --- a/RedBookPlayer.Models/RedBookPlayer.Models.csproj +++ b/RedBookPlayer.Models/RedBookPlayer.Models.csproj @@ -5,6 +5,16 @@ true + + LINUX + + + MAC + + + WINDOWS + + From 6a78fc86e538cc6b91f76692032adcc4be4e81c2 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 21:58:59 -0700 Subject: [PATCH 02/46] Start building out multi-disc framework --- RedBookPlayer.Models/Hardware/Player.cs | 109 ++++++++++++++---------- 1 file changed, 65 insertions(+), 44 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 8651e95..f18175c 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -17,7 +17,26 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _initialized, value); } + /// + /// Currently selected disc + /// + public int CurrentDisc + { + get => _currentDisc; + private set + { + int temp = value; + if (temp < 4) + temp = 0; + else if (temp >= 5) + temp = 0; + + this.RaiseAndSetIfChanged(ref _currentDisc, temp); + } + } + private bool _initialized; + private int _currentDisc; #region OpticalDisc Passthrough @@ -114,27 +133,27 @@ namespace RedBookPlayer.Models.Hardware /// /// Represents the total tracks on the disc /// - public int TotalTracks => _opticalDisc?.TotalTracks ?? 0; + public int TotalTracks => _opticalDiscs[CurrentDisc]?.TotalTracks ?? 0; /// /// Represents the total indices on the disc /// - public int TotalIndexes => _opticalDisc?.TotalIndexes ?? 0; + public int TotalIndexes => _opticalDiscs[CurrentDisc]?.TotalIndexes ?? 0; /// /// Total sectors in the image /// - public ulong TotalSectors => _opticalDisc?.TotalSectors ?? 0; + public ulong TotalSectors => _opticalDiscs[CurrentDisc]?.TotalSectors ?? 0; /// /// Represents the time adjustment offset for the disc /// - public ulong TimeOffset => _opticalDisc?.TimeOffset ?? 0; + public ulong TimeOffset => _opticalDiscs[CurrentDisc]?.TimeOffset ?? 0; /// /// Represents the total playing time for the disc /// - public ulong TotalTime => _opticalDisc?.TotalTime ?? 0; + public ulong TotalTime => _opticalDiscs[CurrentDisc]?.TotalTime ?? 0; private int _currentTrackNumber; private ushort _currentTrackIndex; @@ -215,7 +234,8 @@ namespace RedBookPlayer.Models.Hardware /// /// OpticalDisc object /// - private OpticalDiscBase _opticalDisc; + /// TODO: Make the number of discs in the changer configurable + private OpticalDiscBase[] _opticalDiscs = new OpticalDiscBase[5]; /// /// Last volume for mute toggling @@ -231,6 +251,7 @@ namespace RedBookPlayer.Models.Hardware public Player(int defaultVolume) { Initialized = false; + _currentDisc = 0; _soundOutput = new SoundOutput(defaultVolume); _soundOutput.SetDeEmphasis(false); } @@ -248,15 +269,15 @@ namespace RedBookPlayer.Models.Hardware Initialized = false; // Initalize the disc - _opticalDisc = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay); - if(_opticalDisc == null || !_opticalDisc.Initialized) + _opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay); + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; // Add event handling for the optical disc - _opticalDisc.PropertyChanged += OpticalDiscStateChanged; + _opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged; // Initialize the sound output - _soundOutput.Init(_opticalDisc, repeatMode, autoPlay); + _soundOutput.Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay); if(_soundOutput == null || !_soundOutput.Initialized) return; @@ -278,7 +299,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Play() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutput == null) return; @@ -286,7 +307,7 @@ namespace RedBookPlayer.Models.Hardware return; _soundOutput.Play(); - _opticalDisc.SetTotalIndexes(); + _opticalDiscs[CurrentDisc].SetTotalIndexes(); PlayerState = PlayerState.Playing; } @@ -295,7 +316,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Pause() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutput == null) return; @@ -332,7 +353,7 @@ namespace RedBookPlayer.Models.Hardware /// public void Stop() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutput == null) return; @@ -340,7 +361,7 @@ namespace RedBookPlayer.Models.Hardware return; _soundOutput.Stop(); - _opticalDisc.LoadFirstTrack(); + _opticalDiscs[CurrentDisc].LoadFirstTrack(); PlayerState = PlayerState.Stopped; } @@ -349,14 +370,14 @@ namespace RedBookPlayer.Models.Hardware /// public void Eject() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutput == null) return; Stop(); _soundOutput.Eject(); - _opticalDisc = null; + _opticalDiscs[CurrentDisc] = null; PlayerState = PlayerState.NoDisc; Initialized = false; } @@ -366,15 +387,15 @@ namespace RedBookPlayer.Models.Hardware /// public void NextTrack() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); - _opticalDisc.NextTrack(); - if(_opticalDisc is CompactDisc compactDisc) + _opticalDiscs[CurrentDisc].NextTrack(); + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) @@ -386,15 +407,15 @@ namespace RedBookPlayer.Models.Hardware /// public void PreviousTrack() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); - _opticalDisc.PreviousTrack(); - if(_opticalDisc is CompactDisc compactDisc) + _opticalDiscs[CurrentDisc].PreviousTrack(); + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) @@ -407,15 +428,15 @@ namespace RedBookPlayer.Models.Hardware /// True if index changes can trigger a track change, false otherwise public void NextIndex(bool changeTrack) { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); - _opticalDisc.NextIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) + _opticalDiscs[CurrentDisc].NextIndex(changeTrack); + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) @@ -428,15 +449,15 @@ namespace RedBookPlayer.Models.Hardware /// True if index changes can trigger a track change, false otherwise public void PreviousIndex(bool changeTrack) { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); - _opticalDisc.PreviousIndex(changeTrack); - if(_opticalDisc is CompactDisc compactDisc) + _opticalDiscs[CurrentDisc].PreviousIndex(changeTrack); + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) @@ -448,10 +469,10 @@ namespace RedBookPlayer.Models.Hardware /// public void FastForward() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - _opticalDisc.SetCurrentSector(_opticalDisc.CurrentSector + 75); + _opticalDiscs[CurrentDisc].SetCurrentSector(_opticalDiscs[CurrentDisc].CurrentSector + 75); } /// @@ -459,10 +480,10 @@ namespace RedBookPlayer.Models.Hardware /// public void Rewind() { - if(_opticalDisc == null || !_opticalDisc.Initialized) + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - _opticalDisc.SetCurrentSector(_opticalDisc.CurrentSector - 75); + _opticalDiscs[CurrentDisc].SetCurrentSector(_opticalDiscs[CurrentDisc].CurrentSector - 75); } #endregion @@ -536,12 +557,12 @@ namespace RedBookPlayer.Models.Hardware /// /// /// Output path to write data to - public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) => _opticalDisc?.ExtractTrackToWav(trackNumber, outputDirectory); + public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) => _opticalDiscs[CurrentDisc]?.ExtractTrackToWav(trackNumber, outputDirectory); /// /// Extract all tracks from the image to WAV /// Output path to write data to - public void ExtractAllTracksToWav(string outputDirectory) => _opticalDisc?.ExtractAllTracksToWav(outputDirectory); + public void ExtractAllTracksToWav(string outputDirectory) => _opticalDiscs[CurrentDisc]?.ExtractAllTracksToWav(outputDirectory); /// /// Set data playback method [CompactDisc only] @@ -549,7 +570,7 @@ namespace RedBookPlayer.Models.Hardware /// New playback value public void SetDataPlayback(DataPlayback dataPlayback) { - if(_opticalDisc is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) compactDisc.DataPlayback = dataPlayback; } @@ -559,7 +580,7 @@ namespace RedBookPlayer.Models.Hardware /// True to enable loading hidden tracks, false otherwise public void SetLoadHiddenTracks(bool load) { - if(_opticalDisc is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) compactDisc.LoadHiddenTracks = load; } @@ -575,7 +596,7 @@ namespace RedBookPlayer.Models.Hardware /// New session handling value public void SetSessionHandling(SessionHandling sessionHandling) { - if(_opticalDisc is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) compactDisc.SessionHandling = sessionHandling; } @@ -584,14 +605,14 @@ namespace RedBookPlayer.Models.Hardware /// private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { - CurrentTrackNumber = _opticalDisc.CurrentTrackNumber; - CurrentTrackIndex = _opticalDisc.CurrentTrackIndex; - CurrentSector = _opticalDisc.CurrentSector; - SectionStartSector = _opticalDisc.SectionStartSector; + CurrentTrackNumber = _opticalDiscs[CurrentDisc].CurrentTrackNumber; + CurrentTrackIndex = _opticalDiscs[CurrentDisc].CurrentTrackIndex; + CurrentSector = _opticalDiscs[CurrentDisc].CurrentSector; + SectionStartSector = _opticalDiscs[CurrentDisc].SectionStartSector; HiddenTrack = TimeOffset > 150; - if(_opticalDisc is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { QuadChannel = compactDisc.QuadChannel; IsDataTrack = compactDisc.IsDataTrack; @@ -601,7 +622,7 @@ namespace RedBookPlayer.Models.Hardware else { QuadChannel = false; - IsDataTrack = _opticalDisc.TrackType != TrackType.Audio; + IsDataTrack = _opticalDiscs[CurrentDisc].TrackType != TrackType.Audio; CopyAllowed = false; TrackHasEmphasis = false; } From 34b7fbd790eef245cea3512e44734d6dc6ba2de7 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 22:26:06 -0700 Subject: [PATCH 03/46] Set one SoundOutput per disc --- RedBookPlayer.Models/Hardware/Player.cs | 60 +++++++++++++------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index f18175c..6a17b44 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -229,7 +229,8 @@ namespace RedBookPlayer.Models.Hardware /// /// Sound output handling class /// - private readonly SoundOutput _soundOutput; + /// TODO: Link sound outputs to discs in a 1:1 configuration + private readonly SoundOutput[] _soundOutputs = new SoundOutput[5]; /// /// OpticalDisc object @@ -252,8 +253,11 @@ namespace RedBookPlayer.Models.Hardware { Initialized = false; _currentDisc = 0; - _soundOutput = new SoundOutput(defaultVolume); - _soundOutput.SetDeEmphasis(false); + for (int i = 0; i < 5; i++) + { + _soundOutputs[i] = new SoundOutput(defaultVolume); + _soundOutputs[i].SetDeEmphasis(false); + } } /// @@ -277,12 +281,12 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged; // Initialize the sound output - _soundOutput.Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay); - if(_soundOutput == null || !_soundOutput.Initialized) + _soundOutputs[CurrentDisc].Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay); + if(_soundOutputs[CurrentDisc] == null || !_soundOutputs[CurrentDisc].Initialized) return; // Add event handling for the sound output - _soundOutput.PropertyChanged += SoundOutputStateChanged; + _soundOutputs[CurrentDisc].PropertyChanged += SoundOutputStateChanged; // Mark the player as ready Initialized = true; @@ -301,12 +305,12 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutput == null) + else if(_soundOutputs[CurrentDisc] == null) return; - else if(_soundOutput.PlayerState != PlayerState.Paused && _soundOutput.PlayerState != PlayerState.Stopped) + else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Stopped) return; - _soundOutput.Play(); + _soundOutputs[CurrentDisc].Play(); _opticalDiscs[CurrentDisc].SetTotalIndexes(); PlayerState = PlayerState.Playing; } @@ -318,12 +322,12 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutput == null) + else if(_soundOutputs[CurrentDisc] == null) return; - else if(_soundOutput.PlayerState != PlayerState.Playing) + else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing) return; - _soundOutput?.Pause(); + _soundOutputs[CurrentDisc]?.Pause(); PlayerState = PlayerState.Paused; } @@ -355,12 +359,12 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutput == null) + else if(_soundOutputs[CurrentDisc] == null) return; - else if(_soundOutput.PlayerState != PlayerState.Playing && _soundOutput.PlayerState != PlayerState.Paused) + else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused) return; - _soundOutput.Stop(); + _soundOutputs[CurrentDisc].Stop(); _opticalDiscs[CurrentDisc].LoadFirstTrack(); PlayerState = PlayerState.Stopped; } @@ -372,11 +376,11 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutput == null) + else if(_soundOutputs[CurrentDisc] == null) return; Stop(); - _soundOutput.Eject(); + _soundOutputs[CurrentDisc].Eject(); _opticalDiscs[CurrentDisc] = null; PlayerState = PlayerState.NoDisc; Initialized = false; @@ -396,7 +400,7 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].NextTrack(); if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); + _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); @@ -416,7 +420,7 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].PreviousTrack(); if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); + _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); @@ -437,7 +441,7 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].NextIndex(changeTrack); if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); + _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); @@ -458,7 +462,7 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].PreviousIndex(changeTrack); if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutput.SetDeEmphasis(compactDisc.TrackHasEmphasis); + _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); if(wasPlaying == PlayerState.Playing) Play(); @@ -504,7 +508,7 @@ namespace RedBookPlayer.Models.Hardware /// Set the value for the volume /// /// New volume value - public void SetVolume(int volume) => _soundOutput?.SetVolume(volume); + public void SetVolume(int volume) => _soundOutputs[CurrentDisc]?.SetVolume(volume); /// /// Temporarily mute playback @@ -546,7 +550,7 @@ namespace RedBookPlayer.Models.Hardware /// Set de-emphasis status /// /// - private void SetDeEmphasis(bool apply) => _soundOutput?.SetDeEmphasis(apply); + private void SetDeEmphasis(bool apply) => _soundOutputs[CurrentDisc]?.SetDeEmphasis(apply); #endregion @@ -588,7 +592,7 @@ namespace RedBookPlayer.Models.Hardware /// Set repeat mode /// /// New repeat mode value - public void SetRepeatMode(RepeatMode repeatMode) => _soundOutput?.SetRepeatMode(repeatMode); + public void SetRepeatMode(RepeatMode repeatMode) => _soundOutputs[CurrentDisc]?.SetRepeatMode(repeatMode); /// /// Set the value for session handling [CompactDisc only] @@ -633,10 +637,10 @@ namespace RedBookPlayer.Models.Hardware /// private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e) { - PlayerState = _soundOutput.PlayerState; - RepeatMode = _soundOutput.RepeatMode; - ApplyDeEmphasis = _soundOutput.ApplyDeEmphasis; - Volume = _soundOutput.Volume; + PlayerState = _soundOutputs[CurrentDisc].PlayerState; + RepeatMode = _soundOutputs[CurrentDisc].RepeatMode; + ApplyDeEmphasis = _soundOutputs[CurrentDisc].ApplyDeEmphasis; + Volume = _soundOutputs[CurrentDisc].Volume; } #endregion From 765d2f21c1f9f08f922cbed25b2bb03ea0d4fbf5 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 22:46:46 -0700 Subject: [PATCH 04/46] Add unused disc changing methods to Player --- RedBookPlayer.Models/Hardware/Player.cs | 50 +++++++++++++++++++ .../Hardware/Windows/AudioBackend.cs | 1 - 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 6a17b44..ca89e8b 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -386,6 +386,56 @@ namespace RedBookPlayer.Models.Hardware Initialized = false; } + /// + /// Move to the next loaded disc + /// + public void NextDisc() + { + PlayerState wasPlaying = PlayerState; + if (wasPlaying == PlayerState.Playing) + Stop(); + + int lastdisc = CurrentDisc++; + while (CurrentDisc != lastdisc) + { + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) + break; + + CurrentDisc++; + } + + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + + /// + /// Move to the previous loaded disc + /// + public void PreviousDisc() + { + PlayerState wasPlaying = PlayerState; + if (wasPlaying == PlayerState.Playing) + Stop(); + + int lastdisc = CurrentDisc--; + while (CurrentDisc != lastdisc) + { + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) + break; + + CurrentDisc--; + } + + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + /// /// Move to the next playable track /// diff --git a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs index da3cc6c..d378429 100644 --- a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs +++ b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs @@ -1,5 +1,4 @@ using CSCore.SoundOut; -using PortAudioSharp; namespace RedBookPlayer.Models.Hardware.Windows { From 0a5c3f49c3a8926159f5cfe27561d6fa8802cecd Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 22:58:16 -0700 Subject: [PATCH 05/46] Add new logic to settings; fix Player --- .../ViewModels/PlayerViewModel.cs | 2 +- .../ViewModels/SettingsViewModel.cs | 15 ++++ RedBookPlayer.Models/Hardware/Player.cs | 76 +++++++++++-------- 3 files changed, 59 insertions(+), 34 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 1750063..d7de793 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -364,7 +364,7 @@ namespace RedBookPlayer.GUI.ViewModels ToggleDeEmphasisCommand = ReactiveCommand.Create(ExecuteToggleDeEmphasis); // Initialize Player - _player = new Player(App.Settings.Volume); + _player = new Player(App.Settings.NumberOfDiscs, App.Settings.Volume); PlayerState = PlayerState.NoDisc; } diff --git a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs index 6473f7c..685d38e 100644 --- a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs @@ -44,6 +44,11 @@ namespace RedBookPlayer.GUI.ViewModels /// public bool AutoPlay { get; set; } = false; + /// + /// Indicates the number of discs to allow loading and changing + /// + public int NumberOfDiscs { get; set; } = 1; + /// /// Indicates if an index change can trigger a track change /// @@ -144,6 +149,16 @@ namespace RedBookPlayer.GUI.ViewModels /// public Key EjectKey { get; set; } = Key.OemTilde; + /// + /// Key assigned to move to the next disc + /// + public Key NextDiscKey { get; set; } = Key.PageUp; + + /// + /// Key assigned to move to the previous disc + /// + public Key PreviousDiscKey { get; set; } = Key.PageDown; + /// /// Key assigned to move to the next track /// diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index ca89e8b..4ab1883 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -26,9 +26,9 @@ namespace RedBookPlayer.Models.Hardware private set { int temp = value; - if (temp < 4) - temp = 0; - else if (temp >= 5) + if (temp < 0) + temp = _numberOfDiscs - 1; + else if (temp >= _numberOfDiscs) temp = 0; this.RaiseAndSetIfChanged(ref _currentDisc, temp); @@ -36,6 +36,7 @@ namespace RedBookPlayer.Models.Hardware } private bool _initialized; + private int _numberOfDiscs; private int _currentDisc; #region OpticalDisc Passthrough @@ -229,14 +230,12 @@ namespace RedBookPlayer.Models.Hardware /// /// Sound output handling class /// - /// TODO: Link sound outputs to discs in a 1:1 configuration - private readonly SoundOutput[] _soundOutputs = new SoundOutput[5]; + private readonly SoundOutput[] _soundOutputs; /// /// OpticalDisc object /// - /// TODO: Make the number of discs in the changer configurable - private OpticalDiscBase[] _opticalDiscs = new OpticalDiscBase[5]; + private OpticalDiscBase[] _opticalDiscs; /// /// Last volume for mute toggling @@ -248,12 +247,21 @@ namespace RedBookPlayer.Models.Hardware /// /// Constructor /// + /// Number of discs to allow loading /// Default volume between 0 and 100 to use when starting playback - public Player(int defaultVolume) + public Player(int numberOfDiscs, int defaultVolume) { Initialized = false; + + if (numberOfDiscs <= 0) + numberOfDiscs = 1; + + _numberOfDiscs = numberOfDiscs; + _soundOutputs = new SoundOutput[_numberOfDiscs]; + _opticalDiscs = new OpticalDiscBase[numberOfDiscs]; + _currentDisc = 0; - for (int i = 0; i < 5; i++) + for (int i = 0; i < _numberOfDiscs; i++) { _soundOutputs[i] = new SoundOutput(defaultVolume); _soundOutputs[i].SetDeEmphasis(false); @@ -387,7 +395,7 @@ namespace RedBookPlayer.Models.Hardware } /// - /// Move to the next loaded disc + /// Move to the next disc /// public void NextDisc() { @@ -395,24 +403,25 @@ namespace RedBookPlayer.Models.Hardware if (wasPlaying == PlayerState.Playing) Stop(); - int lastdisc = CurrentDisc++; - while (CurrentDisc != lastdisc) + CurrentDisc++; + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) { - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) - break; + Initialized = true; + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); - CurrentDisc++; + if(wasPlaying == PlayerState.Playing) + Play(); + } + else + { + PlayerState = PlayerState.NoDisc; + Initialized = false; } - - OpticalDiscStateChanged(this, null); - SoundOutputStateChanged(this, null); - - if(wasPlaying == PlayerState.Playing) - Play(); } /// - /// Move to the previous loaded disc + /// Move to the previous disc /// public void PreviousDisc() { @@ -420,20 +429,21 @@ namespace RedBookPlayer.Models.Hardware if (wasPlaying == PlayerState.Playing) Stop(); - int lastdisc = CurrentDisc--; - while (CurrentDisc != lastdisc) + CurrentDisc--; + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) { - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) - break; + Initialized = true; + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); - CurrentDisc--; + if(wasPlaying == PlayerState.Playing) + Play(); + } + else + { + PlayerState = PlayerState.NoDisc; + Initialized = false; } - - OpticalDiscStateChanged(this, null); - SoundOutputStateChanged(this, null); - - if(wasPlaying == PlayerState.Playing) - Play(); } /// From 76415ae04fd3499a1936001c344313824e969054 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Mon, 4 Oct 2021 23:03:10 -0700 Subject: [PATCH 06/46] Add Player passthru for new items --- .../ViewModels/PlayerViewModel.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index d7de793..df91f2f 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -31,6 +31,17 @@ namespace RedBookPlayer.GUI.ViewModels #region Player Passthrough + /// + /// Currently selected disc + /// + public int CurrentDisc + { + get => _currentDisc; + private set => this.RaiseAndSetIfChanged(ref _currentDisc, value); + } + + private int _currentDisc; + #region OpticalDisc Passthrough /// @@ -417,6 +428,16 @@ namespace RedBookPlayer.GUI.ViewModels /// public void ExecuteEject() => _player?.Eject(); + /// + /// Move to the next disc + /// + public void ExecuteNextDisc() => _player?.NextDisc(); + + /// + /// Move to the previous disc + /// + public void ExecutePreviousDisc() => _player?.PreviousDisc(); + /// /// Move to the next playable track /// @@ -800,6 +821,7 @@ namespace RedBookPlayer.GUI.ViewModels Initialized = _player.Initialized; + CurrentDisc = _player.CurrentDisc; CurrentTrackNumber = _player.CurrentTrackNumber; CurrentTrackIndex = _player.CurrentTrackIndex; CurrentTrackSession = _player.CurrentTrackSession; From 7b01715a1124c07becb28dd7e432b7085a33367d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 09:54:02 -0700 Subject: [PATCH 07/46] Safer way of detecing platform --- RedBookPlayer.GUI/RedBookPlayer.GUI.csproj | 1 + RedBookPlayer.Models/Hardware/SoundOutput.cs | 13 +++++-------- RedBookPlayer.Models/RedBookPlayer.Models.csproj | 12 ++---------- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/RedBookPlayer.GUI/RedBookPlayer.GUI.csproj b/RedBookPlayer.GUI/RedBookPlayer.GUI.csproj index 22e946e..c0be0a2 100644 --- a/RedBookPlayer.GUI/RedBookPlayer.GUI.csproj +++ b/RedBookPlayer.GUI/RedBookPlayer.GUI.csproj @@ -9,6 +9,7 @@ WindowsDebug + %(Filename) diff --git a/RedBookPlayer.Models/Hardware/SoundOutput.cs b/RedBookPlayer.Models/Hardware/SoundOutput.cs index ed6c71d..27a1047 100644 --- a/RedBookPlayer.Models/Hardware/SoundOutput.cs +++ b/RedBookPlayer.Models/Hardware/SoundOutput.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using ReactiveUI; using RedBookPlayer.Models.Discs; @@ -118,7 +119,6 @@ namespace RedBookPlayer.Models.Hardware Volume = defaultVolume; _filterStage = new FilterStage(); } - /// /// Initialize the output with a given image @@ -367,13 +367,10 @@ namespace RedBookPlayer.Models.Hardware { _source = new PlayerSource(ProviderRead); -#if LINUX - _soundOut = new Linux.AudioBackend(_source); -#elif MACOS - _soundOut = new Mac.AudioBackend(_source); -#elif WINDOWS - _soundOut = new Windows.AudioBackend(_source); -#endif + if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + _soundOut = new Linux.AudioBackend(_source); + else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _soundOut = new Windows.AudioBackend(_source); } else { diff --git a/RedBookPlayer.Models/RedBookPlayer.Models.csproj b/RedBookPlayer.Models/RedBookPlayer.Models.csproj index 4b505f2..c0a4c68 100644 --- a/RedBookPlayer.Models/RedBookPlayer.Models.csproj +++ b/RedBookPlayer.Models/RedBookPlayer.Models.csproj @@ -2,19 +2,11 @@ netcoreapp3.1 + win-x64;linux-x64 + linux-x64 true - - LINUX - - - MAC - - - WINDOWS - - From 211ee8cecf01c6c7dc67b6efcf2c5c52a8f3e06f Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 10:40:10 -0700 Subject: [PATCH 08/46] Wire up disc changing to UI --- README.md | 2 + RedBookPlayer.GUI/ViewModels/MainViewModel.cs | 12 ++ .../ViewModels/PlayerViewModel.cs | 31 +++ RedBookPlayer.GUI/Views/PlayerView.xaml | 3 +- RedBookPlayer.GUI/Views/SettingsWindow.xaml | 192 ++++++++++-------- RedBookPlayer.GUI/themes/Default/view.xaml | 3 +- RedBookPlayer.Models/Discs/CompactDisc.cs | 3 +- RedBookPlayer.Models/Discs/OpticalDiscBase.cs | 8 +- .../Factories/OpticalDiscFactory.cs | 7 +- RedBookPlayer.Models/Hardware/Player.cs | 11 + 10 files changed, 181 insertions(+), 91 deletions(-) diff --git a/README.md b/README.md index a8c710e..7683f50 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ | **Space** | Toggle Play / Pause | | **Esc** | Stop Playback | | **~** | Eject | +| **Page Up** | Next Disc | +| **Page Down** | Previous Disc | | **→** | Next Track | | **←** | Previous Track | | **]** | Next Index | diff --git a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs index c3a2a0e..06d6099 100644 --- a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs @@ -75,6 +75,18 @@ namespace RedBookPlayer.GUI.ViewModels PlayerView?.ViewModel?.ExecuteEject(); } + // Next Disc + else if(e.Key == App.Settings.NextDiscKey) + { + PlayerView?.ViewModel?.ExecuteNextDisc(); + } + + // Previous Disc + else if(e.Key == App.Settings.PreviousDiscKey) + { + PlayerView?.ViewModel?.ExecutePreviousDisc(); + } + // Next Track else if(e.Key == App.Settings.NextTrackKey || e.Key == Key.MediaNextTrack) { diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index df91f2f..3a15593 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -44,6 +44,15 @@ namespace RedBookPlayer.GUI.ViewModels #region OpticalDisc Passthrough + /// + /// Path to the disc image + /// + public string ImagePath + { + get => _imagePath; + private set => this.RaiseAndSetIfChanged(ref _imagePath, value); + } + /// /// Current track number /// @@ -159,6 +168,7 @@ namespace RedBookPlayer.GUI.ViewModels /// public ulong TotalTime => _player.TotalTime; + private string _imagePath; private int _currentTrackNumber; private ushort _currentTrackIndex; private ushort _currentTrackSession; @@ -274,6 +284,16 @@ namespace RedBookPlayer.GUI.ViewModels /// public ReactiveCommand EjectCommand { get; } + /// + /// Command for moving to the next disc + /// + public ReactiveCommand NextDiscCommand { get; } + + /// + /// Command for moving to the previous disc + /// + public ReactiveCommand PreviousDiscCommand { get; } + /// /// Command for moving to the next track /// @@ -359,6 +379,8 @@ namespace RedBookPlayer.GUI.ViewModels TogglePlayPauseCommand = ReactiveCommand.Create(ExecuteTogglePlayPause); StopCommand = ReactiveCommand.Create(ExecuteStop); EjectCommand = ReactiveCommand.Create(ExecuteEject); + NextDiscCommand = ReactiveCommand.Create(ExecuteNextDisc); + PreviousDiscCommand = ReactiveCommand.Create(ExecutePreviousDisc); NextTrackCommand = ReactiveCommand.Create(ExecuteNextTrack); PreviousTrackCommand = ReactiveCommand.Create(ExecutePreviousTrack); NextIndexCommand = ReactiveCommand.Create(ExecuteNextIndex); @@ -819,8 +841,17 @@ namespace RedBookPlayer.GUI.ViewModels }); } + ImagePath = _player.ImagePath; Initialized = _player.Initialized; + if (!string.IsNullOrWhiteSpace(ImagePath) && Initialized) + { + Dispatcher.UIThread.InvokeAsync(() => + { + App.MainWindow.Title = "RedBookPlayer - " + ImagePath.Split('/').Last().Split('\\').Last(); + }); + } + CurrentDisc = _player.CurrentDisc; CurrentTrackNumber = _player.CurrentTrackNumber; CurrentTrackIndex = _player.CurrentTrackIndex; diff --git a/RedBookPlayer.GUI/Views/PlayerView.xaml b/RedBookPlayer.GUI/Views/PlayerView.xaml index c599c86..a396c2a 100644 --- a/RedBookPlayer.GUI/Views/PlayerView.xaml +++ b/RedBookPlayer.GUI/Views/PlayerView.xaml @@ -103,7 +103,8 @@ 4CH HIDDEN HIDDEN - + + \ No newline at end of file diff --git a/RedBookPlayer.GUI/Views/SettingsWindow.xaml b/RedBookPlayer.GUI/Views/SettingsWindow.xaml index 4000a8a..8c42316 100644 --- a/RedBookPlayer.GUI/Views/SettingsWindow.xaml +++ b/RedBookPlayer.GUI/Views/SettingsWindow.xaml @@ -58,120 +58,144 @@ - - - - - - - - - - - - - - - - - - - - - - - + - Load Image - + + Load Image + + - Save Track(s) - + + Save Track(s) + + - Toggle Play/Pause - + + Toggle Play/Pause + + - Stop Playback - + + Stop Playback + + - Eject Disc - + + Eject Disc + + + + + + Next Disc + + + + + + Previous Disc + + - Next Track - + + Next Track + + - Previous Track - + + Previous Track + + - Next Index - + + Next Index + + - Previous Index - + + Previous Index + + - Fast-Forward - + + Fast-Forward + + - Rewind - + + Rewind + + - Volume Up - + + Volume Up + + - Volume Down - + + Volume Down + + - Toggle Mute - + + Toggle Mute + + - Toggle De-Emphasis - - + + Toggle De-Emphasis + + + diff --git a/RedBookPlayer.GUI/themes/Default/view.xaml b/RedBookPlayer.GUI/themes/Default/view.xaml index 83d1b6f..54bfa19 100644 --- a/RedBookPlayer.GUI/themes/Default/view.xaml +++ b/RedBookPlayer.GUI/themes/Default/view.xaml @@ -103,7 +103,8 @@ 4CH HIDDEN HIDDEN - + + \ No newline at end of file diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 9759b6f..3cf7e8d 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -299,13 +299,14 @@ namespace RedBookPlayer.Models.Discs } /// - public override void Init(IOpticalMediaImage image, bool autoPlay) + public override void Init(string path, IOpticalMediaImage image, bool autoPlay) { // If the image is null, we can't do anything if(image == null) return; // Set the current disc image + ImagePath = path; _image = image; // Attempt to load the TOC diff --git a/RedBookPlayer.Models/Discs/OpticalDiscBase.cs b/RedBookPlayer.Models/Discs/OpticalDiscBase.cs index 0a31b9b..c871713 100644 --- a/RedBookPlayer.Models/Discs/OpticalDiscBase.cs +++ b/RedBookPlayer.Models/Discs/OpticalDiscBase.cs @@ -8,6 +8,11 @@ namespace RedBookPlayer.Models.Discs { #region Public Fields + /// + /// Path to the disc image + /// + public string ImagePath { get; protected set; } + /// /// Indicate if the disc is ready to be used /// @@ -93,9 +98,10 @@ namespace RedBookPlayer.Models.Discs /// /// Initialize the disc with a given image /// + /// Path of the image /// Aaruformat image to load /// True if playback should begin immediately, false otherwise - public abstract void Init(IOpticalMediaImage image, bool autoPlay); + public abstract void Init(string path, IOpticalMediaImage image, bool autoPlay); #region Seeking diff --git a/RedBookPlayer.Models/Factories/OpticalDiscFactory.cs b/RedBookPlayer.Models/Factories/OpticalDiscFactory.cs index 5023a6b..fc0e0f0 100644 --- a/RedBookPlayer.Models/Factories/OpticalDiscFactory.cs +++ b/RedBookPlayer.Models/Factories/OpticalDiscFactory.cs @@ -32,7 +32,7 @@ namespace RedBookPlayer.Models.Factories image.Open(filter); // Generate and instantiate the disc - return GenerateFromImage(image, options, autoPlay); + return GenerateFromImage(path, image, options, autoPlay); } catch { @@ -44,11 +44,12 @@ namespace RedBookPlayer.Models.Factories /// /// Generate an OpticalDisc from an input IOpticalMediaImage /// + /// Path of the image /// IOpticalMediaImage to create from /// Options to pass to the optical disc factory /// True if the image should be playable immediately, false otherwise /// Instantiated OpticalDisc, if possible - public static OpticalDiscBase GenerateFromImage(IOpticalMediaImage image, OpticalDiscOptions options, bool autoPlay) + public static OpticalDiscBase GenerateFromImage(string path, IOpticalMediaImage image, OpticalDiscOptions options, bool autoPlay) { // If the image is not usable, we don't do anything if(!IsUsableImage(image)) @@ -74,7 +75,7 @@ namespace RedBookPlayer.Models.Factories return opticalDisc; // Instantiate the disc and return - opticalDisc.Init(image, autoPlay); + opticalDisc.Init(path, image, autoPlay); return opticalDisc; } diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 4ab1883..0d04f33 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -41,6 +41,15 @@ namespace RedBookPlayer.Models.Hardware #region OpticalDisc Passthrough + /// + /// Path to the disc image + /// + public string ImagePath + { + get => _imagePath; + private set => this.RaiseAndSetIfChanged(ref _imagePath, value); + } + /// /// Current track number /// @@ -156,6 +165,7 @@ namespace RedBookPlayer.Models.Hardware /// public ulong TotalTime => _opticalDiscs[CurrentDisc]?.TotalTime ?? 0; + private string _imagePath; private int _currentTrackNumber; private ushort _currentTrackIndex; private ushort _currentTrackSession; @@ -669,6 +679,7 @@ namespace RedBookPlayer.Models.Hardware /// private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { + ImagePath = _opticalDiscs[CurrentDisc].ImagePath; CurrentTrackNumber = _opticalDiscs[CurrentDisc].CurrentTrackNumber; CurrentTrackIndex = _opticalDiscs[CurrentDisc].CurrentTrackIndex; CurrentSector = _opticalDiscs[CurrentDisc].CurrentSector; From 6cd039a2b3b9831a047955b988e916121f2a3e47 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 10:41:12 -0700 Subject: [PATCH 09/46] Add note in enum --- RedBookPlayer.Models/Enums.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/RedBookPlayer.Models/Enums.cs b/RedBookPlayer.Models/Enums.cs index 4066c37..add206b 100644 --- a/RedBookPlayer.Models/Enums.cs +++ b/RedBookPlayer.Models/Enums.cs @@ -50,6 +50,7 @@ namespace RedBookPlayer.Models /// /// Playback repeat mode /// + /// TODO: Add cross-disc repeat public enum RepeatMode { /// From 2bbfd2b2a7d37d116ce3db5bdbd0103bfca5cfcd Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 10:43:44 -0700 Subject: [PATCH 10/46] Add disc switching note --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7683f50..7edfb9f 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,9 @@ For Save Track(s): - Holding no modifying keys will prompt to save the current track - Holding **Shift** will prompt to save all tracks (including hidden) +For Disc Switching: +- If you change the number of discs in the internal changer, you must restart the program for it to take effect + For both Volume Up and Volume Down: - Holding **Ctrl** will move in increments of 2 - Holding **Shift** will move in increments of 5 From 321490bbb4607f0513652a9e5cf19b27e8d91813 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 11:17:19 -0700 Subject: [PATCH 11/46] Allow loading multiple images at once --- RedBookPlayer.GUI/ViewModels/MainViewModel.cs | 27 ++++++++-- .../ViewModels/PlayerViewModel.cs | 42 ++++++++++++--- RedBookPlayer.Models/Hardware/Player.cs | 51 +++++++++++++++++++ 3 files changed, 108 insertions(+), 12 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs index 06d6099..79a8ef0 100644 --- a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using Avalonia.Controls; using Avalonia.Input; using RedBookPlayer.GUI.Views; @@ -163,19 +164,35 @@ namespace RedBookPlayer.GUI.ViewModels } /// - /// Load the first valid drag-and-dropped disc image + /// Load the all valid drag-and-dropped disc images /// + /// If more than the number of discs in the changer are added, it will begin to overwrite public async void ExecuteLoadDragDrop(object sender, DragEventArgs e) { if(PlayerView?.ViewModel == null) return; IEnumerable fileNames = e.Data.GetFileNames(); - foreach(string filename in fileNames) + if(fileNames == null || fileNames.Count() == 0) { - bool loaded = await PlayerView.ViewModel.LoadImage(filename); - if(loaded) - break; + return; + } + else if(fileNames.Count() == 1) + { + await PlayerView.ViewModel.LoadImage(fileNames.FirstOrDefault()); + } + else + { + int lastDisc = PlayerView.ViewModel.CurrentDisc; + foreach(string path in fileNames) + { + await PlayerView.ViewModel.LoadImage(path); + + if(PlayerView.ViewModel.Initialized) + PlayerView.ViewModel.ExecuteNextDisc(); + } + + PlayerView.ViewModel.SelectDisc(lastDisc); } } diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 3a15593..2a1ac21 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -17,6 +17,7 @@ using RedBookPlayer.Models.Hardware; namespace RedBookPlayer.GUI.ViewModels { + // TODO: Add direct index selection by number public class PlayerViewModel : ReactiveObject { /// @@ -583,11 +584,28 @@ namespace RedBookPlayer.GUI.ViewModels /// public async void ExecuteLoad() { - string path = await GetPath(); - if(path == null) + string[] paths = await GetPaths(); + if(paths == null || paths.Length == 0) + { return; + } + else if(paths.Length == 1) + { + await LoadImage(paths[0]); + } + else + { + int lastDisc = CurrentDisc; + foreach(string path in paths) + { + await LoadImage(path); + + if(Initialized) + ExecuteNextDisc(); + } - await LoadImage(path); + SelectDisc(lastDisc); + } } /// @@ -680,6 +698,16 @@ namespace RedBookPlayer.GUI.ViewModels /// Output path to write data to public void ExtractAllTracksToWav(string outputDirectory) => _player?.ExtractAllTracksToWav(outputDirectory); + /// + /// Select a particular disc by number + /// + public void SelectDisc(int discNumber) => _player?.SelectDisc(discNumber); + + /// + /// Select a particular track by number + /// + public void SelectTrack(int trackNumber) => _player?.SelectTrack(trackNumber); + /// /// Set data playback method [CompactDisc only] /// @@ -780,12 +808,12 @@ namespace RedBookPlayer.GUI.ViewModels /// /// Generate a path selection dialog box /// - /// User-selected path, if possible - private async Task GetPath() + /// User-selected paths, if possible + private async Task GetPaths() { return await Dispatcher.UIThread.InvokeAsync(async () => { - var dialog = new OpenFileDialog { AllowMultiple = false }; + var dialog = new OpenFileDialog { AllowMultiple = true }; List knownExtensions = new Aaru.DiscImages.AaruFormat().KnownExtensions.ToList(); dialog.Filters.Add(new FileDialogFilter() { @@ -793,7 +821,7 @@ namespace RedBookPlayer.GUI.ViewModels Extensions = knownExtensions.ConvertAll(e => e.TrimStart('.')) }); - return (await dialog.ShowAsync(App.MainWindow))?.FirstOrDefault(); + return (await dialog.ShowAsync(App.MainWindow)); }); } diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 0d04f33..425e5ff 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -6,6 +6,7 @@ using RedBookPlayer.Models.Factories; namespace RedBookPlayer.Models.Hardware { + // TODO: Add direct index selection by number public class Player : ReactiveObject { /// @@ -404,6 +405,32 @@ namespace RedBookPlayer.Models.Hardware Initialized = false; } + /// + /// Select a particular disc by number + /// + public void SelectDisc(int discNumber) + { + PlayerState wasPlaying = PlayerState; + if (wasPlaying == PlayerState.Playing) + Stop(); + + CurrentDisc = discNumber; + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) + { + Initialized = true; + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + else + { + PlayerState = PlayerState.NoDisc; + Initialized = false; + } + } + /// /// Move to the next disc /// @@ -456,6 +483,30 @@ namespace RedBookPlayer.Models.Hardware } } + /// + /// Select a particular track by number + /// + public void SelectTrack(int trackNumber) + { + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) + return; + + PlayerState wasPlaying = PlayerState; + if(wasPlaying == PlayerState.Playing) + Pause(); + + if(trackNumber < (HiddenTrack ? 0 : 1) || trackNumber > TotalTracks) + _opticalDiscs[CurrentDisc].LoadFirstTrack(); + else + _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); + + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + /// /// Move to the next playable track /// From ee0fbc4ccc3630cc6dbd88108f5ee75e74f14958 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 21:40:41 -0700 Subject: [PATCH 12/46] Discs and audio output should be less aware --- .../ViewModels/PlayerViewModel.cs | 19 +- .../ViewModels/SettingsViewModel.cs | 2 +- RedBookPlayer.Models/Discs/CompactDisc.cs | 344 ++------ RedBookPlayer.Models/Discs/OpticalDiscBase.cs | 39 +- .../Discs/OpticalDiscOptions.cs | 15 - RedBookPlayer.Models/Enums.cs | 9 +- .../Hardware/Linux/AudioBackend.cs | 2 +- .../Hardware/Mac/AudioBackend.cs | 52 -- RedBookPlayer.Models/Hardware/Player.cs | 748 +++++++++++++----- .../Hardware/PlayerOptions.cs | 25 + RedBookPlayer.Models/Hardware/SoundOutput.cs | 224 +----- .../Hardware/Windows/AudioBackend.cs | 2 +- 12 files changed, 685 insertions(+), 796 deletions(-) delete mode 100644 RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs create mode 100644 RedBookPlayer.Models/Hardware/PlayerOptions.cs diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 2a1ac21..aa8013f 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -406,17 +406,17 @@ namespace RedBookPlayer.GUI.ViewModels /// Initialize the view model with a given image path /// /// Path to the disc image - /// Options to pass to the optical disc factory - /// RepeatMode for sound output + /// Options to pass to the player + /// Options to pass to the optical disc factory /// True if playback should begin immediately, false otherwise - public void Init(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay) + public void Init(string path, PlayerOptions playerOptions, OpticalDiscOptions opticalDiscOptions, bool autoPlay) { // Stop current playback, if necessary if(PlayerState != PlayerState.NoDisc) ExecuteStop(); // Attempt to initialize Player - _player.Init(path, options, repeatMode, autoPlay); + _player.Init(path, playerOptions, opticalDiscOptions, autoPlay); if(_player.Initialized) { _player.PropertyChanged += PlayerStateChanged; @@ -654,19 +654,24 @@ namespace RedBookPlayer.GUI.ViewModels { return await Dispatcher.UIThread.InvokeAsync(() => { - OpticalDiscOptions options = new OpticalDiscOptions + PlayerOptions playerOptions = new PlayerOptions { DataPlayback = App.Settings.DataPlayback, - GenerateMissingToc = App.Settings.GenerateMissingTOC, LoadHiddenTracks = App.Settings.PlayHiddenTracks, + RepeatMode = App.Settings.RepeatMode, SessionHandling = App.Settings.SessionHandling, }; + OpticalDiscOptions opticalDiscOptions = new OpticalDiscOptions + { + GenerateMissingToc = App.Settings.GenerateMissingTOC, + }; + // Ensure the context and view model are set App.PlayerView.DataContext = this; App.PlayerView.ViewModel = this; - Init(path, options, App.Settings.RepeatMode, App.Settings.AutoPlay); + Init(path, playerOptions, opticalDiscOptions, App.Settings.AutoPlay); if(Initialized) App.MainWindow.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last(); diff --git a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs index 685d38e..178273f 100644 --- a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs @@ -77,7 +77,7 @@ namespace RedBookPlayer.GUI.ViewModels /// /// Indicates how to repeat tracks /// - public RepeatMode RepeatMode { get; set; } = RepeatMode.All; + public RepeatMode RepeatMode { get; set; } = RepeatMode.AllSingleDisc; /// /// Indicates how to handle tracks on different sessions diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 3cf7e8d..051fef0 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -27,85 +27,25 @@ namespace RedBookPlayer.Models.Discs if(_image == null) return; - // Data tracks only and flag disabled means we can't do anything - if(_image.Tracks.All(t => t.TrackType != TrackType.Audio) && DataPlayback == DataPlayback.Skip) + // Invalid value means we can't do anything + if (value > _image.Tracks.Max(t => t.TrackSequence)) + return; + else if (value < _image.Tracks.Min(t => t.TrackSequence)) return; - // Cache the value and the current track number - int cachedValue = value; - int cachedTrackNumber; - - // Check if we're incrementing or decrementing the track - bool increment = cachedValue >= _currentTrackNumber; - - do - { - // If we're over the last track, wrap around - if(cachedValue > _image.Tracks.Max(t => t.TrackSequence)) - { - cachedValue = (int)_image.Tracks.Min(t => t.TrackSequence); - if(cachedValue == 0 && !LoadHiddenTracks) - cachedValue++; - } - - // If we're under the first track and we're not loading hidden tracks, wrap around - else if(cachedValue < 1 && !LoadHiddenTracks) - { - cachedValue = (int)_image.Tracks.Max(t => t.TrackSequence); - } - - // If we're under the first valid track, wrap around - else if(cachedValue < _image.Tracks.Min(t => t.TrackSequence)) - { - cachedValue = (int)_image.Tracks.Max(t => t.TrackSequence); - } - - cachedTrackNumber = cachedValue; - - // Cache the current track for easy access - Track track = GetTrack(cachedTrackNumber); - if(track == null) - return; - - // Set track flags from subchannel data, if possible - SetTrackFlags(track); - - // If the track is playable, just return - if((TrackType == TrackType.Audio || DataPlayback != DataPlayback.Skip) - && (SessionHandling == SessionHandling.AllSessions || track.TrackSession == 1)) - { - break; - } - - // If we're not playing the track, skip - if(increment) - cachedValue++; - else - cachedValue--; - } - while(cachedValue != _currentTrackNumber); - - // If we looped around, ensure it reloads - if(cachedValue == _currentTrackNumber) - { - this.RaiseAndSetIfChanged(ref _currentTrackNumber, -1); - - Track track = GetTrack(cachedValue); - if(track == null) - return; - - SetTrackFlags(track); - } - - this.RaiseAndSetIfChanged(ref _currentTrackNumber, cachedValue); - - Track cachedTrack = GetTrack(cachedValue); - if(cachedTrack == null) + // Cache the current track for easy access + Track track = GetTrack(value); + if(track == null) return; - TotalIndexes = cachedTrack.Indexes.Keys.Max(); - CurrentTrackIndex = cachedTrack.Indexes.Keys.Min(); - CurrentTrackSession = cachedTrack.TrackSession; + // Set all track flags and values + SetTrackFlags(track); + TotalIndexes = track.Indexes.Keys.Max(); + CurrentTrackIndex = track.Indexes.Keys.Min(); + CurrentTrackSession = track.TrackSession; + + // Mark the track as changed + this.RaiseAndSetIfChanged(ref _currentTrackNumber, value); } } @@ -124,14 +64,13 @@ namespace RedBookPlayer.Models.Discs if(track == null) return; - // Ensure that the value is valid, wrapping around if necessary - ushort fixedValue = value; - if(value > track.Indexes.Keys.Max()) - fixedValue = track.Indexes.Keys.Min(); - else if(value < track.Indexes.Keys.Min()) - fixedValue = track.Indexes.Keys.Max(); + // Invalid value means we can't do anything + if (value > track.Indexes.Keys.Max()) + return; + else if (value < track.Indexes.Keys.Min()) + return; - this.RaiseAndSetIfChanged(ref _currentTrackIndex, fixedValue); + this.RaiseAndSetIfChanged(ref _currentTrackIndex, value); // Set new index-specific data SectionStartSector = (ulong)track.Indexes[CurrentTrackIndex]; @@ -156,19 +95,18 @@ namespace RedBookPlayer.Models.Discs if(_image == null) return; - // If the sector is over the end of the image, then loop - ulong tempSector = value; - if(tempSector > _image.Info.Sectors) - tempSector = 0; - else if(tempSector < 0) - tempSector = _image.Info.Sectors - 1; + // Invalid value means we can't do anything + if(value > _image.Info.Sectors) + return; + else if(value < 0) + return; // Cache the current track for easy access Track track = GetTrack(CurrentTrackNumber); if(track == null) return; - this.RaiseAndSetIfChanged(ref _currentSector, tempSector); + this.RaiseAndSetIfChanged(ref _currentSector, value); // If the current sector is outside of the last known track, seek to the right one if(CurrentSector < track.TrackStartSector || CurrentSector > track.TrackEndSector) @@ -194,6 +132,11 @@ namespace RedBookPlayer.Models.Discs /// public override int BytesPerSector => GetTrack(CurrentTrackNumber)?.TrackRawBytesPerSector ?? 0; + /// + /// Readonly list of all tracks in the image + /// + public List Tracks => _image?.Tracks; + /// /// Represents the 4CH flag /// @@ -230,21 +173,6 @@ namespace RedBookPlayer.Models.Discs private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value); } - /// - /// Indicate how data tracks should be handled - /// - public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip; - - /// - /// Indicate if hidden tracks should be loaded - /// - public bool LoadHiddenTracks { get; set; } = false; - - /// - /// Indicates how tracks on different session should be handled - /// - public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions; - private bool _quadChannel; private bool _isDataTrack; private bool _copyAllowed; @@ -290,13 +218,7 @@ namespace RedBookPlayer.Models.Discs /// Constructor /// /// Set of options for a new disc - public CompactDisc(OpticalDiscOptions options) - { - DataPlayback = options.DataPlayback; - _generateMissingToc = options.GenerateMissingToc; - LoadHiddenTracks = options.LoadHiddenTracks; - SessionHandling = options.SessionHandling; - } + public CompactDisc(OpticalDiscOptions options) => _generateMissingToc = options.GenerateMissingToc; /// public override void Init(string path, IOpticalMediaImage image, bool autoPlay) @@ -313,8 +235,9 @@ namespace RedBookPlayer.Models.Discs if(!LoadTOC()) return; - // Load the first track - LoadFirstTrack(); + // Load the first track by default + CurrentTrackNumber = 1; + LoadTrack(CurrentTrackNumber); // Reset total indexes if not in autoplay if(!autoPlay) @@ -330,134 +253,32 @@ namespace RedBookPlayer.Models.Discs Initialized = true; } - #region Seeking - - /// - public override void NextTrack() - { - if(_image == null) - return; - - CurrentTrackNumber++; - LoadTrack(CurrentTrackNumber); - } - - /// - public override void PreviousTrack() - { - if(_image == null) - return; - - CurrentTrackNumber--; - LoadTrack(CurrentTrackNumber); - } - - /// - public override bool NextIndex(bool changeTrack) - { - if(_image == null) - return false; - - // Cache the current track for easy access - Track track = GetTrack(CurrentTrackNumber); - if(track == null) - return false; - - // If the index is greater than the highest index, change tracks if needed - if(CurrentTrackIndex + 1 > track.Indexes.Keys.Max()) - { - if(changeTrack) - { - NextTrack(); - CurrentSector = (ulong)GetTrack(CurrentTrackNumber).Indexes.Values.Min(); - return true; - } - } - - // If the next index has an invalid offset, change tracks if needed - else if(track.Indexes[(ushort)(CurrentTrackIndex + 1)] < 0) - { - if(changeTrack) - { - NextTrack(); - CurrentSector = (ulong)GetTrack(CurrentTrackNumber).Indexes.Values.Min(); - return true; - } - } - - // Otherwise, just move to the next index - else - { - CurrentSector = (ulong)track.Indexes[++CurrentTrackIndex]; - } - - return false; - } - - /// - public override bool PreviousIndex(bool changeTrack) - { - if(_image == null) - return false; - - // Cache the current track for easy access - Track track = GetTrack(CurrentTrackNumber); - if(track == null) - return false; - - // If the index is less than the lowest index, change tracks if needed - if(CurrentTrackIndex - 1 < track.Indexes.Keys.Min()) - { - if(changeTrack) - { - PreviousTrack(); - CurrentSector = (ulong)GetTrack(CurrentTrackNumber).Indexes.Values.Max(); - return true; - } - } - - // If the previous index has an invalid offset, change tracks if needed - else if(track.Indexes[(ushort)(CurrentTrackIndex - 1)] < 0) - { - if(changeTrack) - { - PreviousTrack(); - CurrentSector = (ulong)GetTrack(CurrentTrackNumber).Indexes.Values.Max(); - return true; - } - } - - // Otherwise, just move to the previous index - else - { - CurrentSector = (ulong)track.Indexes[--CurrentTrackIndex]; - } - - return false; - } - - #endregion - #region Helpers /// - public override void ExtractTrackToWav(uint trackNumber, string outputDirectory) + public override void ExtractTrackToWav(uint trackNumber, string outputDirectory) => ExtractTrackToWav(trackNumber, outputDirectory, DataPlayback.Skip); + + /// + /// Extract a track to WAV + /// + /// Track number to extract + /// Output path to write data to + /// DataPlayback value indicating how to handle data tracks + public void ExtractTrackToWav(uint trackNumber, string outputDirectory, DataPlayback dataPlayback) { if(_image == null) return; // Get the track with that value, if possible Track track = _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber); - - // If the track isn't valid, we can't do anything - if(track == null || !(DataPlayback != DataPlayback.Skip || track.TrackType == TrackType.Audio)) + if (track == null) return; // Get the number of sectors to read uint length = (uint)(track.TrackEndSector - track.TrackStartSector); // Read in the track data to a buffer - byte[] buffer = ReadSectors(track.TrackStartSector, length); + byte[] buffer = ReadSectors(track.TrackStartSector, length, dataPlayback); // Build the WAV output string filename = Path.Combine(outputDirectory, $"Track {trackNumber.ToString().PadLeft(2, '0')}.wav"); @@ -471,15 +292,20 @@ namespace RedBookPlayer.Models.Discs } } - /// - public override void ExtractAllTracksToWav(string outputDirectory) + /// + /// Get the track with the given sequence value, if possible + /// + /// Track number to retrieve + /// Track object for the requested sequence, null on error + public Track GetTrack(int trackNumber) { - if(_image == null) - return; - - foreach(Track track in _image.Tracks) + try { - ExtractTrackToWav(track.TrackSequence, outputDirectory); + return _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber); + } + catch + { + return null; } } @@ -501,28 +327,49 @@ namespace RedBookPlayer.Models.Discs } /// - public override void LoadFirstTrack() + public override void LoadIndex(ushort index) { - CurrentTrackNumber = 1; - LoadTrack(CurrentTrackNumber); + if(_image == null) + return; + + // Cache the current track for easy access + Track track = GetTrack(CurrentTrackNumber); + if (track == null) + return; + + // If the index is invalid, just return + if(index < track.Indexes.Keys.Min() || index > track.Indexes.Keys.Max()) + return; + + // Select the first index that has a sector offset greater than or equal to 0 + CurrentSector = (ulong)track.Indexes[index]; } /// - public override byte[] ReadSectors(uint sectorsToRead) => ReadSectors(CurrentSector, sectorsToRead); + public override byte[] ReadSectors(uint sectorsToRead) => ReadSectors(CurrentSector, sectorsToRead, DataPlayback.Skip); + + /// + /// Read sector data from the base image starting from the specified sector + /// + /// Current number of sectors to read + /// DataPlayback value indicating how to handle data tracks + /// Byte array representing the read sectors, if possible + public byte[] ReadSectors(uint sectorsToRead, DataPlayback dataPlayback) => ReadSectors(CurrentSector, sectorsToRead, DataPlayback.Skip); /// /// Read sector data from the base image starting from the specified sector /// /// Sector to start at for reading /// Current number of sectors to read + /// DataPlayback value indicating how to handle data tracks /// Byte array representing the read sectors, if possible - private byte[] ReadSectors(ulong startSector, uint sectorsToRead) + private byte[] ReadSectors(ulong startSector, uint sectorsToRead, DataPlayback dataPlayback) { - if(TrackType == TrackType.Audio || DataPlayback == DataPlayback.Play) + if(TrackType == TrackType.Audio || dataPlayback == DataPlayback.Play) { return _image.ReadSectors(startSector, sectorsToRead); } - else if(DataPlayback == DataPlayback.Blank) + else if(dataPlayback == DataPlayback.Blank) { byte[] sectors = _image.ReadSectors(startSector, sectorsToRead); Array.Clear(sectors, 0, sectors.Length); @@ -543,23 +390,6 @@ namespace RedBookPlayer.Models.Discs TotalIndexes = GetTrack(CurrentTrackNumber)?.Indexes.Keys.Max() ?? 0; } - /// - /// Get the track with the given sequence value, if possible - /// - /// Track number to retrieve - /// Track object for the requested sequence, null on error - private Track GetTrack(int trackNumber) - { - try - { - return _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber); - } - catch - { - return null; - } - } - /// /// Load TOC for the current disc image /// diff --git a/RedBookPlayer.Models/Discs/OpticalDiscBase.cs b/RedBookPlayer.Models/Discs/OpticalDiscBase.cs index c871713..5dcd612 100644 --- a/RedBookPlayer.Models/Discs/OpticalDiscBase.cs +++ b/RedBookPlayer.Models/Discs/OpticalDiscBase.cs @@ -103,34 +103,6 @@ namespace RedBookPlayer.Models.Discs /// True if playback should begin immediately, false otherwise public abstract void Init(string path, IOpticalMediaImage image, bool autoPlay); - #region Seeking - - /// - /// Try to move to the next track, wrapping around if necessary - /// - public abstract void NextTrack(); - - /// - /// Try to move to the previous track, wrapping around if necessary - /// - public abstract void PreviousTrack(); - - /// - /// 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 /// @@ -140,12 +112,6 @@ namespace RedBookPlayer.Models.Discs /// Output path to write data to public abstract void ExtractTrackToWav(uint trackNumber, string outputDirectory); - /// - /// Extract all tracks to WAV - /// - /// Output path to write data to - public abstract void ExtractAllTracksToWav(string outputDirectory); - /// /// Load the desired track, if possible /// @@ -153,9 +119,10 @@ namespace RedBookPlayer.Models.Discs public abstract void LoadTrack(int track); /// - /// Load the first valid track in the image + /// Load the desired index, if possible /// - public abstract void LoadFirstTrack(); + /// Index number to load + public abstract void LoadIndex(ushort index); /// /// Read sector data from the base image starting from the current sector diff --git a/RedBookPlayer.Models/Discs/OpticalDiscOptions.cs b/RedBookPlayer.Models/Discs/OpticalDiscOptions.cs index a636c39..d814919 100644 --- a/RedBookPlayer.Models/Discs/OpticalDiscOptions.cs +++ b/RedBookPlayer.Models/Discs/OpticalDiscOptions.cs @@ -4,26 +4,11 @@ namespace RedBookPlayer.Models.Discs { #region CompactDisc - /// - /// Indicate how data tracks should be handled - /// - public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip; - /// /// Indicate if a TOC should be generated if missing /// public bool GenerateMissingToc { get; set; } = false; - /// - /// Indicate if hidden tracks should be loaded - /// - public bool LoadHiddenTracks { get; set; } = false; - - /// - /// Indicates how tracks on different session should be handled - /// - public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions; - #endregion } } \ No newline at end of file diff --git a/RedBookPlayer.Models/Enums.cs b/RedBookPlayer.Models/Enums.cs index add206b..472267f 100644 --- a/RedBookPlayer.Models/Enums.cs +++ b/RedBookPlayer.Models/Enums.cs @@ -64,9 +64,14 @@ namespace RedBookPlayer.Models Single, /// - /// Repeat all tracks + /// Repeat all tracks on a single disc /// - All, + AllSingleDisc, + + /// + /// Repeat all tracks on a multiple discs + /// + AllMultiDisc, } /// diff --git a/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs index b84ffa2..92043fb 100644 --- a/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs +++ b/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs @@ -7,7 +7,7 @@ namespace RedBookPlayer.Models.Hardware.Linux /// /// Sound output instance /// - private ALSoundOut _soundOut; + private readonly ALSoundOut _soundOut; public AudioBackend() { } diff --git a/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs deleted file mode 100644 index 6f7949d..0000000 --- a/RedBookPlayer.Models/Hardware/Mac/AudioBackend.cs +++ /dev/null @@ -1,52 +0,0 @@ -using CSCore.SoundOut; - -namespace RedBookPlayer.Models.Hardware.Mac -{ - public class AudioBackend : IAudioBackend - { - /// - /// Sound output instance - /// - private ALSoundOut _soundOut; - - public AudioBackend() { } - - public AudioBackend(PlayerSource source) - { - _soundOut = new ALSoundOut(100); - _soundOut.Initialize(source); - } - - #region IAudioBackend Implementation - - /// - public void Pause() => _soundOut.Pause(); - - /// - public void Play() => _soundOut.Play(); - - /// - public void Stop() => _soundOut.Stop(); - - /// - public PlayerState GetPlayerState() - { - return (_soundOut?.PlaybackState) switch - { - PlaybackState.Paused => PlayerState.Paused, - PlaybackState.Playing => PlayerState.Playing, - PlaybackState.Stopped => PlayerState.Stopped, - _ => PlayerState.NoDisc, - }; - } - - /// - public void SetVolume(float volume) - { - if (_soundOut != null) - _soundOut.Volume = volume; - } - - #endregion - } -} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 425e5ff..684d421 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1,5 +1,10 @@ +using System; using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; using Aaru.CommonTypes.Enums; +using Aaru.CommonTypes.Structs; +using Avalonia.Threading; using ReactiveUI; using RedBookPlayer.Models.Discs; using RedBookPlayer.Models.Factories; @@ -36,9 +41,19 @@ namespace RedBookPlayer.Models.Hardware } } + /// + /// Should invoke playback mode changes + /// + private bool ShouldInvokePlaybackModes + { + get => _shouldInvokePlaybackModes; + set => this.RaiseAndSetIfChanged(ref _shouldInvokePlaybackModes, value); + } + private bool _initialized; private int _numberOfDiscs; private int _currentDisc; + private bool _shouldInvokePlaybackModes; #region OpticalDisc Passthrough @@ -201,6 +216,15 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value); } + /// + /// Indicate if hidden tracks should be loaded + /// + public bool LoadHiddenTracks + { + get => _loadHiddenTracks; + private set => this.RaiseAndSetIfChanged(ref _loadHiddenTracks, value); + } + /// /// Indicates the repeat mode /// @@ -210,6 +234,15 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _repeatMode, value); } + /// + /// Indicates how tracks on different session should be handled + /// + public SessionHandling SessionHandling + { + get => _sessionHandling; + private set => this.RaiseAndSetIfChanged(ref _sessionHandling, value); + } + /// /// Indicates if de-emphasis should be applied /// @@ -230,7 +263,9 @@ namespace RedBookPlayer.Models.Hardware private PlayerState _playerState; private DataPlayback _dataPlayback; + private bool _loadHiddenTracks; private RepeatMode _repeatMode; + private SessionHandling _sessionHandling; private bool _applyDeEmphasis; private int _volume; @@ -241,7 +276,7 @@ namespace RedBookPlayer.Models.Hardware /// /// Sound output handling class /// - private readonly SoundOutput[] _soundOutputs; + private readonly SoundOutput _soundOutput; /// /// OpticalDisc object @@ -253,6 +288,21 @@ namespace RedBookPlayer.Models.Hardware /// private int? _lastVolume = null; + /// + /// Filtering stage for audio output + /// + private FilterStage _filterStage; + + /// + /// Current position in the sector for reading + /// + private int _currentSectorReadPosition = 0; + + /// + /// Lock object for reading track data + /// + private readonly object _readingImage = new object(); + #endregion /// @@ -268,44 +318,51 @@ namespace RedBookPlayer.Models.Hardware numberOfDiscs = 1; _numberOfDiscs = numberOfDiscs; - _soundOutputs = new SoundOutput[_numberOfDiscs]; _opticalDiscs = new OpticalDiscBase[numberOfDiscs]; - _currentDisc = 0; - for (int i = 0; i < _numberOfDiscs; i++) - { - _soundOutputs[i] = new SoundOutput(defaultVolume); - _soundOutputs[i].SetDeEmphasis(false); - } + + _filterStage = new FilterStage(); + _soundOutput = new SoundOutput(defaultVolume); + + PropertyChanged += HandlePlaybackModes; } /// /// Initializes player from a given image path /// /// Path to the disc image - /// Options to pass to the optical disc factory - /// RepeatMode for sound output + /// Options to pass to the player + /// Options to pass to the optical disc factory /// True if playback should begin immediately, false otherwise - public void Init(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay) + public void Init(string path, PlayerOptions playerOptions, OpticalDiscOptions opticalDiscOptions, bool autoPlay) { // Reset initialization Initialized = false; + // Set player options + DataPlayback = playerOptions.DataPlayback; + LoadHiddenTracks = playerOptions.LoadHiddenTracks; + RepeatMode = playerOptions.RepeatMode; + SessionHandling = playerOptions.SessionHandling; + // Initalize the disc - _opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay); + _opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, opticalDiscOptions, autoPlay); if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; // Add event handling for the optical disc _opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged; + // Setup de-emphasis filters + _filterStage.SetupFilters(); + // Initialize the sound output - _soundOutputs[CurrentDisc].Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay); - if(_soundOutputs[CurrentDisc] == null || !_soundOutputs[CurrentDisc].Initialized) + _soundOutput.Init(ProviderRead, autoPlay); + if(_soundOutput == null || !_soundOutput.Initialized) return; // Add event handling for the sound output - _soundOutputs[CurrentDisc].PropertyChanged += SoundOutputStateChanged; + _soundOutput.PropertyChanged += SoundOutputStateChanged; // Mark the player as ready Initialized = true; @@ -315,7 +372,7 @@ namespace RedBookPlayer.Models.Hardware SoundOutputStateChanged(this, null); } - #region Playback + #region Playback (UI) /// /// Begin playback @@ -324,12 +381,12 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutputs[CurrentDisc] == null) + else if(_soundOutput == null) return; - else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Stopped) + else if(_soundOutput.PlayerState != PlayerState.Paused && _soundOutput.PlayerState != PlayerState.Stopped) return; - _soundOutputs[CurrentDisc].Play(); + _soundOutput.Play(); _opticalDiscs[CurrentDisc].SetTotalIndexes(); PlayerState = PlayerState.Playing; } @@ -341,12 +398,12 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutputs[CurrentDisc] == null) + else if(_soundOutput == null) return; - else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing) + else if(_soundOutput.PlayerState != PlayerState.Playing) return; - _soundOutputs[CurrentDisc]?.Pause(); + _soundOutput.Pause(); PlayerState = PlayerState.Paused; } @@ -378,13 +435,14 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutputs[CurrentDisc] == null) - return; - else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused) + else if(_soundOutput == null) return; + else if(_soundOutput.PlayerState != PlayerState.Playing && _soundOutput.PlayerState != PlayerState.Paused) + return; - _soundOutputs[CurrentDisc].Stop(); - _opticalDiscs[CurrentDisc].LoadFirstTrack(); + _soundOutput.Stop(); + CurrentTrackNumber = 0; + SelectTrack(1); PlayerState = PlayerState.Stopped; } @@ -395,199 +453,47 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; - else if(_soundOutputs[CurrentDisc] == null) + else if(_soundOutput == null) return; Stop(); - _soundOutputs[CurrentDisc].Eject(); + _soundOutput.Eject(); _opticalDiscs[CurrentDisc] = null; PlayerState = PlayerState.NoDisc; Initialized = false; } - /// - /// Select a particular disc by number - /// - public void SelectDisc(int discNumber) - { - PlayerState wasPlaying = PlayerState; - if (wasPlaying == PlayerState.Playing) - Stop(); - - CurrentDisc = discNumber; - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) - { - Initialized = true; - OpticalDiscStateChanged(this, null); - SoundOutputStateChanged(this, null); - - if(wasPlaying == PlayerState.Playing) - Play(); - } - else - { - PlayerState = PlayerState.NoDisc; - Initialized = false; - } - } - /// /// Move to the next disc /// - public void NextDisc() - { - PlayerState wasPlaying = PlayerState; - if (wasPlaying == PlayerState.Playing) - Stop(); - - CurrentDisc++; - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) - { - Initialized = true; - OpticalDiscStateChanged(this, null); - SoundOutputStateChanged(this, null); - - if(wasPlaying == PlayerState.Playing) - Play(); - } - else - { - PlayerState = PlayerState.NoDisc; - Initialized = false; - } - } + public void NextDisc() => SelectDisc(CurrentDisc + 1); /// /// Move to the previous disc /// - public void PreviousDisc() - { - PlayerState wasPlaying = PlayerState; - if (wasPlaying == PlayerState.Playing) - Stop(); - - CurrentDisc--; - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) - { - Initialized = true; - OpticalDiscStateChanged(this, null); - SoundOutputStateChanged(this, null); - - if(wasPlaying == PlayerState.Playing) - Play(); - } - else - { - PlayerState = PlayerState.NoDisc; - Initialized = false; - } - } - - /// - /// Select a particular track by number - /// - public void SelectTrack(int trackNumber) - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - if(trackNumber < (HiddenTrack ? 0 : 1) || trackNumber > TotalTracks) - _opticalDiscs[CurrentDisc].LoadFirstTrack(); - else - _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); - - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); - - if(wasPlaying == PlayerState.Playing) - Play(); - } + public void PreviousDisc() => SelectDisc(CurrentDisc - 1); /// /// Move to the next playable track /// - public void NextTrack() - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - _opticalDiscs[CurrentDisc].NextTrack(); - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); - - if(wasPlaying == PlayerState.Playing) - Play(); - } + public void NextTrack() => SelectTrack(CurrentTrackNumber + 1); /// /// Move to the previous playable track /// - public void PreviousTrack() - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - _opticalDiscs[CurrentDisc].PreviousTrack(); - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); - - if(wasPlaying == PlayerState.Playing) - Play(); - } + public void PreviousTrack() => SelectTrack(CurrentTrackNumber - 1); /// /// Move to the next index /// /// True if index changes can trigger a track change, false otherwise - public void NextIndex(bool changeTrack) - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - _opticalDiscs[CurrentDisc].NextIndex(changeTrack); - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); - - if(wasPlaying == PlayerState.Playing) - Play(); - } + public void NextIndex(bool changeTrack) => SelectIndex((ushort)(CurrentTrackIndex + 1), changeTrack); /// /// Move to the previous index /// /// True if index changes can trigger a track change, false otherwise - public void PreviousIndex(bool changeTrack) - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - _opticalDiscs[CurrentDisc].PreviousIndex(changeTrack); - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - _soundOutputs[CurrentDisc].SetDeEmphasis(compactDisc.TrackHasEmphasis); - - if(wasPlaying == PlayerState.Playing) - Play(); - } + public void PreviousIndex(bool changeTrack) => SelectIndex((ushort)(CurrentTrackIndex - 1), changeTrack); /// /// Fast-forward playback by 75 sectors @@ -613,6 +519,348 @@ namespace RedBookPlayer.Models.Hardware #endregion + #region Playback (Internal) + + /// + /// Fill the current byte buffer with playable data + /// + /// Buffer to load data into + /// Offset in the buffer to load at + /// Number of bytes to load + /// Number of bytes read + public int ProviderRead(byte[] buffer, int offset, int count) + { + // If we have an unreadable amount + if (count <= 0) + { + Array.Clear(buffer, offset, count); + return count; + } + + // If we have an unreadable track, just return + if(_opticalDiscs[CurrentDisc].BytesPerSector <= 0) + { + Array.Clear(buffer, offset, count); + return count; + } + + // Determine how many sectors we can read + DetermineReadAmount(count, out ulong sectorsToRead, out ulong zeroSectorsAmount); + + // Get data to return + byte[] audioDataSegment = ReadData(count, sectorsToRead, zeroSectorsAmount); + if(audioDataSegment == null) + { + Array.Clear(buffer, offset, count); + return count; + } + + // Write out the audio data to the buffer + Array.Copy(audioDataSegment, 0, buffer, offset, count); + + // Set the read position in the sector for easier access + _currentSectorReadPosition += count; + if(_currentSectorReadPosition >= _opticalDiscs[CurrentDisc].BytesPerSector) + { + ulong newSectorValue = _opticalDiscs[CurrentDisc].CurrentSector + (ulong)(_currentSectorReadPosition / _opticalDiscs[CurrentDisc].BytesPerSector); + if(newSectorValue >= _opticalDiscs[CurrentDisc].TotalSectors) + { + ShouldInvokePlaybackModes = true; + } + else if(RepeatMode == RepeatMode.Single && _opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + { + ulong trackEndSector = compactDisc.GetTrack(CurrentTrackNumber).TrackEndSector; + if (newSectorValue > trackEndSector) + { + ShouldInvokePlaybackModes = true; + } + else + { + _opticalDiscs[CurrentDisc].SetCurrentSector(newSectorValue); + _currentSectorReadPosition %= _opticalDiscs[CurrentDisc].BytesPerSector; + } + } + else + { + _opticalDiscs[CurrentDisc].SetCurrentSector(newSectorValue); + _currentSectorReadPosition %= _opticalDiscs[CurrentDisc].BytesPerSector; + } + } + + return count; + } + + /// + /// Select a disc by number + /// + /// Disc number to attempt to load + public void SelectDisc(int discNumber) + { + PlayerState wasPlaying = PlayerState; + if (wasPlaying == PlayerState.Playing) + Stop(); + + _currentSectorReadPosition = 0; + + CurrentDisc = discNumber; + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) + { + Initialized = true; + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + else + { + PlayerState = PlayerState.NoDisc; + Initialized = false; + } + } + + /// + /// Select a disc by number + /// + /// Track index to attempt to load + /// True if index changes can trigger a track change, false otherwise + public void SelectIndex(ushort index, bool changeTrack) + { + PlayerState wasPlaying = PlayerState; + if (wasPlaying == PlayerState.Playing) + Pause(); + + // CompactDisc needs special handling of track wraparound + if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + { + // Cache the current track for easy access + Track track = compactDisc.GetTrack(CurrentTrackNumber); + if(track == null) + return; + + // Check if we're incrementing or decrementing the track + bool increment = (short)index >= (short)CurrentTrackIndex; + + // If the index is greater than the highest index, change tracks if needed + if((short)index > (short)track.Indexes.Keys.Max()) + { + if(changeTrack) + NextTrack(); + } + + // If the index is less than the lowest index, change tracks if needed + else if((short)index < (short)track.Indexes.Keys.Min()) + { + if(changeTrack) + { + PreviousTrack(); + compactDisc.SetCurrentSector((ulong)compactDisc.GetTrack(CurrentTrackNumber).Indexes.Values.Max()); + } + } + + // If the next index has an invalid offset, change tracks if needed + else if(track.Indexes[index] < 0) + { + if(changeTrack) + { + if(increment) + { + NextTrack(); + } + else + { + PreviousTrack(); + compactDisc.SetCurrentSector((ulong)compactDisc.GetTrack(CurrentTrackNumber).Indexes.Values.Min()); + } + } + } + + // Otherwise, just move to the next index + else + { + compactDisc.SetCurrentSector((ulong)track.Indexes[index]); + } + } + else + { + // TODO: Fill in for non-CD media + } + + if(wasPlaying == PlayerState.Playing) + Play(); + } + + /// + /// Select a track by number + /// + /// Track number to attempt to load + /// Changing track with RepeatMode.AllMultiDisc should switch discs + public void SelectTrack(int trackNumber) + { + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) + return; + + PlayerState wasPlaying = PlayerState; + if(wasPlaying == PlayerState.Playing) + Pause(); + + // CompactDisc needs special handling of track wraparound + if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + { + // Cache the value and the current track number + int cachedValue = trackNumber; + int cachedTrackNumber; + + // If we have an invalid current track number, set it to the minimum + if(!compactDisc.Tracks.Any(t => t.TrackSequence == _currentTrackNumber)) + _currentTrackNumber = (int)compactDisc.Tracks.Min(t => t.TrackSequence); + + // Check if we're incrementing or decrementing the track + bool increment = cachedValue >= _currentTrackNumber; + + do + { + // If we're over the last track, wrap around + if(cachedValue > compactDisc.Tracks.Max(t => t.TrackSequence)) + { + cachedValue = (int)compactDisc.Tracks.Min(t => t.TrackSequence); + if(cachedValue == 0 && !LoadHiddenTracks) + cachedValue++; + } + + // If we're under the first track and we're not loading hidden tracks, wrap around + else if(cachedValue < 1 && !LoadHiddenTracks) + { + cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); + } + + // If we're under the first valid track, wrap around + else if(cachedValue < compactDisc.Tracks.Min(t => t.TrackSequence)) + { + cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); + } + + cachedTrackNumber = cachedValue; + + // Cache the current track for easy access + Track track = compactDisc.GetTrack(cachedTrackNumber); + if(track == null) + return; + + // If the track is playable, just return + if((track.TrackType == TrackType.Audio || DataPlayback != DataPlayback.Skip) + && (SessionHandling == SessionHandling.AllSessions || track.TrackSession == 1)) + { + break; + } + + // If we're not playing the track, skip + if(increment) + cachedValue++; + else + cachedValue--; + } + while(cachedValue != _currentTrackNumber); + + // Load the now-valid value + compactDisc.LoadTrack(cachedTrackNumber); + ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + } + else + { + if(trackNumber >= _opticalDiscs[CurrentDisc].TotalTracks) + trackNumber = 1; + else if(trackNumber < 1) + trackNumber = _opticalDiscs[CurrentDisc].TotalTracks - 1; + + _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); + } + + if(wasPlaying == PlayerState.Playing) + Play(); + } + + /// + /// Determine the number of real and zero sectors to read + /// + /// Number of requested bytes to read + /// Number of sectors to read + /// Number of zeroed sectors to concatenate + private void DetermineReadAmount(int count, out ulong sectorsToRead, out ulong zeroSectorsAmount) + { + // Attempt to read 10 more sectors than requested + sectorsToRead = ((ulong)count / (ulong)_opticalDiscs[CurrentDisc].BytesPerSector) + 10; + zeroSectorsAmount = 0; + + // Avoid overreads by padding with 0-byte data at the end + if(_opticalDiscs[CurrentDisc].CurrentSector + sectorsToRead > _opticalDiscs[CurrentDisc].TotalSectors) + { + ulong oldSectorsToRead = sectorsToRead; + sectorsToRead = _opticalDiscs[CurrentDisc].TotalSectors - _opticalDiscs[CurrentDisc].CurrentSector; + + int tempZeroSectorCount = (int)(oldSectorsToRead - sectorsToRead); + zeroSectorsAmount = (ulong)(tempZeroSectorCount < 0 ? 0 : tempZeroSectorCount); + } + } + + /// + /// Read the requested amount of data from an input + /// + /// Number of bytes to load + /// Number of sectors to read + /// Number of zeroed sectors to concatenate + /// The requested amount of data, if possible + private byte[] ReadData(int count, ulong sectorsToRead, ulong zeroSectorsAmount) + { + // If the amount of zeroes being asked for is the same as the sectors, return null + if (sectorsToRead == zeroSectorsAmount) + return null; + + // Create padding data for overreads + byte[] zeroSectors = new byte[(int)zeroSectorsAmount * _opticalDiscs[CurrentDisc].BytesPerSector]; + byte[] audioData; + + // Attempt to read the required number of sectors + var readSectorTask = Task.Run(() => + { + lock(_readingImage) + { + try + { + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + return compactDisc.ReadSectors((uint)sectorsToRead, DataPlayback).Concat(zeroSectors).ToArray(); + else + return _opticalDiscs[CurrentDisc].ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + } + catch { } + + return zeroSectors; + } + }); + + // Wait 100ms at longest for the read to occur + if(readSectorTask.Wait(TimeSpan.FromMilliseconds(100))) + audioData = readSectorTask.Result; + else + return null; + + // Load only the requested audio segment + byte[] audioDataSegment = new byte[count]; + int copyAmount = Math.Min(count, audioData.Length - _currentSectorReadPosition); + if(Math.Max(0, copyAmount) == 0) + return null; + + Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, copyAmount); + + // Apply de-emphasis filtering, only if enabled + if(ApplyDeEmphasis) + _filterStage.ProcessAudioData(audioDataSegment); + + return audioDataSegment; + } + + #endregion + #region Volume /// @@ -629,7 +877,7 @@ namespace RedBookPlayer.Models.Hardware /// Set the value for the volume /// /// New volume value - public void SetVolume(int volume) => _soundOutputs[CurrentDisc]?.SetVolume(volume); + public void SetVolume(int volume) => _soundOutput?.SetVolume(volume); /// /// Temporarily mute playback @@ -670,59 +918,137 @@ namespace RedBookPlayer.Models.Hardware /// /// Set de-emphasis status /// - /// - private void SetDeEmphasis(bool apply) => _soundOutputs[CurrentDisc]?.SetDeEmphasis(apply); + /// + private void SetDeEmphasis(bool applyDeEmphasis) => ApplyDeEmphasis = applyDeEmphasis; #endregion - #region Helpers + #region Extraction /// /// Extract a single track from the image to WAV /// /// /// Output path to write data to - public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) => _opticalDiscs[CurrentDisc]?.ExtractTrackToWav(trackNumber, outputDirectory); + public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) + { + OpticalDiscBase opticalDisc = _opticalDiscs[CurrentDisc]; + if(opticalDisc == null || !opticalDisc.Initialized) + return; + + if(opticalDisc is CompactDisc compactDisc) + { + // Get the track with that value, if possible + Track track = compactDisc.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber); + + // If the track isn't valid, we can't do anything + if(track == null || !(DataPlayback != DataPlayback.Skip || track.TrackType == TrackType.Audio)) + return; + + // Extract the track if it's valid + compactDisc.ExtractTrackToWav(trackNumber, outputDirectory, DataPlayback); + } + else + { + opticalDisc?.ExtractTrackToWav(trackNumber, outputDirectory); + } + } /// /// Extract all tracks from the image to WAV + /// /// Output path to write data to - public void ExtractAllTracksToWav(string outputDirectory) => _opticalDiscs[CurrentDisc]?.ExtractAllTracksToWav(outputDirectory); + public void ExtractAllTracksToWav(string outputDirectory) + { + OpticalDiscBase opticalDisc = _opticalDiscs[CurrentDisc]; + if(opticalDisc == null || !opticalDisc.Initialized) + return; + + if(opticalDisc is CompactDisc compactDisc) + { + foreach(Track track in compactDisc.Tracks) + { + ExtractSingleTrackToWav(track.TrackSequence, outputDirectory); + } + } + else + { + for(uint i = 0; i < opticalDisc.TotalTracks; i++) + { + ExtractSingleTrackToWav(i, outputDirectory); + } + } + } + + #endregion + + #region Setters /// /// Set data playback method [CompactDisc only] /// /// New playback value - public void SetDataPlayback(DataPlayback dataPlayback) - { - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - compactDisc.DataPlayback = dataPlayback; - } + public void SetDataPlayback(DataPlayback dataPlayback) => DataPlayback = dataPlayback; /// /// Set the value for loading hidden tracks [CompactDisc only] /// - /// True to enable loading hidden tracks, false otherwise - public void SetLoadHiddenTracks(bool load) - { - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - compactDisc.LoadHiddenTracks = load; - } + /// True to enable loading hidden tracks, false otherwise + public void SetLoadHiddenTracks(bool loadHiddenTracks) => LoadHiddenTracks = loadHiddenTracks; /// /// Set repeat mode /// /// New repeat mode value - public void SetRepeatMode(RepeatMode repeatMode) => _soundOutputs[CurrentDisc]?.SetRepeatMode(repeatMode); + public void SetRepeatMode(RepeatMode repeatMode) => RepeatMode = repeatMode; /// /// Set the value for session handling [CompactDisc only] /// /// New session handling value - public void SetSessionHandling(SessionHandling sessionHandling) + public void SetSessionHandling(SessionHandling sessionHandling) => SessionHandling = sessionHandling; + + #endregion + + #region State Change Event Handlers + + /// + /// Handle special playback modes if we get flagged to + /// + private async void HandlePlaybackModes(object sender, PropertyChangedEventArgs e) { - if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - compactDisc.SessionHandling = sessionHandling; + if(e.PropertyName != nameof(ShouldInvokePlaybackModes)) + return; + + // Always stop before doing anything else + PlayerState wasPlaying = PlayerState; + await Dispatcher.UIThread.InvokeAsync(Stop); + + switch(RepeatMode) + { + case RepeatMode.None: + // No-op + break; + case RepeatMode.Single: + _opticalDiscs[CurrentDisc].LoadTrack(CurrentTrackNumber); + break; + case RepeatMode.AllSingleDisc: + SelectTrack(1); + break; + case RepeatMode.AllMultiDisc: + do + { + NextDisc(); + } + while(_opticalDiscs[CurrentDisc] != null && !_opticalDiscs[CurrentDisc].Initialized); + + SelectTrack(1); + break; + } + + _shouldInvokePlaybackModes = false; + if(wasPlaying == PlayerState.Playing) + await Dispatcher.UIThread.InvokeAsync(Play); } /// @@ -759,10 +1085,8 @@ namespace RedBookPlayer.Models.Hardware /// private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e) { - PlayerState = _soundOutputs[CurrentDisc].PlayerState; - RepeatMode = _soundOutputs[CurrentDisc].RepeatMode; - ApplyDeEmphasis = _soundOutputs[CurrentDisc].ApplyDeEmphasis; - Volume = _soundOutputs[CurrentDisc].Volume; + PlayerState = _soundOutput.PlayerState; + Volume = _soundOutput.Volume; } #endregion diff --git a/RedBookPlayer.Models/Hardware/PlayerOptions.cs b/RedBookPlayer.Models/Hardware/PlayerOptions.cs new file mode 100644 index 0000000..43a0cbd --- /dev/null +++ b/RedBookPlayer.Models/Hardware/PlayerOptions.cs @@ -0,0 +1,25 @@ +namespace RedBookPlayer.Models.Discs +{ + public class PlayerOptions + { + /// + /// Indicate how data tracks should be handled + /// + public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip; + + /// + /// Indicate if hidden tracks should be loaded + /// + public bool LoadHiddenTracks { get; set; } = false; + + /// + /// Indicates the repeat mode + /// + public RepeatMode RepeatMode { get; set; } = RepeatMode.None; + + /// + /// Indicates how tracks on different session should be handled + /// + public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions; + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/SoundOutput.cs b/RedBookPlayer.Models/Hardware/SoundOutput.cs index 27a1047..246c004 100644 --- a/RedBookPlayer.Models/Hardware/SoundOutput.cs +++ b/RedBookPlayer.Models/Hardware/SoundOutput.cs @@ -1,9 +1,5 @@ -using System; -using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; using ReactiveUI; -using RedBookPlayer.Models.Discs; namespace RedBookPlayer.Models.Hardware { @@ -29,24 +25,6 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _playerState, value); } - /// - /// Indicates the repeat mode - /// - public RepeatMode RepeatMode - { - get => _repeatMode; - private set => this.RaiseAndSetIfChanged(ref _repeatMode, value); - } - - /// - /// Indicates if de-emphasis should be applied - /// - public bool ApplyDeEmphasis - { - get => _applyDeEmphasis; - private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); - } - /// /// Current playback volume /// @@ -67,22 +45,12 @@ namespace RedBookPlayer.Models.Hardware private bool _initialized; private PlayerState _playerState; - private RepeatMode _repeatMode; - private bool _applyDeEmphasis; private int _volume; #endregion #region Private State Variables - /// - /// OpticalDisc from the parent player for easy access - /// - /// - /// TODO: Can we remove the need for a local reference to OpticalDisc? - /// - private OpticalDiscBase _opticalDisc; - /// /// Data provider for sound output /// @@ -93,60 +61,26 @@ namespace RedBookPlayer.Models.Hardware /// private IAudioBackend _soundOut; - /// - /// Filtering stage for audio output - /// - private FilterStage _filterStage; - - /// - /// Current position in the sector - /// - private int _currentSectorReadPosition = 0; - - /// - /// Lock object for reading track data - /// - private readonly object _readingImage = new object(); - #endregion /// /// Constructor /// /// Default volume between 0 and 100 to use when starting playback - public SoundOutput(int defaultVolume = 100) - { - Volume = defaultVolume; - _filterStage = new FilterStage(); - } + public SoundOutput(int defaultVolume = 100) => Volume = defaultVolume; /// /// Initialize the output with a given image /// - /// OpticalDisc to load from - /// RepeatMode for sound output + /// ReadFunction to use during decoding /// True if playback should begin immediately, false otherwise - public void Init(OpticalDiscBase opticalDisc, RepeatMode repeatMode, bool autoPlay) + public void Init(PlayerSource.ReadFunction read, bool autoPlay) { - // If we have an unusable disc, just return - 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) - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - - // Setup de-emphasis filters - _filterStage.SetupFilters(); + // Reset initialization + Initialized = false; // Setup the audio output - SetupAudio(); - - // Setup the repeat mode - RepeatMode = repeatMode; + SetupAudio(read); // Initialize playback, if necessary if(autoPlay) @@ -166,61 +100,10 @@ namespace RedBookPlayer.Models.Hardware public void Reset() { _soundOut.Stop(); - _opticalDisc = null; Initialized = false; PlayerState = PlayerState.NoDisc; } - /// - /// Fill the current byte buffer with playable data - /// - /// Buffer to load data into - /// Offset in the buffer to load at - /// Number of bytes to load - /// Number of bytes read - public int ProviderRead(byte[] buffer, int offset, int count) - { - // Set the current volume - _soundOut.SetVolume((float)Volume / 100); - - // If we have an unreadable track, just return - if(_opticalDisc.BytesPerSector <= 0) - { - Array.Clear(buffer, offset, count); - return count; - } - - // Determine how many sectors we can read - DetermineReadAmount(count, out ulong sectorsToRead, out ulong zeroSectorsAmount); - - // Get data to return - byte[] audioDataSegment = ReadData(count, sectorsToRead, zeroSectorsAmount); - if(audioDataSegment == null) - { - Array.Clear(buffer, offset, count); - return count; - } - - // Write out the audio data to the buffer - Array.Copy(audioDataSegment, 0, buffer, offset, count); - - // Set the read position in the sector for easier access - _currentSectorReadPosition += count; - if(_currentSectorReadPosition >= _opticalDisc.BytesPerSector) - { - int currentTrack = _opticalDisc.CurrentTrackNumber; - _opticalDisc.SetCurrentSector(_opticalDisc.CurrentSector + (ulong)(_currentSectorReadPosition / _opticalDisc.BytesPerSector)); - _currentSectorReadPosition %= _opticalDisc.BytesPerSector; - - if(RepeatMode == RepeatMode.None && _opticalDisc.CurrentTrackNumber < currentTrack) - Stop(); - else if(RepeatMode == RepeatMode.Single && _opticalDisc.CurrentTrackNumber != currentTrack) - _opticalDisc.LoadTrack(currentTrack); - } - - return count; - } - #region Playback /// @@ -265,108 +148,25 @@ namespace RedBookPlayer.Models.Hardware #region Helpers - /// - /// Set de-emphasis status - /// - /// New de-emphasis status - public void SetDeEmphasis(bool apply) => ApplyDeEmphasis = apply; - - /// - /// Set repeat mode - /// - /// New repeat mode value - public void SetRepeatMode(RepeatMode repeatMode) => RepeatMode = repeatMode; - /// /// Set the value for the volume /// /// New volume value - public void SetVolume(int volume) => Volume = volume; - - /// - /// Determine the number of real and zero sectors to read - /// - /// Number of requested bytes to read - /// Number of sectors to read - /// Number of zeroed sectors to concatenate - private void DetermineReadAmount(int count, out ulong sectorsToRead, out ulong zeroSectorsAmount) + public void SetVolume(int volume) { - // Attempt to read 10 more sectors than requested - sectorsToRead = ((ulong)count / (ulong)_opticalDisc.BytesPerSector) + 10; - zeroSectorsAmount = 0; - - // Avoid overreads by padding with 0-byte data at the end - if(_opticalDisc.CurrentSector + sectorsToRead > _opticalDisc.TotalSectors) - { - ulong oldSectorsToRead = sectorsToRead; - sectorsToRead = _opticalDisc.TotalSectors - _opticalDisc.CurrentSector; - - int tempZeroSectorCount = (int)(oldSectorsToRead - sectorsToRead); - zeroSectorsAmount = (ulong)(tempZeroSectorCount < 0 ? 0 : tempZeroSectorCount); - } - } - - /// - /// Read the requested amount of data from an input - /// - /// Number of bytes to load - /// Number of sectors to read - /// Number of zeroed sectors to concatenate - /// The requested amount of data, if possible - private byte[] ReadData(int count, ulong sectorsToRead, ulong zeroSectorsAmount) - { - // Create padding data for overreads - byte[] zeroSectors = new byte[(int)zeroSectorsAmount * _opticalDisc.BytesPerSector]; - byte[] audioData; - - // Attempt to read the required number of sectors - var readSectorTask = Task.Run(() => - { - lock(_readingImage) - { - for(int i = 0; i < 4; i++) - { - try - { - return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); - } - catch { } - } - - return zeroSectors; - } - }); - - // Wait 100ms at longest for the read to occur - if(readSectorTask.Wait(TimeSpan.FromMilliseconds(100))) - audioData = readSectorTask.Result; - else - return null; - - // Load only the requested audio segment - byte[] audioDataSegment = new byte[count]; - int copyAmount = Math.Min(count, audioData.Length - _currentSectorReadPosition); - if(Math.Max(0, copyAmount) == 0) - return null; - - Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, copyAmount); - - // Apply de-emphasis filtering, only if enabled - if(ApplyDeEmphasis) - _filterStage.ProcessAudioData(audioDataSegment); - - return audioDataSegment; + Volume = volume; + _soundOut.SetVolume((float)Volume / 100); } /// /// Sets or resets the audio playback objects /// - private void SetupAudio() + /// ReadFunction to use during decoding + private void SetupAudio(PlayerSource.ReadFunction read) { if(_source == null) { - _source = new PlayerSource(ProviderRead); - + _source = new PlayerSource(read); if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) _soundOut = new Linux.AudioBackend(_source); else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs index d378429..c55b0ff 100644 --- a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs +++ b/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs @@ -7,7 +7,7 @@ namespace RedBookPlayer.Models.Hardware.Windows /// /// Sound output instance /// - private ALSoundOut _soundOut; + private readonly ALSoundOut _soundOut; public AudioBackend() { } From 38aa2ba53cc0e4041545f52dfe9c5ec3bb946a9c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 22:02:10 -0700 Subject: [PATCH 13/46] Fix track wraparound and playback --- .../ViewModels/PlayerViewModel.cs | 10 +++- RedBookPlayer.Models/Enums.cs | 1 - RedBookPlayer.Models/Hardware/Player.cs | 59 ++++++++++++++++++- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index aa8013f..4f7afd3 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -17,7 +17,6 @@ using RedBookPlayer.Models.Hardware; namespace RedBookPlayer.GUI.ViewModels { - // TODO: Add direct index selection by number public class PlayerViewModel : ReactiveObject { /// @@ -706,11 +705,20 @@ namespace RedBookPlayer.GUI.ViewModels /// /// Select a particular disc by number /// + /// Disc number to attempt to load public void SelectDisc(int discNumber) => _player?.SelectDisc(discNumber); + /// + /// Select a particular index by number + /// + /// Track index to attempt to load + /// True if index changes can trigger a track change, false otherwise + public void SelectIndex(ushort index, bool changeTrack) => _player?.SelectIndex(index, changeTrack); + /// /// Select a particular track by number /// + /// Track number to attempt to load public void SelectTrack(int trackNumber) => _player?.SelectTrack(trackNumber); /// diff --git a/RedBookPlayer.Models/Enums.cs b/RedBookPlayer.Models/Enums.cs index 472267f..a94aae9 100644 --- a/RedBookPlayer.Models/Enums.cs +++ b/RedBookPlayer.Models/Enums.cs @@ -50,7 +50,6 @@ namespace RedBookPlayer.Models /// /// Playback repeat mode /// - /// TODO: Add cross-disc repeat public enum RepeatMode { /// diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 684d421..4e26d8c 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -11,7 +11,6 @@ using RedBookPlayer.Models.Factories; namespace RedBookPlayer.Models.Hardware { - // TODO: Add direct index selection by number public class Player : ReactiveObject { /// @@ -694,7 +693,6 @@ namespace RedBookPlayer.Models.Hardware /// Select a track by number /// /// Track number to attempt to load - /// Changing track with RepeatMode.AllMultiDisc should switch discs public void SelectTrack(int trackNumber) { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) @@ -711,6 +709,38 @@ namespace RedBookPlayer.Models.Hardware int cachedValue = trackNumber; int cachedTrackNumber; + // Take care of disc switching first + if(RepeatMode == RepeatMode.AllMultiDisc) + { + if(trackNumber > (int)compactDisc.Tracks.Max(t => t.TrackSequence)) + { + do + { + NextDisc(); + } + while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); + + if(wasPlaying == PlayerState.Playing) + Play(); + + return; + } + else if((trackNumber < 1 && !LoadHiddenTracks) || (trackNumber < (int)compactDisc.Tracks.Min(t => t.TrackSequence))) + { + do + { + PreviousDisc(); + } + while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); + + SelectTrack(-1); + if(wasPlaying == PlayerState.Playing) + Play(); + + return; + } + } + // If we have an invalid current track number, set it to the minimum if(!compactDisc.Tracks.Any(t => t.TrackSequence == _currentTrackNumber)) _currentTrackNumber = (int)compactDisc.Tracks.Min(t => t.TrackSequence); @@ -769,9 +799,32 @@ namespace RedBookPlayer.Models.Hardware else { if(trackNumber >= _opticalDiscs[CurrentDisc].TotalTracks) + { + if(RepeatMode == RepeatMode.AllMultiDisc) + { + do + { + NextDisc(); + } + while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); + } + trackNumber = 1; + } else if(trackNumber < 1) + { + if(RepeatMode == RepeatMode.AllMultiDisc) + { + do + { + PreviousDisc(); + } + while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); + trackNumber = 1; + } + trackNumber = _opticalDiscs[CurrentDisc].TotalTracks - 1; + } _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); } @@ -1040,7 +1093,7 @@ namespace RedBookPlayer.Models.Hardware { NextDisc(); } - while(_opticalDiscs[CurrentDisc] != null && !_opticalDiscs[CurrentDisc].Initialized); + while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); SelectTrack(1); break; From 0b63ebf18bd371daa81bcf6b1a71db915f9f51e7 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 22:11:59 -0700 Subject: [PATCH 14/46] Wire up disc changer to UI --- RedBookPlayer.GUI/Views/SettingsWindow.xaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/RedBookPlayer.GUI/Views/SettingsWindow.xaml b/RedBookPlayer.GUI/Views/SettingsWindow.xaml index 8c42316..dad4f79 100644 --- a/RedBookPlayer.GUI/Views/SettingsWindow.xaml +++ b/RedBookPlayer.GUI/Views/SettingsWindow.xaml @@ -26,20 +26,25 @@ Play hidden tracks - Data Track Playback - Data Track Playback + - Session Handling - Session Handling + - Repeat Mode - Repeat Mode + + + Discs in Changer + + Generate a TOC if the disc is missing one From 05875b919c77d68fcd0db247e202bfd22273e4bf Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 5 Oct 2021 22:52:12 -0700 Subject: [PATCH 15/46] Separate out disc handling --- .../ViewModels/PlayerViewModel.cs | 8 ++ .../ViewModels/SettingsViewModel.cs | 18 ++- RedBookPlayer.GUI/Views/SettingsWindow.xaml | 5 + RedBookPlayer.Models/Enums.cs | 26 +++- RedBookPlayer.Models/Hardware/Player.cs | 131 ++++++++++-------- .../Hardware/PlayerOptions.cs | 5 + 6 files changed, 130 insertions(+), 63 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 4f7afd3..f8a7b31 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -656,6 +656,7 @@ namespace RedBookPlayer.GUI.ViewModels PlayerOptions playerOptions = new PlayerOptions { DataPlayback = App.Settings.DataPlayback, + DiscHandling = App.Settings.DiscHandling, LoadHiddenTracks = App.Settings.PlayHiddenTracks, RepeatMode = App.Settings.RepeatMode, SessionHandling = App.Settings.SessionHandling, @@ -684,6 +685,7 @@ namespace RedBookPlayer.GUI.ViewModels public void RefreshFromSettings() { SetDataPlayback(App.Settings.DataPlayback); + SetDiscHandling(App.Settings.DiscHandling); SetLoadHiddenTracks(App.Settings.PlayHiddenTracks); SetRepeatMode(App.Settings.RepeatMode); SetSessionHandling(App.Settings.SessionHandling); @@ -727,6 +729,12 @@ namespace RedBookPlayer.GUI.ViewModels /// New playback value public void SetDataPlayback(DataPlayback dataPlayback) => _player?.SetDataPlayback(dataPlayback); + /// + /// Set disc handling method + /// + /// New playback value + public void SetDiscHandling(DiscHandling discHandling) => _player?.SetDiscHandling(discHandling); + /// /// Set the value for loading hidden tracks [CompactDisc only] /// diff --git a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs index 178273f..e51125d 100644 --- a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs @@ -21,6 +21,12 @@ namespace RedBookPlayer.GUI.ViewModels [JsonIgnore] public List DataPlaybackValues => GenerateDataPlaybackList(); + /// + /// List of all disc handling values + /// + [JsonIgnore] + public List DiscHandlingValues => GenerateDiscHandlingList(); + /// /// List of all repeat mode values /// @@ -49,6 +55,11 @@ namespace RedBookPlayer.GUI.ViewModels /// public int NumberOfDiscs { get; set; } = 1; + /// + /// Indicates how to deal with multiple discs + /// + public DiscHandling DiscHandling { get; set; } = DiscHandling.SingleDisc; + /// /// Indicates if an index change can trigger a track change /// @@ -77,7 +88,7 @@ namespace RedBookPlayer.GUI.ViewModels /// /// Indicates how to repeat tracks /// - public RepeatMode RepeatMode { get; set; } = RepeatMode.AllSingleDisc; + public RepeatMode RepeatMode { get; set; } = RepeatMode.All; /// /// Indicates how to handle tracks on different sessions @@ -290,6 +301,11 @@ namespace RedBookPlayer.GUI.ViewModels /// private List GenerateDataPlaybackList() => Enum.GetValues(typeof(DataPlayback)).Cast().ToList(); + /// + /// Generate the list of DiscHandling values + /// + private List GenerateDiscHandlingList() => Enum.GetValues(typeof(DiscHandling)).Cast().ToList(); + /// /// Generate the list of Key values /// diff --git a/RedBookPlayer.GUI/Views/SettingsWindow.xaml b/RedBookPlayer.GUI/Views/SettingsWindow.xaml index dad4f79..b348de8 100644 --- a/RedBookPlayer.GUI/Views/SettingsWindow.xaml +++ b/RedBookPlayer.GUI/Views/SettingsWindow.xaml @@ -45,6 +45,11 @@ + + Disc Handling + + Generate a TOC if the disc is missing one diff --git a/RedBookPlayer.Models/Enums.cs b/RedBookPlayer.Models/Enums.cs index a94aae9..5619e11 100644 --- a/RedBookPlayer.Models/Enums.cs +++ b/RedBookPlayer.Models/Enums.cs @@ -21,6 +21,23 @@ namespace RedBookPlayer.Models Play = 2, } + /// + /// Determine how to handle multiple discs + /// + /// Used with both repeat and shuffle + public enum DiscHandling + { + /// + /// Only deal with tracks on the current disc + /// + SingleDisc = 0, + + /// + /// Deal with tracks on all loaded discs + /// + MultiDisc = 1, + } + /// /// Current player state /// @@ -63,14 +80,9 @@ namespace RedBookPlayer.Models Single, /// - /// Repeat all tracks on a single disc + /// Repeat all tracks /// - AllSingleDisc, - - /// - /// Repeat all tracks on a multiple discs - /// - AllMultiDisc, + All, } /// diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 4e26d8c..eed7163 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -22,6 +22,8 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _initialized, value); } + #region Playback Passthrough + /// /// Currently selected disc /// @@ -40,6 +42,60 @@ namespace RedBookPlayer.Models.Hardware } } + /// + /// Indicates how to deal with multiple discs + /// + public DiscHandling DiscHandling + { + get => _discHandling; + private set => this.RaiseAndSetIfChanged(ref _discHandling, value); + } + + /// + /// Indicates how to handle playback of data tracks + /// + public DataPlayback DataPlayback + { + get => _dataPlayback; + private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value); + } + + /// + /// Indicate if hidden tracks should be loaded + /// + public bool LoadHiddenTracks + { + get => _loadHiddenTracks; + private set => this.RaiseAndSetIfChanged(ref _loadHiddenTracks, value); + } + + /// + /// Indicates the repeat mode + /// + public RepeatMode RepeatMode + { + get => _repeatMode; + private set => this.RaiseAndSetIfChanged(ref _repeatMode, value); + } + + /// + /// Indicates how tracks on different session should be handled + /// + public SessionHandling SessionHandling + { + get => _sessionHandling; + private set => this.RaiseAndSetIfChanged(ref _sessionHandling, value); + } + + /// + /// Indicates if de-emphasis should be applied + /// + public bool ApplyDeEmphasis + { + get => _applyDeEmphasis; + private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); + } + /// /// Should invoke playback mode changes /// @@ -52,8 +108,16 @@ namespace RedBookPlayer.Models.Hardware private bool _initialized; private int _numberOfDiscs; private int _currentDisc; + private DiscHandling _discHandling; + private bool _loadHiddenTracks; + private DataPlayback _dataPlayback; + private RepeatMode _repeatMode; + private SessionHandling _sessionHandling; + private bool _applyDeEmphasis; private bool _shouldInvokePlaybackModes; + #endregion + #region OpticalDisc Passthrough /// @@ -206,51 +270,6 @@ namespace RedBookPlayer.Models.Hardware private set => this.RaiseAndSetIfChanged(ref _playerState, value); } - /// - /// Indicates how to handle playback of data tracks - /// - public DataPlayback DataPlayback - { - get => _dataPlayback; - private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value); - } - - /// - /// Indicate if hidden tracks should be loaded - /// - public bool LoadHiddenTracks - { - get => _loadHiddenTracks; - private set => this.RaiseAndSetIfChanged(ref _loadHiddenTracks, value); - } - - /// - /// Indicates the repeat mode - /// - public RepeatMode RepeatMode - { - get => _repeatMode; - private set => this.RaiseAndSetIfChanged(ref _repeatMode, value); - } - - /// - /// Indicates how tracks on different session should be handled - /// - public SessionHandling SessionHandling - { - get => _sessionHandling; - private set => this.RaiseAndSetIfChanged(ref _sessionHandling, value); - } - - /// - /// Indicates if de-emphasis should be applied - /// - public bool ApplyDeEmphasis - { - get => _applyDeEmphasis; - private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); - } - /// /// Current playback volume /// @@ -261,11 +280,6 @@ namespace RedBookPlayer.Models.Hardware } private PlayerState _playerState; - private DataPlayback _dataPlayback; - private bool _loadHiddenTracks; - private RepeatMode _repeatMode; - private SessionHandling _sessionHandling; - private bool _applyDeEmphasis; private int _volume; #endregion @@ -340,6 +354,7 @@ namespace RedBookPlayer.Models.Hardware // Set player options DataPlayback = playerOptions.DataPlayback; + DiscHandling = playerOptions.DiscHandling; LoadHiddenTracks = playerOptions.LoadHiddenTracks; RepeatMode = playerOptions.RepeatMode; SessionHandling = playerOptions.SessionHandling; @@ -710,7 +725,7 @@ namespace RedBookPlayer.Models.Hardware int cachedTrackNumber; // Take care of disc switching first - if(RepeatMode == RepeatMode.AllMultiDisc) + if(DiscHandling == DiscHandling.MultiDisc) { if(trackNumber > (int)compactDisc.Tracks.Max(t => t.TrackSequence)) { @@ -800,7 +815,7 @@ namespace RedBookPlayer.Models.Hardware { if(trackNumber >= _opticalDiscs[CurrentDisc].TotalTracks) { - if(RepeatMode == RepeatMode.AllMultiDisc) + if(DiscHandling == DiscHandling.MultiDisc) { do { @@ -813,7 +828,7 @@ namespace RedBookPlayer.Models.Hardware } else if(trackNumber < 1) { - if(RepeatMode == RepeatMode.AllMultiDisc) + if(DiscHandling == DiscHandling.MultiDisc) { do { @@ -1043,6 +1058,12 @@ namespace RedBookPlayer.Models.Hardware /// New playback value public void SetDataPlayback(DataPlayback dataPlayback) => DataPlayback = dataPlayback; + /// + /// Set disc handling method + /// + /// New playback value + public void SetDiscHandling(DiscHandling discHandling) => DiscHandling = discHandling; + /// /// Set the value for loading hidden tracks [CompactDisc only] /// @@ -1085,10 +1106,10 @@ namespace RedBookPlayer.Models.Hardware case RepeatMode.Single: _opticalDiscs[CurrentDisc].LoadTrack(CurrentTrackNumber); break; - case RepeatMode.AllSingleDisc: + case RepeatMode.All when DiscHandling == DiscHandling.SingleDisc: SelectTrack(1); break; - case RepeatMode.AllMultiDisc: + case RepeatMode.All when DiscHandling == DiscHandling.MultiDisc: do { NextDisc(); diff --git a/RedBookPlayer.Models/Hardware/PlayerOptions.cs b/RedBookPlayer.Models/Hardware/PlayerOptions.cs index 43a0cbd..d65170d 100644 --- a/RedBookPlayer.Models/Hardware/PlayerOptions.cs +++ b/RedBookPlayer.Models/Hardware/PlayerOptions.cs @@ -7,6 +7,11 @@ namespace RedBookPlayer.Models.Discs /// public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip; + /// + /// Indicates how to deal with multiple discs + /// + public DiscHandling DiscHandling { get; set; } = DiscHandling.SingleDisc; + /// /// Indicate if hidden tracks should be loaded /// From 3fa0ddca17debb8e72cee121ed7be9c6ada1690b Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 09:38:46 -0700 Subject: [PATCH 16/46] Load track list (not hooked up) --- RedBookPlayer.Models/Hardware/Player.cs | 29 ++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index eed7163..517e51f 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; @@ -292,10 +293,15 @@ namespace RedBookPlayer.Models.Hardware private readonly SoundOutput _soundOutput; /// - /// OpticalDisc object + /// OpticalDisc objects /// private OpticalDiscBase[] _opticalDiscs; + /// + /// List of available tracks + /// + private Dictionary> _trackList; + /// /// Last volume for mute toggling /// @@ -337,6 +343,12 @@ namespace RedBookPlayer.Models.Hardware _filterStage = new FilterStage(); _soundOutput = new SoundOutput(defaultVolume); + _trackList = new Dictionary>(); + for(int i = 0; i < _numberOfDiscs; i++) + { + _trackList.Add(i, new List()); + } + PropertyChanged += HandlePlaybackModes; } @@ -378,6 +390,9 @@ namespace RedBookPlayer.Models.Hardware // Add event handling for the sound output _soundOutput.PropertyChanged += SoundOutputStateChanged; + // Load in the track list for the current disc + LoadTrackList(); + // Mark the player as ready Initialized = true; @@ -386,6 +401,18 @@ namespace RedBookPlayer.Models.Hardware SoundOutputStateChanged(this, null); } + /// + /// Load the track list into the track dictionary for the current disc + /// + private void LoadTrackList() + { + OpticalDiscBase opticalDisc = _opticalDiscs[CurrentDisc]; + if (opticalDisc is CompactDisc compactDisc) + _trackList[CurrentDisc] = compactDisc.Tracks.Select(t => (int)t.TrackSequence).OrderBy(s => s).ToList(); + else + _trackList[CurrentDisc] = Enumerable.Range(1, opticalDisc.TotalTracks).ToList(); + } + #region Playback (UI) /// From ef95acef8f29531e82e8f59ae4a0fed0735f1be1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 09:53:07 -0700 Subject: [PATCH 17/46] Add playback order (nw); fix eject --- RedBookPlayer.Models/Hardware/Player.cs | 58 ++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 517e51f..6f208da 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -298,9 +298,14 @@ namespace RedBookPlayer.Models.Hardware private OpticalDiscBase[] _opticalDiscs; /// - /// List of available tracks + /// List of available tracks organized by disc /// - private Dictionary> _trackList; + private Dictionary> _availableTrackList; + + /// + /// Current track playback order + /// + private List> _trackPlaybackOrder; /// /// Last volume for mute toggling @@ -343,12 +348,14 @@ namespace RedBookPlayer.Models.Hardware _filterStage = new FilterStage(); _soundOutput = new SoundOutput(defaultVolume); - _trackList = new Dictionary>(); + _availableTrackList = new Dictionary>(); for(int i = 0; i < _numberOfDiscs; i++) { - _trackList.Add(i, new List()); + _availableTrackList.Add(i, new List()); } + _trackPlaybackOrder = new List>(); + PropertyChanged += HandlePlaybackModes; } @@ -407,10 +414,31 @@ namespace RedBookPlayer.Models.Hardware private void LoadTrackList() { OpticalDiscBase opticalDisc = _opticalDiscs[CurrentDisc]; - if (opticalDisc is CompactDisc compactDisc) - _trackList[CurrentDisc] = compactDisc.Tracks.Select(t => (int)t.TrackSequence).OrderBy(s => s).ToList(); + + // If the disc exists, add it to the dictionary + if(_opticalDiscs[CurrentDisc] != null) + { + if (opticalDisc is CompactDisc compactDisc) + _availableTrackList[CurrentDisc] = compactDisc.Tracks.Select(t => (int)t.TrackSequence).OrderBy(s => s).ToList(); + else + _availableTrackList[CurrentDisc] = Enumerable.Range(1, opticalDisc.TotalTracks).ToList(); + } + + // If the disc is null, then make sure it's removed else - _trackList[CurrentDisc] = Enumerable.Range(1, opticalDisc.TotalTracks).ToList(); + { + _availableTrackList[CurrentDisc] = new List(); + } + + // Loop through the dictionary and repopulate the playback order + for(int i = 0; i < _numberOfDiscs; i++) + { + if(_availableTrackList[i] == null || _availableTrackList[i].Count == 0) + continue; + + List availableTracks = _availableTrackList[i]; + _trackPlaybackOrder.AddRange(availableTracks.Select(t => new KeyValuePair(i, t))); + } } #region Playback (UI) @@ -498,10 +526,20 @@ namespace RedBookPlayer.Models.Hardware return; Stop(); - _soundOutput.Eject(); _opticalDiscs[CurrentDisc] = null; - PlayerState = PlayerState.NoDisc; - Initialized = false; + LoadTrackList(); + + // Only de-initialize the player if all discs are ejected + if(_opticalDiscs.All(d => d == null || !d.Initialized)) + { + _soundOutput.Eject(); + PlayerState = PlayerState.NoDisc; + Initialized = false; + } + else + { + PlayerState = PlayerState.Stopped; + } } /// From 847205923245e08a5ee4ddf0ca7f04ccaea4be2d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 10:03:25 -0700 Subject: [PATCH 18/46] Fix playback order; add shuffle method --- RedBookPlayer.Models/Hardware/Player.cs | 48 ++++++++++++++++++++----- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 6f208da..360ace7 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -418,7 +418,7 @@ namespace RedBookPlayer.Models.Hardware // If the disc exists, add it to the dictionary if(_opticalDiscs[CurrentDisc] != null) { - if (opticalDisc is CompactDisc compactDisc) + if(opticalDisc is CompactDisc compactDisc) _availableTrackList[CurrentDisc] = compactDisc.Tracks.Select(t => (int)t.TrackSequence).OrderBy(s => s).ToList(); else _availableTrackList[CurrentDisc] = Enumerable.Range(1, opticalDisc.TotalTracks).ToList(); @@ -430,14 +430,22 @@ namespace RedBookPlayer.Models.Hardware _availableTrackList[CurrentDisc] = new List(); } - // Loop through the dictionary and repopulate the playback order - for(int i = 0; i < _numberOfDiscs; i++) + // Repopulate the playback order + _trackPlaybackOrder = new List>(); + if(DiscHandling == DiscHandling.SingleDisc) { - if(_availableTrackList[i] == null || _availableTrackList[i].Count == 0) - continue; - - List availableTracks = _availableTrackList[i]; - _trackPlaybackOrder.AddRange(availableTracks.Select(t => new KeyValuePair(i, t))); + List availableTracks = _availableTrackList[CurrentDisc]; + if(availableTracks != null && availableTracks.Count > 0) + _trackPlaybackOrder.AddRange(availableTracks.Select(t => new KeyValuePair(CurrentDisc, t))); + } + else if(DiscHandling == DiscHandling.MultiDisc) + { + for(int i = 0; i < _numberOfDiscs; i++) + { + List availableTracks = _availableTrackList[i]; + if(availableTracks != null && availableTracks.Count > 0) + _trackPlaybackOrder.AddRange(availableTracks.Select(t => new KeyValuePair(i, t))); + } } } @@ -497,6 +505,24 @@ namespace RedBookPlayer.Models.Hardware } } + /// + /// Shuffle the current track order + /// + public void ShuffleTracks() + { + List> newPlaybackOrder = new List>(); + Random random = new Random(); + + while(_trackPlaybackOrder.Count > 0) + { + int next = random.Next(0, _trackPlaybackOrder.Count - 1); + newPlaybackOrder.Add(_trackPlaybackOrder[next]); + _trackPlaybackOrder.RemoveAt(next); + } + + _trackPlaybackOrder = newPlaybackOrder; + } + /// /// Stop current playback /// @@ -1127,7 +1153,11 @@ namespace RedBookPlayer.Models.Hardware /// Set disc handling method /// /// New playback value - public void SetDiscHandling(DiscHandling discHandling) => DiscHandling = discHandling; + public void SetDiscHandling(DiscHandling discHandling) + { + DiscHandling = discHandling; + LoadTrackList(); + } /// /// Set the value for loading hidden tracks [CompactDisc only] From 879154b0f4b0fdcb646aa8997e1cfd97544fd593 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 10:16:44 -0700 Subject: [PATCH 19/46] Current track order? --- RedBookPlayer.Models/Hardware/Player.cs | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 360ace7..712c2aa 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -307,6 +307,11 @@ namespace RedBookPlayer.Models.Hardware /// private List> _trackPlaybackOrder; + /// + /// Current track in playback order list + /// + private int _currentTrackInOrder; + /// /// Last volume for mute toggling /// @@ -355,6 +360,7 @@ namespace RedBookPlayer.Models.Hardware } _trackPlaybackOrder = new List>(); + _currentTrackInOrder = 0; PropertyChanged += HandlePlaybackModes; } @@ -447,6 +453,26 @@ namespace RedBookPlayer.Models.Hardware _trackPlaybackOrder.AddRange(availableTracks.Select(t => new KeyValuePair(i, t))); } } + + // Try to get back to the last loaded track + int currentFoundTrack = 0; + if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) + { + currentFoundTrack = 0; + } + else if(_trackPlaybackOrder.Any(kvp => kvp.Key == CurrentDisc)) + { + currentFoundTrack = _trackPlaybackOrder.FindIndex(kvp => kvp.Key == CurrentDisc && kvp.Value == CurrentTrackNumber); + if(currentFoundTrack == -1) + currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == CurrentDisc).Min(kvp => kvp.Value); + } + else + { + int lowestDiscNumber = _trackPlaybackOrder.Min(kvp => kvp.Key); + currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == lowestDiscNumber).Min(kvp => kvp.Value); + } + + _currentTrackInOrder = currentFoundTrack; } #region Playback (UI) @@ -581,11 +607,13 @@ namespace RedBookPlayer.Models.Hardware /// /// Move to the next playable track /// + /// This should follow the track playback order public void NextTrack() => SelectTrack(CurrentTrackNumber + 1); /// /// Move to the previous playable track /// + /// This should follow the track playback order public void PreviousTrack() => SelectTrack(CurrentTrackNumber - 1); /// From a83549824db75236e5f31beeec9bf153f2761e51 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 10:24:37 -0700 Subject: [PATCH 20/46] Add implementation notes --- RedBookPlayer.Models/Hardware/Player.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 712c2aa..d2da495 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -607,13 +607,13 @@ namespace RedBookPlayer.Models.Hardware /// /// Move to the next playable track /// - /// This should follow the track playback order + /// TODO: This should follow the track playback order public void NextTrack() => SelectTrack(CurrentTrackNumber + 1); /// /// Move to the previous playable track /// - /// This should follow the track playback order + /// TODO: This should follow the track playback order public void PreviousTrack() => SelectTrack(CurrentTrackNumber - 1); /// @@ -727,6 +727,7 @@ namespace RedBookPlayer.Models.Hardware /// Select a disc by number /// /// Disc number to attempt to load + /// TODO: This needs to reset the pointer in the track playback order public void SelectDisc(int discNumber) { PlayerState wasPlaying = PlayerState; @@ -827,6 +828,8 @@ namespace RedBookPlayer.Models.Hardware /// Select a track by number /// /// Track number to attempt to load + /// TODO: This needs to reset the pointer in the track playback order + /// TODO: There needs to be a SelectRelativeTrack variant that follows order and then invokes this public void SelectTrack(int trackNumber) { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) From f5127ee35122696f1a1edd290b4da91548c46080 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 11:01:07 -0700 Subject: [PATCH 21/46] Create SelectRelativeTrack (unused) --- RedBookPlayer.Models/Hardware/Player.cs | 46 +++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index d2da495..89cecb0 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -828,12 +828,12 @@ namespace RedBookPlayer.Models.Hardware /// Select a track by number /// /// Track number to attempt to load + /// True if the track was changed, false otherwise /// TODO: This needs to reset the pointer in the track playback order - /// TODO: There needs to be a SelectRelativeTrack variant that follows order and then invokes this - public void SelectTrack(int trackNumber) + public bool SelectTrack(int trackNumber) { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return; + return false; PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) @@ -860,7 +860,7 @@ namespace RedBookPlayer.Models.Hardware if(wasPlaying == PlayerState.Playing) Play(); - return; + return true; } else if((trackNumber < 1 && !LoadHiddenTracks) || (trackNumber < (int)compactDisc.Tracks.Min(t => t.TrackSequence))) { @@ -874,7 +874,7 @@ namespace RedBookPlayer.Models.Hardware if(wasPlaying == PlayerState.Playing) Play(); - return; + return true; } } @@ -912,7 +912,7 @@ namespace RedBookPlayer.Models.Hardware // Cache the current track for easy access Track track = compactDisc.GetTrack(cachedTrackNumber); if(track == null) - return; + return false; // If the track is playable, just return if((track.TrackType == TrackType.Audio || DataPlayback != DataPlayback.Skip) @@ -966,6 +966,40 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); } + if(wasPlaying == PlayerState.Playing) + Play(); + + return true; + } + + /// + /// Select a track in the relative track list by number + /// + /// Relative track number to attempt to load + public void SelectRelativeTrack(int relativeTrackNumber) + { + if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) + return; + + PlayerState wasPlaying = PlayerState; + if(wasPlaying == PlayerState.Playing) + Pause(); + + if(relativeTrackNumber < 0) + relativeTrackNumber = _trackPlaybackOrder.Count - 1; + else if(relativeTrackNumber >= _trackPlaybackOrder.Count) + relativeTrackNumber = 0; + + do + { + _currentTrackInOrder = relativeTrackNumber; + KeyValuePair discTrackPair = _trackPlaybackOrder[relativeTrackNumber]; + SelectDisc(discTrackPair.Key); + if(SelectTrack(discTrackPair.Value)) + break; + } + while(true); + if(wasPlaying == PlayerState.Playing) Play(); } From de226a816a88e5d54a79d729a71cc0446861f88d Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 11:09:14 -0700 Subject: [PATCH 22/46] Add by-disc track selection --- RedBookPlayer.Models/Hardware/Player.cs | 96 ++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 89cecb0..fef2b22 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -829,7 +829,6 @@ namespace RedBookPlayer.Models.Hardware /// /// Track number to attempt to load /// True if the track was changed, false otherwise - /// TODO: This needs to reset the pointer in the track playback order public bool SelectTrack(int trackNumber) { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) @@ -972,6 +971,99 @@ namespace RedBookPlayer.Models.Hardware return true; } + /// + /// Select a track by number within a disc + /// + /// Track number to attempt to load + /// True if the track was changed, false otherwise + public bool SelectTrackWithinDisc(int trackNumber) + { + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) + return false; + + PlayerState wasPlaying = PlayerState; + if(wasPlaying == PlayerState.Playing) + Pause(); + + // CompactDisc needs special handling of track wraparound + if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + { + // Cache the value and the current track number + int cachedValue = trackNumber; + int cachedTrackNumber; + + // If we have an invalid current track number, set it to the minimum + if(!compactDisc.Tracks.Any(t => t.TrackSequence == _currentTrackNumber)) + _currentTrackNumber = (int)compactDisc.Tracks.Min(t => t.TrackSequence); + + // Check if we're incrementing or decrementing the track + bool increment = cachedValue >= _currentTrackNumber; + + do + { + // If we're over the last track, wrap around + if(cachedValue > compactDisc.Tracks.Max(t => t.TrackSequence)) + { + cachedValue = (int)compactDisc.Tracks.Min(t => t.TrackSequence); + if(cachedValue == 0 && !LoadHiddenTracks) + cachedValue++; + } + + // If we're under the first track and we're not loading hidden tracks, wrap around + else if(cachedValue < 1 && !LoadHiddenTracks) + { + cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); + } + + // If we're under the first valid track, wrap around + else if(cachedValue < compactDisc.Tracks.Min(t => t.TrackSequence)) + { + cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); + } + + cachedTrackNumber = cachedValue; + + // Cache the current track for easy access + Track track = compactDisc.GetTrack(cachedTrackNumber); + if(track == null) + return false; + + // If the track is playable, just return + if((track.TrackType == TrackType.Audio || DataPlayback != DataPlayback.Skip) + && (SessionHandling == SessionHandling.AllSessions || track.TrackSession == 1)) + { + break; + } + + // If we're not playing the track, skip + if(increment) + cachedValue++; + else + cachedValue--; + } + while(cachedValue != _currentTrackNumber); + + // Load the now-valid value + compactDisc.LoadTrack(cachedTrackNumber); + ApplyDeEmphasis = compactDisc.TrackHasEmphasis; + } + else + { + if(trackNumber >= _opticalDiscs[CurrentDisc].TotalTracks) + trackNumber = 1; + else if(trackNumber < 1) + trackNumber = _opticalDiscs[CurrentDisc].TotalTracks - 1; + + _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); + } + + LoadTrackList(); + if(wasPlaying == PlayerState.Playing) + Play(); + + return true; + } + /// /// Select a track in the relative track list by number /// @@ -995,7 +1087,7 @@ namespace RedBookPlayer.Models.Hardware _currentTrackInOrder = relativeTrackNumber; KeyValuePair discTrackPair = _trackPlaybackOrder[relativeTrackNumber]; SelectDisc(discTrackPair.Key); - if(SelectTrack(discTrackPair.Value)) + if(SelectTrackWithinDisc(discTrackPair.Value)) break; } while(true); From 9c63904245585bbba5feec33bc25e27d050a4f78 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 11:33:49 -0700 Subject: [PATCH 23/46] Use relative track locations --- RedBookPlayer.Models/Hardware/Player.cs | 256 ++++++------------------ 1 file changed, 60 insertions(+), 196 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index fef2b22..73d8e70 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -465,11 +465,17 @@ namespace RedBookPlayer.Models.Hardware currentFoundTrack = _trackPlaybackOrder.FindIndex(kvp => kvp.Key == CurrentDisc && kvp.Value == CurrentTrackNumber); if(currentFoundTrack == -1) currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == CurrentDisc).Min(kvp => kvp.Value); + + CurrentDisc = _trackPlaybackOrder[currentFoundTrack].Key; + CurrentTrackNumber = _trackPlaybackOrder[currentFoundTrack].Value; } else { int lowestDiscNumber = _trackPlaybackOrder.Min(kvp => kvp.Key); currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == lowestDiscNumber).Min(kvp => kvp.Value); + + CurrentDisc = _trackPlaybackOrder[currentFoundTrack].Key; + CurrentTrackNumber = _trackPlaybackOrder[currentFoundTrack].Value; } _currentTrackInOrder = currentFoundTrack; @@ -562,8 +568,7 @@ namespace RedBookPlayer.Models.Hardware return; _soundOutput.Stop(); - CurrentTrackNumber = 0; - SelectTrack(1); + SelectRelativeTrack(0); PlayerState = PlayerState.Stopped; } @@ -581,6 +586,10 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc] = null; LoadTrackList(); + // Force a refresh of the state information + OpticalDiscStateChanged(this, null); + SoundOutputStateChanged(this, null); + // Only de-initialize the player if all discs are ejected if(_opticalDiscs.All(d => d == null || !d.Initialized)) { @@ -608,13 +617,13 @@ namespace RedBookPlayer.Models.Hardware /// Move to the next playable track /// /// TODO: This should follow the track playback order - public void NextTrack() => SelectTrack(CurrentTrackNumber + 1); + public void NextTrack() => SelectRelativeTrack(_currentTrackInOrder + 1); /// /// Move to the previous playable track /// /// TODO: This should follow the track playback order - public void PreviousTrack() => SelectTrack(CurrentTrackNumber - 1); + public void PreviousTrack() => SelectRelativeTrack(_currentTrackInOrder - 1); /// /// Move to the next index @@ -695,6 +704,7 @@ namespace RedBookPlayer.Models.Hardware _currentSectorReadPosition += count; if(_currentSectorReadPosition >= _opticalDiscs[CurrentDisc].BytesPerSector) { + int previousTrack = CurrentTrackNumber; ulong newSectorValue = _opticalDiscs[CurrentDisc].CurrentSector + (ulong)(_currentSectorReadPosition / _opticalDiscs[CurrentDisc].BytesPerSector); if(newSectorValue >= _opticalDiscs[CurrentDisc].TotalSectors) { @@ -718,6 +728,10 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].SetCurrentSector(newSectorValue); _currentSectorReadPosition %= _opticalDiscs[CurrentDisc].BytesPerSector; } + + // If we are supposed to change tracks, get the next one from the list + if(CurrentTrackNumber != previousTrack) + NextTrack(); } return count; @@ -727,7 +741,6 @@ namespace RedBookPlayer.Models.Hardware /// Select a disc by number /// /// Disc number to attempt to load - /// TODO: This needs to reset the pointer in the track playback order public void SelectDisc(int discNumber) { PlayerState wasPlaying = PlayerState; @@ -740,6 +753,7 @@ namespace RedBookPlayer.Models.Hardware if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) { Initialized = true; + SelectTrack(1); OpticalDiscStateChanged(this, null); SoundOutputStateChanged(this, null); @@ -824,159 +838,12 @@ namespace RedBookPlayer.Models.Hardware Play(); } - /// - /// Select a track by number - /// - /// Track number to attempt to load - /// True if the track was changed, false otherwise - public bool SelectTrack(int trackNumber) - { - if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) - return false; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - // CompactDisc needs special handling of track wraparound - if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) - { - // Cache the value and the current track number - int cachedValue = trackNumber; - int cachedTrackNumber; - - // Take care of disc switching first - if(DiscHandling == DiscHandling.MultiDisc) - { - if(trackNumber > (int)compactDisc.Tracks.Max(t => t.TrackSequence)) - { - do - { - NextDisc(); - } - while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); - - if(wasPlaying == PlayerState.Playing) - Play(); - - return true; - } - else if((trackNumber < 1 && !LoadHiddenTracks) || (trackNumber < (int)compactDisc.Tracks.Min(t => t.TrackSequence))) - { - do - { - PreviousDisc(); - } - while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); - - SelectTrack(-1); - if(wasPlaying == PlayerState.Playing) - Play(); - - return true; - } - } - - // If we have an invalid current track number, set it to the minimum - if(!compactDisc.Tracks.Any(t => t.TrackSequence == _currentTrackNumber)) - _currentTrackNumber = (int)compactDisc.Tracks.Min(t => t.TrackSequence); - - // Check if we're incrementing or decrementing the track - bool increment = cachedValue >= _currentTrackNumber; - - do - { - // If we're over the last track, wrap around - if(cachedValue > compactDisc.Tracks.Max(t => t.TrackSequence)) - { - cachedValue = (int)compactDisc.Tracks.Min(t => t.TrackSequence); - if(cachedValue == 0 && !LoadHiddenTracks) - cachedValue++; - } - - // If we're under the first track and we're not loading hidden tracks, wrap around - else if(cachedValue < 1 && !LoadHiddenTracks) - { - cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); - } - - // If we're under the first valid track, wrap around - else if(cachedValue < compactDisc.Tracks.Min(t => t.TrackSequence)) - { - cachedValue = (int)compactDisc.Tracks.Max(t => t.TrackSequence); - } - - cachedTrackNumber = cachedValue; - - // Cache the current track for easy access - Track track = compactDisc.GetTrack(cachedTrackNumber); - if(track == null) - return false; - - // If the track is playable, just return - if((track.TrackType == TrackType.Audio || DataPlayback != DataPlayback.Skip) - && (SessionHandling == SessionHandling.AllSessions || track.TrackSession == 1)) - { - break; - } - - // If we're not playing the track, skip - if(increment) - cachedValue++; - else - cachedValue--; - } - while(cachedValue != _currentTrackNumber); - - // Load the now-valid value - compactDisc.LoadTrack(cachedTrackNumber); - ApplyDeEmphasis = compactDisc.TrackHasEmphasis; - } - else - { - if(trackNumber >= _opticalDiscs[CurrentDisc].TotalTracks) - { - if(DiscHandling == DiscHandling.MultiDisc) - { - do - { - NextDisc(); - } - while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); - } - - trackNumber = 1; - } - else if(trackNumber < 1) - { - if(DiscHandling == DiscHandling.MultiDisc) - { - do - { - PreviousDisc(); - } - while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); - trackNumber = 1; - } - - trackNumber = _opticalDiscs[CurrentDisc].TotalTracks - 1; - } - - _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); - } - - if(wasPlaying == PlayerState.Playing) - Play(); - - return true; - } - /// /// Select a track by number within a disc /// /// Track number to attempt to load /// True if the track was changed, false otherwise - public bool SelectTrackWithinDisc(int trackNumber) + public bool SelectTrack(int trackNumber) { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return false; @@ -1064,38 +931,6 @@ namespace RedBookPlayer.Models.Hardware return true; } - /// - /// Select a track in the relative track list by number - /// - /// Relative track number to attempt to load - public void SelectRelativeTrack(int relativeTrackNumber) - { - if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) - return; - - PlayerState wasPlaying = PlayerState; - if(wasPlaying == PlayerState.Playing) - Pause(); - - if(relativeTrackNumber < 0) - relativeTrackNumber = _trackPlaybackOrder.Count - 1; - else if(relativeTrackNumber >= _trackPlaybackOrder.Count) - relativeTrackNumber = 0; - - do - { - _currentTrackInOrder = relativeTrackNumber; - KeyValuePair discTrackPair = _trackPlaybackOrder[relativeTrackNumber]; - SelectDisc(discTrackPair.Key); - if(SelectTrackWithinDisc(discTrackPair.Value)) - break; - } - while(true); - - if(wasPlaying == PlayerState.Playing) - Play(); - } - /// /// Determine the number of real and zero sectors to read /// @@ -1175,6 +1010,44 @@ namespace RedBookPlayer.Models.Hardware return audioDataSegment; } + /// + /// Select a track in the relative track list by number + /// + /// Relative track number to attempt to load + private void SelectRelativeTrack(int relativeTrackNumber) + { + if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) + return; + + PlayerState wasPlaying = PlayerState; + if(wasPlaying == PlayerState.Playing) + Pause(); + + if(relativeTrackNumber < 0) + relativeTrackNumber = _trackPlaybackOrder.Count - 1; + else if(relativeTrackNumber >= _trackPlaybackOrder.Count) + relativeTrackNumber = 0; + + do + { + _currentTrackInOrder = relativeTrackNumber; + KeyValuePair discTrackPair = _trackPlaybackOrder[relativeTrackNumber]; + SelectDisc(discTrackPair.Key); + if(SelectTrack(discTrackPair.Value)) + break; + + relativeTrackNumber++; + if(relativeTrackNumber < 0) + relativeTrackNumber = _trackPlaybackOrder.Count - 1; + else if(relativeTrackNumber >= _trackPlaybackOrder.Count) + relativeTrackNumber = 0; + } + while(true); + + if(wasPlaying == PlayerState.Playing) + Play(); + } + #endregion #region Volume @@ -1358,17 +1231,8 @@ namespace RedBookPlayer.Models.Hardware case RepeatMode.Single: _opticalDiscs[CurrentDisc].LoadTrack(CurrentTrackNumber); break; - case RepeatMode.All when DiscHandling == DiscHandling.SingleDisc: - SelectTrack(1); - break; - case RepeatMode.All when DiscHandling == DiscHandling.MultiDisc: - do - { - NextDisc(); - } - while(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized); - - SelectTrack(1); + case RepeatMode.All: + SelectRelativeTrack(0); break; } From b789b5cf477f58f66bff7fe5741051ff490fc824 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 11:51:37 -0700 Subject: [PATCH 24/46] Fix repeat mode handling --- RedBookPlayer.Models/Hardware/Player.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 73d8e70..b5fdf30 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -730,7 +730,7 @@ namespace RedBookPlayer.Models.Hardware } // If we are supposed to change tracks, get the next one from the list - if(CurrentTrackNumber != previousTrack) + if(CurrentTrackNumber != previousTrack && !ShouldInvokePlaybackModes) NextTrack(); } @@ -1219,20 +1219,20 @@ namespace RedBookPlayer.Models.Hardware if(e.PropertyName != nameof(ShouldInvokePlaybackModes)) return; - // Always stop before doing anything else + // Always pause before doing anything else PlayerState wasPlaying = PlayerState; - await Dispatcher.UIThread.InvokeAsync(Stop); + await Dispatcher.UIThread.InvokeAsync(Pause); switch(RepeatMode) { case RepeatMode.None: - // No-op + await Dispatcher.UIThread.InvokeAsync(Stop); break; case RepeatMode.Single: _opticalDiscs[CurrentDisc].LoadTrack(CurrentTrackNumber); break; case RepeatMode.All: - SelectRelativeTrack(0); + NextTrack(); break; } From 08548265836ac1b727f0bf5fc46f40f12988338f Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 12:11:55 -0700 Subject: [PATCH 25/46] Implement shuffle (not surfaced) --- RedBookPlayer.GUI/ViewModels/MainViewModel.cs | 6 +++++ .../ViewModels/PlayerViewModel.cs | 5 +++++ .../ViewModels/SettingsViewModel.cs | 5 +++++ RedBookPlayer.Models/Hardware/Player.cs | 22 +++++++++++++++++-- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs index 79a8ef0..17172c8 100644 --- a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs @@ -100,6 +100,12 @@ namespace RedBookPlayer.GUI.ViewModels PlayerView?.ViewModel?.ExecutePreviousTrack(); } + // Shuffle Track List + // else if(e.Key == App.Settings.ShuffleTracksKey) + // { + // PlayerView?.ViewModel?.ExecuteShuffle(); + // } + // Next Index else if(e.Key == App.Settings.NextIndexKey) { diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index f8a7b31..45a9d08 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -470,6 +470,11 @@ namespace RedBookPlayer.GUI.ViewModels /// public void ExecutePreviousTrack() => _player?.PreviousTrack(); + /// + /// Shuffle the current track list + /// + public void ExecuteShuffle() => _player?.ShuffleTracks(); + /// /// Move to the next index /// diff --git a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs index e51125d..4754fb9 100644 --- a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs @@ -180,6 +180,11 @@ namespace RedBookPlayer.GUI.ViewModels /// public Key PreviousTrackKey { get; set; } = Key.Left; + /// + /// Key assigned to shuffling the track list + /// + // public Key ShuffleTracksKey { get; set; } = Key.R; + /// /// Key assigned to move to the next index /// diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index b5fdf30..bd3ce3a 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -415,7 +415,7 @@ namespace RedBookPlayer.Models.Hardware } /// - /// Load the track list into the track dictionary for the current disc + /// Load the track list into the track dictionary /// private void LoadTrackList() { @@ -455,6 +455,14 @@ namespace RedBookPlayer.Models.Hardware } // Try to get back to the last loaded track + SetTrackOrderIndex(); + } + + /// + /// Set the current track order index, if possible + /// + private void SetTrackOrderIndex() + { int currentFoundTrack = 0; if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) { @@ -553,6 +561,16 @@ namespace RedBookPlayer.Models.Hardware } _trackPlaybackOrder = newPlaybackOrder; + switch(PlayerState) + { + case PlayerState.Stopped: + _currentTrackInOrder = 0; + break; + case PlayerState.Paused: + case PlayerState.Playing: + SetTrackOrderIndex(); + break; + } } /// @@ -924,7 +942,7 @@ namespace RedBookPlayer.Models.Hardware _opticalDiscs[CurrentDisc].LoadTrack(trackNumber); } - LoadTrackList(); + SetTrackOrderIndex(); if(wasPlaying == PlayerState.Playing) Play(); From d8ae23441d3721793ebbc925b290322a5418c8b1 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 12:49:40 -0700 Subject: [PATCH 26/46] Fix disc changing, especially in single-disc mode --- RedBookPlayer.Models/Hardware/Player.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index bd3ce3a..12377df 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -765,9 +765,19 @@ namespace RedBookPlayer.Models.Hardware if (wasPlaying == PlayerState.Playing) Stop(); + if(discNumber >= _numberOfDiscs) + discNumber = 0; + else if(discNumber < 0) + discNumber = _numberOfDiscs - 1; + _currentSectorReadPosition = 0; CurrentDisc = discNumber; + + // If we're in single disc mode, we need to reload the full track list + if(DiscHandling == DiscHandling.SingleDisc) + LoadTrackList(); + if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) { Initialized = true; From c5a4b6a153488d4a0dec1932dadc427267f17722 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 12:58:26 -0700 Subject: [PATCH 27/46] Don't change anything if it's the same --- RedBookPlayer.Models/Hardware/Player.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 12377df..bc67f86 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -761,6 +761,10 @@ namespace RedBookPlayer.Models.Hardware /// Disc number to attempt to load public void SelectDisc(int discNumber) { + // If the disc didn't change, don't do anything + if(_currentDisc == discNumber) + return; + PlayerState wasPlaying = PlayerState; if (wasPlaying == PlayerState.Playing) Stop(); @@ -802,6 +806,13 @@ namespace RedBookPlayer.Models.Hardware /// True if index changes can trigger a track change, false otherwise public void SelectIndex(ushort index, bool changeTrack) { + if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) + return; + + // If the index didn't change, don't do anything + if(_currentTrackIndex == index) + return; + PlayerState wasPlaying = PlayerState; if (wasPlaying == PlayerState.Playing) Pause(); @@ -876,6 +887,10 @@ namespace RedBookPlayer.Models.Hardware if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return false; + // If the disc didn't change, don't do anything + if(_currentTrackNumber == trackNumber) + return false; + PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); From ff5006063a8fe8e16ba588099257e79afa931e8c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 12:59:11 -0700 Subject: [PATCH 28/46] Expose shuffling to UI --- README.md | 5 +++++ RedBookPlayer.GUI/ViewModels/MainViewModel.cs | 8 ++++---- RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs | 2 +- RedBookPlayer.GUI/Views/SettingsWindow.xaml | 8 ++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7edfb9f..75cdedd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ | **Page Down** | Previous Disc | | **→** | Next Track | | **←** | Previous Track | +| **R** | Shuffle Tracks | | **]** | Next Index | | **[** | Previous Index | | **.** | Fast Forward | @@ -36,6 +37,10 @@ For Save Track(s): For Disc Switching: - If you change the number of discs in the internal changer, you must restart the program for it to take effect +For Shuffling: +- Shuffling only works on the current set of playable tracks +- If you are in single disc mode and switch discs, it will not automatically shuffle the new tracks + For both Volume Up and Volume Down: - Holding **Ctrl** will move in increments of 2 - Holding **Shift** will move in increments of 5 diff --git a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs index 17172c8..8537759 100644 --- a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs @@ -101,10 +101,10 @@ namespace RedBookPlayer.GUI.ViewModels } // Shuffle Track List - // else if(e.Key == App.Settings.ShuffleTracksKey) - // { - // PlayerView?.ViewModel?.ExecuteShuffle(); - // } + else if(e.Key == App.Settings.ShuffleTracksKey) + { + PlayerView?.ViewModel?.ExecuteShuffle(); + } // Next Index else if(e.Key == App.Settings.NextIndexKey) diff --git a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs index 4754fb9..fad9566 100644 --- a/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/SettingsViewModel.cs @@ -183,7 +183,7 @@ namespace RedBookPlayer.GUI.ViewModels /// /// Key assigned to shuffling the track list /// - // public Key ShuffleTracksKey { get; set; } = Key.R; + public Key ShuffleTracksKey { get; set; } = Key.R; /// /// Key assigned to move to the next index diff --git a/RedBookPlayer.GUI/Views/SettingsWindow.xaml b/RedBookPlayer.GUI/Views/SettingsWindow.xaml index b348de8..de5fa6b 100644 --- a/RedBookPlayer.GUI/Views/SettingsWindow.xaml +++ b/RedBookPlayer.GUI/Views/SettingsWindow.xaml @@ -142,6 +142,14 @@ HorizontalAlignment="Right" Margin="8,0,0,0" Width="120"/> + + + Shuffle Tracks + + + Next Index From 5994ea4585554c318f64b1cb123d5c704974fef3 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 13:16:47 -0700 Subject: [PATCH 29/46] Fix eject for single disc --- RedBookPlayer.Models/Hardware/Player.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index bc67f86..656c849 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1289,6 +1289,9 @@ namespace RedBookPlayer.Models.Hardware /// private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { + if(_opticalDiscs[CurrentDisc] == null) + return; + ImagePath = _opticalDiscs[CurrentDisc].ImagePath; CurrentTrackNumber = _opticalDiscs[CurrentDisc].CurrentTrackNumber; CurrentTrackIndex = _opticalDiscs[CurrentDisc].CurrentTrackIndex; From 55ef82f6c554a64fe0340e8967ae685898c2f080 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 14:33:43 -0700 Subject: [PATCH 30/46] Organize PlayerViewModel like Player --- .../ViewModels/PlayerViewModel.cs | 252 ++++++++++-------- 1 file changed, 134 insertions(+), 118 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 45a9d08..931fdac 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -423,7 +423,7 @@ namespace RedBookPlayer.GUI.ViewModels } } - #region Playback + #region Playback (UI) /// /// Begin playback @@ -440,6 +440,11 @@ namespace RedBookPlayer.GUI.ViewModels /// public void ExecuteTogglePlayPause() => _player?.TogglePlayback(); + /// + /// Shuffle the current track list + /// + public void ExecuteShuffle() => _player?.ShuffleTracks(); + /// /// Stop current playback /// @@ -470,11 +475,6 @@ namespace RedBookPlayer.GUI.ViewModels /// public void ExecutePreviousTrack() => _player?.PreviousTrack(); - /// - /// Shuffle the current track list - /// - public void ExecuteShuffle() => _player?.ShuffleTracks(); - /// /// Move to the next index /// @@ -497,6 +497,29 @@ namespace RedBookPlayer.GUI.ViewModels #endregion + #region Playback (Internal) + + /// + /// Select a particular disc by number + /// + /// Disc number to attempt to load + public void SelectDisc(int discNumber) => _player?.SelectDisc(discNumber); + + /// + /// Select a particular index by number + /// + /// Track index to attempt to load + /// True if index changes can trigger a track change, false otherwise + public void SelectIndex(ushort index, bool changeTrack) => _player?.SelectIndex(index, changeTrack); + + /// + /// Select a particular track by number + /// + /// Track number to attempt to load + public void SelectTrack(int trackNumber) => _player?.SelectTrack(trackNumber); + + #endregion + #region Volume /// @@ -541,6 +564,111 @@ namespace RedBookPlayer.GUI.ViewModels #endregion + #region Extraction + + /// + /// Extract a single track from the image to WAV + /// + /// + /// Output path to write data to + public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) => _player?.ExtractSingleTrackToWav(trackNumber, outputDirectory); + + /// + /// Extract all tracks from the image to WAV + /// + /// Output path to write data to + public void ExtractAllTracksToWav(string outputDirectory) => _player?.ExtractAllTracksToWav(outputDirectory); + + #endregion + + #region Setters + + /// + /// Set data playback method [CompactDisc only] + /// + /// New playback value + public void SetDataPlayback(DataPlayback dataPlayback) => _player?.SetDataPlayback(dataPlayback); + + /// + /// Set disc handling method + /// + /// New playback value + public void SetDiscHandling(DiscHandling discHandling) => _player?.SetDiscHandling(discHandling); + + /// + /// Set the value for loading hidden tracks [CompactDisc only] + /// + /// True to enable loading hidden tracks, false otherwise + public void SetLoadHiddenTracks(bool load) => _player?.SetLoadHiddenTracks(load); + + /// + /// Set repeat mode + /// + /// New repeat mode value + public void SetRepeatMode(RepeatMode repeatMode) => _player?.SetRepeatMode(repeatMode); + + /// + /// Set session handling + /// + /// New session handling value + public void SetSessionHandling(SessionHandling sessionHandling) => _player?.SetSessionHandling(sessionHandling); + + #endregion + + #region State Change Event Handlers + + /// + /// Update the view-model from the Player + /// + private void PlayerStateChanged(object sender, PropertyChangedEventArgs e) + { + if(_player == null) + return; + + if(!_player.Initialized) + { + Dispatcher.UIThread.InvokeAsync(() => + { + App.MainWindow.Title = "RedBookPlayer"; + }); + } + + ImagePath = _player.ImagePath; + Initialized = _player.Initialized; + + if (!string.IsNullOrWhiteSpace(ImagePath) && Initialized) + { + Dispatcher.UIThread.InvokeAsync(() => + { + App.MainWindow.Title = "RedBookPlayer - " + ImagePath.Split('/').Last().Split('\\').Last(); + }); + } + + CurrentDisc = _player.CurrentDisc; + CurrentTrackNumber = _player.CurrentTrackNumber; + CurrentTrackIndex = _player.CurrentTrackIndex; + CurrentTrackSession = _player.CurrentTrackSession; + CurrentSector = _player.CurrentSector; + SectionStartSector = _player.SectionStartSector; + + HiddenTrack = _player.HiddenTrack; + + QuadChannel = _player.QuadChannel; + IsDataTrack = _player.IsDataTrack; + CopyAllowed = _player.CopyAllowed; + TrackHasEmphasis = _player.TrackHasEmphasis; + + PlayerState = _player.PlayerState; + DataPlayback = _player.DataPlayback; + RepeatMode = _player.RepeatMode; + ApplyDeEmphasis = _player.ApplyDeEmphasis; + Volume = _player.Volume; + + UpdateDigits(); + } + + #endregion + #region Helpers /// @@ -696,68 +824,6 @@ namespace RedBookPlayer.GUI.ViewModels SetSessionHandling(App.Settings.SessionHandling); } - /// - /// Extract a single track from the image to WAV - /// - /// - /// Output path to write data to - public void ExtractSingleTrackToWav(uint trackNumber, string outputDirectory) => _player?.ExtractSingleTrackToWav(trackNumber, outputDirectory); - - /// - /// Extract all tracks from the image to WAV - /// - /// Output path to write data to - public void ExtractAllTracksToWav(string outputDirectory) => _player?.ExtractAllTracksToWav(outputDirectory); - - /// - /// Select a particular disc by number - /// - /// Disc number to attempt to load - public void SelectDisc(int discNumber) => _player?.SelectDisc(discNumber); - - /// - /// Select a particular index by number - /// - /// Track index to attempt to load - /// True if index changes can trigger a track change, false otherwise - public void SelectIndex(ushort index, bool changeTrack) => _player?.SelectIndex(index, changeTrack); - - /// - /// Select a particular track by number - /// - /// Track number to attempt to load - public void SelectTrack(int trackNumber) => _player?.SelectTrack(trackNumber); - - /// - /// Set data playback method [CompactDisc only] - /// - /// New playback value - public void SetDataPlayback(DataPlayback dataPlayback) => _player?.SetDataPlayback(dataPlayback); - - /// - /// Set disc handling method - /// - /// New playback value - public void SetDiscHandling(DiscHandling discHandling) => _player?.SetDiscHandling(discHandling); - - /// - /// Set the value for loading hidden tracks [CompactDisc only] - /// - /// True to enable loading hidden tracks, false otherwise - public void SetLoadHiddenTracks(bool load) => _player?.SetLoadHiddenTracks(load); - - /// - /// Set repeat mode - /// - /// New repeat mode value - public void SetRepeatMode(RepeatMode repeatMode) => _player?.SetRepeatMode(repeatMode); - - /// - /// Set session handling - /// - /// New session handling value - public void SetSessionHandling(SessionHandling sessionHandling) => _player?.SetSessionHandling(sessionHandling); - /// /// Generate the digit string to be interpreted by the frontend /// @@ -879,56 +945,6 @@ namespace RedBookPlayer.GUI.ViewModels UpdateDigits(); } - /// - /// Update the view-model from the Player - /// - private void PlayerStateChanged(object sender, PropertyChangedEventArgs e) - { - if(_player == null) - return; - - if(!_player.Initialized) - { - Dispatcher.UIThread.InvokeAsync(() => - { - App.MainWindow.Title = "RedBookPlayer"; - }); - } - - ImagePath = _player.ImagePath; - Initialized = _player.Initialized; - - if (!string.IsNullOrWhiteSpace(ImagePath) && Initialized) - { - Dispatcher.UIThread.InvokeAsync(() => - { - App.MainWindow.Title = "RedBookPlayer - " + ImagePath.Split('/').Last().Split('\\').Last(); - }); - } - - CurrentDisc = _player.CurrentDisc; - CurrentTrackNumber = _player.CurrentTrackNumber; - CurrentTrackIndex = _player.CurrentTrackIndex; - CurrentTrackSession = _player.CurrentTrackSession; - CurrentSector = _player.CurrentSector; - SectionStartSector = _player.SectionStartSector; - - HiddenTrack = _player.HiddenTrack; - - QuadChannel = _player.QuadChannel; - IsDataTrack = _player.IsDataTrack; - CopyAllowed = _player.CopyAllowed; - TrackHasEmphasis = _player.TrackHasEmphasis; - - PlayerState = _player.PlayerState; - DataPlayback = _player.DataPlayback; - RepeatMode = _player.RepeatMode; - ApplyDeEmphasis = _player.ApplyDeEmphasis; - Volume = _player.Volume; - - UpdateDigits(); - } - /// /// Update UI /// From 2b7084cd872d68442f5fb6dc811412c690c26d35 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 20:37:12 -0700 Subject: [PATCH 31/46] Fix CompactDisc.ReadSectors --- RedBookPlayer.Models/Discs/CompactDisc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 051fef0..751b777 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -354,7 +354,7 @@ namespace RedBookPlayer.Models.Discs /// Current number of sectors to read /// DataPlayback value indicating how to handle data tracks /// Byte array representing the read sectors, if possible - public byte[] ReadSectors(uint sectorsToRead, DataPlayback dataPlayback) => ReadSectors(CurrentSector, sectorsToRead, DataPlayback.Skip); + public byte[] ReadSectors(uint sectorsToRead, DataPlayback dataPlayback) => ReadSectors(CurrentSector, sectorsToRead, dataPlayback); /// /// Read sector data from the base image starting from the specified sector From 43e181134907e6fa171c8862a0355500d4336282 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 20:42:25 -0700 Subject: [PATCH 32/46] Add subchannel reading helpers --- RedBookPlayer.Models/Discs/CompactDisc.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 751b777..1cf5cbc 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -356,6 +356,13 @@ namespace RedBookPlayer.Models.Discs /// Byte array representing the read sectors, if possible public byte[] ReadSectors(uint sectorsToRead, DataPlayback dataPlayback) => ReadSectors(CurrentSector, sectorsToRead, dataPlayback); + /// + /// Read subchannel data from the base image starting from the specified sector + /// + /// Current number of sectors to read + /// Byte array representing the read subchannels, if possible + public byte[] ReadSubchannels(uint sectorsToRead) => ReadSubchannels(CurrentSector, sectorsToRead); + /// /// Read sector data from the base image starting from the specified sector /// @@ -381,6 +388,15 @@ namespace RedBookPlayer.Models.Discs } } + /// + /// Read subchannel data from the base image starting from the specified sector + /// + /// Sector to start at for reading + /// Current number of sectors to read + /// Byte array representing the read subchannels, if possible + private byte[] ReadSubchannels(ulong startSector, uint sectorsToRead) + => _image.ReadSectorsTag(startSector, sectorsToRead, SectorTagType.CdSectorSubchannel); + /// public override void SetTotalIndexes() { From 4eecc68cae815a3fe8af997caa1b811ff3f98e0f Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 20:46:28 -0700 Subject: [PATCH 33/46] Read subchannel data on read (nw) --- RedBookPlayer.Models/Hardware/Player.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 656c849..0eb284b 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1022,9 +1022,14 @@ namespace RedBookPlayer.Models.Hardware try { if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + { + byte[] subchannelData = compactDisc.ReadSubchannels((uint)sectorsToRead); return compactDisc.ReadSectors((uint)sectorsToRead, DataPlayback).Concat(zeroSectors).ToArray(); + } else + { return _opticalDiscs[CurrentDisc].ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); + } } catch { } From e6222352e9988fa9be616a30664040b546a335c8 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 20:52:16 -0700 Subject: [PATCH 34/46] Audio to its own namespace --- RedBookPlayer.Models/{Hardware => Audio}/DeEmphasisFilter.cs | 2 +- RedBookPlayer.Models/{Hardware => Audio}/FilterStage.cs | 2 +- RedBookPlayer.Models/{Hardware => Audio}/IAudioBackend.cs | 2 +- RedBookPlayer.Models/{Hardware => Audio}/Linux/AudioBackend.cs | 2 +- RedBookPlayer.Models/{Hardware => Audio}/PlayerSource.cs | 2 +- RedBookPlayer.Models/{Hardware => Audio}/SoundOutput.cs | 2 +- .../{Hardware => Audio}/Windows/AudioBackend.cs | 2 +- RedBookPlayer.Models/Hardware/Player.cs | 1 + 8 files changed, 8 insertions(+), 7 deletions(-) rename RedBookPlayer.Models/{Hardware => Audio}/DeEmphasisFilter.cs (97%) rename RedBookPlayer.Models/{Hardware => Audio}/FilterStage.cs (97%) rename RedBookPlayer.Models/{Hardware => Audio}/IAudioBackend.cs (93%) rename RedBookPlayer.Models/{Hardware => Audio}/Linux/AudioBackend.cs (96%) rename RedBookPlayer.Models/{Hardware => Audio}/PlayerSource.cs (96%) rename RedBookPlayer.Models/{Hardware => Audio}/SoundOutput.cs (99%) rename RedBookPlayer.Models/{Hardware => Audio}/Windows/AudioBackend.cs (96%) diff --git a/RedBookPlayer.Models/Hardware/DeEmphasisFilter.cs b/RedBookPlayer.Models/Audio/DeEmphasisFilter.cs similarity index 97% rename from RedBookPlayer.Models/Hardware/DeEmphasisFilter.cs rename to RedBookPlayer.Models/Audio/DeEmphasisFilter.cs index 5b74b1d..3ee46f7 100644 --- a/RedBookPlayer.Models/Hardware/DeEmphasisFilter.cs +++ b/RedBookPlayer.Models/Audio/DeEmphasisFilter.cs @@ -1,7 +1,7 @@ using System; using NWaves.Filters.BiQuad; -namespace RedBookPlayer.Models.Hardware +namespace RedBookPlayer.Models.Audio { /// /// Filter for applying de-emphasis to audio diff --git a/RedBookPlayer.Models/Hardware/FilterStage.cs b/RedBookPlayer.Models/Audio/FilterStage.cs similarity index 97% rename from RedBookPlayer.Models/Hardware/FilterStage.cs rename to RedBookPlayer.Models/Audio/FilterStage.cs index b9189ec..f53a8b5 100644 --- a/RedBookPlayer.Models/Hardware/FilterStage.cs +++ b/RedBookPlayer.Models/Audio/FilterStage.cs @@ -1,7 +1,7 @@ using NWaves.Audio; using NWaves.Filters.BiQuad; -namespace RedBookPlayer.Models.Hardware +namespace RedBookPlayer.Models.Audio { /// /// Output stage that represents all filters on the audio diff --git a/RedBookPlayer.Models/Hardware/IAudioBackend.cs b/RedBookPlayer.Models/Audio/IAudioBackend.cs similarity index 93% rename from RedBookPlayer.Models/Hardware/IAudioBackend.cs rename to RedBookPlayer.Models/Audio/IAudioBackend.cs index b461c2a..b8417dc 100644 --- a/RedBookPlayer.Models/Hardware/IAudioBackend.cs +++ b/RedBookPlayer.Models/Audio/IAudioBackend.cs @@ -1,4 +1,4 @@ -namespace RedBookPlayer.Models.Hardware +namespace RedBookPlayer.Models.Audio { public interface IAudioBackend { diff --git a/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs b/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs similarity index 96% rename from RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs rename to RedBookPlayer.Models/Audio/Linux/AudioBackend.cs index 92043fb..1ee5ec5 100644 --- a/RedBookPlayer.Models/Hardware/Linux/AudioBackend.cs +++ b/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs @@ -1,6 +1,6 @@ using CSCore.SoundOut; -namespace RedBookPlayer.Models.Hardware.Linux +namespace RedBookPlayer.Models.Audio.Linux { public class AudioBackend : IAudioBackend { diff --git a/RedBookPlayer.Models/Hardware/PlayerSource.cs b/RedBookPlayer.Models/Audio/PlayerSource.cs similarity index 96% rename from RedBookPlayer.Models/Hardware/PlayerSource.cs rename to RedBookPlayer.Models/Audio/PlayerSource.cs index 203ff4f..41c8360 100644 --- a/RedBookPlayer.Models/Hardware/PlayerSource.cs +++ b/RedBookPlayer.Models/Audio/PlayerSource.cs @@ -2,7 +2,7 @@ using System; using CSCore; using WaveFormat = CSCore.WaveFormat; -namespace RedBookPlayer.Models.Hardware +namespace RedBookPlayer.Models.Audio { public class PlayerSource : IWaveSource { diff --git a/RedBookPlayer.Models/Hardware/SoundOutput.cs b/RedBookPlayer.Models/Audio/SoundOutput.cs similarity index 99% rename from RedBookPlayer.Models/Hardware/SoundOutput.cs rename to RedBookPlayer.Models/Audio/SoundOutput.cs index 246c004..14b2b85 100644 --- a/RedBookPlayer.Models/Hardware/SoundOutput.cs +++ b/RedBookPlayer.Models/Audio/SoundOutput.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using ReactiveUI; -namespace RedBookPlayer.Models.Hardware +namespace RedBookPlayer.Models.Audio { public class SoundOutput : ReactiveObject { diff --git a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs b/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs similarity index 96% rename from RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs rename to RedBookPlayer.Models/Audio/Windows/AudioBackend.cs index c55b0ff..0026b7a 100644 --- a/RedBookPlayer.Models/Hardware/Windows/AudioBackend.cs +++ b/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs @@ -1,6 +1,6 @@ using CSCore.SoundOut; -namespace RedBookPlayer.Models.Hardware.Windows +namespace RedBookPlayer.Models.Audio.Windows { public class AudioBackend : IAudioBackend { diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 0eb284b..2297f42 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -7,6 +7,7 @@ using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Structs; using Avalonia.Threading; using ReactiveUI; +using RedBookPlayer.Models.Audio; using RedBookPlayer.Models.Discs; using RedBookPlayer.Models.Factories; From 82a856915e46af731611d76b88ce1c8dc51ffe7a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 20:56:33 -0700 Subject: [PATCH 35/46] Add subchannel read note --- RedBookPlayer.Models/Discs/CompactDisc.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 1cf5cbc..7624e30 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -370,6 +370,7 @@ namespace RedBookPlayer.Models.Discs /// Current number of sectors to read /// DataPlayback value indicating how to handle data tracks /// Byte array representing the read sectors, if possible + /// Should be a multiple of 96 bytes private byte[] ReadSectors(ulong startSector, uint sectorsToRead, DataPlayback dataPlayback) { if(TrackType == TrackType.Audio || dataPlayback == DataPlayback.Play) @@ -394,6 +395,7 @@ namespace RedBookPlayer.Models.Discs /// Sector to start at for reading /// Current number of sectors to read /// Byte array representing the read subchannels, if possible + /// Should be a multiple of 96 bytes private byte[] ReadSubchannels(ulong startSector, uint sectorsToRead) => _image.ReadSectorsTag(startSector, sectorsToRead, SectorTagType.CdSectorSubchannel); From b02ca60b1ee7c8e02e67bf2ef5a1436d55418137 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 21:27:15 -0700 Subject: [PATCH 36/46] Add subchannel formatting helpers --- RedBookPlayer.Models/Hardware/Player.cs | 105 ++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 2297f42..e33391d 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1332,5 +1332,110 @@ namespace RedBookPlayer.Models.Hardware } #endregion + + #region Helpers + + /// + /// Reformat raw subchannel data for a single sector + /// + /// Raw subchannel data to format + /// Dictionary mapping subchannel to formatted data + public Dictionary ConvertSingleSubchannel(byte[] subchannelData) + { + if(subchannelData == null || subchannelData.Length != 96) + return null; + + // Create the output dictionary for the formatted data + Dictionary formattedData = new Dictionary + { + ['P'] = new byte[12], + ['Q'] = new byte[12], + ['R'] = new byte[12], + ['S'] = new byte[12], + ['T'] = new byte[12], + ['U'] = new byte[12], + ['V'] = new byte[12], + ['W'] = new byte[12], + }; + + // Loop through all bytes in the subchannel data and populate + int index = -1; + for(int i = 0; i < 96; i++) + { + // Get the modulo value of the current byte + int modValue = i % 8; + if(modValue == 0) + index++; + + // Retrieve the next byte + byte b = subchannelData[i]; + + // Set the respective bit in the new byte data + formattedData['P'][index] |= (byte)(HasBitSet(b, 7) ? 1 << modValue : 0); + formattedData['Q'][index] |= (byte)(HasBitSet(b, 6) ? 1 << modValue : 0); + formattedData['R'][index] |= (byte)(HasBitSet(b, 5) ? 1 << modValue : 0); + formattedData['S'][index] |= (byte)(HasBitSet(b, 4) ? 1 << modValue : 0); + formattedData['T'][index] |= (byte)(HasBitSet(b, 3) ? 1 << modValue : 0); + formattedData['U'][index] |= (byte)(HasBitSet(b, 2) ? 1 << modValue : 0); + formattedData['V'][index] |= (byte)(HasBitSet(b, 1) ? 1 << modValue : 0); + formattedData['W'][index] |= (byte)(HasBitSet(b, 0) ? 1 << modValue : 0); + } + + return formattedData; + } + + /// + /// Reformat raw subchannel data for multiple sectors + /// + /// Raw subchannel data to format + /// Dictionary mapping subchannel to formatted data + public Dictionary ConvertSubchannels(byte[] subchannelData) + { + if(subchannelData == null || subchannelData.Length % 96 != 0) + return null; + + // Prepare the output formatted data + int modValue = subchannelData.Length / 96; + Dictionary formattedData = new Dictionary + { + ['P'] = new byte[12 * modValue], + ['Q'] = new byte[12 * modValue], + ['R'] = new byte[12 * modValue], + ['S'] = new byte[12 * modValue], + ['T'] = new byte[12 * modValue], + ['U'] = new byte[12 * modValue], + ['V'] = new byte[12 * modValue], + ['W'] = new byte[12 * modValue], + }; + + // Read in 96-byte chunks + for(int i = 0; i < modValue; i++) + { + byte[] buffer = new byte[96]; + Array.Copy(subchannelData, i * 96, buffer, 0, 96); + Dictionary singleData = ConvertSingleSubchannel(buffer); + + Array.Copy(singleData['P'], 0, formattedData['P'], 12 * i, 12); + Array.Copy(singleData['Q'], 0, formattedData['Q'], 12 * i, 12); + Array.Copy(singleData['R'], 0, formattedData['R'], 12 * i, 12); + Array.Copy(singleData['S'], 0, formattedData['S'], 12 * i, 12); + Array.Copy(singleData['T'], 0, formattedData['T'], 12 * i, 12); + Array.Copy(singleData['U'], 0, formattedData['U'], 12 * i, 12); + Array.Copy(singleData['V'], 0, formattedData['V'], 12 * i, 12); + Array.Copy(singleData['W'], 0, formattedData['W'], 12 * i, 12); + } + + return formattedData; + } + + /// + /// Check if a bit is set in a byte + /// + /// Byte value to check + /// Index of the bit to check + /// True if the bit was set, false otherwise + private bool HasBitSet(byte value, int bitIndex) => (value & (1 << bitIndex)) != 0; + + #endregion } } \ No newline at end of file From 8643bf36a66ac6fe59547821424e8ae7cf505ae6 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 6 Oct 2021 22:46:50 -0700 Subject: [PATCH 37/46] Code cleanup, fix playback again --- .../ViewModels/PlayerViewModel.cs | 2 +- .../Audio/Linux/AudioBackend.cs | 2 +- .../Audio/Windows/AudioBackend.cs | 2 +- RedBookPlayer.Models/Discs/CompactDisc.cs | 12 +-- RedBookPlayer.Models/Hardware/Player.cs | 78 +++++++++---------- 5 files changed, 46 insertions(+), 50 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 931fdac..2282ad1 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -636,7 +636,7 @@ namespace RedBookPlayer.GUI.ViewModels ImagePath = _player.ImagePath; Initialized = _player.Initialized; - if (!string.IsNullOrWhiteSpace(ImagePath) && Initialized) + if(!string.IsNullOrWhiteSpace(ImagePath) && Initialized) { Dispatcher.UIThread.InvokeAsync(() => { diff --git a/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs b/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs index 1ee5ec5..9ee5c12 100644 --- a/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs +++ b/RedBookPlayer.Models/Audio/Linux/AudioBackend.cs @@ -43,7 +43,7 @@ namespace RedBookPlayer.Models.Audio.Linux /// public void SetVolume(float volume) { - if (_soundOut != null) + if(_soundOut != null) _soundOut.Volume = volume; } diff --git a/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs b/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs index 0026b7a..3f01aea 100644 --- a/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs +++ b/RedBookPlayer.Models/Audio/Windows/AudioBackend.cs @@ -43,7 +43,7 @@ namespace RedBookPlayer.Models.Audio.Windows /// public void SetVolume(float volume) { - if (_soundOut != null) + if(_soundOut != null) _soundOut.Volume = volume; } diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index 7624e30..b0955d5 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -28,9 +28,9 @@ namespace RedBookPlayer.Models.Discs return; // Invalid value means we can't do anything - if (value > _image.Tracks.Max(t => t.TrackSequence)) + if(value > _image.Tracks.Max(t => t.TrackSequence)) return; - else if (value < _image.Tracks.Min(t => t.TrackSequence)) + else if(value < _image.Tracks.Min(t => t.TrackSequence)) return; // Cache the current track for easy access @@ -65,9 +65,9 @@ namespace RedBookPlayer.Models.Discs return; // Invalid value means we can't do anything - if (value > track.Indexes.Keys.Max()) + if(value > track.Indexes.Keys.Max()) return; - else if (value < track.Indexes.Keys.Min()) + else if(value < track.Indexes.Keys.Min()) return; this.RaiseAndSetIfChanged(ref _currentTrackIndex, value); @@ -271,7 +271,7 @@ namespace RedBookPlayer.Models.Discs // Get the track with that value, if possible Track track = _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber); - if (track == null) + if(track == null) return; // Get the number of sectors to read @@ -334,7 +334,7 @@ namespace RedBookPlayer.Models.Discs // Cache the current track for easy access Track track = GetTrack(CurrentTrackNumber); - if (track == null) + if(track == null) return; // If the index is invalid, just return diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index e33391d..9c7310f 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -35,9 +35,9 @@ namespace RedBookPlayer.Models.Hardware private set { int temp = value; - if (temp < 0) + if(temp < 0) temp = _numberOfDiscs - 1; - else if (temp >= _numberOfDiscs) + else if(temp >= _numberOfDiscs) temp = 0; this.RaiseAndSetIfChanged(ref _currentDisc, temp); @@ -296,7 +296,7 @@ namespace RedBookPlayer.Models.Hardware /// /// OpticalDisc objects /// - private OpticalDiscBase[] _opticalDiscs; + private readonly OpticalDiscBase[] _opticalDiscs; /// /// List of available tracks organized by disc @@ -321,7 +321,7 @@ namespace RedBookPlayer.Models.Hardware /// /// Filtering stage for audio output /// - private FilterStage _filterStage; + private readonly FilterStage _filterStage; /// /// Current position in the sector for reading @@ -344,7 +344,7 @@ namespace RedBookPlayer.Models.Hardware { Initialized = false; - if (numberOfDiscs <= 0) + if(numberOfDiscs <= 0) numberOfDiscs = 1; _numberOfDiscs = numberOfDiscs; @@ -467,26 +467,23 @@ namespace RedBookPlayer.Models.Hardware int currentFoundTrack = 0; if(_trackPlaybackOrder == null || _trackPlaybackOrder.Count == 0) { - currentFoundTrack = 0; + _currentTrackInOrder = 0; + return; } else if(_trackPlaybackOrder.Any(kvp => kvp.Key == CurrentDisc)) { currentFoundTrack = _trackPlaybackOrder.FindIndex(kvp => kvp.Key == CurrentDisc && kvp.Value == CurrentTrackNumber); if(currentFoundTrack == -1) - currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == CurrentDisc).Min(kvp => kvp.Value); - - CurrentDisc = _trackPlaybackOrder[currentFoundTrack].Key; - CurrentTrackNumber = _trackPlaybackOrder[currentFoundTrack].Value; + currentFoundTrack = _trackPlaybackOrder.FindIndex(kvp => kvp.Key == CurrentDisc && kvp.Value == _trackPlaybackOrder.Min(kvp => kvp.Value)); } else { int lowestDiscNumber = _trackPlaybackOrder.Min(kvp => kvp.Key); - currentFoundTrack = _trackPlaybackOrder.Where(kvp => kvp.Key == lowestDiscNumber).Min(kvp => kvp.Value); - - CurrentDisc = _trackPlaybackOrder[currentFoundTrack].Key; - CurrentTrackNumber = _trackPlaybackOrder[currentFoundTrack].Value; + currentFoundTrack = _trackPlaybackOrder.FindIndex(kvp => kvp.Key == lowestDiscNumber && kvp.Value == _trackPlaybackOrder.Min(kvp => kvp.Value)); } + CurrentDisc = _trackPlaybackOrder[currentFoundTrack].Key; + CurrentTrackNumber = _trackPlaybackOrder[currentFoundTrack].Value; _currentTrackInOrder = currentFoundTrack; } @@ -692,7 +689,7 @@ namespace RedBookPlayer.Models.Hardware public int ProviderRead(byte[] buffer, int offset, int count) { // If we have an unreadable amount - if (count <= 0) + if(count <= 0) { Array.Clear(buffer, offset, count); return count; @@ -706,7 +703,7 @@ namespace RedBookPlayer.Models.Hardware } // Determine how many sectors we can read - DetermineReadAmount(count, out ulong sectorsToRead, out ulong zeroSectorsAmount); + DetermineReadAmount(out ulong sectorsToRead, out ulong zeroSectorsAmount); // Get data to return byte[] audioDataSegment = ReadData(count, sectorsToRead, zeroSectorsAmount); @@ -732,7 +729,7 @@ namespace RedBookPlayer.Models.Hardware else if(RepeatMode == RepeatMode.Single && _opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { ulong trackEndSector = compactDisc.GetTrack(CurrentTrackNumber).TrackEndSector; - if (newSectorValue > trackEndSector) + if(newSectorValue > trackEndSector) { ShouldInvokePlaybackModes = true; } @@ -750,7 +747,7 @@ namespace RedBookPlayer.Models.Hardware // If we are supposed to change tracks, get the next one from the list if(CurrentTrackNumber != previousTrack && !ShouldInvokePlaybackModes) - NextTrack(); + Dispatcher.UIThread.InvokeAsync(NextTrack).ConfigureAwait(false).GetAwaiter().GetResult(); } return count; @@ -767,7 +764,7 @@ namespace RedBookPlayer.Models.Hardware return; PlayerState wasPlaying = PlayerState; - if (wasPlaying == PlayerState.Playing) + if(wasPlaying == PlayerState.Playing) Stop(); if(discNumber >= _numberOfDiscs) @@ -783,7 +780,7 @@ namespace RedBookPlayer.Models.Hardware if(DiscHandling == DiscHandling.SingleDisc) LoadTrackList(); - if (_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) + if(_opticalDiscs[CurrentDisc] != null && _opticalDiscs[CurrentDisc].Initialized) { Initialized = true; SelectTrack(1); @@ -815,11 +812,11 @@ namespace RedBookPlayer.Models.Hardware return; PlayerState wasPlaying = PlayerState; - if (wasPlaying == PlayerState.Playing) + if(wasPlaying == PlayerState.Playing) Pause(); // CompactDisc needs special handling of track wraparound - if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { // Cache the current track for easy access Track track = compactDisc.GetTrack(CurrentTrackNumber); @@ -888,16 +885,12 @@ namespace RedBookPlayer.Models.Hardware if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return false; - // If the disc didn't change, don't do anything - if(_currentTrackNumber == trackNumber) - return false; - PlayerState wasPlaying = PlayerState; if(wasPlaying == PlayerState.Playing) Pause(); // CompactDisc needs special handling of track wraparound - if (_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) + if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { // Cache the value and the current track number int cachedValue = trackNumber; @@ -978,13 +971,12 @@ namespace RedBookPlayer.Models.Hardware /// /// Determine the number of real and zero sectors to read /// - /// Number of requested bytes to read /// Number of sectors to read /// Number of zeroed sectors to concatenate - private void DetermineReadAmount(int count, out ulong sectorsToRead, out ulong zeroSectorsAmount) + private void DetermineReadAmount(out ulong sectorsToRead, out ulong zeroSectorsAmount) { - // Attempt to read 10 more sectors than requested - sectorsToRead = ((ulong)count / (ulong)_opticalDiscs[CurrentDisc].BytesPerSector) + 10; + // Always attempt to read one frame of data + sectorsToRead = 75; zeroSectorsAmount = 0; // Avoid overreads by padding with 0-byte data at the end @@ -1008,7 +1000,7 @@ namespace RedBookPlayer.Models.Hardware private byte[] ReadData(int count, ulong sectorsToRead, ulong zeroSectorsAmount) { // If the amount of zeroes being asked for is the same as the sectors, return null - if (sectorsToRead == zeroSectorsAmount) + if(sectorsToRead == zeroSectorsAmount) return null; // Create padding data for overreads @@ -1024,7 +1016,7 @@ namespace RedBookPlayer.Models.Hardware { if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { - byte[] subchannelData = compactDisc.ReadSubchannels((uint)sectorsToRead); + //byte[] subchannelData = compactDisc.ReadSubchannels((uint)sectorsToRead); return compactDisc.ReadSectors((uint)sectorsToRead, DataPlayback).Concat(zeroSectors).ToArray(); } else @@ -1296,7 +1288,11 @@ namespace RedBookPlayer.Models.Hardware private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { if(_opticalDiscs[CurrentDisc] == null) + { + ImagePath = null; + CurrentTrackNumber = 1; return; + } ImagePath = _opticalDiscs[CurrentDisc].ImagePath; CurrentTrackNumber = _opticalDiscs[CurrentDisc].CurrentTrackNumber; @@ -1371,14 +1367,14 @@ namespace RedBookPlayer.Models.Hardware byte b = subchannelData[i]; // Set the respective bit in the new byte data - formattedData['P'][index] |= (byte)(HasBitSet(b, 7) ? 1 << modValue : 0); - formattedData['Q'][index] |= (byte)(HasBitSet(b, 6) ? 1 << modValue : 0); - formattedData['R'][index] |= (byte)(HasBitSet(b, 5) ? 1 << modValue : 0); - formattedData['S'][index] |= (byte)(HasBitSet(b, 4) ? 1 << modValue : 0); - formattedData['T'][index] |= (byte)(HasBitSet(b, 3) ? 1 << modValue : 0); - formattedData['U'][index] |= (byte)(HasBitSet(b, 2) ? 1 << modValue : 0); - formattedData['V'][index] |= (byte)(HasBitSet(b, 1) ? 1 << modValue : 0); - formattedData['W'][index] |= (byte)(HasBitSet(b, 0) ? 1 << modValue : 0); + formattedData['P'][index] |= (byte)(HasBitSet(b, 7) ? 1 << (7 - modValue) : 0); + formattedData['Q'][index] |= (byte)(HasBitSet(b, 6) ? 1 << (7 - modValue) : 0); + formattedData['R'][index] |= (byte)(HasBitSet(b, 5) ? 1 << (7 - modValue) : 0); + formattedData['S'][index] |= (byte)(HasBitSet(b, 4) ? 1 << (7 - modValue) : 0); + formattedData['T'][index] |= (byte)(HasBitSet(b, 3) ? 1 << (7 - modValue) : 0); + formattedData['U'][index] |= (byte)(HasBitSet(b, 2) ? 1 << (7 - modValue) : 0); + formattedData['V'][index] |= (byte)(HasBitSet(b, 1) ? 1 << (7 - modValue) : 0); + formattedData['W'][index] |= (byte)(HasBitSet(b, 0) ? 1 << (7 - modValue) : 0); } return formattedData; From a13141090ffe56af69edbf63f6937055979dff77 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 7 Oct 2021 09:20:40 -0700 Subject: [PATCH 38/46] Fix comment --- RedBookPlayer.Models/Hardware/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 9c7310f..0def289 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -975,7 +975,7 @@ namespace RedBookPlayer.Models.Hardware /// Number of zeroed sectors to concatenate private void DetermineReadAmount(out ulong sectorsToRead, out ulong zeroSectorsAmount) { - // Always attempt to read one frame of data + // Always attempt to read one second of data sectorsToRead = 75; zeroSectorsAmount = 0; From c34d07e26670d9f83afdec5bba4d2aef5854ef36 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Oct 2021 22:34:46 -0700 Subject: [PATCH 39/46] Fix volume issues --- RedBookPlayer.Models/Hardware/Player.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 0def289..7491fb2 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -364,6 +364,9 @@ namespace RedBookPlayer.Models.Hardware _currentTrackInOrder = 0; PropertyChanged += HandlePlaybackModes; + + // Force a refresh of the state information + SoundOutputStateChanged(this, null); } /// @@ -1101,7 +1104,7 @@ namespace RedBookPlayer.Models.Hardware /// /// Decrement the volume value /// - public void VolumeDown() => SetVolume(Volume + 1); + public void VolumeDown() => SetVolume(Volume - 1); /// /// Set the value for the volume From 27480a9e6964945326bc7ed51b25d3d3b51d49b8 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Oct 2021 23:02:36 -0700 Subject: [PATCH 40/46] Make volume setting EVEN safer --- RedBookPlayer.Models/Audio/SoundOutput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedBookPlayer.Models/Audio/SoundOutput.cs b/RedBookPlayer.Models/Audio/SoundOutput.cs index 14b2b85..3decacc 100644 --- a/RedBookPlayer.Models/Audio/SoundOutput.cs +++ b/RedBookPlayer.Models/Audio/SoundOutput.cs @@ -155,7 +155,7 @@ namespace RedBookPlayer.Models.Audio public void SetVolume(int volume) { Volume = volume; - _soundOut.SetVolume((float)Volume / 100); + _soundOut?.SetVolume((float)Volume / 100); } /// From 09f9b2d77560e18a19d6db40d0b0a62d2f5fb3ea Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Oct 2021 23:16:02 -0700 Subject: [PATCH 41/46] Disconnect volume from Init --- .../ViewModels/PlayerViewModel.cs | 5 ++-- RedBookPlayer.Models/Audio/SoundOutput.cs | 30 ++++++++----------- RedBookPlayer.Models/Hardware/Player.cs | 8 ++--- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 2282ad1..87ffdec 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -398,6 +398,8 @@ namespace RedBookPlayer.GUI.ViewModels // Initialize Player _player = new Player(App.Settings.NumberOfDiscs, App.Settings.Volume); + _player.PropertyChanged += PlayerStateChanged; + PlayerStateChanged(this, null); PlayerState = PlayerState.NoDisc; } @@ -417,10 +419,7 @@ namespace RedBookPlayer.GUI.ViewModels // Attempt to initialize Player _player.Init(path, playerOptions, opticalDiscOptions, autoPlay); if(_player.Initialized) - { - _player.PropertyChanged += PlayerStateChanged; PlayerStateChanged(this, null); - } } #region Playback (UI) diff --git a/RedBookPlayer.Models/Audio/SoundOutput.cs b/RedBookPlayer.Models/Audio/SoundOutput.cs index 3decacc..4c526ae 100644 --- a/RedBookPlayer.Models/Audio/SoundOutput.cs +++ b/RedBookPlayer.Models/Audio/SoundOutput.cs @@ -66,22 +66,23 @@ namespace RedBookPlayer.Models.Audio /// /// Constructor /// + /// ReadFunction to use during decoding /// Default volume between 0 and 100 to use when starting playback - public SoundOutput(int defaultVolume = 100) => Volume = defaultVolume; + public SoundOutput(PlayerSource.ReadFunction read, int defaultVolume = 100) + { + Volume = defaultVolume; + SetupAudio(read); + } /// /// Initialize the output with a given image /// - /// ReadFunction to use during decoding /// True if playback should begin immediately, false otherwise - public void Init(PlayerSource.ReadFunction read, bool autoPlay) + public void Init(bool autoPlay) { // Reset initialization Initialized = false; - // Setup the audio output - SetupAudio(read); - // Initialize playback, if necessary if(autoPlay) _soundOut.Play(); @@ -164,18 +165,11 @@ namespace RedBookPlayer.Models.Audio /// ReadFunction to use during decoding private void SetupAudio(PlayerSource.ReadFunction read) { - if(_source == null) - { - _source = new PlayerSource(read); - if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - _soundOut = new Linux.AudioBackend(_source); - else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - _soundOut = new Windows.AudioBackend(_source); - } - else - { - _soundOut.Stop(); - } + _source = new PlayerSource(read); + if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + _soundOut = new Linux.AudioBackend(_source); + else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _soundOut = new Windows.AudioBackend(_source); } #endregion diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index 7491fb2..cda02de 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -352,7 +352,8 @@ namespace RedBookPlayer.Models.Hardware _currentDisc = 0; _filterStage = new FilterStage(); - _soundOutput = new SoundOutput(defaultVolume); + _soundOutput = new SoundOutput(ProviderRead, defaultVolume); + _soundOutput.PropertyChanged += SoundOutputStateChanged; _availableTrackList = new Dictionary>(); for(int i = 0; i < _numberOfDiscs; i++) @@ -400,13 +401,10 @@ namespace RedBookPlayer.Models.Hardware _filterStage.SetupFilters(); // Initialize the sound output - _soundOutput.Init(ProviderRead, autoPlay); + _soundOutput.Init(autoPlay); if(_soundOutput == null || !_soundOutput.Initialized) return; - // Add event handling for the sound output - _soundOutput.PropertyChanged += SoundOutputStateChanged; - // Load in the track list for the current disc LoadTrackList(); From e5147bdbb5fd4fdd670630bd34f17b6ebce70e17 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Oct 2021 23:30:17 -0700 Subject: [PATCH 42/46] Remove override media volume keys --- RedBookPlayer.GUI/ViewModels/MainViewModel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs index 8537759..aaac01b 100644 --- a/RedBookPlayer.GUI/ViewModels/MainViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/MainViewModel.cs @@ -131,7 +131,7 @@ namespace RedBookPlayer.GUI.ViewModels } // Volume Up - else if(e.Key == App.Settings.VolumeUpKey || e.Key == Key.VolumeUp) + else if(e.Key == App.Settings.VolumeUpKey) { int increment = 1; if(e.KeyModifiers.HasFlag(KeyModifiers.Control)) @@ -144,7 +144,7 @@ namespace RedBookPlayer.GUI.ViewModels } // Volume Down - else if(e.Key == App.Settings.VolumeDownKey || e.Key == Key.VolumeDown) + else if(e.Key == App.Settings.VolumeDownKey) { int decrement = 1; if(e.KeyModifiers.HasFlag(KeyModifiers.Control)) @@ -157,7 +157,7 @@ namespace RedBookPlayer.GUI.ViewModels } // Mute Toggle - else if(e.Key == App.Settings.ToggleMuteKey || e.Key == Key.VolumeMute) + else if(e.Key == App.Settings.ToggleMuteKey) { PlayerView?.ViewModel?.ExecuteToggleMute(); } From 8e606f368da3a372ada05d1bffeaeb590d889636 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 8 Oct 2021 23:33:55 -0700 Subject: [PATCH 43/46] Change UI to use 1-based disc numbering --- RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs index 87ffdec..21610ba 100644 --- a/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs +++ b/RedBookPlayer.GUI/ViewModels/PlayerViewModel.cs @@ -643,7 +643,7 @@ namespace RedBookPlayer.GUI.ViewModels }); } - CurrentDisc = _player.CurrentDisc; + CurrentDisc = _player.CurrentDisc + 1; CurrentTrackNumber = _player.CurrentTrackNumber; CurrentTrackIndex = _player.CurrentTrackIndex; CurrentTrackSession = _player.CurrentTrackSession; From e7e05d7137c3a462747d495ac7d16f115670962a Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 9 Oct 2021 09:34:37 -0700 Subject: [PATCH 44/46] Add track-readahead --- RedBookPlayer.Models/Discs/CompactDisc.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RedBookPlayer.Models/Discs/CompactDisc.cs b/RedBookPlayer.Models/Discs/CompactDisc.cs index b0955d5..7c97058 100644 --- a/RedBookPlayer.Models/Discs/CompactDisc.cs +++ b/RedBookPlayer.Models/Discs/CompactDisc.cs @@ -324,6 +324,11 @@ namespace RedBookPlayer.Models.Discs // Select the first index that has a sector offset greater than or equal to 0 CurrentSector = (ulong)(track?.Indexes.OrderBy(kvp => kvp.Key).First(kvp => kvp.Value >= 0).Value ?? 0); + + // Load and debug output + uint sectorCount = (uint)(track.TrackEndSector - track.TrackStartSector); + byte[] trackData = ReadSectors(sectorCount); + Console.WriteLine($"DEBUG: Track {trackNumber} - {sectorCount} sectors / {trackData.Length} bytes"); } /// From bcea992857d6d8f6181b3834f567da2fbf64568c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sat, 9 Oct 2021 16:05:12 -0700 Subject: [PATCH 45/46] Fix focus issue for keyboard input --- RedBookPlayer.GUI/Views/PlayerView.xaml | 28 +++++++++++----------- RedBookPlayer.GUI/themes/Default/view.xaml | 28 +++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/RedBookPlayer.GUI/Views/PlayerView.xaml b/RedBookPlayer.GUI/Views/PlayerView.xaml index a396c2a..d954853 100644 --- a/RedBookPlayer.GUI/Views/PlayerView.xaml +++ b/RedBookPlayer.GUI/Views/PlayerView.xaml @@ -8,19 +8,19 @@ - + - - - + + + - - - - - Rewind - Fast Forward + + + + + Rewind + Fast Forward @@ -81,12 +81,12 @@ - - diff --git a/RedBookPlayer.GUI/themes/Default/view.xaml b/RedBookPlayer.GUI/themes/Default/view.xaml index 54bfa19..e3d9e4d 100644 --- a/RedBookPlayer.GUI/themes/Default/view.xaml +++ b/RedBookPlayer.GUI/themes/Default/view.xaml @@ -8,19 +8,19 @@ - + - - - + + + - - - - - Rewind - Fast Forward + + + + + Rewind + Fast Forward @@ -81,12 +81,12 @@ - - From a189001d96c898928364889729e28ac633708eda Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 29 Oct 2021 22:20:37 -0700 Subject: [PATCH 46/46] Redefine subchannel data for later (nw) --- RedBookPlayer.Models/Hardware/Player.cs | 92 ++++--------------- .../Hardware/SubchannelData.cs | 69 ++++++++++++++ .../Hardware/SubchannelPacket.cs | 89 ++++++++++++++++++ 3 files changed, 176 insertions(+), 74 deletions(-) create mode 100644 RedBookPlayer.Models/Hardware/SubchannelData.cs create mode 100644 RedBookPlayer.Models/Hardware/SubchannelPacket.cs diff --git a/RedBookPlayer.Models/Hardware/Player.cs b/RedBookPlayer.Models/Hardware/Player.cs index cda02de..639cc6c 100644 --- a/RedBookPlayer.Models/Hardware/Player.cs +++ b/RedBookPlayer.Models/Hardware/Player.cs @@ -1332,55 +1332,6 @@ namespace RedBookPlayer.Models.Hardware #region Helpers - /// - /// Reformat raw subchannel data for a single sector - /// - /// Raw subchannel data to format - /// Dictionary mapping subchannel to formatted data - public Dictionary ConvertSingleSubchannel(byte[] subchannelData) - { - if(subchannelData == null || subchannelData.Length != 96) - return null; - - // Create the output dictionary for the formatted data - Dictionary formattedData = new Dictionary - { - ['P'] = new byte[12], - ['Q'] = new byte[12], - ['R'] = new byte[12], - ['S'] = new byte[12], - ['T'] = new byte[12], - ['U'] = new byte[12], - ['V'] = new byte[12], - ['W'] = new byte[12], - }; - - // Loop through all bytes in the subchannel data and populate - int index = -1; - for(int i = 0; i < 96; i++) - { - // Get the modulo value of the current byte - int modValue = i % 8; - if(modValue == 0) - index++; - - // Retrieve the next byte - byte b = subchannelData[i]; - - // Set the respective bit in the new byte data - formattedData['P'][index] |= (byte)(HasBitSet(b, 7) ? 1 << (7 - modValue) : 0); - formattedData['Q'][index] |= (byte)(HasBitSet(b, 6) ? 1 << (7 - modValue) : 0); - formattedData['R'][index] |= (byte)(HasBitSet(b, 5) ? 1 << (7 - modValue) : 0); - formattedData['S'][index] |= (byte)(HasBitSet(b, 4) ? 1 << (7 - modValue) : 0); - formattedData['T'][index] |= (byte)(HasBitSet(b, 3) ? 1 << (7 - modValue) : 0); - formattedData['U'][index] |= (byte)(HasBitSet(b, 2) ? 1 << (7 - modValue) : 0); - formattedData['V'][index] |= (byte)(HasBitSet(b, 1) ? 1 << (7 - modValue) : 0); - formattedData['W'][index] |= (byte)(HasBitSet(b, 0) ? 1 << (7 - modValue) : 0); - } - - return formattedData; - } - /// /// Reformat raw subchannel data for multiple sectors /// @@ -1395,14 +1346,14 @@ namespace RedBookPlayer.Models.Hardware int modValue = subchannelData.Length / 96; Dictionary formattedData = new Dictionary { - ['P'] = new byte[12 * modValue], - ['Q'] = new byte[12 * modValue], - ['R'] = new byte[12 * modValue], - ['S'] = new byte[12 * modValue], - ['T'] = new byte[12 * modValue], - ['U'] = new byte[12 * modValue], - ['V'] = new byte[12 * modValue], - ['W'] = new byte[12 * modValue], + ['P'] = new byte[8 * modValue], + ['Q'] = new byte[8 * modValue], + ['R'] = new byte[8 * modValue], + ['S'] = new byte[8 * modValue], + ['T'] = new byte[8 * modValue], + ['U'] = new byte[8 * modValue], + ['V'] = new byte[8 * modValue], + ['W'] = new byte[8 * modValue], }; // Read in 96-byte chunks @@ -1410,29 +1361,22 @@ namespace RedBookPlayer.Models.Hardware { byte[] buffer = new byte[96]; Array.Copy(subchannelData, i * 96, buffer, 0, 96); - Dictionary singleData = ConvertSingleSubchannel(buffer); + var singleSubchannel = new SubchannelData(buffer); + Dictionary singleData = singleSubchannel.ConvertData(); - Array.Copy(singleData['P'], 0, formattedData['P'], 12 * i, 12); - Array.Copy(singleData['Q'], 0, formattedData['Q'], 12 * i, 12); - Array.Copy(singleData['R'], 0, formattedData['R'], 12 * i, 12); - Array.Copy(singleData['S'], 0, formattedData['S'], 12 * i, 12); - Array.Copy(singleData['T'], 0, formattedData['T'], 12 * i, 12); - Array.Copy(singleData['U'], 0, formattedData['U'], 12 * i, 12); - Array.Copy(singleData['V'], 0, formattedData['V'], 12 * i, 12); - Array.Copy(singleData['W'], 0, formattedData['W'], 12 * i, 12); + Array.Copy(singleData['P'], 0, formattedData['P'], 8 * i, 8); + Array.Copy(singleData['Q'], 0, formattedData['Q'], 8 * i, 8); + Array.Copy(singleData['R'], 0, formattedData['R'], 8 * i, 8); + Array.Copy(singleData['S'], 0, formattedData['S'], 8 * i, 8); + Array.Copy(singleData['T'], 0, formattedData['T'], 8 * i, 8); + Array.Copy(singleData['U'], 0, formattedData['U'], 8 * i, 8); + Array.Copy(singleData['V'], 0, formattedData['V'], 8 * i, 8); + Array.Copy(singleData['W'], 0, formattedData['W'], 8 * i, 8); } return formattedData; } - /// - /// Check if a bit is set in a byte - /// - /// Byte value to check - /// Index of the bit to check - /// True if the bit was set, false otherwise - private bool HasBitSet(byte value, int bitIndex) => (value & (1 << bitIndex)) != 0; - #endregion } } \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/SubchannelData.cs b/RedBookPlayer.Models/Hardware/SubchannelData.cs new file mode 100644 index 0000000..93b2d24 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/SubchannelData.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; + +namespace RedBookPlayer.Models.Hardware +{ + /// + /// Represents subchannel data for a single sector + /// + /// + internal class SubchannelData + { + public SubchannelPacket[] Packets { get; private set; } = new SubchannelPacket[4]; + + /// + /// Create a new subchannel data from a byte array + /// + public SubchannelData(byte[] bytes) + { + if(bytes == null || bytes.Length != 96) + return; + + byte[] buffer = new byte[24]; + for(int i = 0; i < 4; i++) + { + Array.Copy(bytes, 24 * i, buffer, 0, 24); + Packets[i] = new SubchannelPacket(buffer); + } + } + + /// + /// Convert the packet data into separate named subchannels + /// + public Dictionary ConvertData() + { + if(this.Packets == null || this.Packets.Length != 4) + return null; + + // Prepare the output formatted data + Dictionary formattedData = new Dictionary + { + ['P'] = new byte[8], + ['Q'] = new byte[8], + ['R'] = new byte[8], + ['S'] = new byte[8], + ['T'] = new byte[8], + ['U'] = new byte[8], + ['V'] = new byte[8], + ['W'] = new byte[8], + }; + + // Loop through all subchannel packets + for(int i = 0; i < 4; i++) + { + Dictionary singleData = Packets[i].ConvertData(); + + Array.Copy(singleData['P'], 0, formattedData['P'], 2 * i, 2); + Array.Copy(singleData['Q'], 0, formattedData['Q'], 2 * i, 2); + Array.Copy(singleData['R'], 0, formattedData['R'], 2 * i, 2); + Array.Copy(singleData['S'], 0, formattedData['S'], 2 * i, 2); + Array.Copy(singleData['T'], 0, formattedData['T'], 2 * i, 2); + Array.Copy(singleData['U'], 0, formattedData['U'], 2 * i, 2); + Array.Copy(singleData['V'], 0, formattedData['V'], 2 * i, 2); + Array.Copy(singleData['W'], 0, formattedData['W'], 2 * i, 2); + } + + return formattedData; + } + } +} \ No newline at end of file diff --git a/RedBookPlayer.Models/Hardware/SubchannelPacket.cs b/RedBookPlayer.Models/Hardware/SubchannelPacket.cs new file mode 100644 index 0000000..2051439 --- /dev/null +++ b/RedBookPlayer.Models/Hardware/SubchannelPacket.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace RedBookPlayer.Models.Hardware +{ + /// + /// Represents a single packet of subcode data + /// + /// + internal class SubchannelPacket + { + public byte Command { get; private set; } + public byte Instruction { get; private set; } + public byte[] ParityQ { get; private set; } = new byte[2]; + public byte[] Data { get; private set; } = new byte[16]; + public byte[] ParityP { get; private set; } = new byte[4]; + + /// + /// Create a new subchannel packet from a byte array + /// + public SubchannelPacket(byte[] bytes) + { + if(bytes == null || bytes.Length != 24) + return; + + this.Command = bytes[0]; + this.Instruction = bytes[1]; + + Array.Copy(bytes, 2, this.ParityQ, 0, 2); + Array.Copy(bytes, 4, this.Data, 0, 16); + Array.Copy(bytes, 20, this.ParityP, 0, 4); + } + + /// + /// Convert the data into separate named subchannels + /// + public Dictionary ConvertData() + { + if(this.Data == null || this.Data.Length != 16) + return null; + + // Create the output dictionary for the formatted data + Dictionary formattedData = new Dictionary + { + ['P'] = new byte[2], + ['Q'] = new byte[2], + ['R'] = new byte[2], + ['S'] = new byte[2], + ['T'] = new byte[2], + ['U'] = new byte[2], + ['V'] = new byte[2], + ['W'] = new byte[2], + }; + + // Loop through all bytes in the subchannel data and populate + int index = -1; + for(int i = 0; i < 16; i++) + { + // Get the modulo value of the current byte + int modValue = i % 8; + if(modValue == 0) + index++; + + // Retrieve the next byte + byte b = this.Data[i]; + + // Set the respective bit in the new byte data + formattedData['P'][index] |= (byte)(HasBitSet(b, 7) ? 1 << (7 - modValue) : 0); + formattedData['Q'][index] |= (byte)(HasBitSet(b, 6) ? 1 << (7 - modValue) : 0); + formattedData['R'][index] |= (byte)(HasBitSet(b, 5) ? 1 << (7 - modValue) : 0); + formattedData['S'][index] |= (byte)(HasBitSet(b, 4) ? 1 << (7 - modValue) : 0); + formattedData['T'][index] |= (byte)(HasBitSet(b, 3) ? 1 << (7 - modValue) : 0); + formattedData['U'][index] |= (byte)(HasBitSet(b, 2) ? 1 << (7 - modValue) : 0); + formattedData['V'][index] |= (byte)(HasBitSet(b, 1) ? 1 << (7 - modValue) : 0); + formattedData['W'][index] |= (byte)(HasBitSet(b, 0) ? 1 << (7 - modValue) : 0); + } + + return formattedData; + } + + /// + /// Check if a bit is set in a byte + /// + /// Byte value to check + /// Index of the bit to check + /// True if the bit was set, false otherwise + private bool HasBitSet(byte value, int bitIndex) => (value & (1 << bitIndex)) != 0; + } +} \ No newline at end of file