Initial commit

This commit is contained in:
deagahelio
2021-03-19 17:07:27 -03:00
commit abe06fcdb9
32 changed files with 1593 additions and 0 deletions

338
.gitignore vendored Normal file
View File

@@ -0,0 +1,338 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
## Visual Studio Code specific files and folder
.vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.jsons
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "Aaru"]
path = Aaru
url = https://github.com/aaru-dps/Aaru.git
[submodule "cscore"]
path = cscore
url = https://github.com/deagahelio/cscore.git

1
Aaru Submodule

Submodule Aaru added at 65739fa966

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# RedBookPlayer
[Audio CD](https://en.wikipedia.org/wiki/Compact_Disc_Digital_Audio) player for [Aaru format](https://github.com/aaru-dps/Aaru).

34
RedBookPlayer.sln Normal file
View File

@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedBookPlayer", "RedBookPlayer\RedBookPlayer.csproj", "{94944959-0352-4ABF-9C5C-19FF33747ECE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|x64.ActiveCfg = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|x64.Build.0 = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|x86.ActiveCfg = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Debug|x86.Build.0 = Debug|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|Any CPU.Build.0 = Release|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|x64.ActiveCfg = Release|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|x64.Build.0 = Release|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|x86.ActiveCfg = Release|Any CPU
{94944959-0352-4ABF-9C5C-19FF33747ECE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

8
RedBookPlayer/App.xaml Normal file
View File

@@ -0,0 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="RedBookPlayer.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
</Application.Styles>
</Application>

40
RedBookPlayer/App.xaml.cs Normal file
View File

@@ -0,0 +1,40 @@
using System;
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace RedBookPlayer
{
public class App : Application
{
public static string CurrentTheme = "default";
static App()
{
Directory.SetCurrentDirectory(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName));
}
public override void Initialize()
{
AppDomain.CurrentDomain.UnhandledException += (e, f) =>
{
Console.WriteLine(((Exception)f.ExceptionObject).ToString());
};
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
}
base.OnFrameworkInitializationCompleted();
}
}
}

BIN
RedBookPlayer/Assets/-.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

BIN
RedBookPlayer/Assets/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

BIN
RedBookPlayer/Assets/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

BIN
RedBookPlayer/Assets/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

BIN
RedBookPlayer/Assets/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

BIN
RedBookPlayer/Assets/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

BIN
RedBookPlayer/Assets/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

BIN
RedBookPlayer/Assets/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

BIN
RedBookPlayer/Assets/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

BIN
RedBookPlayer/Assets/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
RedBookPlayer/Assets/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

BIN
RedBookPlayer/Assets/:.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

View File

@@ -0,0 +1,42 @@
using System;
using NWaves.Filters.BiQuad;
namespace RedBookPlayer
{
public class DeEmphasisFilter : BiQuadFilter
{
static double B0, B1, B2, A0, A1, A2;
static DeEmphasisFilter()
{
double fc = 5277;
double slope = 0.4850;
double gain = -9.465;
double w0 = 2 * Math.PI * fc / 44100;
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 cs = Math.Cos(w0);
double v = 2 * Math.Sqrt(A) * alpha;
B0 = A * ((A + 1) + (A - 1) * cs + v);
B1 = -2 * A * ((A - 1) + (A + 1) * cs);
B2 = A * ((A + 1) + (A - 1) * cs - v);
A0 = (A + 1) - (A - 1) * cs + v;
A1 = 2 * ((A - 1) - (A + 1) * cs);
A2 = (A + 1) - (A - 1) * cs - v;
B2 /= A0;
B1 /= A0;
B0 /= A0;
A2 /= A0;
A1 /= A0;
A0 = 1;
}
public DeEmphasisFilter() : base(B0, B1, B2, A0, A1, A2)
{
}
}
}

126
RedBookPlayer/HiResTimer.cs Normal file
View File

@@ -0,0 +1,126 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace RedBookPlayer {
public class HiResTimer
{
private static readonly float tickFrequency = 1000f / Stopwatch.Frequency;
public event EventHandler<HiResTimerElapsedEventArgs> Elapsed;
private volatile float interval;
private volatile bool isRunning;
public HiResTimer() : this(1f)
{
}
public HiResTimer(float interval)
{
if (interval < 0f || Single.IsNaN(interval))
throw new ArgumentOutOfRangeException(nameof(interval));
this.interval = interval;
}
public float Interval
{
get { return interval; }
set
{
if (value < 0f || Single.IsNaN(value))
throw new ArgumentOutOfRangeException(nameof(value));
interval = value;
}
}
public bool Enabled
{
set
{
if (value)
Start();
else
Stop();
}
get { return isRunning; }
}
public void Start()
{
if (isRunning)
return;
isRunning = true;
Thread thread = new Thread(ExecuteTimer);
thread.Priority = ThreadPriority.Highest;
thread.Start();
}
public void Stop()
{
isRunning = false;
}
private void ExecuteTimer()
{
float nextTrigger = 0f;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
while (isRunning)
{
nextTrigger += interval;
float elapsed;
while (true)
{
elapsed = ElapsedHiRes(stopwatch);
float diff = nextTrigger - elapsed;
if (diff <= 0f)
break;
if (diff < 1f)
Thread.SpinWait(10);
else if (diff < 5f)
Thread.SpinWait(100);
else if (diff < 15f)
Thread.Sleep(1);
else
Thread.Sleep(10);
if (!isRunning)
return;
}
float delay = elapsed - nextTrigger;
Elapsed?.Invoke(this, new HiResTimerElapsedEventArgs(delay));
if (stopwatch.Elapsed.TotalHours >= 1d)
{
stopwatch.Restart();
nextTrigger = 0f;
}
}
stopwatch.Stop();
}
private static float ElapsedHiRes(Stopwatch stopwatch)
{
return stopwatch.ElapsedTicks * tickFrequency;
}
}
public class HiResTimerElapsedEventArgs : EventArgs
{
public float Delay { get; }
internal HiResTimerElapsedEventArgs(float delay)
{
Delay = delay;
}
}
}

View File

@@ -0,0 +1,10 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="RedBookPlayer.MainWindow"
Title="RedBookPlayer"
SizeToContent="WidthAndHeight">
<ContentControl Name="Content"/>
</Window>

View File

@@ -0,0 +1,55 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Input;
namespace RedBookPlayer
{
public class MainWindow : Window
{
public static MainWindow Instance;
public ContentControl ContentControl;
public Window themeSelectionWindow;
public MainWindow()
{
Instance = this;
InitializeComponent();
}
public void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F1)
{
themeSelectionWindow = new ThemeSelectionWindow();
themeSelectionWindow.Show();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
ContentControl = this.FindControl<ContentControl>("Content");
ContentControl.Content = new PlayerView();
MainWindow.Instance.MaxWidth = ((PlayerView)MainWindow.Instance.ContentControl.Content).Width;
MainWindow.Instance.MaxHeight = ((PlayerView)MainWindow.Instance.ContentControl.Content).Height;
ContentControl.Content = new PlayerView();
this.CanResize = false;
this.KeyDown += OnKeyDown;
this.Closing += (s, e) =>
{
themeSelectionWindow?.Close();
themeSelectionWindow = null;
};
this.Closing += (e, f) =>
{
PlayerView.player.Shutdown();
};
}
}
}

435
RedBookPlayer/Player.cs Normal file
View File

@@ -0,0 +1,435 @@
using System;
using System.Threading.Tasks;
using Aaru.CommonTypes.Enums;
using Aaru.Decoders.CD;
using Aaru.DiscImages;
using Aaru.Helpers;
using System.Linq;
using CSCore.SoundOut;
using CSCore;
using NWaves.Audio;
using NWaves.Filters.BiQuad;
namespace RedBookPlayer
{
public class Player
{
public bool Initialized = false;
private int currentTrack = 0;
public int CurrentTrack
{
get
{
return currentTrack;
}
private set
{
if (Image != null)
{
if (value >= Image.Tracks.Count)
{
currentTrack = 0;
}
else if (value < 0)
{
currentTrack = Image.Tracks.Count - 1;
}
else
{
currentTrack = value;
}
byte[] flagsData = Image.ReadSectorTag(Image.Tracks[CurrentTrack].TrackSequence, SectorTagType.CdTrackFlags);
HasPreEmphasis = ((CdFlags)flagsData[0]).HasFlag(CdFlags.PreEmphasis);
CurrentIndex = Image.Tracks[CurrentTrack].Indexes.Keys.GetEnumerator().Current;
}
}
}
public ushort CurrentIndex { get; private set; } = 1;
private ulong currentSector = 0;
private int currentSectorReadPosition = 0;
public ulong CurrentSector
{
get
{
return currentSector;
}
private set
{
currentSector = value;
if (Image != null)
{
if (CurrentTrack < Image.Tracks.Count - 1 && CurrentSector >= Image.Tracks[CurrentTrack + 1].TrackStartSector)
{
CurrentTrack++;
}
else if (CurrentTrack > 0 && CurrentSector < Image.Tracks[CurrentTrack].TrackStartSector)
{
CurrentTrack--;
}
foreach (var item in Image.Tracks[CurrentTrack].Indexes.Reverse())
{
if ((int)CurrentSector >= item.Value)
{
CurrentIndex = item.Key;
return;
}
}
CurrentIndex = 0;
}
}
}
public bool HasPreEmphasis { get; private set; } = false;
public AaruFormat Image { get; private set; }
FullTOC.CDFullTOC toc;
PlayerSource source;
ALSoundOut soundOut;
BiQuadFilter deEmphasisFilterLeft;
BiQuadFilter deEmphasisFilterRight;
bool readingImage = false;
public async void Init(AaruFormat image)
{
this.Image = image;
if (await Task.Run(() => image.Info.ReadableMediaTags?.Contains(MediaTagType.CD_FullTOC)) != true)
{
Console.WriteLine("Full TOC not found");
return;
}
byte[] tocBytes = await Task.Run(() => image.ReadDiskTag(MediaTagType.CD_FullTOC));
if ((tocBytes?.Length ?? 0) == 0)
{
Console.WriteLine("Error reading TOC from disc image");
return;
}
if (Swapping.Swap(BitConverter.ToUInt16(tocBytes, 0)) + 2 != tocBytes.Length)
{
byte[] tmp = new byte[tocBytes.Length + 2];
Array.Copy(tocBytes, 0, tmp, 2, tocBytes.Length);
tmp[0] = (byte)((tocBytes.Length & 0xFF00) >> 8);
tmp[1] = (byte)(tocBytes.Length & 0xFF);
tocBytes = tmp;
}
FullTOC.CDFullTOC? nullableToc = await Task.Run(() => FullTOC.Decode(tocBytes));
if (nullableToc == null)
{
Console.WriteLine("Error decoding TOC");
return;
}
toc = nullableToc.Value;
Console.WriteLine(FullTOC.Prettify(toc));
CurrentTrack = 0;
CurrentSector = 0;
deEmphasisFilterLeft = new DeEmphasisFilter();
deEmphasisFilterRight = new DeEmphasisFilter();
source = new PlayerSource(ProviderRead);
soundOut?.Dispose();
soundOut = new ALSoundOut(50);
soundOut.Initialize(source);
soundOut.Play();
LoadTrack(CurrentTrack);
Initialized = true;
source.Start();
}
public int ProviderRead(byte[] buffer, int offset, int count)
{
ulong sectorsToRead;
ulong zeroSectorsAmount;
do
{
sectorsToRead = (ulong)count / 2352 + 2;
zeroSectorsAmount = 0;
if (CurrentSector + sectorsToRead > Image.Info.Sectors)
{
ulong oldSectorsToRead = sectorsToRead;
sectorsToRead = Image.Info.Sectors - CurrentSector;
zeroSectorsAmount = oldSectorsToRead - sectorsToRead;
}
if (sectorsToRead <= 0)
{
CurrentSector = 0;
currentSectorReadPosition = 0;
}
} while (sectorsToRead <= 0);
byte[] zeroSectors = new Byte[zeroSectorsAmount * 2352];
Array.Clear(zeroSectors, 0, zeroSectors.Length);
byte[] audioData;
Task<byte[]> task = Task.Run(() =>
{
try
{
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
}
catch (System.ArgumentOutOfRangeException)
{
CurrentSector = 0;
return Image.ReadSectors(CurrentSector, (uint)sectorsToRead).Concat(zeroSectors).ToArray();
}
});
if (task.Wait(TimeSpan.FromMilliseconds(100)))
{
audioData = task.Result;
}
else
{
Array.Clear(buffer, offset, count);
return count;
}
Task.Run(() =>
{
if (!readingImage)
{
readingImage = true;
Image.ReadSector(CurrentSector + 375);
readingImage = false;
}
});
byte[] audioDataSegment = new byte[count];
Array.Copy(audioData, currentSectorReadPosition, audioDataSegment, 0, count);
if (HasPreEmphasis)
{
float[][] floatAudioData = new float[2][];
floatAudioData[0] = new float[audioDataSegment.Length / 4];
floatAudioData[1] = new float[audioDataSegment.Length / 4];
ByteConverter.ToFloats16Bit(audioDataSegment, floatAudioData);
for (int i = 0; i < floatAudioData[0].Length; i++)
{
floatAudioData[0][i] = deEmphasisFilterLeft.Process(floatAudioData[0][i]);
floatAudioData[1][i] = deEmphasisFilterRight.Process(floatAudioData[1][i]);
}
ByteConverter.FromFloats16Bit(floatAudioData, audioDataSegment);
}
Array.Copy(audioDataSegment, 0, buffer, offset, count);
currentSectorReadPosition += count;
if (currentSectorReadPosition >= 2352)
{
CurrentSector += (ulong)currentSectorReadPosition / 2352;
currentSectorReadPosition %= 2352;
}
return count;
}
public void LoadTrack(int index)
{
bool oldRun = source.Run;
source.Stop();
CurrentSector = Image.Tracks[index].TrackStartSector;
source.Run = oldRun;
}
public void Play()
{
if (Image == null)
{
return;
}
soundOut.Play();
}
public void Pause()
{
if (Image == null)
{
return;
}
soundOut.Stop();
}
public void Stop()
{
if (Image == null)
{
return;
}
soundOut.Stop();
LoadTrack(CurrentTrack);
}
public void NextTrack()
{
if (Image == null)
{
return;
}
if (++CurrentTrack >= Image.Tracks.Count)
{
CurrentTrack = 0;
}
LoadTrack(CurrentTrack);
}
public void PreviousTrack()
{
if (Image == null)
{
return;
}
if (--CurrentTrack < 0)
{
CurrentTrack = Image.Tracks.Count - 1;
}
LoadTrack(CurrentTrack);
}
public void NextIndex()
{
if (Image == null)
{
return;
}
if (++CurrentIndex > Image.Tracks[CurrentTrack].Indexes.Keys.Max())
{
NextTrack();
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[1];
}
else
{
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[CurrentIndex];
}
}
public void PreviousIndex()
{
if (Image == null)
{
return;
}
if (CurrentIndex <= 1 || --CurrentIndex < Image.Tracks[CurrentTrack].Indexes.Keys.Min())
{
PreviousTrack();
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes.Values.Max();
}
else
{
CurrentSector = (ulong)Image.Tracks[CurrentTrack].Indexes[CurrentIndex];
}
}
public void FastForward()
{
if (Image == null)
{
return;
}
CurrentSector = Math.Min(Image.Info.Sectors - 1, CurrentSector + 75);
}
public void Rewind()
{
if (Image == null)
{
return;
}
if (CurrentSector >= 75)
CurrentSector -= 75;
}
public void EnableDeEmphasis()
{
HasPreEmphasis = true;
}
public void DisableDeEmphasis()
{
HasPreEmphasis = false;
}
public void Shutdown()
{
soundOut?.Stop();
source?.Stop();
}
}
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 PlayerSource(ReadFunction read)
{
this.read = read;
}
public int Read(byte[] buffer, int offset, int count)
{
if (!Run)
{
Array.Clear(buffer, offset, count);
return count;
}
else
{
return read(buffer, offset, count);
}
}
public void Start()
{
Run = true;
}
public void Stop()
{
Run = false;
}
public void Dispose()
{
}
}
}

View File

@@ -0,0 +1,57 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="RedBookPlayer.PlayerView"
Width="900" Height="300">
<StackPanel Margin="16" VerticalAlignment="Center">
<Button Click="LoadButton_Click" Margin="32,0,32,16">Load</Button>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
<Button Click="PlayButton_Click" Width="100" Margin="0,0,16,0">Play</Button>
<Button Click="PauseButton_Click" Width="100" Margin="0,0,16,0">Pause</Button>
<Button Click="StopButton_Click" Width="100" Margin="0,0,16,0">Stop</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
<Button Click="PreviousTrackButton_Click" Width="100" Margin="0,0,16,0">Previous Track</Button>
<Button Click="NextTrackButton_Click" Width="100" Margin="0,0,16,0">Next Track</Button>
<Button Click="PreviousIndexButton_Click" Width="100" Margin="0,0,16,0">Previous Index</Button>
<Button Click="NextIndexButton_Click" Width="100" Margin="0,0,16,0">Next Index</Button>
<RepeatButton Click="RewindButton_Click" Width="100" Margin="0,0,16,0">Rewind</RepeatButton>
<RepeatButton Click="FastForwardButton_Click" Width="100">Fast Forward</RepeatButton>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,16">
<StackPanel Margin="0,0,32,0">
<TextBlock Margin="0,0,0,4">TRACK</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Name="TrackDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Name="TrackDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
</StackPanel>
</StackPanel>
<StackPanel Margin="0,0,32,0">
<TextBlock Margin="0,0,0,4">INDEX</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Name="IndexDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Name="IndexDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
</StackPanel>
</StackPanel>
<StackPanel>
<TextBlock Margin="0,0,0,4">TIME</TextBlock>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Name="TimeDigit1" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Name="TimeDigit2" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Width="11" Height="51" Source="/Assets/:.png"></Image>
<Image Name="TimeDigit3" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Name="TimeDigit4" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Width="11" Height="51" Source="/Assets/:.png"></Image>
<Image Name="TimeDigit5" Width="42" Height="51" Source="/Assets/-.png"></Image>
<Image Name="TimeDigit6" Width="42" Height="51" Source="/Assets/-.png"></Image>
</StackPanel>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Click="EnableDeEmphasisButton_Click" IsVisible="{Binding !PreEmphasis}" Width="200" Margin="0,0,16,0">Enable De-Emphasis</Button>
<Button Click="DisableDeEmphasisButton_Click" IsVisible="{Binding PreEmphasis}" Width="200" Margin="0,0,16,0">Disable De-Emphasis</Button>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,249 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using Aaru.CommonTypes.Interfaces;
using Aaru.DiscImages;
using Aaru.Filters;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using ReactiveUI;
namespace RedBookPlayer
{
public class PlayerView : UserControl
{
public PlayerView()
{
InitializeComponent(null);
}
public PlayerView(string xaml)
{
InitializeComponent(xaml);
}
public static Player player = new Player();
TextBlock currentTrack;
Image[] digits;
Timer updateTimer;
public async void LoadButton_Click(object sender, RoutedEventArgs e)
{
string path = await GetPath();
if (path == null)
{
return;
}
await Task.Run(() =>
{
AaruFormat image = new AaruFormat();
IFilter filter = new ZZZNoFilter();
filter.Open(path);
image.Open(filter);
player?.Shutdown();
player = new Player();
player.Init(image);
});
await Dispatcher.UIThread.InvokeAsync(() =>
{
MainWindow.Instance.Title = "RedBookPlayer - " + path.Split('/').Last().Split('\\').Last();
});
}
public async Task<string> GetPath()
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.AllowMultiple = false;
List<string> knownExtensions = (new Aaru.DiscImages.AaruFormat()).KnownExtensions.ToList();
dialog.Filters.Add(new FileDialogFilter()
{
Name = "Aaru Image Format (*" + string.Join(", *", knownExtensions) + ")",
Extensions = knownExtensions.ConvertAll(e => e.Substring(1))
}
);
return (await dialog.ShowAsync((Window)this.Parent.Parent))?.FirstOrDefault();
}
public void PlayButton_Click(object sender, RoutedEventArgs e)
{
player.Play();
}
public void PauseButton_Click(object sender, RoutedEventArgs e)
{
player.Pause();
}
public void StopButton_Click(object sender, RoutedEventArgs e)
{
player.Stop();
}
public void NextTrackButton_Click(object sender, RoutedEventArgs e)
{
player.NextTrack();
}
public void PreviousTrackButton_Click(object sender, RoutedEventArgs e)
{
player.PreviousTrack();
}
public void NextIndexButton_Click(object sender, RoutedEventArgs e)
{
player.NextIndex();
}
public void PreviousIndexButton_Click(object sender, RoutedEventArgs e)
{
player.PreviousIndex();
}
public void FastForwardButton_Click(object sender, RoutedEventArgs e)
{
player.FastForward();
}
public void RewindButton_Click(object sender, RoutedEventArgs e)
{
player.Rewind();
}
public void EnableDeEmphasisButton_Click(object sender, RoutedEventArgs e)
{
player.EnableDeEmphasis();
}
public void DisableDeEmphasisButton_Click(object sender, RoutedEventArgs e)
{
player.DisableDeEmphasis();
}
private void UpdateView(object sender, ElapsedEventArgs e)
{
if (player.Initialized)
{
string track = (player.CurrentTrack + 1).ToString().PadLeft(2, '0');
string index = (player.CurrentIndex).ToString().PadLeft(2, '0');
string frames = (player.CurrentSector % 75).ToString().PadLeft(2, '0');
string seconds = ((player.CurrentSector / 75) % 60).ToString().PadLeft(2, '0');
string minutes = ((player.CurrentSector / (75 * 60)) % 60).ToString().PadLeft(2, '0');
Dispatcher.UIThread.InvokeAsync(() =>
{
int i = 0;
foreach (char digit in track + index + minutes + seconds + frames)
if (digits[i] != null)
digits[i++].Source = GetBitmap(digit);
((PlayerViewModel)DataContext).PreEmphasis = player.HasPreEmphasis;
});
}
else
{
Dispatcher.UIThread.InvokeAsync(() =>
{
foreach (Image digit in digits)
{
if (digit != null)
digit.Source = GetBitmap('-');
}
});
}
}
private Bitmap GetBitmap(char character)
{
if (App.CurrentTheme == "default")
{
IAssetLoader assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
return new Bitmap(assets.Open(new Uri($"avares://RedBookPlayer/Assets/{character}.png")));
}
else
{
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + App.CurrentTheme;
Bitmap bitmap;
using (FileStream stream = File.Open(themeDirectory + $"/{character}.png", FileMode.Open))
{
bitmap = new Bitmap(stream);
}
return bitmap;
}
}
public void Initialize()
{
digits = new Image[10];
digits[0] = this.FindControl<Image>("TrackDigit1");
digits[1] = this.FindControl<Image>("TrackDigit2");
digits[2] = this.FindControl<Image>("IndexDigit1");
digits[3] = this.FindControl<Image>("IndexDigit2");
digits[4] = this.FindControl<Image>("TimeDigit1");
digits[5] = this.FindControl<Image>("TimeDigit2");
digits[6] = this.FindControl<Image>("TimeDigit3");
digits[7] = this.FindControl<Image>("TimeDigit4");
digits[8] = this.FindControl<Image>("TimeDigit5");
digits[9] = this.FindControl<Image>("TimeDigit6");
currentTrack = this.FindControl<TextBlock>("CurrentTrack");
}
private void InitializeComponent(string xaml)
{
DataContext = new PlayerViewModel();
if (xaml != null)
{
new AvaloniaXamlLoader().Load(xaml, null, this);
}
else
{
AvaloniaXamlLoader.Load(this);
}
Initialize();
updateTimer = new Timer(1000 / 60);
updateTimer.Elapsed += (sender, e) =>
{
try
{
UpdateView(sender, e);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
};
updateTimer.AutoReset = true;
updateTimer.Start();
}
}
public class PlayerViewModel : ReactiveObject
{
private bool preEmphasis;
public bool PreEmphasis
{
get => preEmphasis;
set => this.RaiseAndSetIfChanged(ref preEmphasis, value);
}
}
}

27
RedBookPlayer/Program.cs Normal file
View File

@@ -0,0 +1,27 @@
using Avalonia;
using Avalonia.Logging.Serilog;
namespace RedBookPlayer
{
class Program
{
public static void Main(string[] args)
{
#if Windows
AllocConsole();
#endif
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
#if Windows
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AllocConsole();
#endif
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToDebug();
}
}

View File

@@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'win-x64'">
<DefineConstants>Windows</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<AvaloniaResource Include="**\*.xaml" Exclude="bin\**;obj\**">
<SubType>Designer</SubType>
</AvaloniaResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.9.12" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.12" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.12" />
<PackageReference Include="NWaves" Version="0.9.4" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.3" />
<PackageReference Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.2" ExcludeAssets="all" />
<PackageReference Include="SkiaSharp" Version="1.68.3" />
<PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Aaru\Aaru.Images\Aaru.Images.csproj" />
<ProjectReference Include="..\Aaru\Aaru.CommonTypes\Aaru.CommonTypes.csproj" />
<ProjectReference Include="..\Aaru\Aaru.Decoders\Aaru.Decoders.csproj" />
<ProjectReference Include="..\cscore\CSCore\CSCore.csproj" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,22 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RedBookPlayer.ThemeSelectionWindow"
Title="Select Theme"
Width="450" Height="600">
<Border Padding="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" Name="ThemeList" SelectionMode="Single" Margin="0,0,0,16"/>
<Button Grid.Row="1" Grid.Column="0" Name="ApplyButton">Apply</Button>
</Grid>
</Border>
</Window>

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace RedBookPlayer
{
public class ThemeSelectionWindow : Window
{
ListBox themeList;
string selectedTheme;
public ThemeSelectionWindow()
{
InitializeComponent();
}
public void ThemeList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count == 0)
{
return;
}
selectedTheme = (string)e.AddedItems[0];
}
public void ApplyTheme(object sender, RoutedEventArgs e)
{
if (selectedTheme == "" || selectedTheme == null)
{
return;
}
if (selectedTheme == "default")
{
MainWindow.Instance.ContentControl.Content = new PlayerView();
}
else
{
string themeDirectory = Directory.GetCurrentDirectory() + "/themes/" + selectedTheme;
string xamlPath = themeDirectory + "/view.xaml";
try
{
MainWindow.Instance.ContentControl.Content = new PlayerView(
File.ReadAllText(xamlPath).Replace("Source=\"", $"Source=\"file://{themeDirectory}/")
);
}
catch (System.Xml.XmlException ex)
{
Console.WriteLine($"Error: invalid theme XAML ({ex.Message}), reverting to default");
MainWindow.Instance.ContentControl.Content = new PlayerView();
}
}
MainWindow.Instance.Width = ((PlayerView)MainWindow.Instance.ContentControl.Content).Width;
MainWindow.Instance.Height = ((PlayerView)MainWindow.Instance.ContentControl.Content).Height;
App.CurrentTheme = selectedTheme;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
themeList = this.FindControl<ListBox>("ThemeList");
themeList.SelectionChanged += ThemeList_SelectionChanged;
List<String> items = new List<String>();
items.Add("default");
if (Directory.Exists("themes/"))
{
foreach (string dir in Directory.EnumerateDirectories("themes/"))
{
string themeName = dir.Split('/')[1];
if (!File.Exists($"themes/{themeName}/view.xaml"))
{
continue;
}
items.Add(themeName);
}
}
themeList.Items = items;
this.FindControl<Button>("ApplyButton").Click += ApplyTheme;
}
}
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" />
</packageSources>
</configuration>

1
cscore Submodule

Submodule cscore added at d389736ff9