diff --git a/README.md b/README.md
index 6ccbb4b..7b0ec36 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,27 @@
[Audio CD](https://en.wikipedia.org/wiki/Compact_Disc_Digital_Audio) player for [Aaru format](https://github.com/aaru-dps/Aaru).
* This project is fully sponsored by the [Game Preservation Society](https://www.gamepres.org/en/).
+
+## Default Player Controls
+
+| Key | Action |
+| --- | ------ |
+| **F1** | Open Settings Window |
+| **F2** | Load New Image |
+| **Space** | Toggle Play / Pause |
+| **Esc** | Stop Playback |
+| **→** | Next Track |
+| **←** | Previous Track |
+| **]** | Next Index |
+| **[** | Previous Index |
+| **.** | Fast Forward |
+| **,** | Rewind |
+| **Numpad +** | Volume Up |
+| **Numpad -** | Volume Down |
+| **M** | Mute |
+| **E** | Toggle Emphasis |
+
+For both Volume Up and Volume Down:
+- Holding **Ctrl** will move in increments of 2
+- Holding **Shift** will move in increments of 5
+- Holding both will move in increments of 10
\ No newline at end of file
diff --git a/RedBookPlayer/Discs/CompactDisc.cs b/RedBookPlayer/Discs/CompactDisc.cs
index 87b670a..21e8fc8 100644
--- a/RedBookPlayer/Discs/CompactDisc.cs
+++ b/RedBookPlayer/Discs/CompactDisc.cs
@@ -79,8 +79,8 @@ namespace RedBookPlayer.Discs
// Ensure that the value is valid, wrapping around if necessary
if(value > track.Indexes.Keys.Max())
- _currentTrackIndex = 0;
- else if(value < 0)
+ _currentTrackIndex = track.Indexes.Keys.Min();
+ else if(value < track.Indexes.Keys.Min())
_currentTrackIndex = track.Indexes.Keys.Max();
else
_currentTrackIndex = value;
diff --git a/RedBookPlayer/GUI/MainWindow.xaml.cs b/RedBookPlayer/GUI/MainWindow.xaml.cs
index 474037b..9c46297 100644
--- a/RedBookPlayer/GUI/MainWindow.xaml.cs
+++ b/RedBookPlayer/GUI/MainWindow.xaml.cs
@@ -90,7 +90,7 @@ namespace RedBookPlayer.GUI
Closing += (e, f) =>
{
- PlayerView.Player.Stop();
+ ((PlayerView)ContentControl.Content).StopButton_Click(this, null);
};
AddHandler(DragDrop.DropEvent, MainWindow_Drop);
@@ -115,11 +115,106 @@ namespace RedBookPlayer.GUI
public void OnKeyDown(object sender, KeyEventArgs e)
{
- if(e.Key == Key.F1)
+ PlayerView playerView = ContentControl.Content as PlayerView;
+
+ // Open settings window
+ if(e.Key == App.Settings.OpenSettingsKey)
{
settingsWindow = new SettingsWindow(App.Settings);
settingsWindow.Show();
}
+
+ // Load image
+ else if (e.Key == App.Settings.LoadImageKey)
+ {
+ playerView?.LoadButton_Click(this, null);
+ }
+
+ // Toggle playback
+ else if(e.Key == App.Settings.TogglePlaybackKey || e.Key == Key.MediaPlayPause)
+ {
+ playerView?.PlayPauseButton_Click(this, null);
+ }
+
+ // Stop playback
+ else if(e.Key == App.Settings.StopPlaybackKey || e.Key == Key.MediaStop)
+ {
+ playerView?.StopButton_Click(this, null);
+ }
+
+ // Next Track
+ else if(e.Key == App.Settings.NextTrackKey || e.Key == Key.MediaNextTrack)
+ {
+ playerView?.NextTrackButton_Click(this, null);
+ }
+
+ // Previous Track
+ else if(e.Key == App.Settings.PreviousTrackKey || e.Key == Key.MediaPreviousTrack)
+ {
+ playerView?.PreviousTrackButton_Click(this, null);
+ }
+
+ // Next Index
+ else if(e.Key == App.Settings.NextIndexKey)
+ {
+ playerView?.NextIndexButton_Click(this, null);
+ }
+
+ // Previous Index
+ else if(e.Key == App.Settings.PreviousIndexKey)
+ {
+ playerView?.PreviousIndexButton_Click(this, null);
+ }
+
+ // Fast Foward
+ else if(e.Key == App.Settings.FastForwardPlaybackKey)
+ {
+ playerView?.FastForwardButton_Click(this, null);
+ }
+
+ // Rewind
+ else if(e.Key == App.Settings.RewindPlaybackKey)
+ {
+ playerView?.RewindButton_Click(this, null);
+ }
+
+ // Volume Up
+ else if(e.Key == App.Settings.VolumeUpKey || e.Key == Key.VolumeUp)
+ {
+ int increment = 1;
+ if(e.KeyModifiers.HasFlag(KeyModifiers.Control))
+ increment *= 2;
+ if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
+ increment *= 5;
+
+ if(playerView?.PlayerViewModel?.Volume != null)
+ playerView.PlayerViewModel.Volume += increment;
+ }
+
+ // Volume Down
+ else if(e.Key == App.Settings.VolumeDownKey || e.Key == Key.VolumeDown)
+ {
+ int decrement = 1;
+ if(e.KeyModifiers.HasFlag(KeyModifiers.Control))
+ decrement *= 2;
+ if(e.KeyModifiers.HasFlag(KeyModifiers.Shift))
+ decrement *= 5;
+
+ if (playerView?.PlayerViewModel?.Volume != null)
+ playerView.PlayerViewModel.Volume -= decrement;
+ }
+
+ // Mute Toggle
+ else if(e.Key == App.Settings.ToggleMuteKey || e.Key == Key.VolumeMute)
+ {
+ playerView?.MuteToggleButton_Click(this, null);
+ }
+
+ // Emphasis Toggle
+ else if(e.Key == App.Settings.ToggleDeEmphasisKey)
+ {
+ playerView?.EnableDisableDeEmphasisButton_Click(this, null);
+ }
}
#endregion
diff --git a/RedBookPlayer/GUI/PlayerView.xaml.cs b/RedBookPlayer/GUI/PlayerView.xaml.cs
index 533e076..7dabeae 100644
--- a/RedBookPlayer/GUI/PlayerView.xaml.cs
+++ b/RedBookPlayer/GUI/PlayerView.xaml.cs
@@ -11,16 +11,15 @@ using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
-using RedBookPlayer.Hardware;
namespace RedBookPlayer.GUI
{
public class PlayerView : UserControl
{
///
- /// Player representing the internal state
+ /// Read-only access to the view model
///
- public static Player Player = new Player();
+ public PlayerViewModel PlayerViewModel => DataContext as PlayerViewModel;
///
/// Set of images representing the digits for the UI
@@ -64,10 +63,10 @@ namespace RedBookPlayer.GUI
/// Path to the image to load
public async Task LoadImage(string path)
{
- bool result = await Task.Run(() =>
+ bool result = await Dispatcher.UIThread.InvokeAsync(() =>
{
- Player.Init(path, App.Settings.AutoPlay);
- return Player.Initialized;
+ PlayerViewModel.Init(path, App.Settings.AutoPlay, App.Settings.Volume);
+ return PlayerViewModel.Initialized;
});
if(result)
@@ -114,10 +113,17 @@ namespace RedBookPlayer.GUI
DataContext = new PlayerViewModel();
// Load the theme
- if (xaml != null)
- new AvaloniaXamlLoader().Load(xaml, null, this);
- else
+ try
+ {
+ if(xaml != null)
+ new AvaloniaXamlLoader().Load(xaml, null, this);
+ else
+ AvaloniaXamlLoader.Load(this);
+ }
+ catch
+ {
AvaloniaXamlLoader.Load(this);
+ }
InitializeDigits();
@@ -181,14 +187,14 @@ namespace RedBookPlayer.GUI
{
Dispatcher.UIThread.InvokeAsync(() =>
{
- string digitString = Player.GenerateDigitString();
+ string digitString = PlayerViewModel.GenerateDigitString();
for (int i = 0; i < _digits.Length; i++)
{
if (_digits[i] != null)
_digits[i].Source = GetBitmap(digitString[i]);
}
- Player.UpdateDataContext(DataContext as PlayerViewModel);
+ PlayerViewModel?.UpdateView();
});
}
@@ -202,32 +208,40 @@ namespace RedBookPlayer.GUI
if (path == null)
return;
- LoadImage(path);
+ await LoadImage(path);
}
- public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(true);
+ public void PlayButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = true;
- public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(false);
+ public void PauseButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = false;
- public void PlayPauseButton_Click(object sender, RoutedEventArgs e) => Player.TogglePlayPause(!Player.Playing);
+ public void PlayPauseButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = !(PlayerViewModel.Playing ?? false);
- public void StopButton_Click(object sender, RoutedEventArgs e) => Player.Stop();
+ public void StopButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Playing = null;
- public void NextTrackButton_Click(object sender, RoutedEventArgs e) => Player.NextTrack();
+ public void NextTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextTrack();
- public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => Player.PreviousTrack();
+ public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousTrack();
- public void NextIndexButton_Click(object sender, RoutedEventArgs e) => Player.NextIndex(App.Settings.IndexButtonChangeTrack);
+ public void NextIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.NextIndex(App.Settings.IndexButtonChangeTrack);
- public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => Player.PreviousIndex(App.Settings.IndexButtonChangeTrack);
+ public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.PreviousIndex(App.Settings.IndexButtonChangeTrack);
- public void FastForwardButton_Click(object sender, RoutedEventArgs e) => Player.FastForward();
+ public void FastForwardButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.FastForward();
- public void RewindButton_Click(object sender, RoutedEventArgs e) => Player.Rewind();
+ public void RewindButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Rewind();
- public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(true);
+ public void VolumeUpButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume++;
- public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.ToggleDeEmphasis(false);
+ public void VolumeDownButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.Volume--;
+
+ public void MuteToggleButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ToggleMute();
+
+ public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = true;
+
+ public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = false;
+
+ public void EnableDisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => PlayerViewModel.ApplyDeEmphasis = !PlayerViewModel.ApplyDeEmphasis;
#endregion
}
diff --git a/RedBookPlayer/GUI/PlayerViewModel.cs b/RedBookPlayer/GUI/PlayerViewModel.cs
index 5090377..4030ba5 100644
--- a/RedBookPlayer/GUI/PlayerViewModel.cs
+++ b/RedBookPlayer/GUI/PlayerViewModel.cs
@@ -1,16 +1,99 @@
+using System.Linq;
+using Aaru.CommonTypes.Enums;
using ReactiveUI;
+using RedBookPlayer.Discs;
+using RedBookPlayer.Hardware;
namespace RedBookPlayer.GUI
{
public class PlayerViewModel : ReactiveObject
{
- private bool _applyDeEmphasis;
+ ///
+ /// Player representing the internal state
+ ///
+ private Player _player;
+
+ ///
+ /// Last volume for mute toggling
+ ///
+ private int? _lastVolume = null;
+
+ #region Player Status
+
+ ///
+ /// Indicate if the model is ready to be used
+ ///
+ public bool Initialized => _player?.Initialized ?? false;
+
+ ///
+ /// Indicate the player state
+ ///
+ public bool? Playing
+ {
+ get => _player?.Playing ?? false;
+ set
+ {
+ if(_player != null)
+ _player.Playing = value;
+ }
+ }
+
+ ///
+ /// Indicate the current playback volume
+ ///
+ public int Volume
+ {
+ get => _player?.Volume ?? 100;
+ set
+ {
+ if(_player != null)
+ _player.Volume = value;
+ }
+ }
+
+ ///
+ /// Indicates if de-emphasis should be applied
+ ///
public bool ApplyDeEmphasis
{
- get => _applyDeEmphasis;
- set => this.RaiseAndSetIfChanged(ref _applyDeEmphasis, value);
+ get => _player?.ApplyDeEmphasis ?? false;
+ set
+ {
+ if(_player != null)
+ _player.ApplyDeEmphasis = value;
+ }
}
+ #endregion
+
+ #region Model-Provided Playback Information
+
+ private ulong _currentSector;
+ public ulong CurrentSector
+ {
+ get => _currentSector;
+ set => this.RaiseAndSetIfChanged(ref _currentSector, value);
+ }
+
+ public int CurrentFrame => (int)(_currentSector / (75 * 60));
+ public int CurrentSecond => (int)(_currentSector / 75 % 60);
+ public int CurrentMinute => (int)(_currentSector % 75);
+
+ private ulong _totalSectors;
+ public ulong TotalSectors
+ {
+ get => _totalSectors;
+ set => this.RaiseAndSetIfChanged(ref _totalSectors, value);
+ }
+
+ public int TotalFrames => (int)(_totalSectors / (75 * 60));
+ public int TotalSeconds => (int)(_totalSectors / 75 % 60);
+ public int TotalMinutes => (int)(_totalSectors % 75);
+
+ #endregion
+
+ #region Disc Flags
+
private bool _quadChannel;
public bool QuadChannel
{
@@ -45,5 +128,143 @@ namespace RedBookPlayer.GUI
get => _hiddenTrack;
set => this.RaiseAndSetIfChanged(ref _hiddenTrack, value);
}
+
+ #endregion
+
+ ///
+ /// Initialize the view model with a given image path
+ ///
+ /// Path to the disc image
+ /// True if playback should begin immediately, false otherwise
+ /// Default volume between 0 and 100 to use when starting playback
+ public void Init(string path, bool autoPlay, int defaultVolume)
+ {
+ // Stop current playback, if necessary
+ if(Playing != null) Playing = null;
+
+ // Create and attempt to initialize new Player
+ _player = new Player(path, autoPlay, defaultVolume);
+ if(Initialized)
+ UpdateView();
+ }
+
+ #region Playback
+
+ ///
+ /// Move to the next playable track
+ ///
+ public void NextTrack() => _player?.NextTrack();
+
+ ///
+ /// Move to the previous playable track
+ ///
+ public void PreviousTrack() => _player?.PreviousTrack();
+
+ ///
+ /// Move to the next index
+ ///
+ /// True if index changes can trigger a track change, false otherwise
+ public void NextIndex(bool changeTrack) => _player?.NextIndex(changeTrack);
+
+ ///
+ /// Move to the previous index
+ ///
+ /// True if index changes can trigger a track change, false otherwise
+ public void PreviousIndex(bool changeTrack) => _player?.PreviousIndex(changeTrack);
+
+ ///
+ /// Fast-forward playback by 75 sectors, if possible
+ ///
+ public void FastForward() => _player?.FastForward();
+
+ ///
+ /// Rewind playback by 75 sectors, if possible
+ ///
+ public void Rewind() => _player?.Rewind();
+
+ #endregion
+
+ #region Helpers
+
+ ///
+ /// Generate the digit string to be interpreted by the frontend
+ ///
+ /// String representing the digits for the frontend
+ public string GenerateDigitString()
+ {
+ // If the disc isn't initialized, return all '-' characters
+ if(_player?.OpticalDisc == null || !_player.OpticalDisc.Initialized)
+ return string.Empty.PadLeft(20, '-');
+
+ // Otherwise, take the current time into account
+ ulong sectorTime = _player.GetCurrentSectorTime();
+
+ int[] numbers = new int[]
+ {
+ _player.OpticalDisc.CurrentTrackNumber + 1,
+ _player.OpticalDisc.CurrentTrackIndex,
+
+ (int)(sectorTime / (75 * 60)),
+ (int)(sectorTime / 75 % 60),
+ (int)(sectorTime % 75),
+
+ _player.OpticalDisc.TotalTracks,
+ _player.OpticalDisc.TotalIndexes,
+
+ (int)(_player.OpticalDisc.TotalTime / (75 * 60)),
+ (int)(_player.OpticalDisc.TotalTime / 75 % 60),
+ (int)(_player.OpticalDisc.TotalTime % 75),
+ };
+
+ return string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2)));
+ }
+
+ ///
+ /// Temporarily mute playback
+ ///
+ public void ToggleMute()
+ {
+ if(_lastVolume == null)
+ {
+ _lastVolume = Volume;
+ Volume = 0;
+ }
+ else
+ {
+ Volume = _lastVolume.Value;
+ _lastVolume = null;
+ }
+ }
+
+ ///
+ /// Update the UI from the internal player
+ ///
+ public void UpdateView()
+ {
+ if(_player?.Initialized != true)
+ return;
+
+ CurrentSector = _player.GetCurrentSectorTime();
+ TotalSectors = _player.OpticalDisc.TotalTime;
+
+ HiddenTrack = _player.OpticalDisc.TimeOffset > 150;
+
+ if(_player.OpticalDisc is CompactDisc compactDisc)
+ {
+ QuadChannel = compactDisc.QuadChannel;
+ IsDataTrack = compactDisc.IsDataTrack;
+ CopyAllowed = compactDisc.CopyAllowed;
+ TrackHasEmphasis = compactDisc.TrackHasEmphasis;
+ }
+ else
+ {
+ QuadChannel = false;
+ IsDataTrack = _player.OpticalDisc.TrackType != TrackType.Audio;
+ CopyAllowed = false;
+ TrackHasEmphasis = false;
+ }
+ }
+
+ #endregion
}
}
\ No newline at end of file
diff --git a/RedBookPlayer/GUI/SettingsWindow.xaml b/RedBookPlayer/GUI/SettingsWindow.xaml
index 7f5c54a..ba416ba 100644
--- a/RedBookPlayer/GUI/SettingsWindow.xaml
+++ b/RedBookPlayer/GUI/SettingsWindow.xaml
@@ -1,40 +1,122 @@
-
- Themes
-
-
-
- Auto-play CD on load
-
-
-
- Index navigation can change track
-
-
-
- Treat index 0 of track 1 as track 0 (hidden track)
-
-
-
- Play data tracks like old, non-compliant players
-
-
-
- Generate a TOC if the disc is missing one
-
-
- Volume
-
-
-
-
-
-
-
-
+ d:DesignHeight="450" x:Class="RedBookPlayer.GUI.SettingsWindow" Title="Settings" SizeToContent="WidthAndHeight">
+
+
+
+
+ Themes
+
+
+
+ Auto-play CD on load
+
+
+
+ Index navigation can change track
+
+
+
+ Treat index 0 of track 1 as track 0 (hidden track)
+
+
+
+ Play data tracks like old, non-compliant players
+
+
+
+ Generate a TOC if the disc is missing one
+
+
+ Default Volume
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Load Image
+
+
+
+ Toggle Play/Pause
+
+
+
+ Stop Playback
+
+
+
+ Next Track
+
+
+
+ Previous Track
+
+
+
+ Next Index
+
+
+
+ Previous Index
+
+
+
+ Fast-Forward
+
+
+
+ Rewind
+
+
+
+ Volume Up
+
+
+
+ Volume Down
+
+
+
+ Toggle Mute
+
+
+
+ Toggle De-Emphasis
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RedBookPlayer/GUI/SettingsWindow.xaml.cs b/RedBookPlayer/GUI/SettingsWindow.xaml.cs
index cfe293e..3f1d226 100644
--- a/RedBookPlayer/GUI/SettingsWindow.xaml.cs
+++ b/RedBookPlayer/GUI/SettingsWindow.xaml.cs
@@ -1,6 +1,8 @@
+using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Controls;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
@@ -36,6 +38,7 @@ namespace RedBookPlayer.GUI
MainWindow.ApplyTheme(_selectedTheme);
}
+ SaveKeyboardList();
_settings.Save();
}
@@ -46,6 +49,7 @@ namespace RedBookPlayer.GUI
AvaloniaXamlLoader.Load(this);
PopulateThemes();
+ PopulateKeyboardList();
this.FindControl
public bool Initialized { get; private set; } = false;
+ ///
+ /// OpticalDisc object
+ ///
+ public OpticalDisc OpticalDisc { get; private set; }
+
///
/// Indicate if the disc is playing
///
- public bool Playing => _soundOutput?.Playing ?? false;
+ public bool? Playing
+ {
+ get => _soundOutput?.Playing;
+ set
+ {
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
+ return;
+
+ // If the playing state has not changed, do nothing
+ if(value == _soundOutput?.Playing)
+ return;
+
+ if(value == true)
+ {
+ _soundOutput.Play();
+ OpticalDisc.SetTotalIndexes();
+ }
+ else if(value == false)
+ {
+ _soundOutput.Stop();
+ }
+ else
+ {
+ _soundOutput.Stop();
+ OpticalDisc.LoadFirstTrack();
+ }
+ }
+ }
+
+ ///
+ /// Indicate the current playback volume
+ ///
+ public int Volume
+ {
+ get => _soundOutput?.Volume ?? 100;
+ set
+ {
+ if(_soundOutput != null)
+ _soundOutput.Volume = value;
+ }
+ }
+
+ ///
+ /// Indicates if de-emphasis should be applied
+ ///
+ public bool ApplyDeEmphasis
+ {
+ get => _soundOutput?.ApplyDeEmphasis ?? false;
+ set => _soundOutput?.SetDeEmphasis(value);
+ }
#endregion
#region Private State Variables
- ///
- /// OpticalDisc object
- ///
- private OpticalDisc _opticalDisc;
-
///
/// Sound output handling class
///
@@ -40,17 +86,18 @@ namespace RedBookPlayer.Hardware
#endregion
///
- /// Initialize the player with a given image path
+ /// Create a new Player from a given image path
///
/// Path to the disc image
/// True if playback should begin immediately, false otherwise
- public void Init(string path, bool autoPlay = false)
+ /// Default volume between 0 and 100 to use when starting playback
+ public Player(string path, bool autoPlay = false, int defaultVolume = 100)
{
- // Reset the internal state for initialization
+ // Set the internal state for initialization
Initialized = false;
_soundOutput = new SoundOutput();
_soundOutput.ApplyDeEmphasis = false;
- _opticalDisc = null;
+ OpticalDisc = null;
try
{
@@ -65,7 +112,7 @@ namespace RedBookPlayer.Hardware
image.Open(filter);
// Generate and instantiate the disc
- _opticalDisc = OpticalDiscFactory.GenerateFromImage(image, App.Settings.AutoPlay);
+ OpticalDisc = OpticalDiscFactory.GenerateFromImage(image, autoPlay);
}
catch
{
@@ -74,7 +121,7 @@ namespace RedBookPlayer.Hardware
}
// Initialize the sound output
- _soundOutput.Init(_opticalDisc, autoPlay);
+ _soundOutput.Init(OpticalDisc, autoPlay, defaultVolume);
if(_soundOutput == null || !_soundOutput.Initialized)
return;
@@ -84,54 +131,22 @@ namespace RedBookPlayer.Hardware
#region Playback
- ///
- /// Toggle audio playback
- ///
- /// True to start playback, false to pause
- public void TogglePlayPause(bool start)
- {
- if(_opticalDisc == null || !_opticalDisc.Initialized)
- return;
-
- if(start)
- {
- _soundOutput.Play();
- _opticalDisc.SetTotalIndexes();
- }
- else
- {
- _soundOutput.Stop();
- }
- }
-
- ///
- /// Stop the current audio playback
- ///
- public void Stop()
- {
- if(_opticalDisc == null || !_opticalDisc.Initialized)
- return;
-
- _soundOutput.Stop();
- _opticalDisc.LoadFirstTrack();
- }
-
///
/// Move to the next playable track
///
public void NextTrack()
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- bool wasPlaying = Playing;
- if(wasPlaying) TogglePlayPause(false);
+ bool? wasPlaying = Playing;
+ if(wasPlaying == true) Playing = false;
- _opticalDisc.NextTrack();
- if(_opticalDisc is CompactDisc compactDisc)
+ OpticalDisc.NextTrack();
+ if(OpticalDisc is CompactDisc compactDisc)
_soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
- if(wasPlaying) TogglePlayPause(true);
+ if(wasPlaying == true) Playing = true;
}
///
@@ -139,17 +154,17 @@ namespace RedBookPlayer.Hardware
///
public void PreviousTrack()
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- bool wasPlaying = Playing;
- if(wasPlaying) TogglePlayPause(false);
+ bool? wasPlaying = Playing;
+ if(wasPlaying == true) Playing = false;
- _opticalDisc.PreviousTrack();
- if(_opticalDisc is CompactDisc compactDisc)
+ OpticalDisc.PreviousTrack();
+ if(OpticalDisc is CompactDisc compactDisc)
_soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
- if(wasPlaying) TogglePlayPause(true);
+ if(wasPlaying == true) Playing = true;
}
///
@@ -158,17 +173,17 @@ namespace RedBookPlayer.Hardware
/// True if index changes can trigger a track change, false otherwise
public void NextIndex(bool changeTrack)
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- bool wasPlaying = Playing;
- if(wasPlaying) TogglePlayPause(false);
+ bool? wasPlaying = Playing;
+ if(wasPlaying == true) Playing = false;
- _opticalDisc.NextIndex(changeTrack);
- if(_opticalDisc is CompactDisc compactDisc)
+ OpticalDisc.NextIndex(changeTrack);
+ if(OpticalDisc is CompactDisc compactDisc)
_soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
- if(wasPlaying) TogglePlayPause(true);
+ if(wasPlaying == true) Playing = true;
}
///
@@ -177,17 +192,17 @@ namespace RedBookPlayer.Hardware
/// True if index changes can trigger a track change, false otherwise
public void PreviousIndex(bool changeTrack)
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- bool wasPlaying = Playing;
- if(wasPlaying) TogglePlayPause(false);
+ bool? wasPlaying = Playing;
+ if(wasPlaying == true) Playing = false;
- _opticalDisc.PreviousIndex(changeTrack);
- if(_opticalDisc is CompactDisc compactDisc)
+ OpticalDisc.PreviousIndex(changeTrack);
+ if(OpticalDisc is CompactDisc compactDisc)
_soundOutput.ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
- if(wasPlaying) TogglePlayPause(true);
+ if(wasPlaying == true) Playing = true;
}
///
@@ -195,10 +210,10 @@ namespace RedBookPlayer.Hardware
///
public void FastForward()
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- _opticalDisc.CurrentSector = Math.Min(_opticalDisc.TotalSectors, _opticalDisc.CurrentSector + 75);
+ OpticalDisc.CurrentSector = Math.Min(OpticalDisc.TotalSectors, OpticalDisc.CurrentSector + 75);
}
///
@@ -206,11 +221,11 @@ namespace RedBookPlayer.Hardware
///
public void Rewind()
{
- if(_opticalDisc == null || !_opticalDisc.Initialized)
+ if(OpticalDisc == null || !OpticalDisc.Initialized)
return;
- if(_opticalDisc.CurrentSector >= 75)
- _opticalDisc.CurrentSector -= 75;
+ if(OpticalDisc.CurrentSector >= 75)
+ OpticalDisc.CurrentSector -= 75;
}
#endregion
@@ -218,75 +233,25 @@ namespace RedBookPlayer.Hardware
#region Helpers
///
- /// Generate the digit string to be interpreted by the frontend
+ /// Get current sector time, accounting for offsets
///
- /// String representing the digits for the frontend
- public string GenerateDigitString()
+ /// ulong representing the current sector time
+ public ulong GetCurrentSectorTime()
{
- // 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;
+ ulong sectorTime = OpticalDisc.CurrentSector;
+ if (OpticalDisc.SectionStartSector != 0)
+ sectorTime -= OpticalDisc.SectionStartSector;
else
- sectorTime += _opticalDisc.TimeOffset;
+ 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)));
+ return sectorTime;
}
///
- /// Toggle de-emphasis processing
+ /// Set if de-emphasis should be applied
///
- /// True to apply de-emphasis, false otherwise
- public void ToggleDeEmphasis(bool enable) => _soundOutput.ToggleDeEmphasis(enable);
-
- ///
- /// Update the data context for the frontend
- ///
- /// Data context to be updated
- public void UpdateDataContext(PlayerViewModel dataContext)
- {
- if(!Initialized || dataContext == null)
- return;
-
- dataContext.HiddenTrack = _opticalDisc.TimeOffset > 150;
- dataContext.ApplyDeEmphasis = _soundOutput.ApplyDeEmphasis;
-
- if(_opticalDisc is CompactDisc compactDisc)
- {
- dataContext.QuadChannel = compactDisc.QuadChannel;
- dataContext.IsDataTrack = compactDisc.IsDataTrack;
- dataContext.CopyAllowed = compactDisc.CopyAllowed;
- dataContext.TrackHasEmphasis = compactDisc.TrackHasEmphasis;
- }
- else
- {
- dataContext.QuadChannel = false;
- dataContext.IsDataTrack = _opticalDisc.TrackType != TrackType.Audio;
- dataContext.CopyAllowed = false;
- dataContext.TrackHasEmphasis = false;
- }
- }
+ /// True to enable, false to disable
+ public void SetDeEmphasis(bool apply) => _soundOutput?.SetDeEmphasis(apply);
#endregion
}
diff --git a/RedBookPlayer/Hardware/SoundOutput.cs b/RedBookPlayer/Hardware/SoundOutput.cs
index a88dab7..d31b68c 100644
--- a/RedBookPlayer/Hardware/SoundOutput.cs
+++ b/RedBookPlayer/Hardware/SoundOutput.cs
@@ -27,6 +27,23 @@ namespace RedBookPlayer.Hardware
///
public bool Playing => _soundOut.PlaybackState == PlaybackState.Playing;
+ ///
+ /// Current playback volume
+ ///
+ public int Volume
+ {
+ get => _volume;
+ set
+ {
+ if(value > 100)
+ _volume = 100;
+ else if(value < 0)
+ _volume = 0;
+ else
+ _volume = value;
+ }
+ }
+
#endregion
#region Private State Variables
@@ -44,6 +61,11 @@ namespace RedBookPlayer.Hardware
///
private OpticalDisc _opticalDisc;
+ ///
+ /// Internal value for the volume
+ ///
+ private int _volume;
+
///
/// Data provider for sound output
///
@@ -76,7 +98,8 @@ namespace RedBookPlayer.Hardware
///
/// OpticalDisc to load from
/// True if playback should begin immediately, false otherwise
- public void Init(OpticalDisc opticalDisc, bool autoPlay = false)
+ /// Default volume between 0 and 100 to use when starting playback
+ public void Init(OpticalDisc opticalDisc, bool autoPlay = false, int defaultVolume = 100)
{
// If we have an unusable disc, just return
if(opticalDisc == null || !opticalDisc.Initialized)
@@ -85,6 +108,9 @@ namespace RedBookPlayer.Hardware
// Save a reference to the disc
_opticalDisc = opticalDisc;
+ // Set the initial playback volume
+ Volume = defaultVolume;
+
// Enable de-emphasis for CDs, if necessary
if(opticalDisc is CompactDisc compactDisc)
ApplyDeEmphasis = compactDisc.TrackHasEmphasis;
@@ -116,7 +142,7 @@ namespace RedBookPlayer.Hardware
public int ProviderRead(byte[] buffer, int offset, int count)
{
// Set the current volume
- _soundOut.Volume = (float)App.Settings.Volume / 100;
+ _soundOut.Volume = (float)Volume / 100;
// Determine how many sectors we can read
ulong sectorsToRead;
@@ -243,7 +269,7 @@ namespace RedBookPlayer.Hardware
/// Toggle de-emphasis processing
///
/// True to apply de-emphasis, false otherwise
- public void ToggleDeEmphasis(bool enable) => ApplyDeEmphasis = enable;
+ public void SetDeEmphasis(bool enable) => ApplyDeEmphasis = enable;
///
/// Sets or resets the de-emphasis filters
diff --git a/RedBookPlayer/Settings.cs b/RedBookPlayer/Settings.cs
index fe144a0..36e95fb 100644
--- a/RedBookPlayer/Settings.cs
+++ b/RedBookPlayer/Settings.cs
@@ -1,12 +1,15 @@
using System;
using System.IO;
using System.Text.Json;
+using Avalonia.Input;
using RedBookPlayer.GUI;
namespace RedBookPlayer
{
public class Settings
{
+ #region Player Settings
+
///
/// Indicates if discs should start playing on load
///
@@ -35,18 +38,111 @@ namespace RedBookPlayer
///
/// Indicates the default playback volume
///
- public int Volume { get; set; } = 100;
+ public int Volume
+ {
+ get => _volume;
+ set
+ {
+ if(value > 100)
+ _volume = 100;
+ else if(value < 0)
+ _volume = 0;
+ else
+ _volume = value;
+ }
+ }
///
/// Indicates the currently selected theme
///
public string SelectedTheme { get; set; } = "default";
+ #endregion
+
+ #region Key Mappings
+
+ ///
+ /// Key assigned to open settings
+ ///
+ public Key OpenSettingsKey { get; set; } = Key.F1;
+
+ ///
+ /// Key assigned to load a new image
+ ///
+ public Key LoadImageKey { get; set; } = Key.F2;
+
+ ///
+ /// Key assigned to toggle play and pause
+ ///
+ public Key TogglePlaybackKey { get; set; } = Key.Space;
+
+ ///
+ /// Key assigned to stop playback
+ ///
+ public Key StopPlaybackKey { get; set; } = Key.Escape;
+
+ ///
+ /// Key assigned to move to the next track
+ ///
+ public Key NextTrackKey { get; set; } = Key.Right;
+
+ ///
+ /// Key assigned to move to the previous track
+ ///
+ public Key PreviousTrackKey { get; set; } = Key.Left;
+
+ ///
+ /// Key assigned to move to the next index
+ ///
+ public Key NextIndexKey { get; set; } = Key.OemCloseBrackets;
+
+ ///
+ /// Key assigned to move to the previous index
+ ///
+ public Key PreviousIndexKey { get; set; } = Key.OemOpenBrackets;
+
+ ///
+ /// Key assigned to fast forward playback
+ ///
+ public Key FastForwardPlaybackKey { get; set; } = Key.OemPeriod;
+
+ ///
+ /// Key assigned to rewind playback
+ ///
+ public Key RewindPlaybackKey { get; set; } = Key.OemComma;
+
+ ///
+ /// Key assigned to raise volume
+ ///
+ public Key VolumeUpKey { get; set; } = Key.Add;
+
+ ///
+ /// Key assigned to lower volume
+ ///
+ public Key VolumeDownKey { get; set; } = Key.Subtract;
+
+ ///
+ /// Key assigned to toggle mute
+ ///
+ public Key ToggleMuteKey { get; set; } = Key.M;
+
+ ///
+ /// Key assigned to toggle de-emphasis
+ ///
+ public Key ToggleDeEmphasisKey { get; set; } = Key.E;
+
+ #endregion
+
///
/// Path to the settings file
///
private string _filePath;
+ ///
+ /// Internal value for the volume
+ ///
+ private int _volume = 100;
+
public Settings() {}
public Settings(string filePath) => _filePath = filePath;