using System.ComponentModel; using Aaru.CommonTypes.Enums; using ReactiveUI; using RedBookPlayer.Models.Discs; using RedBookPlayer.Models.Factories; namespace RedBookPlayer.Models.Hardware { public class Player : ReactiveObject { /// /// Indicate if the player is ready to be used /// public bool Initialized { get => _initialized; 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 /// /// Current track number /// public int CurrentTrackNumber { get => _currentTrackNumber; private set => this.RaiseAndSetIfChanged(ref _currentTrackNumber, value); } /// /// Current track index /// public ushort CurrentTrackIndex { get => _currentTrackIndex; private set => this.RaiseAndSetIfChanged(ref _currentTrackIndex, value); } /// /// Current track session /// public ushort CurrentTrackSession { get => _currentTrackSession; private set => this.RaiseAndSetIfChanged(ref _currentTrackSession, value); } /// /// Current sector number /// public ulong CurrentSector { get => _currentSector; private set => this.RaiseAndSetIfChanged(ref _currentSector, value); } /// /// Represents the sector starting the section /// public ulong SectionStartSector { get => _sectionStartSector; protected set => this.RaiseAndSetIfChanged(ref _sectionStartSector, value); } /// /// Represents if the disc has a hidden track /// public bool HiddenTrack { get => _hasHiddenTrack; private set => this.RaiseAndSetIfChanged(ref _hasHiddenTrack, value); } /// /// Represents the 4CH flag [CompactDisc only] /// public bool QuadChannel { get => _quadChannel; private set => this.RaiseAndSetIfChanged(ref _quadChannel, value); } /// /// Represents the DATA flag [CompactDisc only] /// public bool IsDataTrack { get => _isDataTrack; private set => this.RaiseAndSetIfChanged(ref _isDataTrack, value); } /// /// Represents the DCP flag [CompactDisc only] /// public bool CopyAllowed { get => _copyAllowed; private set => this.RaiseAndSetIfChanged(ref _copyAllowed, value); } /// /// Represents the PRE flag [CompactDisc only] /// public bool TrackHasEmphasis { get => _trackHasEmphasis; private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value); } /// /// Represents the total tracks on the disc /// public int TotalTracks => _opticalDiscs[CurrentDisc]?.TotalTracks ?? 0; /// /// Represents the total indices on the disc /// public int TotalIndexes => _opticalDiscs[CurrentDisc]?.TotalIndexes ?? 0; /// /// Total sectors in the image /// public ulong TotalSectors => _opticalDiscs[CurrentDisc]?.TotalSectors ?? 0; /// /// Represents the time adjustment offset for the disc /// public ulong TimeOffset => _opticalDiscs[CurrentDisc]?.TimeOffset ?? 0; /// /// Represents the total playing time for the disc /// public ulong TotalTime => _opticalDiscs[CurrentDisc]?.TotalTime ?? 0; private int _currentTrackNumber; private ushort _currentTrackIndex; private ushort _currentTrackSession; private ulong _currentSector; private ulong _sectionStartSector; private bool _hasHiddenTrack; private bool _quadChannel; private bool _isDataTrack; private bool _copyAllowed; private bool _trackHasEmphasis; #endregion #region SoundOutput Passthrough /// /// Indicates the current player state /// public PlayerState PlayerState { get => _playerState; 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); } /// /// 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 /// public int Volume { get => _volume; private set => this.RaiseAndSetIfChanged(ref _volume, value); } private PlayerState _playerState; private DataPlayback _dataPlayback; private RepeatMode _repeatMode; private bool _applyDeEmphasis; private int _volume; #endregion #region Private State Variables /// /// Sound output handling class /// /// TODO: Link sound outputs to discs in a 1:1 configuration private readonly SoundOutput[] _soundOutputs = new SoundOutput[5]; /// /// OpticalDisc object /// /// TODO: Make the number of discs in the changer configurable private OpticalDiscBase[] _opticalDiscs = new OpticalDiscBase[5]; /// /// Last volume for mute toggling /// private int? _lastVolume = null; #endregion /// /// Constructor /// /// Default volume between 0 and 100 to use when starting playback public Player(int defaultVolume) { Initialized = false; _currentDisc = 0; for (int i = 0; i < 5; i++) { _soundOutputs[i] = new SoundOutput(defaultVolume); _soundOutputs[i].SetDeEmphasis(false); } } /// /// Initializes player from a given image path /// /// Path to the disc image /// Options to pass to the optical disc factory /// RepeatMode for sound output /// True if playback should begin immediately, false otherwise public void Init(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay) { // Reset initialization Initialized = false; // Initalize the disc _opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay); if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; // Add event handling for the optical disc _opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged; // Initialize the sound output _soundOutputs[CurrentDisc].Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay); if(_soundOutputs[CurrentDisc] == null || !_soundOutputs[CurrentDisc].Initialized) return; // Add event handling for the sound output _soundOutputs[CurrentDisc].PropertyChanged += SoundOutputStateChanged; // Mark the player as ready Initialized = true; // Force a refresh of the state information OpticalDiscStateChanged(this, null); SoundOutputStateChanged(this, null); } #region Playback /// /// Begin playback /// public void Play() { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutputs[CurrentDisc] == null) return; else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Stopped) return; _soundOutputs[CurrentDisc].Play(); _opticalDiscs[CurrentDisc].SetTotalIndexes(); PlayerState = PlayerState.Playing; } /// /// Pause current playback /// public void Pause() { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutputs[CurrentDisc] == null) return; else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing) return; _soundOutputs[CurrentDisc]?.Pause(); PlayerState = PlayerState.Paused; } /// /// Toggle current playback /// public void TogglePlayback() { switch(PlayerState) { case PlayerState.NoDisc: break; case PlayerState.Stopped: Play(); break; case PlayerState.Paused: Play(); break; case PlayerState.Playing: Pause(); break; } } /// /// Stop current playback /// public void Stop() { 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) return; _soundOutputs[CurrentDisc].Stop(); _opticalDiscs[CurrentDisc].LoadFirstTrack(); PlayerState = PlayerState.Stopped; } /// /// Eject the currently loaded disc /// public void Eject() { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; else if(_soundOutputs[CurrentDisc] == null) return; Stop(); _soundOutputs[CurrentDisc].Eject(); _opticalDiscs[CurrentDisc] = null; PlayerState = PlayerState.NoDisc; Initialized = false; } /// /// 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(); } /// /// 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(); } /// /// 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(); } /// /// 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(); } /// /// Fast-forward playback by 75 sectors /// public void FastForward() { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; _opticalDiscs[CurrentDisc].SetCurrentSector(_opticalDiscs[CurrentDisc].CurrentSector + 75); } /// /// Rewind playback by 75 sectors /// public void Rewind() { if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized) return; _opticalDiscs[CurrentDisc].SetCurrentSector(_opticalDiscs[CurrentDisc].CurrentSector - 75); } #endregion #region Volume /// /// Increment the volume value /// public void VolumeUp() => SetVolume(Volume + 1); /// /// Decrement the volume value /// public void VolumeDown() => SetVolume(Volume + 1); /// /// Set the value for the volume /// /// New volume value public void SetVolume(int volume) => _soundOutputs[CurrentDisc]?.SetVolume(volume); /// /// Temporarily mute playback /// public void ToggleMute() { if(_lastVolume == null) { _lastVolume = Volume; SetVolume(0); } else { SetVolume(_lastVolume.Value); _lastVolume = null; } } #endregion #region Emphasis /// /// Enable de-emphasis /// public void EnableDeEmphasis() => SetDeEmphasis(true); /// /// Disable de-emphasis /// public void DisableDeEmphasis() => SetDeEmphasis(false); /// /// Toggle de-emphasis /// public void ToggleDeEmphasis() => SetDeEmphasis(!ApplyDeEmphasis); /// /// Set de-emphasis status /// /// private void SetDeEmphasis(bool apply) => _soundOutputs[CurrentDisc]?.SetDeEmphasis(apply); #endregion #region Helpers /// /// 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); /// /// Extract all tracks from the image to WAV /// Output path to write data to public void ExtractAllTracksToWav(string outputDirectory) => _opticalDiscs[CurrentDisc]?.ExtractAllTracksToWav(outputDirectory); /// /// Set data playback method [CompactDisc only] /// /// New playback value public void SetDataPlayback(DataPlayback dataPlayback) { if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) compactDisc.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; } /// /// Set repeat mode /// /// New repeat mode value public void SetRepeatMode(RepeatMode repeatMode) => _soundOutputs[CurrentDisc]?.SetRepeatMode(repeatMode); /// /// Set the value for session handling [CompactDisc only] /// /// New session handling value public void SetSessionHandling(SessionHandling sessionHandling) { if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) compactDisc.SessionHandling = sessionHandling; } /// /// Update the player from the current OpticalDisc /// private void OpticalDiscStateChanged(object sender, PropertyChangedEventArgs e) { CurrentTrackNumber = _opticalDiscs[CurrentDisc].CurrentTrackNumber; CurrentTrackIndex = _opticalDiscs[CurrentDisc].CurrentTrackIndex; CurrentSector = _opticalDiscs[CurrentDisc].CurrentSector; SectionStartSector = _opticalDiscs[CurrentDisc].SectionStartSector; HiddenTrack = TimeOffset > 150; if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc) { QuadChannel = compactDisc.QuadChannel; IsDataTrack = compactDisc.IsDataTrack; CopyAllowed = compactDisc.CopyAllowed; TrackHasEmphasis = compactDisc.TrackHasEmphasis; } else { QuadChannel = false; IsDataTrack = _opticalDiscs[CurrentDisc].TrackType != TrackType.Audio; CopyAllowed = false; TrackHasEmphasis = false; } } /// /// Update the player from the current SoundOutput /// private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e) { PlayerState = _soundOutputs[CurrentDisc].PlayerState; RepeatMode = _soundOutputs[CurrentDisc].RepeatMode; ApplyDeEmphasis = _soundOutputs[CurrentDisc].ApplyDeEmphasis; Volume = _soundOutputs[CurrentDisc].Volume; } #endregion } }