mirror of
https://github.com/aaru-dps/RedBookPlayer.git
synced 2025-12-16 19:24:41 +00:00
Discs and audio output should be less aware
This commit is contained in:
@@ -406,17 +406,17 @@ namespace RedBookPlayer.GUI.ViewModels
|
|||||||
/// Initialize the view model with a given image path
|
/// Initialize the view model with a given image path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to the disc image</param>
|
/// <param name="path">Path to the disc image</param>
|
||||||
/// <param name="options">Options to pass to the optical disc factory</param>
|
/// <param name="playerOptions">Options to pass to the player</param>
|
||||||
/// <param name="repeatMode">RepeatMode for sound output</param>
|
/// <param name="opticalDiscOptions">Options to pass to the optical disc factory</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(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay)
|
public void Init(string path, PlayerOptions playerOptions, OpticalDiscOptions opticalDiscOptions, bool autoPlay)
|
||||||
{
|
{
|
||||||
// Stop current playback, if necessary
|
// Stop current playback, if necessary
|
||||||
if(PlayerState != PlayerState.NoDisc)
|
if(PlayerState != PlayerState.NoDisc)
|
||||||
ExecuteStop();
|
ExecuteStop();
|
||||||
|
|
||||||
// Attempt to initialize Player
|
// Attempt to initialize Player
|
||||||
_player.Init(path, options, repeatMode, autoPlay);
|
_player.Init(path, playerOptions, opticalDiscOptions, autoPlay);
|
||||||
if(_player.Initialized)
|
if(_player.Initialized)
|
||||||
{
|
{
|
||||||
_player.PropertyChanged += PlayerStateChanged;
|
_player.PropertyChanged += PlayerStateChanged;
|
||||||
@@ -654,19 +654,24 @@ namespace RedBookPlayer.GUI.ViewModels
|
|||||||
{
|
{
|
||||||
return await Dispatcher.UIThread.InvokeAsync(() =>
|
return await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
OpticalDiscOptions options = new OpticalDiscOptions
|
PlayerOptions playerOptions = new PlayerOptions
|
||||||
{
|
{
|
||||||
DataPlayback = App.Settings.DataPlayback,
|
DataPlayback = App.Settings.DataPlayback,
|
||||||
GenerateMissingToc = App.Settings.GenerateMissingTOC,
|
|
||||||
LoadHiddenTracks = App.Settings.PlayHiddenTracks,
|
LoadHiddenTracks = App.Settings.PlayHiddenTracks,
|
||||||
|
RepeatMode = App.Settings.RepeatMode,
|
||||||
SessionHandling = App.Settings.SessionHandling,
|
SessionHandling = App.Settings.SessionHandling,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OpticalDiscOptions opticalDiscOptions = new OpticalDiscOptions
|
||||||
|
{
|
||||||
|
GenerateMissingToc = App.Settings.GenerateMissingTOC,
|
||||||
|
};
|
||||||
|
|
||||||
// Ensure the context and view model are set
|
// Ensure the context and view model are set
|
||||||
App.PlayerView.DataContext = this;
|
App.PlayerView.DataContext = this;
|
||||||
App.PlayerView.ViewModel = this;
|
App.PlayerView.ViewModel = this;
|
||||||
|
|
||||||
Init(path, options, App.Settings.RepeatMode, App.Settings.AutoPlay);
|
Init(path, playerOptions, opticalDiscOptions, App.Settings.AutoPlay);
|
||||||
if(Initialized)
|
if(Initialized)
|
||||||
App.MainWindow.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
App.MainWindow.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ namespace RedBookPlayer.GUI.ViewModels
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates how to repeat tracks
|
/// Indicates how to repeat tracks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RepeatMode RepeatMode { get; set; } = RepeatMode.All;
|
public RepeatMode RepeatMode { get; set; } = RepeatMode.AllSingleDisc;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates how to handle tracks on different sessions
|
/// Indicates how to handle tracks on different sessions
|
||||||
|
|||||||
@@ -27,85 +27,25 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
if(_image == null)
|
if(_image == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Data tracks only and flag disabled means we can't do anything
|
// Invalid value means we can't do anything
|
||||||
if(_image.Tracks.All(t => t.TrackType != TrackType.Audio) && DataPlayback == DataPlayback.Skip)
|
if (value > _image.Tracks.Max(t => t.TrackSequence))
|
||||||
|
return;
|
||||||
|
else if (value < _image.Tracks.Min(t => t.TrackSequence))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Cache the value and the current track number
|
// Cache the current track for easy access
|
||||||
int cachedValue = value;
|
Track track = GetTrack(value);
|
||||||
int cachedTrackNumber;
|
if(track == null)
|
||||||
|
|
||||||
// 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)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TotalIndexes = cachedTrack.Indexes.Keys.Max();
|
// Set all track flags and values
|
||||||
CurrentTrackIndex = cachedTrack.Indexes.Keys.Min();
|
SetTrackFlags(track);
|
||||||
CurrentTrackSession = cachedTrack.TrackSession;
|
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)
|
if(track == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ensure that the value is valid, wrapping around if necessary
|
// Invalid value means we can't do anything
|
||||||
ushort fixedValue = value;
|
if (value > track.Indexes.Keys.Max())
|
||||||
if(value > track.Indexes.Keys.Max())
|
return;
|
||||||
fixedValue = track.Indexes.Keys.Min();
|
else if (value < track.Indexes.Keys.Min())
|
||||||
else if(value < track.Indexes.Keys.Min())
|
return;
|
||||||
fixedValue = track.Indexes.Keys.Max();
|
|
||||||
|
|
||||||
this.RaiseAndSetIfChanged(ref _currentTrackIndex, fixedValue);
|
this.RaiseAndSetIfChanged(ref _currentTrackIndex, value);
|
||||||
|
|
||||||
// Set new index-specific data
|
// Set new index-specific data
|
||||||
SectionStartSector = (ulong)track.Indexes[CurrentTrackIndex];
|
SectionStartSector = (ulong)track.Indexes[CurrentTrackIndex];
|
||||||
@@ -156,19 +95,18 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
if(_image == null)
|
if(_image == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If the sector is over the end of the image, then loop
|
// Invalid value means we can't do anything
|
||||||
ulong tempSector = value;
|
if(value > _image.Info.Sectors)
|
||||||
if(tempSector > _image.Info.Sectors)
|
return;
|
||||||
tempSector = 0;
|
else if(value < 0)
|
||||||
else if(tempSector < 0)
|
return;
|
||||||
tempSector = _image.Info.Sectors - 1;
|
|
||||||
|
|
||||||
// Cache the current track for easy access
|
// Cache the current track for easy access
|
||||||
Track track = GetTrack(CurrentTrackNumber);
|
Track track = GetTrack(CurrentTrackNumber);
|
||||||
if(track == null)
|
if(track == null)
|
||||||
return;
|
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 the current sector is outside of the last known track, seek to the right one
|
||||||
if(CurrentSector < track.TrackStartSector || CurrentSector > track.TrackEndSector)
|
if(CurrentSector < track.TrackStartSector || CurrentSector > track.TrackEndSector)
|
||||||
@@ -194,6 +132,11 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override int BytesPerSector => GetTrack(CurrentTrackNumber)?.TrackRawBytesPerSector ?? 0;
|
public override int BytesPerSector => GetTrack(CurrentTrackNumber)?.TrackRawBytesPerSector ?? 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Readonly list of all tracks in the image
|
||||||
|
/// </summary>
|
||||||
|
public List<Track> Tracks => _image?.Tracks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the 4CH flag
|
/// Represents the 4CH flag
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -230,21 +173,6 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value);
|
private set => this.RaiseAndSetIfChanged(ref _trackHasEmphasis, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicate how data tracks should be handled
|
|
||||||
/// </summary>
|
|
||||||
public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicate if hidden tracks should be loaded
|
|
||||||
/// </summary>
|
|
||||||
public bool LoadHiddenTracks { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates how tracks on different session should be handled
|
|
||||||
/// </summary>
|
|
||||||
public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions;
|
|
||||||
|
|
||||||
private bool _quadChannel;
|
private bool _quadChannel;
|
||||||
private bool _isDataTrack;
|
private bool _isDataTrack;
|
||||||
private bool _copyAllowed;
|
private bool _copyAllowed;
|
||||||
@@ -290,13 +218,7 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">Set of options for a new disc</param>
|
/// <param name="options">Set of options for a new disc</param>
|
||||||
public CompactDisc(OpticalDiscOptions options)
|
public CompactDisc(OpticalDiscOptions options) => _generateMissingToc = options.GenerateMissingToc;
|
||||||
{
|
|
||||||
DataPlayback = options.DataPlayback;
|
|
||||||
_generateMissingToc = options.GenerateMissingToc;
|
|
||||||
LoadHiddenTracks = options.LoadHiddenTracks;
|
|
||||||
SessionHandling = options.SessionHandling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Init(string path, IOpticalMediaImage image, bool autoPlay)
|
public override void Init(string path, IOpticalMediaImage image, bool autoPlay)
|
||||||
@@ -313,8 +235,9 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
if(!LoadTOC())
|
if(!LoadTOC())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Load the first track
|
// Load the first track by default
|
||||||
LoadFirstTrack();
|
CurrentTrackNumber = 1;
|
||||||
|
LoadTrack(CurrentTrackNumber);
|
||||||
|
|
||||||
// Reset total indexes if not in autoplay
|
// Reset total indexes if not in autoplay
|
||||||
if(!autoPlay)
|
if(!autoPlay)
|
||||||
@@ -330,134 +253,32 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
Initialized = true;
|
Initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Seeking
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void NextTrack()
|
|
||||||
{
|
|
||||||
if(_image == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CurrentTrackNumber++;
|
|
||||||
LoadTrack(CurrentTrackNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override void PreviousTrack()
|
|
||||||
{
|
|
||||||
if(_image == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CurrentTrackNumber--;
|
|
||||||
LoadTrack(CurrentTrackNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
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
|
#region Helpers
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void ExtractTrackToWav(uint trackNumber, string outputDirectory)
|
public override void ExtractTrackToWav(uint trackNumber, string outputDirectory) => ExtractTrackToWav(trackNumber, outputDirectory, DataPlayback.Skip);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extract a track to WAV
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trackNumber">Track number to extract</param>
|
||||||
|
/// <param name="outputDirectory">Output path to write data to</param>
|
||||||
|
/// <param name="dataPlayback">DataPlayback value indicating how to handle data tracks</param>
|
||||||
|
public void ExtractTrackToWav(uint trackNumber, string outputDirectory, DataPlayback dataPlayback)
|
||||||
{
|
{
|
||||||
if(_image == null)
|
if(_image == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the track with that value, if possible
|
// Get the track with that value, if possible
|
||||||
Track track = _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber);
|
Track track = _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber);
|
||||||
|
if (track == null)
|
||||||
// If the track isn't valid, we can't do anything
|
|
||||||
if(track == null || !(DataPlayback != DataPlayback.Skip || track.TrackType == TrackType.Audio))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get the number of sectors to read
|
// Get the number of sectors to read
|
||||||
uint length = (uint)(track.TrackEndSector - track.TrackStartSector);
|
uint length = (uint)(track.TrackEndSector - track.TrackStartSector);
|
||||||
|
|
||||||
// Read in the track data to a buffer
|
// 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
|
// Build the WAV output
|
||||||
string filename = Path.Combine(outputDirectory, $"Track {trackNumber.ToString().PadLeft(2, '0')}.wav");
|
string filename = Path.Combine(outputDirectory, $"Track {trackNumber.ToString().PadLeft(2, '0')}.wav");
|
||||||
@@ -471,15 +292,20 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <summary>
|
||||||
public override void ExtractAllTracksToWav(string outputDirectory)
|
/// Get the track with the given sequence value, if possible
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trackNumber">Track number to retrieve</param>
|
||||||
|
/// <returns>Track object for the requested sequence, null on error</returns>
|
||||||
|
public Track GetTrack(int trackNumber)
|
||||||
{
|
{
|
||||||
if(_image == null)
|
try
|
||||||
return;
|
|
||||||
|
|
||||||
foreach(Track track in _image.Tracks)
|
|
||||||
{
|
{
|
||||||
ExtractTrackToWav(track.TrackSequence, outputDirectory);
|
return _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,28 +327,49 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void LoadFirstTrack()
|
public override void LoadIndex(ushort index)
|
||||||
{
|
{
|
||||||
CurrentTrackNumber = 1;
|
if(_image == null)
|
||||||
LoadTrack(CurrentTrackNumber);
|
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override byte[] ReadSectors(uint sectorsToRead) => ReadSectors(CurrentSector, sectorsToRead);
|
public override byte[] ReadSectors(uint sectorsToRead) => ReadSectors(CurrentSector, sectorsToRead, DataPlayback.Skip);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read sector data from the base image starting from the specified sector
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sectorsToRead">Current number of sectors to read</param>
|
||||||
|
/// <param name="dataPlayback">DataPlayback value indicating how to handle data tracks</param>
|
||||||
|
/// <returns>Byte array representing the read sectors, if possible</returns>
|
||||||
|
public byte[] ReadSectors(uint sectorsToRead, DataPlayback dataPlayback) => ReadSectors(CurrentSector, sectorsToRead, DataPlayback.Skip);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read sector data from the base image starting from the specified sector
|
/// Read sector data from the base image starting from the specified sector
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startSector">Sector to start at for reading</param>
|
/// <param name="startSector">Sector to start at for reading</param>
|
||||||
/// <param name="sectorsToRead">Current number of sectors to read</param>
|
/// <param name="sectorsToRead">Current number of sectors to read</param>
|
||||||
|
/// <param name="dataPlayback">DataPlayback value indicating how to handle data tracks</param>
|
||||||
/// <returns>Byte array representing the read sectors, if possible</returns>
|
/// <returns>Byte array representing the read sectors, if possible</returns>
|
||||||
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);
|
return _image.ReadSectors(startSector, sectorsToRead);
|
||||||
}
|
}
|
||||||
else if(DataPlayback == DataPlayback.Blank)
|
else if(dataPlayback == DataPlayback.Blank)
|
||||||
{
|
{
|
||||||
byte[] sectors = _image.ReadSectors(startSector, sectorsToRead);
|
byte[] sectors = _image.ReadSectors(startSector, sectorsToRead);
|
||||||
Array.Clear(sectors, 0, sectors.Length);
|
Array.Clear(sectors, 0, sectors.Length);
|
||||||
@@ -543,23 +390,6 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
TotalIndexes = GetTrack(CurrentTrackNumber)?.Indexes.Keys.Max() ?? 0;
|
TotalIndexes = GetTrack(CurrentTrackNumber)?.Indexes.Keys.Max() ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the track with the given sequence value, if possible
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="trackNumber">Track number to retrieve</param>
|
|
||||||
/// <returns>Track object for the requested sequence, null on error</returns>
|
|
||||||
private Track GetTrack(int trackNumber)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _image.Tracks.FirstOrDefault(t => t.TrackSequence == trackNumber);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load TOC for the current disc image
|
/// Load TOC for the current disc image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -103,34 +103,6 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
/// <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 abstract void Init(string path, IOpticalMediaImage image, bool autoPlay);
|
public abstract void Init(string path, IOpticalMediaImage image, bool autoPlay);
|
||||||
|
|
||||||
#region Seeking
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to move to the next track, wrapping around if necessary
|
|
||||||
/// </summary>
|
|
||||||
public abstract void NextTrack();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Try to move to the previous track, wrapping around if necessary
|
|
||||||
/// </summary>
|
|
||||||
public abstract void PreviousTrack();
|
|
||||||
|
|
||||||
/// <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
|
#region Helpers
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -140,12 +112,6 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
/// <param name="outputDirectory">Output path to write data to</param>
|
/// <param name="outputDirectory">Output path to write data to</param>
|
||||||
public abstract void ExtractTrackToWav(uint trackNumber, string outputDirectory);
|
public abstract void ExtractTrackToWav(uint trackNumber, string outputDirectory);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extract all tracks to WAV
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="outputDirectory">Output path to write data to</param>
|
|
||||||
public abstract void ExtractAllTracksToWav(string outputDirectory);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load the desired track, if possible
|
/// Load the desired track, if possible
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -153,9 +119,10 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
public abstract void LoadTrack(int track);
|
public abstract void LoadTrack(int track);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load the first valid track in the image
|
/// Load the desired index, if possible
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract void LoadFirstTrack();
|
/// <param name="index">Index number to load</param>
|
||||||
|
public abstract void LoadIndex(ushort index);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read sector data from the base image starting from the current sector
|
/// Read sector data from the base image starting from the current sector
|
||||||
|
|||||||
@@ -4,26 +4,11 @@ namespace RedBookPlayer.Models.Discs
|
|||||||
{
|
{
|
||||||
#region CompactDisc
|
#region CompactDisc
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicate how data tracks should be handled
|
|
||||||
/// </summary>
|
|
||||||
public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicate if a TOC should be generated if missing
|
/// Indicate if a TOC should be generated if missing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool GenerateMissingToc { get; set; } = false;
|
public bool GenerateMissingToc { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicate if hidden tracks should be loaded
|
|
||||||
/// </summary>
|
|
||||||
public bool LoadHiddenTracks { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates how tracks on different session should be handled
|
|
||||||
/// </summary>
|
|
||||||
public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,9 +64,14 @@ namespace RedBookPlayer.Models
|
|||||||
Single,
|
Single,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Repeat all tracks
|
/// Repeat all tracks on a single disc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
All,
|
AllSingleDisc,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Repeat all tracks on a multiple discs
|
||||||
|
/// </summary>
|
||||||
|
AllMultiDisc,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace RedBookPlayer.Models.Hardware.Linux
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sound output instance
|
/// Sound output instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ALSoundOut _soundOut;
|
private readonly ALSoundOut _soundOut;
|
||||||
|
|
||||||
public AudioBackend() { }
|
public AudioBackend() { }
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
using CSCore.SoundOut;
|
|
||||||
|
|
||||||
namespace RedBookPlayer.Models.Hardware.Mac
|
|
||||||
{
|
|
||||||
public class AudioBackend : IAudioBackend
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Sound output instance
|
|
||||||
/// </summary>
|
|
||||||
private ALSoundOut _soundOut;
|
|
||||||
|
|
||||||
public AudioBackend() { }
|
|
||||||
|
|
||||||
public AudioBackend(PlayerSource source)
|
|
||||||
{
|
|
||||||
_soundOut = new ALSoundOut(100);
|
|
||||||
_soundOut.Initialize(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IAudioBackend Implementation
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Pause() => _soundOut.Pause();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Play() => _soundOut.Play();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void Stop() => _soundOut.Stop();
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public PlayerState GetPlayerState()
|
|
||||||
{
|
|
||||||
return (_soundOut?.PlaybackState) switch
|
|
||||||
{
|
|
||||||
PlaybackState.Paused => PlayerState.Paused,
|
|
||||||
PlaybackState.Playing => PlayerState.Playing,
|
|
||||||
PlaybackState.Stopped => PlayerState.Stopped,
|
|
||||||
_ => PlayerState.NoDisc,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void SetVolume(float volume)
|
|
||||||
{
|
|
||||||
if (_soundOut != null)
|
|
||||||
_soundOut.Volume = volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
|
using System;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
|
using Aaru.CommonTypes.Structs;
|
||||||
|
using Avalonia.Threading;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using RedBookPlayer.Models.Discs;
|
using RedBookPlayer.Models.Discs;
|
||||||
using RedBookPlayer.Models.Factories;
|
using RedBookPlayer.Models.Factories;
|
||||||
@@ -36,9 +41,19 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should invoke playback mode changes
|
||||||
|
/// </summary>
|
||||||
|
private bool ShouldInvokePlaybackModes
|
||||||
|
{
|
||||||
|
get => _shouldInvokePlaybackModes;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _shouldInvokePlaybackModes, value);
|
||||||
|
}
|
||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
private int _numberOfDiscs;
|
private int _numberOfDiscs;
|
||||||
private int _currentDisc;
|
private int _currentDisc;
|
||||||
|
private bool _shouldInvokePlaybackModes;
|
||||||
|
|
||||||
#region OpticalDisc Passthrough
|
#region OpticalDisc Passthrough
|
||||||
|
|
||||||
@@ -201,6 +216,15 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value);
|
private set => this.RaiseAndSetIfChanged(ref _dataPlayback, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate if hidden tracks should be loaded
|
||||||
|
/// </summary>
|
||||||
|
public bool LoadHiddenTracks
|
||||||
|
{
|
||||||
|
get => _loadHiddenTracks;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _loadHiddenTracks, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates the repeat mode
|
/// Indicates the repeat mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -210,6 +234,15 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
private set => this.RaiseAndSetIfChanged(ref _repeatMode, value);
|
private set => this.RaiseAndSetIfChanged(ref _repeatMode, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates how tracks on different session should be handled
|
||||||
|
/// </summary>
|
||||||
|
public SessionHandling SessionHandling
|
||||||
|
{
|
||||||
|
get => _sessionHandling;
|
||||||
|
private set => this.RaiseAndSetIfChanged(ref _sessionHandling, value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if de-emphasis should be applied
|
/// Indicates if de-emphasis should be applied
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -230,7 +263,9 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
|
|
||||||
private PlayerState _playerState;
|
private PlayerState _playerState;
|
||||||
private DataPlayback _dataPlayback;
|
private DataPlayback _dataPlayback;
|
||||||
|
private bool _loadHiddenTracks;
|
||||||
private RepeatMode _repeatMode;
|
private RepeatMode _repeatMode;
|
||||||
|
private SessionHandling _sessionHandling;
|
||||||
private bool _applyDeEmphasis;
|
private bool _applyDeEmphasis;
|
||||||
private int _volume;
|
private int _volume;
|
||||||
|
|
||||||
@@ -241,7 +276,7 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sound output handling class
|
/// Sound output handling class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly SoundOutput[] _soundOutputs;
|
private readonly SoundOutput _soundOutput;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// OpticalDisc object
|
/// OpticalDisc object
|
||||||
@@ -253,6 +288,21 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int? _lastVolume = null;
|
private int? _lastVolume = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Filtering stage for audio output
|
||||||
|
/// </summary>
|
||||||
|
private FilterStage _filterStage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current position in the sector for reading
|
||||||
|
/// </summary>
|
||||||
|
private int _currentSectorReadPosition = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lock object for reading track data
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _readingImage = new object();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -268,44 +318,51 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
numberOfDiscs = 1;
|
numberOfDiscs = 1;
|
||||||
|
|
||||||
_numberOfDiscs = numberOfDiscs;
|
_numberOfDiscs = numberOfDiscs;
|
||||||
_soundOutputs = new SoundOutput[_numberOfDiscs];
|
|
||||||
_opticalDiscs = new OpticalDiscBase[numberOfDiscs];
|
_opticalDiscs = new OpticalDiscBase[numberOfDiscs];
|
||||||
|
|
||||||
_currentDisc = 0;
|
_currentDisc = 0;
|
||||||
for (int i = 0; i < _numberOfDiscs; i++)
|
|
||||||
{
|
_filterStage = new FilterStage();
|
||||||
_soundOutputs[i] = new SoundOutput(defaultVolume);
|
_soundOutput = new SoundOutput(defaultVolume);
|
||||||
_soundOutputs[i].SetDeEmphasis(false);
|
|
||||||
}
|
PropertyChanged += HandlePlaybackModes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes player from a given image path
|
/// Initializes player from a given image path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">Path to the disc image</param>
|
/// <param name="path">Path to the disc image</param>
|
||||||
/// <param name="options">Options to pass to the optical disc factory</param>
|
/// <param name="playerOptions">Options to pass to the player</param>
|
||||||
/// <param name="repeatMode">RepeatMode for sound output</param>
|
/// <param name="opticalDiscOptions">Options to pass to the optical disc factory</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(string path, OpticalDiscOptions options, RepeatMode repeatMode, bool autoPlay)
|
public void Init(string path, PlayerOptions playerOptions, OpticalDiscOptions opticalDiscOptions, bool autoPlay)
|
||||||
{
|
{
|
||||||
// Reset initialization
|
// Reset initialization
|
||||||
Initialized = false;
|
Initialized = false;
|
||||||
|
|
||||||
|
// Set player options
|
||||||
|
DataPlayback = playerOptions.DataPlayback;
|
||||||
|
LoadHiddenTracks = playerOptions.LoadHiddenTracks;
|
||||||
|
RepeatMode = playerOptions.RepeatMode;
|
||||||
|
SessionHandling = playerOptions.SessionHandling;
|
||||||
|
|
||||||
// Initalize the disc
|
// Initalize the disc
|
||||||
_opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, options, autoPlay);
|
_opticalDiscs[CurrentDisc] = OpticalDiscFactory.GenerateFromPath(path, opticalDiscOptions, autoPlay);
|
||||||
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Add event handling for the optical disc
|
// Add event handling for the optical disc
|
||||||
_opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged;
|
_opticalDiscs[CurrentDisc].PropertyChanged += OpticalDiscStateChanged;
|
||||||
|
|
||||||
|
// Setup de-emphasis filters
|
||||||
|
_filterStage.SetupFilters();
|
||||||
|
|
||||||
// Initialize the sound output
|
// Initialize the sound output
|
||||||
_soundOutputs[CurrentDisc].Init(_opticalDiscs[CurrentDisc], repeatMode, autoPlay);
|
_soundOutput.Init(ProviderRead, autoPlay);
|
||||||
if(_soundOutputs[CurrentDisc] == null || !_soundOutputs[CurrentDisc].Initialized)
|
if(_soundOutput == null || !_soundOutput.Initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Add event handling for the sound output
|
// Add event handling for the sound output
|
||||||
_soundOutputs[CurrentDisc].PropertyChanged += SoundOutputStateChanged;
|
_soundOutput.PropertyChanged += SoundOutputStateChanged;
|
||||||
|
|
||||||
// Mark the player as ready
|
// Mark the player as ready
|
||||||
Initialized = true;
|
Initialized = true;
|
||||||
@@ -315,7 +372,7 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
SoundOutputStateChanged(this, null);
|
SoundOutputStateChanged(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Playback
|
#region Playback (UI)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Begin playback
|
/// Begin playback
|
||||||
@@ -324,12 +381,12 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
{
|
{
|
||||||
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc] == null)
|
else if(_soundOutput == null)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Stopped)
|
else if(_soundOutput.PlayerState != PlayerState.Paused && _soundOutput.PlayerState != PlayerState.Stopped)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_soundOutputs[CurrentDisc].Play();
|
_soundOutput.Play();
|
||||||
_opticalDiscs[CurrentDisc].SetTotalIndexes();
|
_opticalDiscs[CurrentDisc].SetTotalIndexes();
|
||||||
PlayerState = PlayerState.Playing;
|
PlayerState = PlayerState.Playing;
|
||||||
}
|
}
|
||||||
@@ -341,12 +398,12 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
{
|
{
|
||||||
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc] == null)
|
else if(_soundOutput == null)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing)
|
else if(_soundOutput.PlayerState != PlayerState.Playing)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_soundOutputs[CurrentDisc]?.Pause();
|
_soundOutput.Pause();
|
||||||
PlayerState = PlayerState.Paused;
|
PlayerState = PlayerState.Paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,13 +435,14 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
{
|
{
|
||||||
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc] == null)
|
else if(_soundOutput == null)
|
||||||
return;
|
|
||||||
else if(_soundOutputs[CurrentDisc].PlayerState != PlayerState.Playing && _soundOutputs[CurrentDisc].PlayerState != PlayerState.Paused)
|
|
||||||
return;
|
return;
|
||||||
|
else if(_soundOutput.PlayerState != PlayerState.Playing && _soundOutput.PlayerState != PlayerState.Paused)
|
||||||
|
return;
|
||||||
|
|
||||||
_soundOutputs[CurrentDisc].Stop();
|
_soundOutput.Stop();
|
||||||
_opticalDiscs[CurrentDisc].LoadFirstTrack();
|
CurrentTrackNumber = 0;
|
||||||
|
SelectTrack(1);
|
||||||
PlayerState = PlayerState.Stopped;
|
PlayerState = PlayerState.Stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,199 +453,47 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
{
|
{
|
||||||
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
if(_opticalDiscs[CurrentDisc] == null || !_opticalDiscs[CurrentDisc].Initialized)
|
||||||
return;
|
return;
|
||||||
else if(_soundOutputs[CurrentDisc] == null)
|
else if(_soundOutput == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
_soundOutputs[CurrentDisc].Eject();
|
_soundOutput.Eject();
|
||||||
_opticalDiscs[CurrentDisc] = null;
|
_opticalDiscs[CurrentDisc] = null;
|
||||||
PlayerState = PlayerState.NoDisc;
|
PlayerState = PlayerState.NoDisc;
|
||||||
Initialized = false;
|
Initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Select a particular disc by number
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the next disc
|
/// Move to the next disc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NextDisc()
|
public void NextDisc() => SelectDisc(CurrentDisc + 1);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the previous disc
|
/// Move to the previous disc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PreviousDisc()
|
public void PreviousDisc() => SelectDisc(CurrentDisc - 1);
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Select a particular track by number
|
|
||||||
/// </summary>
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the next playable track
|
/// Move to the next playable track
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void NextTrack()
|
public void NextTrack() => SelectTrack(CurrentTrackNumber + 1);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the previous playable track
|
/// Move to the previous playable track
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void PreviousTrack()
|
public void PreviousTrack() => SelectTrack(CurrentTrackNumber - 1);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the next index
|
/// Move to the next index
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
||||||
public void NextIndex(bool changeTrack)
|
public void NextIndex(bool changeTrack) => SelectIndex((ushort)(CurrentTrackIndex + 1), 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move to the previous index
|
/// Move to the previous index
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
||||||
public void PreviousIndex(bool changeTrack)
|
public void PreviousIndex(bool changeTrack) => SelectIndex((ushort)(CurrentTrackIndex - 1), 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fast-forward playback by 75 sectors
|
/// Fast-forward playback by 75 sectors
|
||||||
@@ -613,6 +519,348 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Playback (Internal)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fill the current byte buffer with playable data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buffer">Buffer to load data into</param>
|
||||||
|
/// <param name="offset">Offset in the buffer to load at</param>
|
||||||
|
/// <param name="count">Number of bytes to load</param>
|
||||||
|
/// <returns>Number of bytes read</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select a disc by number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="discNumber">Disc number to attempt to load</param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select a disc by number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Track index to attempt to load</param>
|
||||||
|
/// <param name="changeTrack">True if index changes can trigger a track change, false otherwise</param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Select a track by number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="trackNumber">Track number to attempt to load</param>
|
||||||
|
/// <remarks>Changing track with RepeatMode.AllMultiDisc should switch discs<remarks>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine the number of real and zero sectors to read
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">Number of requested bytes to read</param>
|
||||||
|
/// <param name="sectorsToRead">Number of sectors to read</param>
|
||||||
|
/// <param name="zeroSectorsAmount">Number of zeroed sectors to concatenate</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read the requested amount of data from an input
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">Number of bytes to load</param>
|
||||||
|
/// <param name="sectorsToRead">Number of sectors to read</param>
|
||||||
|
/// <param name="zeroSectorsAmount">Number of zeroed sectors to concatenate</param>
|
||||||
|
/// <returns>The requested amount of data, if possible</returns>
|
||||||
|
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
|
#region Volume
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -629,7 +877,7 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// Set the value for the volume
|
/// Set the value for the volume
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="volume">New volume value</param>
|
/// <param name="volume">New volume value</param>
|
||||||
public void SetVolume(int volume) => _soundOutputs[CurrentDisc]?.SetVolume(volume);
|
public void SetVolume(int volume) => _soundOutput?.SetVolume(volume);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Temporarily mute playback
|
/// Temporarily mute playback
|
||||||
@@ -670,59 +918,137 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set de-emphasis status
|
/// Set de-emphasis status
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="apply"></param>
|
/// <param name="applyDeEmphasis"></param>
|
||||||
private void SetDeEmphasis(bool apply) => _soundOutputs[CurrentDisc]?.SetDeEmphasis(apply);
|
private void SetDeEmphasis(bool applyDeEmphasis) => ApplyDeEmphasis = applyDeEmphasis;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helpers
|
#region Extraction
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extract a single track from the image to WAV
|
/// Extract a single track from the image to WAV
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="trackNumber"></param>
|
/// <param name="trackNumber"></param>
|
||||||
/// <param name="outputDirectory">Output path to write data to</param>
|
/// <param name="outputDirectory">Output path to write data to</param>
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extract all tracks from the image to WAV
|
/// Extract all tracks from the image to WAV
|
||||||
|
/// </summary>
|
||||||
/// <param name="outputDirectory">Output path to write data to</param>
|
/// <param name="outputDirectory">Output path to write data to</param>
|
||||||
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
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set data playback method [CompactDisc only]
|
/// Set data playback method [CompactDisc only]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dataPlayback">New playback value</param>
|
/// <param name="dataPlayback">New playback value</param>
|
||||||
public void SetDataPlayback(DataPlayback dataPlayback)
|
public void SetDataPlayback(DataPlayback dataPlayback) => DataPlayback = dataPlayback;
|
||||||
{
|
|
||||||
if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc)
|
|
||||||
compactDisc.DataPlayback = dataPlayback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the value for loading hidden tracks [CompactDisc only]
|
/// Set the value for loading hidden tracks [CompactDisc only]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="load">True to enable loading hidden tracks, false otherwise</param>
|
/// <param name="loadHiddenTracks">True to enable loading hidden tracks, false otherwise</param>
|
||||||
public void SetLoadHiddenTracks(bool load)
|
public void SetLoadHiddenTracks(bool loadHiddenTracks) => LoadHiddenTracks = loadHiddenTracks;
|
||||||
{
|
|
||||||
if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc)
|
|
||||||
compactDisc.LoadHiddenTracks = load;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set repeat mode
|
/// Set repeat mode
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="repeatMode">New repeat mode value</param>
|
/// <param name="repeatMode">New repeat mode value</param>
|
||||||
public void SetRepeatMode(RepeatMode repeatMode) => _soundOutputs[CurrentDisc]?.SetRepeatMode(repeatMode);
|
public void SetRepeatMode(RepeatMode repeatMode) => RepeatMode = repeatMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the value for session handling [CompactDisc only]
|
/// Set the value for session handling [CompactDisc only]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionHandling">New session handling value</param>
|
/// <param name="sessionHandling">New session handling value</param>
|
||||||
public void SetSessionHandling(SessionHandling sessionHandling)
|
public void SetSessionHandling(SessionHandling sessionHandling) => SessionHandling = sessionHandling;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region State Change Event Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle special playback modes if we get flagged to
|
||||||
|
/// </summary>
|
||||||
|
private async void HandlePlaybackModes(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if(_opticalDiscs[CurrentDisc] is CompactDisc compactDisc)
|
if(e.PropertyName != nameof(ShouldInvokePlaybackModes))
|
||||||
compactDisc.SessionHandling = sessionHandling;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -759,10 +1085,8 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e)
|
private void SoundOutputStateChanged(object sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
PlayerState = _soundOutputs[CurrentDisc].PlayerState;
|
PlayerState = _soundOutput.PlayerState;
|
||||||
RepeatMode = _soundOutputs[CurrentDisc].RepeatMode;
|
Volume = _soundOutput.Volume;
|
||||||
ApplyDeEmphasis = _soundOutputs[CurrentDisc].ApplyDeEmphasis;
|
|
||||||
Volume = _soundOutputs[CurrentDisc].Volume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
25
RedBookPlayer.Models/Hardware/PlayerOptions.cs
Normal file
25
RedBookPlayer.Models/Hardware/PlayerOptions.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace RedBookPlayer.Models.Discs
|
||||||
|
{
|
||||||
|
public class PlayerOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate how data tracks should be handled
|
||||||
|
/// </summary>
|
||||||
|
public DataPlayback DataPlayback { get; set; } = DataPlayback.Skip;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicate if hidden tracks should be loaded
|
||||||
|
/// </summary>
|
||||||
|
public bool LoadHiddenTracks { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates the repeat mode
|
||||||
|
/// </summary>
|
||||||
|
public RepeatMode RepeatMode { get; set; } = RepeatMode.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates how tracks on different session should be handled
|
||||||
|
/// </summary>
|
||||||
|
public SessionHandling SessionHandling { get; set; } = SessionHandling.AllSessions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using RedBookPlayer.Models.Discs;
|
|
||||||
|
|
||||||
namespace RedBookPlayer.Models.Hardware
|
namespace RedBookPlayer.Models.Hardware
|
||||||
{
|
{
|
||||||
@@ -29,24 +25,6 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
private set => this.RaiseAndSetIfChanged(ref _playerState, value);
|
private set => this.RaiseAndSetIfChanged(ref _playerState, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates the repeat mode
|
|
||||||
/// </summary>
|
|
||||||
public RepeatMode RepeatMode
|
|
||||||
{
|
|
||||||
get => _repeatMode;
|
|
||||||
private set => this.RaiseAndSetIfChanged(ref _repeatMode, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates if de-emphasis should be applied
|
|
||||||
/// </summary>
|
|
||||||
public bool ApplyDeEmphasis
|
|
||||||
{
|
|
||||||
get => _applyDeEmphasis;
|
|
||||||
private set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current playback volume
|
/// Current playback volume
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -67,22 +45,12 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
private PlayerState _playerState;
|
private PlayerState _playerState;
|
||||||
private RepeatMode _repeatMode;
|
|
||||||
private bool _applyDeEmphasis;
|
|
||||||
private int _volume;
|
private int _volume;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private State Variables
|
#region Private State Variables
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// OpticalDisc from the parent player for easy access
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// TODO: Can we remove the need for a local reference to OpticalDisc?
|
|
||||||
/// </remarks>
|
|
||||||
private OpticalDiscBase _opticalDisc;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Data provider for sound output
|
/// Data provider for sound output
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -93,60 +61,26 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private IAudioBackend _soundOut;
|
private IAudioBackend _soundOut;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Filtering stage for audio output
|
|
||||||
/// </summary>
|
|
||||||
private FilterStage _filterStage;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current position in the sector
|
|
||||||
/// </summary>
|
|
||||||
private int _currentSectorReadPosition = 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lock object for reading track data
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _readingImage = new object();
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="defaultVolume">Default volume between 0 and 100 to use when starting playback</param>
|
/// <param name="defaultVolume">Default volume between 0 and 100 to use when starting playback</param>
|
||||||
public SoundOutput(int defaultVolume = 100)
|
public SoundOutput(int defaultVolume = 100) => Volume = defaultVolume;
|
||||||
{
|
|
||||||
Volume = defaultVolume;
|
|
||||||
_filterStage = new FilterStage();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initialize the output with a given image
|
/// Initialize the output with a given image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="opticalDisc">OpticalDisc to load from</param>
|
/// <param name="read">ReadFunction to use during decoding</param>
|
||||||
/// <param name="repeatMode">RepeatMode for sound output</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(OpticalDiscBase opticalDisc, RepeatMode repeatMode, bool autoPlay)
|
public void Init(PlayerSource.ReadFunction read, bool autoPlay)
|
||||||
{
|
{
|
||||||
// If we have an unusable disc, just return
|
// Reset initialization
|
||||||
if(opticalDisc == null || !opticalDisc.Initialized)
|
Initialized = false;
|
||||||
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();
|
|
||||||
|
|
||||||
// Setup the audio output
|
// Setup the audio output
|
||||||
SetupAudio();
|
SetupAudio(read);
|
||||||
|
|
||||||
// Setup the repeat mode
|
|
||||||
RepeatMode = repeatMode;
|
|
||||||
|
|
||||||
// Initialize playback, if necessary
|
// Initialize playback, if necessary
|
||||||
if(autoPlay)
|
if(autoPlay)
|
||||||
@@ -166,61 +100,10 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
_soundOut.Stop();
|
_soundOut.Stop();
|
||||||
_opticalDisc = null;
|
|
||||||
Initialized = false;
|
Initialized = false;
|
||||||
PlayerState = PlayerState.NoDisc;
|
PlayerState = PlayerState.NoDisc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fill the current byte buffer with playable data
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buffer">Buffer to load data into</param>
|
|
||||||
/// <param name="offset">Offset in the buffer to load at</param>
|
|
||||||
/// <param name="count">Number of bytes to load</param>
|
|
||||||
/// <returns>Number of bytes read</returns>
|
|
||||||
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
|
#region Playback
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -265,108 +148,25 @@ namespace RedBookPlayer.Models.Hardware
|
|||||||
|
|
||||||
#region Helpers
|
#region Helpers
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set de-emphasis status
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="apply">New de-emphasis status</param>
|
|
||||||
public void SetDeEmphasis(bool apply) => ApplyDeEmphasis = apply;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set repeat mode
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="repeatMode">New repeat mode value</param>
|
|
||||||
public void SetRepeatMode(RepeatMode repeatMode) => RepeatMode = repeatMode;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the value for the volume
|
/// Set the value for the volume
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="volume">New volume value</param>
|
/// <param name="volume">New volume value</param>
|
||||||
public void SetVolume(int volume) => Volume = volume;
|
public void SetVolume(int volume)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determine the number of real and zero sectors to read
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="count">Number of requested bytes to read</param>
|
|
||||||
/// <param name="sectorsToRead">Number of sectors to read</param>
|
|
||||||
/// <param name="zeroSectorsAmount">Number of zeroed sectors to concatenate</param>
|
|
||||||
private void DetermineReadAmount(int count, out ulong sectorsToRead, out ulong zeroSectorsAmount)
|
|
||||||
{
|
{
|
||||||
// Attempt to read 10 more sectors than requested
|
Volume = volume;
|
||||||
sectorsToRead = ((ulong)count / (ulong)_opticalDisc.BytesPerSector) + 10;
|
_soundOut.SetVolume((float)Volume / 100);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read the requested amount of data from an input
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="count">Number of bytes to load</param>
|
|
||||||
/// <param name="sectorsToRead">Number of sectors to read</param>
|
|
||||||
/// <param name="zeroSectorsAmount">Number of zeroed sectors to concatenate</param>
|
|
||||||
/// <returns>The requested amount of data, if possible</returns>
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets or resets the audio playback objects
|
/// Sets or resets the audio playback objects
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SetupAudio()
|
/// <param name="read">ReadFunction to use during decoding</param>
|
||||||
|
private void SetupAudio(PlayerSource.ReadFunction read)
|
||||||
{
|
{
|
||||||
if(_source == null)
|
if(_source == null)
|
||||||
{
|
{
|
||||||
_source = new PlayerSource(ProviderRead);
|
_source = new PlayerSource(read);
|
||||||
|
|
||||||
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
_soundOut = new Linux.AudioBackend(_source);
|
_soundOut = new Linux.AudioBackend(_source);
|
||||||
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
else if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace RedBookPlayer.Models.Hardware.Windows
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sound output instance
|
/// Sound output instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ALSoundOut _soundOut;
|
private readonly ALSoundOut _soundOut;
|
||||||
|
|
||||||
public AudioBackend() { }
|
public AudioBackend() { }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user