mirror of
https://github.com/aaru-dps/RedBookPlayer.git
synced 2025-12-16 19:24:41 +00:00
General refactor and clean-up.
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
<Application xmlns="https://github.com/avaloniaui"
|
<Application xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
x:Class="RedBookPlayer.App">
|
x:Class="RedBookPlayer.App">
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml" />
|
||||||
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
|
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml" />
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
</Application>
|
</Application>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@@ -11,10 +12,8 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public static Settings Settings;
|
public static Settings Settings;
|
||||||
|
|
||||||
static App()
|
static App() =>
|
||||||
{
|
Directory.SetCurrentDirectory(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
|
||||||
Directory.SetCurrentDirectory(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -28,7 +27,7 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted()
|
public override void OnFrameworkInitializationCompleted()
|
||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
if(ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
desktop.MainWindow = new MainWindow();
|
desktop.MainWindow = new MainWindow();
|
||||||
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public class DeEmphasisFilter : BiQuadFilter
|
public class DeEmphasisFilter : BiQuadFilter
|
||||||
{
|
{
|
||||||
static double B0, B1, B2, A0, A1, A2;
|
static readonly double B0;
|
||||||
|
static readonly double B1;
|
||||||
|
static readonly double B2;
|
||||||
|
static readonly double A0;
|
||||||
|
static readonly double A1;
|
||||||
|
static readonly double A2;
|
||||||
|
|
||||||
static DeEmphasisFilter()
|
static DeEmphasisFilter()
|
||||||
{
|
{
|
||||||
@@ -15,17 +20,17 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
double w0 = 2 * Math.PI * fc / 44100;
|
double w0 = 2 * Math.PI * fc / 44100;
|
||||||
double A = Math.Exp(gain / 40 * Math.Log(10));
|
double A = Math.Exp(gain / 40 * Math.Log(10));
|
||||||
double alpha = Math.Sin(w0) / 2 * Math.Sqrt((A + 1 / A) * (1 / slope - 1) + 2);
|
double alpha = Math.Sin(w0) / 2 * Math.Sqrt(((A + (1 / A)) * ((1 / slope) - 1)) + 2);
|
||||||
|
|
||||||
double cs = Math.Cos(w0);
|
double cs = Math.Cos(w0);
|
||||||
double v = 2 * Math.Sqrt(A) * alpha;
|
double v = 2 * Math.Sqrt(A) * alpha;
|
||||||
|
|
||||||
B0 = A * ((A + 1) + (A - 1) * cs + v);
|
B0 = A * (A + 1 + ((A - 1) * cs) + v);
|
||||||
B1 = -2 * A * ((A - 1) + (A + 1) * cs);
|
B1 = -2 * A * (A - 1 + ((A + 1) * cs));
|
||||||
B2 = A * ((A + 1) + (A - 1) * cs - v);
|
B2 = A * (A + 1 + ((A - 1) * cs) - v);
|
||||||
A0 = (A + 1) - (A - 1) * cs + v;
|
A0 = A + 1 - ((A - 1) * cs) + v;
|
||||||
A1 = 2 * ((A - 1) - (A + 1) * cs);
|
A1 = 2 * (A - 1 - ((A + 1) * cs));
|
||||||
A2 = (A + 1) - (A - 1) * cs - v;
|
A2 = A + 1 - ((A - 1) * cs) - v;
|
||||||
|
|
||||||
B2 /= A0;
|
B2 /= A0;
|
||||||
B1 /= A0;
|
B1 /= A0;
|
||||||
@@ -35,8 +40,6 @@ namespace RedBookPlayer
|
|||||||
A0 = 1;
|
A0 = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeEmphasisFilter() : base(B0, B1, B2, A0, A1, A2)
|
public DeEmphasisFilter() : base(B0, B1, B2, A0, A1, A2) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,34 +2,35 @@ using System;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace RedBookPlayer {
|
namespace RedBookPlayer
|
||||||
|
{
|
||||||
public class HiResTimer
|
public class HiResTimer
|
||||||
{
|
{
|
||||||
private static readonly float tickFrequency = 1000f / Stopwatch.Frequency;
|
static readonly float tickFrequency = 1000f / Stopwatch.Frequency;
|
||||||
|
|
||||||
public event EventHandler<HiResTimerElapsedEventArgs> Elapsed;
|
volatile float interval;
|
||||||
|
volatile bool isRunning;
|
||||||
|
|
||||||
private volatile float interval;
|
public HiResTimer() : this(1f) {}
|
||||||
private volatile bool isRunning;
|
|
||||||
|
|
||||||
public HiResTimer() : this(1f)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public HiResTimer(float interval)
|
public HiResTimer(float interval)
|
||||||
{
|
{
|
||||||
if (interval < 0f || Single.IsNaN(interval))
|
if(interval < 0f ||
|
||||||
|
float.IsNaN(interval))
|
||||||
throw new ArgumentOutOfRangeException(nameof(interval));
|
throw new ArgumentOutOfRangeException(nameof(interval));
|
||||||
|
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float Interval
|
public float Interval
|
||||||
{
|
{
|
||||||
get { return interval; }
|
get => interval;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value < 0f || Single.IsNaN(value))
|
if(value < 0f ||
|
||||||
|
float.IsNaN(value))
|
||||||
throw new ArgumentOutOfRangeException(nameof(value));
|
throw new ArgumentOutOfRangeException(nameof(value));
|
||||||
|
|
||||||
interval = value;
|
interval = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,89 +39,82 @@ namespace RedBookPlayer {
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value)
|
if(value)
|
||||||
Start();
|
Start();
|
||||||
else
|
else
|
||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
get { return isRunning; }
|
get => isRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<HiResTimerElapsedEventArgs> Elapsed;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (isRunning)
|
if(isRunning)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
Thread thread = new Thread(ExecuteTimer);
|
var thread = new Thread(ExecuteTimer);
|
||||||
thread.Priority = ThreadPriority.Highest;
|
thread.Priority = ThreadPriority.Highest;
|
||||||
thread.Start();
|
thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop() => isRunning = false;
|
||||||
{
|
|
||||||
isRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteTimer()
|
void ExecuteTimer()
|
||||||
{
|
{
|
||||||
float nextTrigger = 0f;
|
float nextTrigger = 0f;
|
||||||
|
|
||||||
Stopwatch stopwatch = new Stopwatch();
|
var stopwatch = new Stopwatch();
|
||||||
stopwatch.Start();
|
stopwatch.Start();
|
||||||
|
|
||||||
while (isRunning)
|
while(isRunning)
|
||||||
{
|
{
|
||||||
nextTrigger += interval;
|
nextTrigger += interval;
|
||||||
float elapsed;
|
float elapsed;
|
||||||
|
|
||||||
while (true)
|
while(true)
|
||||||
{
|
{
|
||||||
elapsed = ElapsedHiRes(stopwatch);
|
elapsed = ElapsedHiRes(stopwatch);
|
||||||
float diff = nextTrigger - elapsed;
|
float diff = nextTrigger - elapsed;
|
||||||
if (diff <= 0f)
|
|
||||||
|
if(diff <= 0f)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (diff < 1f)
|
if(diff < 1f)
|
||||||
Thread.SpinWait(10);
|
Thread.SpinWait(10);
|
||||||
else if (diff < 5f)
|
else if(diff < 5f)
|
||||||
Thread.SpinWait(100);
|
Thread.SpinWait(100);
|
||||||
else if (diff < 15f)
|
else if(diff < 15f)
|
||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
else
|
else
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
|
|
||||||
if (!isRunning)
|
if(!isRunning)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float delay = elapsed - nextTrigger;
|
float delay = elapsed - nextTrigger;
|
||||||
Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay));
|
Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay));
|
||||||
|
|
||||||
if (stopwatch.Elapsed.TotalHours >= 1d)
|
if(!(stopwatch.Elapsed.TotalHours >= 1d))
|
||||||
{
|
continue;
|
||||||
|
|
||||||
stopwatch.Restart();
|
stopwatch.Restart();
|
||||||
nextTrigger = 0f;
|
nextTrigger = 0f;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float ElapsedHiRes(Stopwatch stopwatch)
|
static float ElapsedHiRes(Stopwatch stopwatch) => stopwatch.ElapsedTicks * tickFrequency;
|
||||||
{
|
|
||||||
return stopwatch.ElapsedTicks * tickFrequency;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HiResTimerElapsedEventArgs : EventArgs
|
public class HiResTimerElapsedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public float Delay { get; }
|
internal HiResTimerElapsedEventArgs(float delay) => Delay = delay;
|
||||||
|
|
||||||
internal HiResTimerElapsedEventArgs(float delay)
|
public float Delay { get; }
|
||||||
{
|
|
||||||
Delay = delay;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
|
||||||
mc:Ignorable="d"
|
x:Class="RedBookPlayer.MainWindow" Title="RedBookPlayer" SizeToContent="WidthAndHeight">
|
||||||
x:Class="RedBookPlayer.MainWindow"
|
<ContentControl Name="Content" />
|
||||||
Title="RedBookPlayer"
|
|
||||||
SizeToContent="WidthAndHeight">
|
|
||||||
<ContentControl Name="Content"/>
|
|
||||||
</Window>
|
</Window>
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Avalonia.Input;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Xml;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
namespace RedBookPlayer
|
namespace RedBookPlayer
|
||||||
{
|
{
|
||||||
@@ -20,74 +21,76 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public static void ApplyTheme(string theme)
|
public static void ApplyTheme(string theme)
|
||||||
{
|
{
|
||||||
if ((theme ?? "") == "")
|
if((theme ?? "") == "")
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme == "default")
|
if(theme == "default")
|
||||||
{
|
{
|
||||||
MainWindow.Instance.ContentControl.Content = new PlayerView();
|
Instance.ContentControl.Content = new PlayerView();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + theme;
|
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + theme;
|
||||||
string xamlPath = themeDirectory + "/view.xaml";
|
string xamlPath = themeDirectory + "/view.xaml";
|
||||||
|
|
||||||
if (!File.Exists(xamlPath))
|
if(!File.Exists(xamlPath))
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Warning: specified theme doesn't exist, reverting to default");
|
Console.WriteLine("Warning: specified theme doesn't exist, reverting to default");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MainWindow.Instance.ContentControl.Content = new PlayerView(
|
Instance.ContentControl.Content =
|
||||||
File.ReadAllText(xamlPath).Replace("Source=\"", $"Source=\"file://{themeDirectory}/")
|
new PlayerView(File.ReadAllText(xamlPath).
|
||||||
);
|
Replace("Source=\"", $"Source=\"file://{themeDirectory}/"));
|
||||||
}
|
}
|
||||||
catch (System.Xml.XmlException ex)
|
catch(XmlException ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Error: invalid theme XAML ({ex.Message}), reverting to default");
|
Console.WriteLine($"Error: invalid theme XAML ({ex.Message}), reverting to default");
|
||||||
MainWindow.Instance.ContentControl.Content = new PlayerView();
|
Instance.ContentControl.Content = new PlayerView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow.Instance.Width = ((PlayerView)MainWindow.Instance.ContentControl.Content).Width;
|
Instance.Width = ((PlayerView)Instance.ContentControl.Content).Width;
|
||||||
MainWindow.Instance.Height = ((PlayerView)MainWindow.Instance.ContentControl.Content).Height;
|
Instance.Height = ((PlayerView)Instance.ContentControl.Content).Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnKeyDown(object sender, KeyEventArgs e)
|
public void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Key == Key.F1)
|
if(e.Key == Key.F1)
|
||||||
{
|
{
|
||||||
settingsWindow = new SettingsWindow(App.Settings);
|
settingsWindow = new SettingsWindow(App.Settings);
|
||||||
settingsWindow.Show();
|
settingsWindow.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent()
|
void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
ContentControl = this.FindControl<ContentControl>("Content");
|
ContentControl = this.FindControl<ContentControl>("Content");
|
||||||
ContentControl.Content = new PlayerView();
|
ContentControl.Content = new PlayerView();
|
||||||
|
|
||||||
MainWindow.Instance.MaxWidth = ((PlayerView)MainWindow.Instance.ContentControl.Content).Width;
|
Instance.MaxWidth = ((PlayerView)Instance.ContentControl.Content).Width;
|
||||||
MainWindow.Instance.MaxHeight = ((PlayerView)MainWindow.Instance.ContentControl.Content).Height;
|
Instance.MaxHeight = ((PlayerView)Instance.ContentControl.Content).Height;
|
||||||
|
|
||||||
ContentControl.Content = new PlayerView();
|
ContentControl.Content = new PlayerView();
|
||||||
|
|
||||||
this.CanResize = false;
|
CanResize = false;
|
||||||
|
|
||||||
this.KeyDown += OnKeyDown;
|
KeyDown += OnKeyDown;
|
||||||
this.Closing += (s, e) =>
|
|
||||||
|
Closing += (s, e) =>
|
||||||
{
|
{
|
||||||
settingsWindow?.Close();
|
settingsWindow?.Close();
|
||||||
settingsWindow = null;
|
settingsWindow = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.Closing += (e, f) =>
|
Closing += (e, f) =>
|
||||||
{
|
{
|
||||||
PlayerView.Player.Stop();
|
PlayerView.Player.Stop();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Aaru.CommonTypes.Enums;
|
using Aaru.CommonTypes.Enums;
|
||||||
using Aaru.CommonTypes.Structs;
|
using Aaru.CommonTypes.Structs;
|
||||||
using Aaru.Decoders.CD;
|
|
||||||
using static Aaru.Decoders.CD.FullTOC;
|
|
||||||
using Aaru.DiscImages;
|
using Aaru.DiscImages;
|
||||||
using Aaru.Helpers;
|
using Aaru.Helpers;
|
||||||
using CSCore.SoundOut;
|
|
||||||
using CSCore;
|
using CSCore;
|
||||||
|
using CSCore.SoundOut;
|
||||||
using NWaves.Audio;
|
using NWaves.Audio;
|
||||||
using NWaves.Filters.BiQuad;
|
using NWaves.Filters.BiQuad;
|
||||||
|
using static Aaru.Decoders.CD.FullTOC;
|
||||||
|
using WaveFormat = CSCore.WaveFormat;
|
||||||
|
|
||||||
namespace RedBookPlayer
|
namespace RedBookPlayer
|
||||||
{
|
{
|
||||||
@@ -19,48 +18,49 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public enum TrackType
|
public enum TrackType
|
||||||
{
|
{
|
||||||
Audio,
|
Audio, Data
|
||||||
Data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Initialized = false;
|
readonly object readingImage = new object();
|
||||||
private int currentTrack = 0;
|
|
||||||
|
ushort currentIndex = 1;
|
||||||
|
ulong currentSector;
|
||||||
|
int currentSectorReadPosition;
|
||||||
|
int currentTrack;
|
||||||
|
BiQuadFilter deEmphasisFilterLeft;
|
||||||
|
BiQuadFilter deEmphasisFilterRight;
|
||||||
|
public bool Initialized;
|
||||||
|
ALSoundOut soundOut;
|
||||||
|
PlayerSource source;
|
||||||
|
CDFullTOC toc;
|
||||||
|
int volume = 100;
|
||||||
|
|
||||||
public int CurrentTrack
|
public int CurrentTrack
|
||||||
{
|
{
|
||||||
get
|
get => currentTrack;
|
||||||
{
|
|
||||||
return currentTrack;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
if (Image != null)
|
if(Image == null)
|
||||||
{
|
return;
|
||||||
if (value >= Image.Tracks.Count)
|
|
||||||
{
|
if(value >= Image.Tracks.Count)
|
||||||
currentTrack = 0;
|
currentTrack = 0;
|
||||||
}
|
else if(value < 0)
|
||||||
else if (value < 0)
|
currentTrack = Image.Tracks.Count - 1;
|
||||||
{
|
else
|
||||||
currentTrack = Image.Tracks.Count - 1;
|
currentTrack = value;
|
||||||
}
|
|
||||||
else
|
byte[] flagsData =
|
||||||
{
|
Image.ReadSectorTag(Image.Tracks[CurrentTrack].TrackSequence, SectorTagType.CdTrackFlags);
|
||||||
currentTrack = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] flagsData = Image.ReadSectorTag(Image.Tracks[CurrentTrack].TrackSequence, SectorTagType.CdTrackFlags);
|
|
||||||
ApplyDeEmphasis = ((CdFlags)flagsData[0]).HasFlag(CdFlags.PreEmphasis);
|
ApplyDeEmphasis = ((CdFlags)flagsData[0]).HasFlag(CdFlags.PreEmphasis);
|
||||||
|
|
||||||
byte[] subchannel = Image.ReadSectorTag(
|
byte[] subchannel = Image.ReadSectorTag(Image.Tracks[CurrentTrack].TrackStartSector,
|
||||||
Image.Tracks[CurrentTrack].TrackStartSector,
|
SectorTagType.CdSectorSubchannel);
|
||||||
SectorTagType.CdSectorSubchannel
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ApplyDeEmphasis)
|
if(!ApplyDeEmphasis)
|
||||||
{
|
|
||||||
ApplyDeEmphasis = (subchannel[3] & 0b01000000) != 0;
|
ApplyDeEmphasis = (subchannel[3] & 0b01000000) != 0;
|
||||||
}
|
|
||||||
|
|
||||||
CopyAllowed = (subchannel[2] & 0b01000000) != 0;
|
CopyAllowed = (subchannel[2] & 0b01000000) != 0;
|
||||||
TrackType_ = (subchannel[1] & 0b01000000) != 0 ? TrackType.Data : TrackType.Audio;
|
TrackType_ = (subchannel[1] & 0b01000000) != 0 ? TrackType.Data : TrackType.Audio;
|
||||||
@@ -71,14 +71,10 @@ namespace RedBookPlayer
|
|||||||
CurrentIndex = Image.Tracks[CurrentTrack].Indexes.Keys.Min();
|
CurrentIndex = Image.Tracks[CurrentTrack].Indexes.Keys.Min();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ushort currentIndex = 1;
|
|
||||||
public ushort CurrentIndex
|
public ushort CurrentIndex
|
||||||
{
|
{
|
||||||
get
|
get => currentIndex;
|
||||||
{
|
|
||||||
return currentIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
@@ -88,98 +84,92 @@ namespace RedBookPlayer
|
|||||||
TotalTime = Image.Tracks[CurrentTrack].TrackEndSector - Image.Tracks[CurrentTrack].TrackStartSector;
|
TotalTime = Image.Tracks[CurrentTrack].TrackEndSector - Image.Tracks[CurrentTrack].TrackStartSector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private ulong currentSector = 0;
|
|
||||||
private int currentSectorReadPosition = 0;
|
|
||||||
public ulong CurrentSector
|
public ulong CurrentSector
|
||||||
{
|
{
|
||||||
get
|
get => currentSector;
|
||||||
{
|
|
||||||
return currentSector;
|
|
||||||
}
|
|
||||||
|
|
||||||
private set
|
private set
|
||||||
{
|
{
|
||||||
currentSector = value;
|
currentSector = value;
|
||||||
|
|
||||||
if (Image != null)
|
if(Image == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if((CurrentTrack < Image.Tracks.Count - 1 &&
|
||||||
|
CurrentSector >= Image.Tracks[CurrentTrack + 1].TrackStartSector) ||
|
||||||
|
(CurrentTrack > 0 && CurrentSector < Image.Tracks[CurrentTrack].TrackStartSector))
|
||||||
{
|
{
|
||||||
if ((CurrentTrack < Image.Tracks.Count - 1 && CurrentSector >= Image.Tracks[CurrentTrack + 1].TrackStartSector)
|
foreach(Track track in Image.Tracks.ToArray().Reverse())
|
||||||
|| (CurrentTrack > 0 && CurrentSector < Image.Tracks[CurrentTrack].TrackStartSector))
|
|
||||||
{
|
|
||||||
foreach (Track track in Image.Tracks.ToArray().Reverse())
|
|
||||||
{
|
|
||||||
if (CurrentSector >= track.TrackStartSector)
|
|
||||||
{
|
{
|
||||||
|
if(CurrentSector < track.TrackStartSector)
|
||||||
|
continue;
|
||||||
|
|
||||||
CurrentTrack = (int)track.TrackSequence - 1;
|
CurrentTrack = (int)track.TrackSequence - 1;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in Image.Tracks[CurrentTrack].Indexes.Reverse())
|
foreach((ushort key, int i) in Image.Tracks[CurrentTrack].Indexes.Reverse())
|
||||||
{
|
{
|
||||||
if ((int)CurrentSector >= item.Value)
|
if((int)CurrentSector < i)
|
||||||
{
|
continue;
|
||||||
CurrentIndex = item.Key;
|
|
||||||
|
CurrentIndex = key;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
CurrentIndex = 0;
|
CurrentIndex = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
public bool TrackHasEmphasis { get; private set; } = false;
|
public bool TrackHasEmphasis { get; private set; }
|
||||||
public bool ApplyDeEmphasis { get; private set; } = false;
|
public bool ApplyDeEmphasis { get; private set; }
|
||||||
public bool CopyAllowed { get; private set; } = false;
|
public bool CopyAllowed { get; private set; }
|
||||||
public TrackType? TrackType_ { get; private set; }
|
public TrackType? TrackType_ { get; private set; }
|
||||||
public ulong SectionStartSector { get; private set; }
|
public ulong SectionStartSector { get; private set; }
|
||||||
public int TotalTracks { get; private set; } = 0;
|
public int TotalTracks { get; private set; }
|
||||||
public int TotalIndexes { get; private set; } = 0;
|
public int TotalIndexes { get; private set; }
|
||||||
public ulong TimeOffset { get; private set; } = 0;
|
public ulong TimeOffset { get; private set; }
|
||||||
public ulong TotalTime { get; private set; } = 0;
|
public ulong TotalTime { get; private set; }
|
||||||
int volume = 100;
|
|
||||||
public int Volume
|
public int Volume
|
||||||
{
|
{
|
||||||
get
|
get => volume;
|
||||||
{
|
|
||||||
return volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (volume >= 0 && volume <= 100)
|
if(volume >= 0 &&
|
||||||
{
|
volume <= 100)
|
||||||
volume = value;
|
volume = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
public AaruFormat Image { get; private set; }
|
public AaruFormat Image { get; private set; }
|
||||||
FullTOC.CDFullTOC toc;
|
|
||||||
PlayerSource source;
|
|
||||||
ALSoundOut soundOut;
|
|
||||||
BiQuadFilter deEmphasisFilterLeft;
|
|
||||||
BiQuadFilter deEmphasisFilterRight;
|
|
||||||
object readingImage = new object();
|
|
||||||
|
|
||||||
public async void Init(AaruFormat image, bool autoPlay = false)
|
public async void Init(AaruFormat image, bool autoPlay = false)
|
||||||
{
|
{
|
||||||
this.Image = image;
|
Image = image;
|
||||||
|
|
||||||
if (await Task.Run(() => image.Info.ReadableMediaTags?.Contains(MediaTagType.CD_FullTOC)) != true)
|
if(await Task.Run(() => image.Info.ReadableMediaTags?.Contains(MediaTagType.CD_FullTOC)) != true)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Full TOC not found");
|
Console.WriteLine("Full TOC not found");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] tocBytes = await Task.Run(() => image.ReadDiskTag(MediaTagType.CD_FullTOC));
|
byte[] tocBytes = await Task.Run(() => image.ReadDiskTag(MediaTagType.CD_FullTOC));
|
||||||
|
|
||||||
if ((tocBytes?.Length ?? 0) == 0)
|
if((tocBytes?.Length ?? 0) == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error reading TOC from disc image");
|
Console.WriteLine("Error reading TOC from disc image");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Swapping.Swap(BitConverter.ToUInt16(tocBytes, 0)) + 2 != tocBytes.Length)
|
if(Swapping.Swap(BitConverter.ToUInt16(tocBytes, 0)) + 2 != tocBytes.Length)
|
||||||
{
|
{
|
||||||
byte[] tmp = new byte[tocBytes.Length + 2];
|
byte[] tmp = new byte[tocBytes.Length + 2];
|
||||||
Array.Copy(tocBytes, 0, tmp, 2, tocBytes.Length);
|
Array.Copy(tocBytes, 0, tmp, 2, tocBytes.Length);
|
||||||
@@ -188,19 +178,20 @@ namespace RedBookPlayer
|
|||||||
tocBytes = tmp;
|
tocBytes = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
FullTOC.CDFullTOC? nullableToc = await Task.Run(() => FullTOC.Decode(tocBytes));
|
CDFullTOC? nullableToc = await Task.Run(() => Decode(tocBytes));
|
||||||
|
|
||||||
if (nullableToc == null)
|
if(nullableToc == null)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error decoding TOC");
|
Console.WriteLine("Error decoding TOC");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toc = nullableToc.Value;
|
toc = nullableToc.Value;
|
||||||
|
|
||||||
Console.WriteLine(FullTOC.Prettify(toc));
|
Console.WriteLine(Prettify(toc));
|
||||||
|
|
||||||
if (deEmphasisFilterLeft == null)
|
if(deEmphasisFilterLeft == null)
|
||||||
{
|
{
|
||||||
deEmphasisFilterLeft = new DeEmphasisFilter();
|
deEmphasisFilterLeft = new DeEmphasisFilter();
|
||||||
deEmphasisFilterRight = new DeEmphasisFilter();
|
deEmphasisFilterRight = new DeEmphasisFilter();
|
||||||
@@ -211,7 +202,7 @@ namespace RedBookPlayer
|
|||||||
deEmphasisFilterRight.Reset();
|
deEmphasisFilterRight.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (source == null)
|
if(source == null)
|
||||||
{
|
{
|
||||||
source = new PlayerSource(ProviderRead);
|
source = new PlayerSource(ProviderRead);
|
||||||
|
|
||||||
@@ -219,25 +210,19 @@ namespace RedBookPlayer
|
|||||||
soundOut.Initialize(source);
|
soundOut.Initialize(source);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
soundOut.Stop();
|
soundOut.Stop();
|
||||||
}
|
|
||||||
|
|
||||||
CurrentTrack = 0;
|
CurrentTrack = 0;
|
||||||
LoadTrack(0);
|
LoadTrack(0);
|
||||||
|
|
||||||
if (autoPlay)
|
if(autoPlay)
|
||||||
{
|
|
||||||
soundOut.Play();
|
soundOut.Play();
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
TotalIndexes = 0;
|
TotalIndexes = 0;
|
||||||
}
|
|
||||||
|
|
||||||
TotalTracks = image.Tracks.Count;
|
TotalTracks = image.Tracks.Count;
|
||||||
TrackDataDescriptor firstTrack = toc.TrackDescriptors.First(d => d.ADR == 1 && d.POINT == 1);
|
TrackDataDescriptor firstTrack = toc.TrackDescriptors.First(d => d.ADR == 1 && d.POINT == 1);
|
||||||
TimeOffset = (ulong)(firstTrack.PMIN * 60 * 75 + firstTrack.PSEC * 75 + firstTrack.PFRAME);
|
TimeOffset = (ulong)((firstTrack.PMIN * 60 * 75) + (firstTrack.PSEC * 75) + firstTrack.PFRAME);
|
||||||
TotalTime = TimeOffset + image.Tracks.Last().TrackEndSector;
|
TotalTime = TimeOffset + image.Tracks.Last().TrackEndSector;
|
||||||
|
|
||||||
Volume = App.Settings.Volume;
|
Volume = App.Settings.Volume;
|
||||||
@@ -256,56 +241,58 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
sectorsToRead = (ulong)count / 2352 + 2;
|
sectorsToRead = ((ulong)count / 2352) + 2;
|
||||||
zeroSectorsAmount = 0;
|
zeroSectorsAmount = 0;
|
||||||
|
|
||||||
if (CurrentSector + sectorsToRead > Image.Info.Sectors)
|
if(CurrentSector + sectorsToRead > Image.Info.Sectors)
|
||||||
{
|
{
|
||||||
ulong oldSectorsToRead = sectorsToRead;
|
ulong oldSectorsToRead = sectorsToRead;
|
||||||
sectorsToRead = Image.Info.Sectors - CurrentSector;
|
sectorsToRead = Image.Info.Sectors - CurrentSector;
|
||||||
zeroSectorsAmount = oldSectorsToRead - sectorsToRead;
|
zeroSectorsAmount = oldSectorsToRead - sectorsToRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sectorsToRead <= 0)
|
if(sectorsToRead > 0)
|
||||||
{
|
continue;
|
||||||
|
|
||||||
LoadTrack(0);
|
LoadTrack(0);
|
||||||
currentSectorReadPosition = 0;
|
currentSectorReadPosition = 0;
|
||||||
}
|
} while(sectorsToRead <= 0);
|
||||||
} while (sectorsToRead <= 0);
|
|
||||||
|
|
||||||
byte[] zeroSectors = new Byte[zeroSectorsAmount * 2352];
|
byte[] zeroSectors = new byte[zeroSectorsAmount * 2352];
|
||||||
Array.Clear(zeroSectors, 0, zeroSectors.Length);
|
Array.Clear(zeroSectors, 0, zeroSectors.Length);
|
||||||
byte[] audioData;
|
byte[] audioData;
|
||||||
|
|
||||||
Task<byte[]> task = Task.Run(() =>
|
Task<byte[]> task = Task.Run(() =>
|
||||||
{
|
{
|
||||||
lock (readingImage)
|
lock(readingImage)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
|
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
|
||||||
}
|
}
|
||||||
catch (System.ArgumentOutOfRangeException)
|
catch(ArgumentOutOfRangeException)
|
||||||
{
|
{
|
||||||
LoadTrack(0);
|
LoadTrack(0);
|
||||||
|
|
||||||
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
|
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (task.Wait(TimeSpan.FromMilliseconds(100)))
|
if(task.Wait(TimeSpan.FromMilliseconds(100)))
|
||||||
{
|
{
|
||||||
audioData = task.Result;
|
audioData = task.Result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Array.Clear(buffer, offset, count);
|
Array.Clear(buffer, offset, count);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
lock (readingImage)
|
lock(readingImage)
|
||||||
{
|
{
|
||||||
Image.ReadSector(CurrentSector + 375);
|
Image.ReadSector(CurrentSector + 375);
|
||||||
}
|
}
|
||||||
@@ -314,14 +301,14 @@ namespace RedBookPlayer
|
|||||||
byte[] audioDataSegment = new byte[count];
|
byte[] audioDataSegment = new byte[count];
|
||||||
Array.Copy(audioData, currentSectorReadPosition, audioDataSegment, 0, count);
|
Array.Copy(audioData, currentSectorReadPosition, audioDataSegment, 0, count);
|
||||||
|
|
||||||
if (ApplyDeEmphasis)
|
if(ApplyDeEmphasis)
|
||||||
{
|
{
|
||||||
float[][] floatAudioData = new float[2][];
|
float[][] floatAudioData = new float[2][];
|
||||||
floatAudioData[0] = new float[audioDataSegment.Length / 4];
|
floatAudioData[0] = new float[audioDataSegment.Length / 4];
|
||||||
floatAudioData[1] = new float[audioDataSegment.Length / 4];
|
floatAudioData[1] = new float[audioDataSegment.Length / 4];
|
||||||
ByteConverter.ToFloats16Bit(audioDataSegment, floatAudioData);
|
ByteConverter.ToFloats16Bit(audioDataSegment, floatAudioData);
|
||||||
|
|
||||||
for (int i = 0; i < floatAudioData[0].Length; i++)
|
for(int i = 0; i < floatAudioData[0].Length; i++)
|
||||||
{
|
{
|
||||||
floatAudioData[0][i] = deEmphasisFilterLeft.Process(floatAudioData[0][i]);
|
floatAudioData[0][i] = deEmphasisFilterLeft.Process(floatAudioData[0][i]);
|
||||||
floatAudioData[1][i] = deEmphasisFilterRight.Process(floatAudioData[1][i]);
|
floatAudioData[1][i] = deEmphasisFilterRight.Process(floatAudioData[1][i]);
|
||||||
@@ -333,11 +320,12 @@ namespace RedBookPlayer
|
|||||||
Array.Copy(audioDataSegment, 0, buffer, offset, count);
|
Array.Copy(audioDataSegment, 0, buffer, offset, count);
|
||||||
|
|
||||||
currentSectorReadPosition += count;
|
currentSectorReadPosition += count;
|
||||||
if (currentSectorReadPosition >= 2352)
|
|
||||||
{
|
if(currentSectorReadPosition < 2352)
|
||||||
|
return count;
|
||||||
|
|
||||||
CurrentSector += (ulong)currentSectorReadPosition / 2352;
|
CurrentSector += (ulong)currentSectorReadPosition / 2352;
|
||||||
currentSectorReadPosition %= 2352;
|
currentSectorReadPosition %= 2352;
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -354,10 +342,8 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public void Play()
|
public void Play()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
soundOut.Play();
|
soundOut.Play();
|
||||||
TotalIndexes = Image.Tracks[CurrentTrack].Indexes.Keys.Max();
|
TotalIndexes = Image.Tracks[CurrentTrack].Indexes.Keys.Max();
|
||||||
@@ -365,20 +351,16 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public void Pause()
|
public void Pause()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
soundOut.Stop();
|
soundOut.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
soundOut.Stop();
|
soundOut.Stop();
|
||||||
LoadTrack(CurrentTrack);
|
LoadTrack(CurrentTrack);
|
||||||
@@ -386,168 +368,131 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public void NextTrack()
|
public void NextTrack()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentTrack + 1 >= Image.Tracks.Count)
|
if(CurrentTrack + 1 >= Image.Tracks.Count)
|
||||||
{
|
|
||||||
CurrentTrack = 0;
|
CurrentTrack = 0;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
CurrentTrack++;
|
CurrentTrack++;
|
||||||
}
|
|
||||||
|
|
||||||
LoadTrack(CurrentTrack);
|
LoadTrack(CurrentTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PreviousTrack()
|
public void PreviousTrack()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentSector < (ulong)Image.Tracks[CurrentTrack].Indexes[1] + 75)
|
if(CurrentSector < (ulong)Image.Tracks[CurrentTrack].Indexes[1] + 75)
|
||||||
{
|
|
||||||
if (App.Settings.AllowSkipHiddenTrack && CurrentTrack == 0 && CurrentSector >= 75)
|
|
||||||
{
|
{
|
||||||
|
if(App.Settings.AllowSkipHiddenTrack &&
|
||||||
|
CurrentTrack == 0 &&
|
||||||
|
CurrentSector >= 75)
|
||||||
CurrentSector = 0;
|
CurrentSector = 0;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (CurrentTrack - 1 < 0)
|
if(CurrentTrack - 1 < 0)
|
||||||
{
|
|
||||||
CurrentTrack = Image.Tracks.Count - 1;
|
CurrentTrack = Image.Tracks.Count - 1;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
CurrentTrack--;
|
CurrentTrack--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
LoadTrack(CurrentTrack);
|
LoadTrack(CurrentTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NextIndex(bool changeTrack)
|
public void NextIndex(bool changeTrack)
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentIndex + 1 > Image.Tracks[CurrentTrack].Indexes.Keys.Max())
|
if(CurrentIndex + 1 > Image.Tracks[CurrentTrack].Indexes.Keys.Max())
|
||||||
{
|
|
||||||
if (changeTrack)
|
|
||||||
{
|
{
|
||||||
|
if(!changeTrack)
|
||||||
|
return;
|
||||||
|
|
||||||
NextTrack();
|
NextTrack();
|
||||||
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes.Values.Min();
|
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes.Values.Min();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[++CurrentIndex];
|
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[++CurrentIndex];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void PreviousIndex(bool changeTrack)
|
public void PreviousIndex(bool changeTrack)
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentIndex - 1 < Image.Tracks[CurrentTrack].Indexes.Keys.Min())
|
if(CurrentIndex - 1 < Image.Tracks[CurrentTrack].Indexes.Keys.Min())
|
||||||
{
|
|
||||||
if (changeTrack)
|
|
||||||
{
|
{
|
||||||
|
if(!changeTrack)
|
||||||
|
return;
|
||||||
|
|
||||||
PreviousTrack();
|
PreviousTrack();
|
||||||
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes.Values.Max();
|
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes.Values.Max();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[--CurrentIndex];
|
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[--CurrentIndex];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void FastForward()
|
public void FastForward()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
CurrentSector = Math.Min(Image.Info.Sectors - 1, CurrentSector + 75);
|
CurrentSector = Math.Min(Image.Info.Sectors - 1, CurrentSector + 75);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Rewind()
|
public void Rewind()
|
||||||
{
|
{
|
||||||
if (Image == null)
|
if(Image == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (CurrentSector >= 75)
|
if(CurrentSector >= 75)
|
||||||
CurrentSector -= 75;
|
CurrentSector -= 75;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnableDeEmphasis()
|
public void EnableDeEmphasis() => ApplyDeEmphasis = true;
|
||||||
{
|
|
||||||
ApplyDeEmphasis = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableDeEmphasis()
|
public void DisableDeEmphasis() => ApplyDeEmphasis = false;
|
||||||
{
|
|
||||||
ApplyDeEmphasis = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PlayerSource : IWaveSource
|
public class PlayerSource : IWaveSource
|
||||||
{
|
{
|
||||||
public CSCore.WaveFormat WaveFormat => new CSCore.WaveFormat();
|
|
||||||
bool IAudioSource.CanSeek => throw new NotImplementedException();
|
|
||||||
public long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
|
||||||
public long Length => throw new NotImplementedException();
|
|
||||||
|
|
||||||
public bool Run = true;
|
|
||||||
private ReadFunction read;
|
|
||||||
|
|
||||||
public delegate int ReadFunction(byte[] buffer, int offset, int count);
|
public delegate int ReadFunction(byte[] buffer, int offset, int count);
|
||||||
|
|
||||||
public PlayerSource(ReadFunction read)
|
readonly ReadFunction read;
|
||||||
|
|
||||||
|
public bool Run = true;
|
||||||
|
|
||||||
|
public PlayerSource(ReadFunction read) => this.read = read;
|
||||||
|
|
||||||
|
public WaveFormat WaveFormat => new WaveFormat();
|
||||||
|
bool IAudioSource.CanSeek => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public long Position
|
||||||
{
|
{
|
||||||
this.read = read;
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long Length => throw new NotImplementedException();
|
||||||
|
|
||||||
public int Read(byte[] buffer, int offset, int count)
|
public int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
if (!Run)
|
if(Run)
|
||||||
{
|
return read(buffer, offset, count);
|
||||||
|
|
||||||
Array.Clear(buffer, offset, count);
|
Array.Clear(buffer, offset, count);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return read(buffer, offset, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
public void Dispose() {}
|
||||||
{
|
|
||||||
Run = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Stop()
|
public void Start() => Run = true;
|
||||||
{
|
|
||||||
Run = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Stop() => Run = false;
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
<UserControl xmlns="https://github.com/avaloniaui"
|
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
|
||||||
mc:Ignorable="d"
|
x:Class="RedBookPlayer.PlayerView" Width="900" Height="400">
|
||||||
x:Class="RedBookPlayer.PlayerView"
|
|
||||||
Width="900" Height="400">
|
|
||||||
<StackPanel Margin="16" VerticalAlignment="Center">
|
<StackPanel Margin="16" VerticalAlignment="Center">
|
||||||
<Button Click="LoadButton_Click" Margin="32,0,32,16">Load</Button>
|
<Button Click="LoadButton_Click" Margin="32,0,32,16">Load</Button>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||||
@@ -24,28 +21,28 @@
|
|||||||
<StackPanel Margin="0,0,32,0">
|
<StackPanel Margin="0,0,32,0">
|
||||||
<TextBlock Margin="0,0,0,4">TRACK</TextBlock>
|
<TextBlock Margin="0,0,0,4">TRACK</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="TrackDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TrackDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TrackDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TrackDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,32,0">
|
<StackPanel Margin="0,0,32,0">
|
||||||
<TextBlock Margin="0,0,0,4">INDEX</TextBlock>
|
<TextBlock Margin="0,0,0,4">INDEX</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="IndexDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="IndexDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="IndexDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="IndexDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Margin="0,0,0,4">TIME</TextBlock>
|
<TextBlock Margin="0,0,0,4">TIME</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="TimeDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TimeDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Width="11" Height="51" Source="/Assets/colon.png"></Image>
|
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||||
<Image Name="TimeDigit3" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit3" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TimeDigit4" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit4" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Width="11" Height="51" Source="/Assets/colon.png"></Image>
|
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||||
<Image Name="TimeDigit5" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit5" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TimeDigit6" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TimeDigit6" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
@@ -53,34 +50,40 @@
|
|||||||
<StackPanel Margin="0,0,32,0">
|
<StackPanel Margin="0,0,32,0">
|
||||||
<TextBlock Margin="0,0,0,4">TRACKS</TextBlock>
|
<TextBlock Margin="0,0,0,4">TRACKS</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="TotalTracksDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTracksDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TotalTracksDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTracksDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Margin="0,0,32,0">
|
<StackPanel Margin="0,0,32,0">
|
||||||
<TextBlock Margin="0,0,0,4">INDEXES</TextBlock>
|
<TextBlock Margin="0,0,0,4">INDEXES</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="TotalIndexesDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalIndexesDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TotalIndexesDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalIndexesDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<TextBlock Margin="0,0,0,4">TOTAL</TextBlock>
|
<TextBlock Margin="0,0,0,4">TOTAL</TextBlock>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<Image Name="TotalTimeDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit1" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TotalTimeDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit2" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Width="11" Height="51" Source="/Assets/colon.png"></Image>
|
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||||
<Image Name="TotalTimeDigit3" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit3" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TotalTimeDigit4" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit4" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Width="11" Height="51" Source="/Assets/colon.png"></Image>
|
<Image Width="11" Height="51" Source="/Assets/colon.png" />
|
||||||
<Image Name="TotalTimeDigit5" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit5" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
<Image Name="TotalTimeDigit6" Width="42" Height="51" Source="/Assets/-.png"></Image>
|
<Image Name="TotalTimeDigit6" Width="42" Height="51" Source="/Assets/-.png" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
|
||||||
<Button Click="EnableDeEmphasisButton_Click" IsVisible="{Binding !ApplyDeEmphasis}" Width="200" Margin="0,0,16,0">Enable De-Emphasis</Button>
|
<Button Click="EnableDeEmphasisButton_Click" IsVisible="{Binding !ApplyDeEmphasis}" Width="200"
|
||||||
<Button Click="DisableDeEmphasisButton_Click" IsVisible="{Binding ApplyDeEmphasis}" Width="200" Margin="0,0,16,0">Disable De-Emphasis</Button>
|
Margin="0,0,16,0">
|
||||||
|
Enable De-Emphasis
|
||||||
|
</Button>
|
||||||
|
<Button Click="DisableDeEmphasisButton_Click" IsVisible="{Binding ApplyDeEmphasis}" Width="200"
|
||||||
|
Margin="0,0,16,0">
|
||||||
|
Disable De-Emphasis
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsAudioTrack}">AUDIO</TextBlock>
|
<TextBlock Margin="0,0,16,0" Foreground="LightGray" IsVisible="{Binding !IsAudioTrack}">AUDIO</TextBlock>
|
||||||
|
|||||||
@@ -20,33 +20,27 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public class PlayerView : UserControl
|
public class PlayerView : UserControl
|
||||||
{
|
{
|
||||||
public PlayerView()
|
|
||||||
{
|
|
||||||
InitializeComponent(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerView(string xaml)
|
|
||||||
{
|
|
||||||
InitializeComponent(xaml);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Player Player = new Player();
|
public static Player Player = new Player();
|
||||||
TextBlock currentTrack;
|
TextBlock currentTrack;
|
||||||
Image[] digits;
|
Image[] digits;
|
||||||
Timer updateTimer;
|
Timer updateTimer;
|
||||||
|
|
||||||
|
public PlayerView() => InitializeComponent(null);
|
||||||
|
|
||||||
|
public PlayerView(string xaml) => InitializeComponent(xaml);
|
||||||
|
|
||||||
public async void LoadButton_Click(object sender, RoutedEventArgs e)
|
public async void LoadButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
string path = await GetPath();
|
string path = await GetPath();
|
||||||
|
|
||||||
if (path == null)
|
if(path == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
AaruFormat image = new AaruFormat();
|
var image = new AaruFormat();
|
||||||
IFilter filter = new ZZZNoFilter();
|
IFilter filter = new ZZZNoFilter();
|
||||||
filter.Open(path);
|
filter.Open(path);
|
||||||
image.Open(filter);
|
image.Open(filter);
|
||||||
@@ -57,87 +51,56 @@ namespace RedBookPlayer
|
|||||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetPath()
|
public async Task<string> GetPath()
|
||||||
{
|
{
|
||||||
OpenFileDialog dialog = new OpenFileDialog();
|
var dialog = new OpenFileDialog();
|
||||||
dialog.AllowMultiple = false;
|
dialog.AllowMultiple = false;
|
||||||
|
|
||||||
List<string> knownExtensions = (new Aaru.DiscImages.AaruFormat()).KnownExtensions.ToList();
|
List<string> knownExtensions = new AaruFormat().KnownExtensions.ToList();
|
||||||
dialog.Filters.Add(new FileDialogFilter()
|
|
||||||
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
{
|
{
|
||||||
Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")",
|
Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")",
|
||||||
Extensions = knownExtensions.ConvertAll(e => e.Substring(1))
|
Extensions = knownExtensions.ConvertAll(e => e.Substring(1))
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return (await dialog.ShowAsync((Window)this.Parent.Parent))?.FirstOrDefault();
|
return (await dialog.ShowAsync((Window)Parent.Parent))?.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayButton_Click(object sender, RoutedEventArgs e)
|
public void PlayButton_Click(object sender, RoutedEventArgs e) => Player.Play();
|
||||||
{
|
|
||||||
Player.Play();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PauseButton_Click(object sender, RoutedEventArgs e)
|
public void PauseButton_Click(object sender, RoutedEventArgs e) => Player.Pause();
|
||||||
{
|
|
||||||
Player.Pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopButton_Click(object sender, RoutedEventArgs e)
|
public void StopButton_Click(object sender, RoutedEventArgs e) => Player.Stop();
|
||||||
{
|
|
||||||
Player.Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NextTrackButton_Click(object sender, RoutedEventArgs e)
|
public void NextTrackButton_Click(object sender, RoutedEventArgs e) => Player.NextTrack();
|
||||||
{
|
|
||||||
Player.NextTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PreviousTrackButton_Click(object sender, RoutedEventArgs e)
|
public void PreviousTrackButton_Click(object sender, RoutedEventArgs e) => Player.PreviousTrack();
|
||||||
{
|
|
||||||
Player.PreviousTrack();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NextIndexButton_Click(object sender, RoutedEventArgs e)
|
public void NextIndexButton_Click(object sender, RoutedEventArgs e) =>
|
||||||
{
|
|
||||||
Player.NextIndex(App.Settings.IndexButtonChangeTrack);
|
Player.NextIndex(App.Settings.IndexButtonChangeTrack);
|
||||||
}
|
|
||||||
|
|
||||||
public void PreviousIndexButton_Click(object sender, RoutedEventArgs e)
|
public void PreviousIndexButton_Click(object sender, RoutedEventArgs e) =>
|
||||||
{
|
|
||||||
Player.PreviousIndex(App.Settings.IndexButtonChangeTrack);
|
Player.PreviousIndex(App.Settings.IndexButtonChangeTrack);
|
||||||
}
|
|
||||||
|
|
||||||
public void FastForwardButton_Click(object sender, RoutedEventArgs e)
|
public void FastForwardButton_Click(object sender, RoutedEventArgs e) => Player.FastForward();
|
||||||
{
|
|
||||||
Player.FastForward();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RewindButton_Click(object sender, RoutedEventArgs e)
|
public void RewindButton_Click(object sender, RoutedEventArgs e) => Player.Rewind();
|
||||||
{
|
|
||||||
Player.Rewind();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e)
|
public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.EnableDeEmphasis();
|
||||||
{
|
|
||||||
Player.EnableDeEmphasis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e)
|
public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e) => Player.DisableDeEmphasis();
|
||||||
{
|
|
||||||
Player.DisableDeEmphasis();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateView(object sender, ElapsedEventArgs e)
|
void UpdateView(object sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
if (Player.Initialized)
|
if(Player.Initialized)
|
||||||
{
|
{
|
||||||
ulong sectorTime = Player.CurrentSector;
|
ulong sectorTime = Player.CurrentSector;
|
||||||
if (Player.SectionStartSector != 0)
|
|
||||||
|
if(Player.SectionStartSector != 0)
|
||||||
{
|
{
|
||||||
sectorTime -= Player.SectionStartSector;
|
sectorTime -= Player.SectionStartSector;
|
||||||
}
|
}
|
||||||
@@ -146,32 +109,26 @@ namespace RedBookPlayer
|
|||||||
sectorTime += Player.TimeOffset;
|
sectorTime += Player.TimeOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] numbers = new int[]{
|
int[] numbers =
|
||||||
Player.CurrentTrack + 1,
|
{
|
||||||
Player.CurrentIndex,
|
Player.CurrentTrack + 1, Player.CurrentIndex, (int)(sectorTime / (75 * 60)),
|
||||||
(int)(sectorTime / (75 * 60)),
|
(int)(sectorTime / 75 % 60), (int)(sectorTime % 75), Player.TotalTracks, Player.TotalIndexes,
|
||||||
(int)((sectorTime / 75) % 60),
|
(int)(Player.TotalTime / (75 * 60)), (int)(Player.TotalTime / 75 % 60), (int)(Player.TotalTime % 75)
|
||||||
(int)(sectorTime % 75),
|
|
||||||
Player.TotalTracks,
|
|
||||||
Player.TotalIndexes,
|
|
||||||
(int)(Player.TotalTime / (75 * 60)),
|
|
||||||
(int)((Player.TotalTime / 75) % 60),
|
|
||||||
(int)(Player.TotalTime % 75),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
string digitString = String.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2)));
|
string digitString = string.Join("", numbers.Select(i => i.ToString().PadLeft(2, '0').Substring(0, 2)));
|
||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
for (int i = 0; i < digits.Length; i++)
|
for(int i = 0; i < digits.Length; i++)
|
||||||
{
|
{
|
||||||
if (digits[i] != null)
|
if(digits[i] != null)
|
||||||
{
|
{
|
||||||
digits[i].Source = GetBitmap(digitString[i]);
|
digits[i].Source = GetBitmap(digitString[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerViewModel dataContext = (PlayerViewModel)DataContext;
|
var dataContext = (PlayerViewModel)DataContext;
|
||||||
dataContext.HiddenTrack = Player.TimeOffset > 150;
|
dataContext.HiddenTrack = Player.TimeOffset > 150;
|
||||||
dataContext.ApplyDeEmphasis = Player.ApplyDeEmphasis;
|
dataContext.ApplyDeEmphasis = Player.ApplyDeEmphasis;
|
||||||
dataContext.TrackHasEmphasis = Player.TrackHasEmphasis;
|
dataContext.TrackHasEmphasis = Player.TrackHasEmphasis;
|
||||||
@@ -184,9 +141,9 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
foreach (Image digit in digits)
|
foreach(Image digit in digits)
|
||||||
{
|
{
|
||||||
if (digit != null)
|
if(digit != null)
|
||||||
{
|
{
|
||||||
digit.Source = GetBitmap('-');
|
digit.Source = GetBitmap('-');
|
||||||
}
|
}
|
||||||
@@ -195,24 +152,25 @@ namespace RedBookPlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap GetBitmap(char character)
|
Bitmap GetBitmap(char character)
|
||||||
{
|
{
|
||||||
if (App.Settings.SelectedTheme == "default")
|
if(App.Settings.SelectedTheme == "default")
|
||||||
{
|
{
|
||||||
IAssetLoader assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
|
IAssetLoader assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
|
||||||
|
|
||||||
return new Bitmap(assets.Open(new Uri($"avares://RedBookPlayer/Assets/{character}.png")));
|
return new Bitmap(assets.Open(new Uri($"avares://RedBookPlayer/Assets/{character}.png")));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + App.Settings.SelectedTheme;
|
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + App.Settings.SelectedTheme;
|
||||||
Bitmap bitmap;
|
Bitmap bitmap;
|
||||||
using (FileStream stream = File.Open(themeDirectory + $"/{character}.png", FileMode.Open))
|
|
||||||
|
using(FileStream stream = File.Open(themeDirectory + $"/{character}.png", FileMode.Open))
|
||||||
{
|
{
|
||||||
bitmap = new Bitmap(stream);
|
bitmap = new Bitmap(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
@@ -247,11 +205,11 @@ namespace RedBookPlayer
|
|||||||
currentTrack = this.FindControl<TextBlock>("CurrentTrack");
|
currentTrack = this.FindControl<TextBlock>("CurrentTrack");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent(string xaml)
|
void InitializeComponent(string xaml)
|
||||||
{
|
{
|
||||||
DataContext = new PlayerViewModel();
|
DataContext = new PlayerViewModel();
|
||||||
|
|
||||||
if (xaml != null)
|
if(xaml != null)
|
||||||
{
|
{
|
||||||
new AvaloniaXamlLoader().Load(xaml, null, this);
|
new AvaloniaXamlLoader().Load(xaml, null, this);
|
||||||
}
|
}
|
||||||
@@ -263,17 +221,19 @@ namespace RedBookPlayer
|
|||||||
Initialize();
|
Initialize();
|
||||||
|
|
||||||
updateTimer = new Timer(1000 / 60);
|
updateTimer = new Timer(1000 / 60);
|
||||||
|
|
||||||
updateTimer.Elapsed += (sender, e) =>
|
updateTimer.Elapsed += (sender, e) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
UpdateView(sender, e);
|
UpdateView(sender, e);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(ex);
|
Console.WriteLine(ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateTimer.AutoReset = true;
|
updateTimer.AutoReset = true;
|
||||||
updateTimer.Start();
|
updateTimer.Start();
|
||||||
}
|
}
|
||||||
@@ -281,37 +241,43 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public class PlayerViewModel : ReactiveObject
|
public class PlayerViewModel : ReactiveObject
|
||||||
{
|
{
|
||||||
private bool applyDeEmphasis;
|
bool applyDeEmphasis;
|
||||||
|
bool copyAllowed;
|
||||||
|
bool hiddenTrack;
|
||||||
|
bool isAudioTrack;
|
||||||
|
bool isDataTrack;
|
||||||
|
bool trackHasEmphasis;
|
||||||
|
|
||||||
public bool ApplyDeEmphasis
|
public bool ApplyDeEmphasis
|
||||||
{
|
{
|
||||||
get => applyDeEmphasis;
|
get => applyDeEmphasis;
|
||||||
set => this.RaiseAndSetIfChanged(ref applyDeEmphasis, value);
|
set => this.RaiseAndSetIfChanged(ref applyDeEmphasis, value);
|
||||||
}
|
}
|
||||||
private bool trackHasEmphasis;
|
|
||||||
public bool TrackHasEmphasis
|
public bool TrackHasEmphasis
|
||||||
{
|
{
|
||||||
get => trackHasEmphasis;
|
get => trackHasEmphasis;
|
||||||
set => this.RaiseAndSetIfChanged(ref trackHasEmphasis, value);
|
set => this.RaiseAndSetIfChanged(ref trackHasEmphasis, value);
|
||||||
}
|
}
|
||||||
private bool hiddenTrack;
|
|
||||||
public bool HiddenTrack
|
public bool HiddenTrack
|
||||||
{
|
{
|
||||||
get => hiddenTrack;
|
get => hiddenTrack;
|
||||||
set => this.RaiseAndSetIfChanged(ref hiddenTrack, value);
|
set => this.RaiseAndSetIfChanged(ref hiddenTrack, value);
|
||||||
}
|
}
|
||||||
private bool copyAllowed;
|
|
||||||
public bool CopyAllowed
|
public bool CopyAllowed
|
||||||
{
|
{
|
||||||
get => copyAllowed;
|
get => copyAllowed;
|
||||||
set => this.RaiseAndSetIfChanged(ref copyAllowed, value);
|
set => this.RaiseAndSetIfChanged(ref copyAllowed, value);
|
||||||
}
|
}
|
||||||
private bool isAudioTrack;
|
|
||||||
public bool IsAudioTrack
|
public bool IsAudioTrack
|
||||||
{
|
{
|
||||||
get => isAudioTrack;
|
get => isAudioTrack;
|
||||||
set => this.RaiseAndSetIfChanged(ref isAudioTrack, value);
|
set => this.RaiseAndSetIfChanged(ref isAudioTrack, value);
|
||||||
}
|
}
|
||||||
private bool isDataTrack;
|
|
||||||
public bool IsDataTrack
|
public bool IsDataTrack
|
||||||
{
|
{
|
||||||
get => isDataTrack;
|
get => isDataTrack;
|
||||||
|
|||||||
@@ -1,28 +1,24 @@
|
|||||||
using System.Runtime.InteropServices;
|
using Avalonia;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Logging.Serilog;
|
using Avalonia.Logging.Serilog;
|
||||||
|
|
||||||
namespace RedBookPlayer
|
namespace RedBookPlayer
|
||||||
{
|
{
|
||||||
class Program
|
internal class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
#if Windows
|
#if Windows
|
||||||
AllocConsole();
|
AllocConsole();
|
||||||
#endif
|
#endif
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if Windows
|
#if Windows
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
static extern bool AllocConsole();
|
static extern bool AllocConsole();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public static AppBuilder BuildAvaloniaApp()
|
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>().UsePlatformDetect().LogToDebug();
|
||||||
=> AppBuilder.Configure<App>()
|
|
||||||
.UsePlatformDetect()
|
|
||||||
.LogToDebug();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,23 +6,21 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public class Settings
|
public class Settings
|
||||||
{
|
{
|
||||||
public bool AutoPlay { get; set; } = false;
|
|
||||||
public bool IndexButtonChangeTrack { get; set; } = false;
|
|
||||||
public bool AllowSkipHiddenTrack { get; set; } = false;
|
|
||||||
public int Volume { get; set; } = 100;
|
|
||||||
public string SelectedTheme { get; set; } = "default";
|
|
||||||
string filePath;
|
string filePath;
|
||||||
|
|
||||||
public Settings() { }
|
public Settings() {}
|
||||||
|
|
||||||
public Settings(string filePath)
|
public Settings(string filePath) => this.filePath = filePath;
|
||||||
{
|
|
||||||
this.filePath = filePath;
|
public bool AutoPlay { get; set; }
|
||||||
}
|
public bool IndexButtonChangeTrack { get; set; }
|
||||||
|
public bool AllowSkipHiddenTrack { get; set; }
|
||||||
|
public int Volume { get; set; } = 100;
|
||||||
|
public string SelectedTheme { get; set; } = "default";
|
||||||
|
|
||||||
public static Settings Load(string filePath)
|
public static Settings Load(string filePath)
|
||||||
{
|
{
|
||||||
if (File.Exists(filePath))
|
if(File.Exists(filePath))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -33,21 +31,20 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
catch (JsonException)
|
catch(JsonException)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Couldn't parse settings, reverting to default");
|
Console.WriteLine("Couldn't parse settings, reverting to default");
|
||||||
return new Settings(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new Settings(filePath);
|
return new Settings(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Settings(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
JsonSerializerOptions options = new JsonSerializerOptions()
|
var options = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,34 +1,32 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<Window xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
d:DesignHeight="450" x:Class="RedBookPlayer.SettingsWindow" Title="Settings" Width="450" Height="600">
|
||||||
x:Class="RedBookPlayer.SettingsWindow"
|
|
||||||
Title="Settings"
|
|
||||||
Width="450" Height="600">
|
|
||||||
<DockPanel Margin="16">
|
<DockPanel Margin="16">
|
||||||
<TextBlock DockPanel.Dock="Top" Margin="0,0,0,4">Themes</TextBlock>
|
<TextBlock DockPanel.Dock="Top" Margin="0,0,0,4">Themes</TextBlock>
|
||||||
<StackPanel DockPanel.Dock="Bottom">
|
<StackPanel DockPanel.Dock="Bottom">
|
||||||
<WrapPanel Margin="0,0,0,16">
|
<WrapPanel Margin="0,0,0,16">
|
||||||
<CheckBox IsChecked="{Binding AutoPlay}" Margin="0,0,8,0"/>
|
<CheckBox IsChecked="{Binding AutoPlay}" Margin="0,0,8,0" />
|
||||||
<TextBlock VerticalAlignment="Center">Auto-play CD on load</TextBlock>
|
<TextBlock VerticalAlignment="Center">Auto-play CD on load</TextBlock>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
<WrapPanel Margin="0,0,0,16">
|
<WrapPanel Margin="0,0,0,16">
|
||||||
<CheckBox IsChecked="{Binding IndexButtonChangeTrack}" Margin="0,0,8,0"/>
|
<CheckBox IsChecked="{Binding IndexButtonChangeTrack}" Margin="0,0,8,0" />
|
||||||
<TextBlock VerticalAlignment="Center">Index navigation can change track</TextBlock>
|
<TextBlock VerticalAlignment="Center">Index navigation can change track</TextBlock>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
<WrapPanel Margin="0,0,0,16">
|
<WrapPanel Margin="0,0,0,16">
|
||||||
<CheckBox IsChecked="{Binding AllowSkipHiddenTrack}" Margin="0,0,8,0"/>
|
<CheckBox IsChecked="{Binding AllowSkipHiddenTrack}" Margin="0,0,8,0" />
|
||||||
<TextBlock VerticalAlignment="Center">Treat index 0 of track 1 as track 0 (hidden track)</TextBlock>
|
<TextBlock VerticalAlignment="Center">Treat index 0 of track 1 as track 0 (hidden track)</TextBlock>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
<DockPanel Margin="0,0,0,16">
|
<DockPanel Margin="0,0,0,16">
|
||||||
<TextBlock VerticalAlignment="Center" Margin="0,0,8,0" DockPanel.Dock="Left">Volume</TextBlock>
|
<TextBlock VerticalAlignment="Center" Margin="0,0,8,0" DockPanel.Dock="Left">Volume</TextBlock>
|
||||||
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="%"/>
|
<TextBlock VerticalAlignment="Center" DockPanel.Dock="Right" Text="%" />
|
||||||
<TextBlock VerticalAlignment="Center" Margin="8,0,0,0" DockPanel.Dock="Right" Text="{Binding Volume}" Name="VolumeLabel"/>
|
<TextBlock VerticalAlignment="Center" Margin="8,0,0,0" DockPanel.Dock="Right" Text="{Binding Volume}"
|
||||||
<Slider Minimum="0" Maximum="100" SmallChange="1" LargeChange="10" Value="{Binding Volume}" Name="VolumeSlider"/>
|
Name="VolumeLabel" />
|
||||||
|
<Slider Minimum="0" Maximum="100" SmallChange="1" LargeChange="10" Value="{Binding Volume}"
|
||||||
|
Name="VolumeSlider" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<Button Name="ApplyButton">Apply</Button>
|
<Button Name="ApplyButton">Apply</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ListBox Name="ThemeList" SelectionMode="Single" Margin="0,0,0,16"/>
|
<ListBox Name="ThemeList" SelectionMode="Single" Margin="0,0,0,16" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
@@ -9,21 +8,21 @@ namespace RedBookPlayer
|
|||||||
{
|
{
|
||||||
public class SettingsWindow : Window
|
public class SettingsWindow : Window
|
||||||
{
|
{
|
||||||
Settings settings;
|
readonly Settings settings;
|
||||||
ListBox themeList;
|
|
||||||
string selectedTheme;
|
string selectedTheme;
|
||||||
|
ListBox themeList;
|
||||||
|
|
||||||
public SettingsWindow() { }
|
public SettingsWindow() {}
|
||||||
|
|
||||||
public SettingsWindow(Settings settings)
|
public SettingsWindow(Settings settings)
|
||||||
{
|
{
|
||||||
this.DataContext = this.settings = settings;
|
DataContext = this.settings = settings;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ThemeList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
public void ThemeList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.AddedItems.Count == 0)
|
if(e.AddedItems.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -33,7 +32,7 @@ namespace RedBookPlayer
|
|||||||
|
|
||||||
public void ApplySettings(object sender, RoutedEventArgs e)
|
public void ApplySettings(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if ((selectedTheme ?? "") != "")
|
if((selectedTheme ?? "") != "")
|
||||||
{
|
{
|
||||||
settings.SelectedTheme = selectedTheme;
|
settings.SelectedTheme = selectedTheme;
|
||||||
MainWindow.ApplyTheme(selectedTheme);
|
MainWindow.ApplyTheme(selectedTheme);
|
||||||
@@ -44,28 +43,25 @@ namespace RedBookPlayer
|
|||||||
settings.Save();
|
settings.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateView()
|
public void UpdateView() => this.FindControl<TextBlock>("VolumeLabel").Text = settings.Volume.ToString();
|
||||||
{
|
|
||||||
this.FindControl<TextBlock>("VolumeLabel").Text = settings.Volume.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent()
|
void InitializeComponent()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
themeList = this.FindControl<ListBox>("ThemeList");
|
themeList = this.FindControl<ListBox>("ThemeList");
|
||||||
themeList.SelectionChanged += ThemeList_SelectionChanged;
|
themeList.SelectionChanged += ThemeList_SelectionChanged;
|
||||||
|
|
||||||
List<String> items = new List<String>();
|
List<string> items = new List<string>();
|
||||||
items.Add("default");
|
items.Add("default");
|
||||||
|
|
||||||
if (Directory.Exists("themes/"))
|
if(Directory.Exists("themes/"))
|
||||||
{
|
{
|
||||||
foreach (string dir in Directory.EnumerateDirectories("themes/"))
|
foreach(string dir in Directory.EnumerateDirectories("themes/"))
|
||||||
{
|
{
|
||||||
string themeName = dir.Split('/')[1];
|
string themeName = dir.Split('/')[1];
|
||||||
|
|
||||||
if (!File.Exists($"themes/{themeName}/view.xaml"))
|
if(!File.Exists($"themes/{themeName}/view.xaml"))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user