Further separation of code

- Create a new OpticalDisc base class for future use
- Create a new CompactDisc class for most current usage
- Separate out some player logic from the UI
- Remove all direct references to the disc from the UI
- Player initializtion starts from path
This commit is contained in:
Matt Nadareski
2021-06-29 12:08:08 -07:00
parent 14ce896567
commit 6f7095a13f
7 changed files with 569 additions and 387 deletions

View File

@@ -8,24 +8,17 @@ using Aaru.Decoders.CD;
using Aaru.Helpers; using Aaru.Helpers;
using static Aaru.Decoders.CD.FullTOC; using static Aaru.Decoders.CD.FullTOC;
namespace RedBookPlayer namespace RedBookPlayer.Discs
{ {
public class PlayableDisc public class CompactDisc : OpticalDisc
{ {
#region Public Fields #region Public Fields
/// <summary> /// <inheritdoc/>
/// Indicate if the disc is ready to be used public override int CurrentTrackNumber
/// </summary>
public bool Initialized { get; private set; } = false;
/// <summary>
/// Current track number
/// </summary>
public int CurrentTrackNumber
{ {
get => _currentTrackNumber; get => _currentTrackNumber;
set protected set
{ {
// Unset image means we can't do anything // Unset image means we can't do anything
if(_image == null) if(_image == null)
@@ -54,8 +47,6 @@ namespace RedBookPlayer
// Set track flags from subchannel data, if possible // Set track flags from subchannel data, if possible
SetTrackFlags(track); SetTrackFlags(track);
ApplyDeEmphasis = TrackHasEmphasis;
TotalIndexes = track.Indexes.Keys.Max(); TotalIndexes = track.Indexes.Keys.Max();
CurrentTrackIndex = track.Indexes.Keys.Min(); CurrentTrackIndex = track.Indexes.Keys.Min();
@@ -73,13 +64,11 @@ namespace RedBookPlayer
} }
} }
/// <summary> /// <inheritdoc/>
/// Current track index public override ushort CurrentTrackIndex
/// </summary>
public ushort CurrentTrackIndex
{ {
get => _currentTrackIndex; get => _currentTrackIndex;
set protected set
{ {
// Unset image means we can't do anything // Unset image means we can't do anything
if(_image == null) if(_image == null)
@@ -102,10 +91,8 @@ namespace RedBookPlayer
} }
} }
/// <summary> /// <inheritdoc/>
/// Current sector number public override ulong CurrentSector
/// </summary>
public ulong CurrentSector
{ {
get => _currentSector; get => _currentSector;
set set
@@ -146,14 +133,14 @@ namespace RedBookPlayer
} }
/// <summary> /// <summary>
/// Represents the PRE flag /// Represents the 4CH flag
/// </summary> /// </summary>
public bool TrackHasEmphasis { get; private set; } = false; public bool QuadChannel { get; private set; } = false;
/// <summary> /// <summary>
/// Indicates if de-emphasis should be applied /// Represents the DATA flag
/// </summary> /// </summary>
public bool ApplyDeEmphasis { get; private set; } = false; public bool IsDataTrack => TrackType != TrackType.Audio;
/// <summary> /// <summary>
/// Represents the DCP flag /// Represents the DCP flag
@@ -161,54 +148,14 @@ namespace RedBookPlayer
public bool CopyAllowed { get; private set; } = false; public bool CopyAllowed { get; private set; } = false;
/// <summary> /// <summary>
/// Represents the track type /// Represents the PRE flag
/// </summary> /// </summary>
public TrackType TrackType { get; private set; } public bool TrackHasEmphasis { get; private set; } = false;
/// <summary>
/// Represents the 4CH flag
/// </summary>
public bool QuadChannel { get; private set; } = false;
/// <summary>
/// Represents the sector starting the section
/// </summary>
public ulong SectionStartSector { get; private set; }
/// <summary>
/// Represents the total tracks on the disc
/// </summary>
public int TotalTracks { get; private set; } = 0;
/// <summary>
/// Represents the total indices on the disc
/// </summary>
public int TotalIndexes { get; private set; } = 0;
/// <summary>
/// Total sectors in the image
/// </summary>
public ulong TotalSectors => _image.Info.Sectors;
/// <summary>
/// Represents the time adjustment offset for the disc
/// </summary>
public ulong TimeOffset { get; private set; } = 0;
/// <summary>
/// Represents the total playing time for the disc
/// </summary>
public ulong TotalTime { get; private set; } = 0;
#endregion #endregion
#region Private State Variables #region Private State Variables
/// <summary>
/// Currently loaded disc image
/// </summary>
private IOpticalMediaImage _image;
/// <summary> /// <summary>
/// Current track number /// Current track number
/// </summary> /// </summary>
@@ -231,12 +178,8 @@ namespace RedBookPlayer
#endregion #endregion
/// <summary> /// <inheritdoc/>
/// Initialize the disc with a given image public override void Init(IOpticalMediaImage image, bool autoPlay = false)
/// </summary>
/// <param name="image">Aaruformat image to load</param>
/// <param name="autoPlay">True if playback should begin immediately, false otherwise</param>
public void Init(IOpticalMediaImage image, bool autoPlay = false)
{ {
// If the image is null, we can't do anything // If the image is null, we can't do anything
if(image == null) if(image == null)
@@ -268,45 +211,8 @@ namespace RedBookPlayer
#region Seeking #region Seeking
/// <summary> /// <inheritdoc/>
/// Try to move to the next track, wrapping around if necessary public override bool NextIndex(bool changeTrack)
/// </summary>
public void NextTrack()
{
if(_image == null)
return;
CurrentTrackNumber++;
LoadTrack(CurrentTrackNumber);
}
/// <summary>
/// Try to move to the previous track, wrapping around if necessary
/// </summary>
public void PreviousTrack()
{
if(_image == null)
return;
if(CurrentSector < (ulong)_image.Tracks[CurrentTrackNumber].Indexes[1] + 75)
{
if(App.Settings.AllowSkipHiddenTrack && CurrentTrackNumber == 0 && CurrentSector >= 75)
CurrentSector = 0;
else
CurrentTrackNumber--;
}
else
CurrentTrackNumber--;
LoadTrack(CurrentTrackNumber);
}
/// <summary>
/// Try to move to the next track index
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
/// <returns>True if the track was changed, false otherwise</returns>
public bool NextIndex(bool changeTrack)
{ {
if(_image == null) if(_image == null)
return false; return false;
@@ -328,12 +234,8 @@ namespace RedBookPlayer
return false; return false;
} }
/// <summary> /// <inheritdoc/>
/// Try to move to the previous track index public override bool PreviousIndex(bool changeTrack)
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
/// <returns>True if the track was changed, false otherwise</returns>
public bool PreviousIndex(bool changeTrack)
{ {
if(_image == null) if(_image == null)
return false; return false;
@@ -355,59 +257,19 @@ namespace RedBookPlayer
return false; return false;
} }
/// <summary>
/// Fast-forward playback by 75 sectors, if possible
/// </summary>
public void FastForward()
{
if(_image == null)
return;
CurrentSector = Math.Min(_image.Info.Sectors - 1, CurrentSector + 75);
}
/// <summary>
/// Rewind playback by 75 sectors, if possible
/// </summary>
public void Rewind()
{
if(_image == null)
return;
if(CurrentSector >= 75)
CurrentSector -= 75;
}
/// <summary>
/// Toggle de-emphasis processing
/// </summary>
/// <param name="enable">True to apply de-emphasis, false otherwise</param>
public void ToggleDeEmphasis(bool enable) => ApplyDeEmphasis = enable;
#endregion #endregion
#region Helpers #region Helpers
/// <summary> /// <inheritdoc/>
/// Load the first valid track in the image public override void LoadFirstTrack()
/// </summary>
public void LoadFirstTrack()
{ {
CurrentTrackNumber = 0; CurrentTrackNumber = 0;
LoadTrack(CurrentTrackNumber); LoadTrack(CurrentTrackNumber);
} }
/// <summary> /// <inheritdoc/>
/// Read sector data from the base image starting from the current sector public override void SetTotalIndexes()
/// </summary>
/// <param name="sectorsToRead">Current number of sectors to read</param>
/// <returns>Byte array representing the read sectors, if possible</returns>
public byte[] ReadSectors(uint sectorsToRead) => _image.ReadSectors(CurrentSector, sectorsToRead);
/// <summary>
/// Set the total indexes from the current track
/// </summary>
public void SetTotalIndexes()
{ {
if(_image == null) if(_image == null)
return; return;
@@ -415,6 +277,20 @@ namespace RedBookPlayer
TotalIndexes = _image.Tracks[CurrentTrackNumber].Indexes.Keys.Max(); TotalIndexes = _image.Tracks[CurrentTrackNumber].Indexes.Keys.Max();
} }
/// <inheritdoc/>
protected override void LoadTrack(int track)
{
if(_image == null)
return;
if(track < 0 || track >= _image.Tracks.Count)
return;
ushort firstIndex = _image.Tracks[track].Indexes.Keys.Min();
int firstSector = _image.Tracks[track].Indexes[firstIndex];
CurrentSector = (ulong)(firstSector >= 0 ? firstSector : _image.Tracks[track].Indexes[1]);
}
/// <summary> /// <summary>
/// Generate a CDFullTOC object from the current image /// Generate a CDFullTOC object from the current image
/// </summary> /// </summary>
@@ -639,23 +515,6 @@ namespace RedBookPlayer
return true; return true;
} }
/// <summary>
/// Load the desired track, if possible
/// </summary>
/// <param name="track">Track number to load</param>
private void LoadTrack(int track)
{
if(_image == null)
return;
if(track < 0 || track >= _image.Tracks.Count)
return;
ushort firstIndex = _image.Tracks[track].Indexes.Keys.Min();
int firstSector = _image.Tracks[track].Indexes[firstIndex];
CurrentSector = (ulong)(firstSector >= 0 ? firstSector : _image.Tracks[track].Indexes[1]);
}
/// <summary> /// <summary>
/// Set default track flags for the current track /// Set default track flags for the current track
/// </summary> /// </summary>

View File

@@ -0,0 +1,166 @@
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
namespace RedBookPlayer.Discs
{
public abstract class OpticalDisc
{
#region Public Fields
/// <summary>
/// Indicate if the disc is ready to be used
/// </summary>
public bool Initialized { get; protected set; } = false;
/// <summary>
/// Current track number
/// </summary>
public abstract int CurrentTrackNumber { get; protected set; }
/// <summary>
/// Current track index
/// </summary>
public abstract ushort CurrentTrackIndex { get; protected set; }
/// <summary>
/// Current sector number
/// </summary>
public abstract ulong CurrentSector { get; set; }
/// <summary>
/// Represents the sector starting the section
/// </summary>
public ulong SectionStartSector { get; protected set; }
/// <summary>
/// Number of bytes per sector for the current track
/// </summary>
public int BytesPerSector => _image.Tracks[CurrentTrackNumber].TrackBytesPerSector;
/// <summary>
/// Represents the track type
/// </summary>
public TrackType TrackType { get; protected set; }
/// <summary>
/// Represents the total tracks on the disc
/// </summary>
public int TotalTracks { get; protected set; } = 0;
/// <summary>
/// Represents the total indices on the disc
/// </summary>
public int TotalIndexes { get; protected set; } = 0;
/// <summary>
/// Total sectors in the image
/// </summary>
public ulong TotalSectors => _image.Info.Sectors;
/// <summary>
/// Represents the time adjustment offset for the disc
/// </summary>
public ulong TimeOffset { get; protected set; } = 0;
/// <summary>
/// Represents the total playing time for the disc
/// </summary>
public ulong TotalTime { get; protected set; } = 0;
#endregion
#region Protected State Variables
/// <summary>
/// Currently loaded disc image
/// </summary>
protected IOpticalMediaImage _image;
#endregion
/// <summary>
/// Initialize the disc with a given image
/// </summary>
/// <param name="image">Aaruformat image to load</param>
/// <param name="autoPlay">True if playback should begin immediately, false otherwise</param>
public abstract void Init(IOpticalMediaImage image, bool autoPlay = false);
#region Seeking
/// <summary>
/// Try to move to the next track, wrapping around if necessary
/// </summary>
public void NextTrack()
{
if(_image == null)
return;
CurrentTrackNumber++;
LoadTrack(CurrentTrackNumber);
}
/// <summary>
/// Try to move to the previous track, wrapping around if necessary
/// </summary>
public void PreviousTrack()
{
if(_image == null)
return;
if(CurrentSector < (ulong)_image.Tracks[CurrentTrackNumber].Indexes[1] + 75)
{
if(App.Settings.AllowSkipHiddenTrack && CurrentTrackNumber == 0 && CurrentSector >= 75)
CurrentSector = 0;
else
CurrentTrackNumber--;
}
else
CurrentTrackNumber--;
LoadTrack(CurrentTrackNumber);
}
/// <summary>
/// Try to move to the next track index
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
/// <returns>True if the track was changed, false otherwise</returns>
public abstract bool NextIndex(bool changeTrack);
/// <summary>
/// Try to move to the previous track index
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
/// <returns>True if the track was changed, false otherwise</returns>
public abstract bool PreviousIndex(bool changeTrack);
#endregion
#region Helpers
/// <summary>
/// Load the first valid track in the image
/// </summary>
public abstract void LoadFirstTrack();
/// <summary>
/// Read sector data from the base image starting from the current sector
/// </summary>
/// <param name="sectorsToRead">Current number of sectors to read</param>
/// <returns>Byte array representing the read sectors, if possible</returns>
public byte[] ReadSectors(uint sectorsToRead) => _image.ReadSectors(CurrentSector, sectorsToRead);
/// <summary>
/// Set the total indexes from the current track
/// </summary>
public abstract void SetTotalIndexes();
/// <summary>
/// Load the desired track, if possible
/// </summary>
/// <param name="track">Track number to load</param>
protected abstract void LoadTrack(int track);
#endregion
}
}

View File

@@ -0,0 +1,80 @@
using Aaru.CommonTypes.Interfaces;
using Aaru.CommonTypes.Metadata;
namespace RedBookPlayer.Discs
{
public static class OpticalDiscFactory
{
/// <summary>
/// Generate an OpticalDisc from an input IOpticalMediaImage
/// </summary>
/// <param name="image">IOpticalMediaImage to create from</param>
/// <param name="autoPlay">True if the image should be playable immediately, false otherwise</param>
/// <returns>Instantiated OpticalDisc, if possible</returns>
public static OpticalDisc GenerateFromImage(IOpticalMediaImage image, bool autoPlay)
{
// If the image is not usable, we don't do anything
if(!IsUsableImage(image))
return null;
// Create the output object
OpticalDisc opticalDisc;
// Create the proper disc type
switch(GetMediaType(image))
{
case "Compact Disc":
case "GD":
opticalDisc = new CompactDisc();
break;
default:
opticalDisc = null;
break;
}
// Null image means we don't do anything
if(opticalDisc == null)
return opticalDisc;
// Instantiate the disc and return
opticalDisc.Init(image, autoPlay);
return opticalDisc;
}
/// <summary>
/// Gets the human-readable media type from an image
/// </summary>
/// <param name="image">Media image to check</param>
/// <returns>Type from the image, empty string on error</returns>
/// <remarks>TODO: Can we be more granular with sub types?</remarks>
private static string GetMediaType(IOpticalMediaImage image)
{
// Null image means we don't do anything
if(image == null)
return string.Empty;
(string type, string _) = MediaType.MediaTypeToString(image.Info.MediaType);
return type;
}
/// <summary>
/// Indicates if the image is considered "usable" or not
/// </summary>
/// <param name="image">Aaruformat image file</param>
/// <returns>True if the image is playble, false otherwise</returns>
private static bool IsUsableImage(IOpticalMediaImage image)
{
// Invalid images can't be used
if(image == null)
return false;
// Determine based on media type
return GetMediaType(image) switch
{
"Compact Disc" => true,
"GD" => true, // Requires TOC generation
_ => false,
};
}
}
}

View File

@@ -1,9 +1,14 @@
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Aaru.CommonTypes.Enums;
using Aaru.DiscImages;
using Aaru.Filters;
using CSCore.SoundOut; using CSCore.SoundOut;
using NWaves.Audio; using NWaves.Audio;
using NWaves.Filters.BiQuad; using NWaves.Filters.BiQuad;
using RedBookPlayer.Discs;
namespace RedBookPlayer namespace RedBookPlayer
{ {
@@ -16,6 +21,11 @@ namespace RedBookPlayer
/// </summary> /// </summary>
public bool Initialized { get; private set; } = false; public bool Initialized { get; private set; } = false;
/// <summary>
/// Indicates if de-emphasis should be applied
/// </summary>
public bool ApplyDeEmphasis { get; private set; } = false;
/// <summary> /// <summary>
/// Indicate if the disc is playing /// Indicate if the disc is playing
/// </summary> /// </summary>
@@ -25,16 +35,16 @@ namespace RedBookPlayer
#region Private State Variables #region Private State Variables
/// <summary>
/// OpticalDisc object
/// </summary>
private OpticalDisc _opticalDisc;
/// <summary> /// <summary>
/// Current position in the sector /// Current position in the sector
/// </summary> /// </summary>
private int _currentSectorReadPosition = 0; private int _currentSectorReadPosition = 0;
/// <summary>
/// PlaybableDisc object
/// </summary>
private PlayableDisc _playableDisc;
/// <summary> /// <summary>
/// Data provider for sound output /// Data provider for sound output
/// </summary> /// </summary>
@@ -63,20 +73,47 @@ namespace RedBookPlayer
#endregion #endregion
/// <summary> /// <summary>
/// Initialize the player with a given image /// Initialize the player with a given image path
/// </summary> /// </summary>
/// <param name="disc">Initialized disc image</param> /// <param name="path">Path to the disc image</param>
/// <param name="autoPlay">True if playback should begin immediately, false otherwise</param> /// <param name="autoPlay">True if playback should begin immediately, false otherwise</param>
public void Init(PlayableDisc disc, bool autoPlay = false) public void Init(string path, bool autoPlay = false)
{ {
// If the disc is not initalized, we can't do anything // Reset the internal state for initialization
if(!disc.Initialized) Initialized = false;
ApplyDeEmphasis = false;
_opticalDisc = null;
try
{
// Validate the image exists
if(string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return; return;
// Set the internal reference to the disc // Load the disc image to memory
_playableDisc = disc; var image = new AaruFormat();
var filter = new ZZZNoFilter();
filter.Open(path);
image.Open(filter);
// Setup the de-emphasis filters // Generate and instantiate the disc
_opticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay);
}
catch
{
// All errors mean an invalid image in some way
return;
}
// If we have an unusable disc, just return
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
// Enable de-emphasis for CDs, if necessary
if(_opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
// Setup de-emphasis filters
SetupFilters(); SetupFilters();
// Setup the audio output // Setup the audio output
@@ -111,27 +148,27 @@ namespace RedBookPlayer
do do
{ {
// Attempt to read 2 more sectors than requested // Attempt to read 2 more sectors than requested
sectorsToRead = ((ulong)count / 2352) + 2; sectorsToRead = ((ulong)(count / _opticalDisc.BytesPerSector)) + 2;
zeroSectorsAmount = 0; zeroSectorsAmount = 0;
// Avoid overreads by padding with 0-byte data at the end // Avoid overreads by padding with 0-byte data at the end
if(_playableDisc.CurrentSector + sectorsToRead > _playableDisc.TotalSectors) if(_opticalDisc.CurrentSector + sectorsToRead > _opticalDisc.TotalSectors)
{ {
ulong oldSectorsToRead = sectorsToRead; ulong oldSectorsToRead = sectorsToRead;
sectorsToRead = _playableDisc.TotalSectors - _playableDisc.CurrentSector; sectorsToRead = _opticalDisc.TotalSectors - _opticalDisc.CurrentSector;
zeroSectorsAmount = oldSectorsToRead - sectorsToRead; zeroSectorsAmount = oldSectorsToRead - sectorsToRead;
} }
// TODO: Figure out when this value could be negative // TODO: Figure out when this value could be negative
if(sectorsToRead <= 0) if(sectorsToRead <= 0)
{ {
_playableDisc.LoadFirstTrack(); _opticalDisc.LoadFirstTrack();
_currentSectorReadPosition = 0; _currentSectorReadPosition = 0;
} }
} while(sectorsToRead <= 0); } while(sectorsToRead <= 0);
// Create padding data for overreads // Create padding data for overreads
byte[] zeroSectors = new byte[zeroSectorsAmount * 2352]; byte[] zeroSectors = new byte[(int)zeroSectorsAmount * _opticalDisc.BytesPerSector];
byte[] audioData; byte[] audioData;
// Attempt to read the required number of sectors // Attempt to read the required number of sectors
@@ -141,12 +178,12 @@ namespace RedBookPlayer
{ {
try try
{ {
return _playableDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray();
} }
catch(ArgumentOutOfRangeException) catch(ArgumentOutOfRangeException)
{ {
_playableDisc.LoadFirstTrack(); _opticalDisc.LoadFirstTrack();
return _playableDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray(); return _opticalDisc.ReadSectors((uint)sectorsToRead).Concat(zeroSectors).ToArray();
} }
} }
}); });
@@ -167,7 +204,7 @@ namespace RedBookPlayer
Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, Math.Min(count, audioData.Length - _currentSectorReadPosition)); Array.Copy(audioData, _currentSectorReadPosition, audioDataSegment, 0, Math.Min(count, audioData.Length - _currentSectorReadPosition));
// Apply de-emphasis filtering, only if enabled // Apply de-emphasis filtering, only if enabled
if(_playableDisc.ApplyDeEmphasis) if(ApplyDeEmphasis)
{ {
float[][] floatAudioData = new float[2][]; float[][] floatAudioData = new float[2][];
floatAudioData[0] = new float[audioDataSegment.Length / 4]; floatAudioData[0] = new float[audioDataSegment.Length / 4];
@@ -188,10 +225,10 @@ namespace RedBookPlayer
// Set the read position in the sector for easier access // Set the read position in the sector for easier access
_currentSectorReadPosition += count; _currentSectorReadPosition += count;
if(_currentSectorReadPosition >= 2352) if(_currentSectorReadPosition >= _opticalDisc.BytesPerSector)
{ {
_playableDisc.CurrentSector += (ulong)_currentSectorReadPosition / 2352; _opticalDisc.CurrentSector += (ulong)(_currentSectorReadPosition / _opticalDisc.BytesPerSector);
_currentSectorReadPosition %= 2352; _currentSectorReadPosition %= _opticalDisc.BytesPerSector;
} }
return count; return count;
@@ -200,44 +237,209 @@ namespace RedBookPlayer
#region Playback #region Playback
/// <summary> /// <summary>
/// Start audio playback /// Toggle audio playback
/// </summary> /// </summary>
public void Play() /// <param name="start">True to start playback, false to pause</param>
public void TogglePlayPause(bool start)
{ {
if(!_playableDisc.Initialized) if(_opticalDisc == null || !_opticalDisc.Initialized)
return; return;
if(start)
{
_soundOut.Play(); _soundOut.Play();
_playableDisc.SetTotalIndexes(); _opticalDisc.SetTotalIndexes();
} }
else
/// <summary>
/// Pause the current audio playback
/// </summary>
public void Pause()
{ {
if(!_playableDisc.Initialized)
return;
_soundOut.Stop(); _soundOut.Stop();
} }
}
/// <summary> /// <summary>
/// Stop the current audio playback /// Stop the current audio playback
/// </summary> /// </summary>
public void Stop() public void Stop()
{ {
if(!_playableDisc.Initialized) if(_opticalDisc == null || !_opticalDisc.Initialized)
return; return;
_soundOut.Stop(); _soundOut.Stop();
_playableDisc.LoadFirstTrack(); _opticalDisc.LoadFirstTrack();
}
/// <summary>
/// Move to the next playable track
/// </summary>
public void NextTrack()
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
bool wasPlaying = Playing;
if(wasPlaying) TogglePlayPause(false);
_opticalDisc.NextTrack();
if(_opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
if(wasPlaying) TogglePlayPause(true);
}
/// <summary>
/// Move to the previous playable track
/// </summary>
public void PreviousTrack()
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
bool wasPlaying = Playing;
if(wasPlaying) TogglePlayPause(false);
_opticalDisc.PreviousTrack();
if(_opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
if(wasPlaying) TogglePlayPause(true);
}
/// <summary>
/// Move to the next index
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
public void NextIndex(bool changeTrack)
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
bool wasPlaying = Playing;
if(wasPlaying) TogglePlayPause(false);
_opticalDisc.NextIndex(changeTrack);
if(_opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
if(wasPlaying) TogglePlayPause(true);
}
/// <summary>
/// Move to the previous index
/// </summary>
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
public void PreviousIndex(bool changeTrack)
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
bool wasPlaying = Playing;
if(wasPlaying) TogglePlayPause(false);
_opticalDisc.PreviousIndex(changeTrack);
if(_opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
if(wasPlaying) TogglePlayPause(true);
}
/// <summary>
/// Fast-forward playback by 75 sectors, if possible
/// </summary>
public void FastForward()
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
_opticalDisc.CurrentSector = Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75);
}
/// <summary>
/// Rewind playback by 75 sectors, if possible
/// </summary>
public void Rewind()
{
if(_opticalDisc == null || !_opticalDisc.Initialized)
return;
if(_opticalDisc.CurrentSector >= 75)
_opticalDisc.CurrentSector -= 75;
} }
#endregion #endregion
#region Helpers #region Helpers
/// <summary>
/// Generate the digit string to be interpreted by the frontend
/// </summary>
/// <returns>String representing the digits for the frontend</returns>
public string GenerateDigitString()
{
// If the disc isn't initialized, return all '-' characters
if(_opticalDisc == null || !_opticalDisc.Initialized)
return string.Empty.PadLeft(20, '-');
// Otherwise, take the current time into account
ulong sectorTime = _opticalDisc.CurrentSector;
if(_opticalDisc.SectionStartSector != 0)
sectorTime -= _opticalDisc.SectionStartSector;
else
sectorTime += _opticalDisc.TimeOffset;
int[] numbers = new int[]
{
_opticalDisc.CurrentTrackNumber + 1,
_opticalDisc.CurrentTrackIndex,
(int)(sectorTime / (75 * 60)),
(int)(sectorTime / 75 % 60),
(int)(sectorTime % 75),
_opticalDisc.TotalTracks,
_opticalDisc.TotalIndexes,
(int)(_opticalDisc.TotalTime / (75 * 60)),
(int)(_opticalDisc.TotalTime / 75 % 60),
(int)(_opticalDisc.TotalTime % 75),
};
return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2)));
}
/// <summary>
/// Toggle de-emphasis processing
/// </summary>
/// <param name="enable">True to apply de-emphasis, false otherwise</param>
public void ToggleDeEmphasis(bool enable) => ApplyDeEmphasis = enable;
/// <summary>
/// Update the data context for the frontend
/// </summary>
/// <param name="dataContext">Data context to be updated</param>
public void UpdateDataContext(PlayerViewModel dataContext)
{
if(!Initialized || dataContext == null)
return;
dataContext.HiddenTrack = _opticalDisc.TimeOffset > 150;
dataContext.ApplyDeEmphasis = ApplyDeEmphasis;
if(_opticalDisc is CompactDisc compactDisc)
{
dataContext.QuadChannel = compactDisc.QuadChannel;
dataContext.IsDataTrack = compactDisc.IsDataTrack;
dataContext.CopyAllowed = compactDisc.CopyAllowed;
dataContext.TrackHasEmphasis = compactDisc.TrackHasEmphasis;
}
else
{
dataContext.QuadChannel = false;
dataContext.IsDataTrack = _opticalDisc.TrackType != TrackType.Audio;
dataContext.CopyAllowed = false;
dataContext.TrackHasEmphasis = false;
}
}
/// <summary> /// <summary>
/// Sets or resets the de-emphasis filters /// Sets or resets the de-emphasis filters
/// </summary> /// </summary>

View File

@@ -86,8 +86,8 @@
</Button> </Button>
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsAudioTrack}">AUDIO</TextBlock> <TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding IsDataTrack}">AUDIO</TextBlock>
<TextBlock Margin="0,0,16,0" IsVisible="{Binding IsAudioTrack}">AUDIO</TextBlock> <TextBlock Margin="0,0,16,0" IsVisible="{Binding !IsDataTrack}">AUDIO</TextBlock>
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsDataTrack}">DATA</TextBlock> <TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsDataTrack}">DATA</TextBlock>
<TextBlock Margin="0,0,16,0" IsVisible="{Binding IsDataTrack}">DATA</TextBlock> <TextBlock Margin="0,0,16,0" IsVisible="{Binding IsDataTrack}">DATA</TextBlock>
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !TrackHasEmphasis}">EMPHASIS</TextBlock> <TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !TrackHasEmphasis}">EMPHASIS</TextBlock>

View File

@@ -4,10 +4,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Aaru.CommonTypes.Enums;
using Aaru.CommonTypes.Interfaces;
using Aaru.DiscImages;
using Aaru.Filters;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@@ -25,11 +21,6 @@ namespace RedBookPlayer
/// </summary> /// </summary>
public static Player Player = new Player(); public static Player Player = new Player();
/// <summary>
/// Disc representing the loaded image
/// </summary>
public static PlayableDisc PlayableDisc = new PlayableDisc();
/// <summary> /// <summary>
/// Set of images representing the digits for the UI /// Set of images representing the digits for the UI
/// </summary> /// </summary>
@@ -56,7 +47,7 @@ namespace RedBookPlayer
public async Task<string> GetPath() public async Task<string> GetPath()
{ {
var dialog = new OpenFileDialog { AllowMultiple = false }; var dialog = new OpenFileDialog { AllowMultiple = false };
List<string> knownExtensions = new AaruFormat().KnownExtensions.ToList(); List<string> knownExtensions = new Aaru.DiscImages.AaruFormat().KnownExtensions.ToList();
dialog.Filters.Add(new FileDialogFilter() dialog.Filters.Add(new FileDialogFilter()
{ {
Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")", Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")",
@@ -66,43 +57,6 @@ namespace RedBookPlayer
return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault(); return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault();
} }
/// <summary>
/// Generate the digit string to be interpreted by the UI
/// </summary>
/// <returns>String representing the digits for the player</returns>
private string GenerateDigitString()
{
// If the disc or player aren't initialized, return all '-' characters
if (!PlayableDisc.Initialized)
return string.Empty.PadLeft(20, '-');
// Otherwise, take the current time into account
ulong sectorTime = PlayableDisc.CurrentSector;
if (PlayableDisc.SectionStartSector != 0)
sectorTime -= PlayableDisc.SectionStartSector;
else
sectorTime += PlayableDisc.TimeOffset;
int[] numbers = new int[]
{
PlayableDisc.CurrentTrackNumber + 1,
PlayableDisc.CurrentTrackIndex,
(int)(sectorTime / (75 * 60)),
(int)(sectorTime / 75 % 60),
(int)(sectorTime % 75),
PlayableDisc.TotalTracks,
PlayableDisc.TotalIndexes,
(int)(PlayableDisc.TotalTime / (75 * 60)),
(int)(PlayableDisc.TotalTime / 75 % 60),
(int)(PlayableDisc.TotalTime % 75),
};
return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2)));
}
/// <summary> /// <summary>
/// Load the png image for a given character based on the theme /// Load the png image for a given character based on the theme
/// </summary> /// </summary>
@@ -195,28 +149,6 @@ namespace RedBookPlayer
_updateTimer.Start(); _updateTimer.Start();
} }
/// <summary>
/// Indicates if the image is considered "playable" or not
/// </summary>
/// <param name="image">Aaruformat image file</param>
/// <returns>True if the image is playble, false otherwise</returns>
private bool IsPlayableImage(IOpticalMediaImage image)
{
// Invalid images can't be played
if (image == null)
return false;
// Determine based on media type
// TODO: Can we be more granular with sub types?
(string type, string _) = Aaru.CommonTypes.Metadata.MediaType.MediaTypeToString(image.Info.MediaType);
return type switch
{
"Compact Disc" => true,
"GD" => true, // Requires TOC generation
_ => false,
};
}
/// <summary> /// <summary>
/// Load an image from the path /// Load an image from the path
/// </summary> /// </summary>
@@ -225,24 +157,8 @@ namespace RedBookPlayer
{ {
bool result = await Task.Run(() => bool result = await Task.Run(() =>
{ {
var image = new AaruFormat(); Player.Init(path, App.Settings.AutoPlay);
var filter = new ZZZNoFilter(); return Player.Initialized;
filter.Open(path);
image.Open(filter);
if(IsPlayableImage(image))
{
PlayableDisc.Init(image, App.Settings.AutoPlay);
if(PlayableDisc.Initialized)
{
Player.Init(PlayableDisc, App.Settings.AutoPlay);
return true;
}
return false;
}
else
return false;
}); });
if(result) if(result)
@@ -261,24 +177,14 @@ namespace RedBookPlayer
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
{ {
string digitString = GenerateDigitString(); string digitString = Player.GenerateDigitString();
for (int i = 0; i < _digits.Length; i++) for (int i = 0; i < _digits.Length; i++)
{ {
if (_digits[i] != null) if (_digits[i] != null)
_digits[i].Source = GetBitmap(digitString[i]); _digits[i].Source = GetBitmap(digitString[i]);
} }
if (Player.Initialized) Player.UpdateDataContext(DataContext as PlayerViewModel);
{
PlayerViewModel dataContext = (PlayerViewModel)DataContext;
dataContext.HiddenTrack = PlayableDisc.TimeOffset > 150;
dataContext.ApplyDeEmphasis = PlayableDisc.ApplyDeEmphasis;
dataContext.TrackHasEmphasis = PlayableDisc.TrackHasEmphasis;
dataContext.CopyAllowed = PlayableDisc.CopyAllowed;
dataContext.QuadChannel = PlayableDisc.QuadChannel;
dataContext.IsAudioTrack = PlayableDisc.TrackType == TrackType.Audio;
dataContext.IsDataTrack = PlayableDisc.TrackType != TrackType.Audio;
}
}); });
} }
@@ -295,51 +201,27 @@ namespace RedBookPlayer
LoadImage(path); LoadImage(path);
} }
public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.Play(); public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(true);
public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.Pause(); public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(false);
public void StopButton_Click(object sender, RoutedEventArgs e) => Player.Stop(); public void StopButton_Click(object sender, RoutedEventArgs e) => Player.Stop();
public void NextTrackButton_Click(object sender, RoutedEventArgs e) public void NextTrackButton_Click(object sender, RoutedEventArgs e) => Player.NextTrack();
{
bool wasPlaying = Player.Playing;
if(wasPlaying) Player.Pause();
PlayableDisc.NextTrack();
if(wasPlaying) Player.Play();
}
public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => Player.PreviousTrack();
{
bool wasPlaying = Player.Playing;
if(wasPlaying) Player.Pause();
PlayableDisc.PreviousTrack();
if(wasPlaying) Player.Play();
}
public void NextIndexButton_Click(object sender, RoutedEventArgs e) public void NextIndexButton_Click(object sender, RoutedEventArgs e) => Player.NextIndex(App.Settings.IndexButtonChangeTrack);
{
bool wasPlaying = Player.Playing;
if(wasPlaying) Player.Pause();
PlayableDisc.NextIndex(App.Settings.IndexButtonChangeTrack);
if(wasPlaying) Player.Play();
}
public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => Player.PreviousIndex(App.Settings.IndexButtonChangeTrack);
{
bool wasPlaying = Player.Playing;
if(wasPlaying) Player.Pause();
PlayableDisc.PreviousIndex(App.Settings.IndexButtonChangeTrack);
if(wasPlaying) Player.Play();
}
public void FastForwardButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.FastForward(); public void FastForwardButton_Click(object sender, RoutedEventArgs e) => Player.FastForward();
public void RewindButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.Rewind(); public void RewindButton_Click(object sender, RoutedEventArgs e) => Player.Rewind();
public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.ToggleDeEmphasis(true); public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(true);
public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayableDisc.ToggleDeEmphasis(false); public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(false);
#endregion #endregion
} }

View File

@@ -11,6 +11,27 @@ namespace RedBookPlayer
set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value); set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value);
} }
private bool _quadChannel;
public bool QuadChannel
{
get => _quadChannel;
set => this.RaiseAndSetIfChanged(ref _quadChannel, value);
}
private bool _isDataTrack;
public bool IsDataTrack
{
get => _isDataTrack;
set => this.RaiseAndSetIfChanged(ref _isDataTrack, value);
}
private bool _copyAllowed;
public bool CopyAllowed
{
get => _copyAllowed;
set => this.RaiseAndSetIfChanged(ref _copyAllowed, value);
}
private bool _trackHasEmphasis; private bool _trackHasEmphasis;
public bool TrackHasEmphasis public bool TrackHasEmphasis
{ {
@@ -24,33 +45,5 @@ namespace RedBookPlayer
get => _hiddenTrack; get => _hiddenTrack;
set => this.RaiseAndSetIfChanged(ref _hiddenTrack, value); set => this.RaiseAndSetIfChanged(ref _hiddenTrack, value);
} }
private bool _copyAllowed;
public bool CopyAllowed
{
get => _copyAllowed;
set => this.RaiseAndSetIfChanged(ref _copyAllowed, value);
}
private bool _quadChannel;
public bool QuadChannel
{
get => _quadChannel;
set => this.RaiseAndSetIfChanged(ref _quadChannel, value);
}
private bool _isAudioTrack;
public bool IsAudioTrack
{
get => _isAudioTrack;
set => this.RaiseAndSetIfChanged(ref _isAudioTrack, value);
}
private bool _isDataTrack;
public bool IsDataTrack
{
get => _isDataTrack;
set => this.RaiseAndSetIfChanged(ref _isDataTrack, value);
}
} }
} }