General refactor and clean-up.

This commit is contained in:
2021-06-06 20:28:36 +01:00
parent 0702d1a69e
commit 5eae13c3a2
13 changed files with 446 additions and 551 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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) {}
{
}
} }
} }

View File

@@ -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;
}
} }
} }

View File

@@ -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>

View File

@@ -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();
}; };

View File

@@ -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;
{
}
} }
} }

View File

@@ -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>

View File

@@ -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;

View File

@@ -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();
} }
} }

View File

@@ -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
}; };

View File

@@ -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>

View File

@@ -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;
} }