diff --git a/BOXVR Playlist Manager/App.config b/BOXVR Playlist Manager/App.config index eca0953..ea90831 100644 --- a/BOXVR Playlist Manager/App.config +++ b/BOXVR Playlist Manager/App.config @@ -1,30 +1,30 @@ - + - -
-
+ +
+
- + - + - + - + - + - \ No newline at end of file + diff --git a/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj b/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj index 7ecab2a..280cd3a 100644 --- a/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj +++ b/BOXVR Playlist Manager/BOXVR Playlist Manager.csproj @@ -8,12 +8,13 @@ WinExe BoxVR_Playlist_Manager BoxVR Playlist Manager - v4.7.1 + v4.7.2 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 true true + AnyCPU @@ -51,6 +52,12 @@ ..\packages\z440.atl.core.2.5.0\lib\net30\ATL.dll + + ..\..\SpotiSharp\bin\Debug\netcoreapp3.1\HtmlAgilityPack.dll + + + ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + ..\packages\NLog.4.5.10\lib\net45\NLog.dll @@ -72,20 +79,77 @@ 4.0 + + ..\packages\Unity3D.UnityEngine.2018.3.5.1\lib\UnityEngine.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + MSBuild:Compile Designer - + SettingsWindow.xaml @@ -131,9 +195,6 @@ PreserveNewest - - Always - Designer diff --git a/BOXVR Playlist Manager/Extensions.cs b/BOXVR Playlist Manager/Extensions.cs index b98bfa1..062ac5a 100644 --- a/BOXVR Playlist Manager/Extensions.cs +++ b/BOXVR Playlist Manager/Extensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Windows.Interop; @@ -17,6 +18,15 @@ public static System.Windows.Forms.IWin32Window GetIWin32Window(this System.Wind return win; } + public static string Md5Hash(this string text) + { + using(MD5 md5 = MD5.Create()) + { + var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(text)); + return Encoding.UTF8.GetString(hash); + } + } + private class OldWindow : System.Windows.Forms.IWin32Window { private readonly IntPtr _handle; diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/Bar.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/Bar.cs new file mode 100644 index 0000000..7232315 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/Bar.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class Bar + { + [JsonProperty("_avgEnergy")] + public float _avgEnergy; + [JsonProperty("_duration")] + public float _duration; + [JsonProperty("_startTime")] + public float _startTime; + [JsonProperty("_beatIndex")] + public int _beatIndex; + + public float Bpm => Bar.DurationToBpm(this._duration); + + public float EndTime => this._startTime + this._duration; + + public static float DurationToBpm(float duration) => 240f / duration; + + public static float BpmToDuration(float bpm) => 240f / bpm; + + public override string ToString() => "Start:" + (object)this._startTime + " Length:" + (object)this._duration; + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/BarList.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/BarList.cs new file mode 100644 index 0000000..df17d35 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/BarList.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class BarList + { + public List _bars; + + protected float CalcMedianLength() + { + List barList = new List((IEnumerable)this._bars); + barList.Sort((Comparison)((x, y) => (int)Mathf.Sign(x._duration - y._duration))); + return barList[barList.Count / 2]._duration; + } + + public void HalfSpeed() + { + for(int index = 0; index < this._bars.Count - 1; ++index) + { + Bar bar1 = this._bars[index]; + Bar bar2 = this._bars[index + 1]; + bar1._duration += bar2._duration; + bar1._avgEnergy = (float)(((double)bar1._avgEnergy + (double)bar2._avgEnergy) / 2.0); + this._bars.RemoveAt(index + 1); + } + } + + public void DoubleSpeed(BeatList beats) + { + for(int index = 0; index < this._bars.Count; index += 2) + { + Bar bar1 = this._bars[index]; + Bar bar2 = new Bar(); + this._bars.Insert(index + 1, bar2); + float num = bar1._duration / 2f; + bar1._duration = num; + bar2._duration = num; + bar2._startTime = bar1._startTime + num; + } + } + + public void UpdateEnergies(List beats) + { + for(int index1 = 0; index1 < this._bars.Count; ++index1) + { + Bar bar = this._bars[index1]; + for(int index2 = 0; index2 <= 3; ++index2) + { + int index3 = bar._beatIndex + index2; + if(index3 < beats.Count && beats[index3]._beatInBar == index2 + 1) + bar._avgEnergy += beats[index3]._magnitude; + } + bar._avgEnergy /= 4f; + } + } + + public void FillEmptyRegions(float deltaBpm, float songLength) + { + float duration1 = this.CalcMedianLength(); + while(true) + { + float num = this._bars[0]._startTime - duration1; + if((double)num >= 0.0) + this._bars.Insert(0, new Bar() + { + _duration = duration1, + _startTime = num + }); + else + break; + } + float duration2 = Bar.BpmToDuration(Bar.DurationToBpm(duration1) + deltaBpm); + for(int index = 0; index < this._bars.Count - 1; ++index) + { + double startTime = (double)this._bars[index + 1]._startTime; + float endTime = this._bars[index].EndTime; + double num1 = (double)endTime; + float num2 = (float)(startTime - num1); + if((double)num2 >= (double)duration1) + this._bars.Insert(index + 1, new Bar() + { + _duration = duration1, + _startTime = endTime + }); + else if((double)num2 >= (double)duration2) + this._bars.Insert(index + 1, new Bar() + { + _duration = num2, + _startTime = endTime + }); + else if((double)num2 > 0.0) + this._bars[index]._duration += num2; + } + while(true) + { + Bar bar = this._bars[this._bars.Count - 1]; + if((double)bar.EndTime + (double)duration1 <= (double)songLength) + this._bars.Add(new Bar() + { + _startTime = bar.EndTime, + _duration = duration1 + }); + else + break; + } + } + + public void RemoveDeviants(float bpmDelta) + { + float num = 240f / this.CalcMedianLength(); + int index = 0; + while(index < this._bars.Count) + { + Bar bar = this._bars[index]; + if((double)Mathf.Abs(bar.Bpm - num) > (double)bpmDelta) + this._bars.Remove(bar); + else + ++index; + } + } + + public void CreateFromBeats(List beats) + { + this._bars = new List(); + Bar bar = (Bar)null; + for(int index = 0; index < beats.Count; ++index) + { + BeatInfo beat = beats[index]; + if(beat._beatInBar == 1) + { + bar = new Bar() + { + _startTime = beat._triggerTime, + _beatIndex = index + }; + this._bars.Add(bar); + } + if(bar != null && beat._beatInBar == 4) + bar._duration = beat._triggerTime + beat._beatLength - bar._startTime; + } + this.UpdateEnergies(beats); + } + + public int FindClosestBar(float time, List beats) + { + float num1 = float.MaxValue; + int num2 = -1; + for(int index = 0; index < this._bars.Count; ++index) + { + float num3 = Mathf.Abs(this._bars[index]._startTime - time); + if((double)num3 < (double)num1) + { + num2 = index; + num1 = num3; + } + } + return num2; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/Beat.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/Beat.cs new file mode 100644 index 0000000..59d1f51 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/Beat.cs @@ -0,0 +1,16 @@ +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class Beat + { + public float length; + public float bpm; + public int index; + + public Beat(float length, float bpm, int index) + { + this.length = length; + this.bpm = bpm; + this.index = index; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/BeatInfo.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatInfo.cs new file mode 100644 index 0000000..8a2a9b4 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatInfo.cs @@ -0,0 +1,45 @@ +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class BeatInfo + { + public int _index; + public int _beatInBar; + public float _magnitude; + public float _triggerTime; + public float _beatLength; + public float _bpm; + public bool _isLastBeat; + public Segment _segment; + + public BeatInfo( + int index, + float triggerTime, + float mag, + float beatLength, + float bpm, + int beatInBar, + bool isLastBeat) + { + this._beatInBar = beatInBar; + this._index = index; + this._triggerTime = triggerTime; + this._magnitude = mag; + this._beatLength = beatLength; + this._bpm = bpm; + this._isLastBeat = isLastBeat; + } + + public BeatInfo(int index, float triggerTime, float mag, Beat beat, bool isLastBeat) + : this(index, triggerTime, mag, beat.length, beat.bpm, -1, isLastBeat) + { + } + + public override string ToString() + { + string str = string.Format("{2,3} [{0,6:F2} s][{1,6} bpm][{3, 6:F2} magnitude]", (object)this._triggerTime, (object)this._bpm, (object)this._index, (object)this._magnitude); + if(this._segment != null) + str += this._segment.ToString(); + return str + "\n"; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/BeatList.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatList.cs new file mode 100644 index 0000000..a2aba49 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatList.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using BoxVR_Playlist_Manager.FitXr.Tools; +using UnityEngine; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class BeatList + { + public List _beats; + public float AverageBpm; + + private float CalcMedianBpm(List beatList) + { + List beatInfoList = new List((IEnumerable)beatList); + beatInfoList.Sort((Comparison)((x, y) => (int)Mathf.Sign(x._bpm - y._bpm))); + return beatInfoList[beatInfoList.Count / 2]._bpm; + } + + public void CreateFromBars(BarList barList) + { + this._beats = new List(); + int index1 = 0; + for(int index2 = 0; index2 < barList._bars.Count; ++index2) + { + Bar bar = barList._bars[index2]; + bar._beatIndex = index1; + float startTime = bar._startTime; + float num = bar._duration / 4f; + for(int beatInBar = 1; beatInBar < 5; ++beatInBar) + { + this._beats.Add(new BeatInfo(index1, startTime, -1f, -1f, -1f, beatInBar, false)); + ++index1; + startTime += num; + } + } + for(int index2 = 0; index2 < this._beats.Count; ++index2) + { + this._beats[index2]._beatLength = index2 >= this._beats.Count - 1 ? this._beats[index2 - 1]._beatLength : this._beats[index2 + 1]._triggerTime - this._beats[index2]._triggerTime; + this._beats[index2]._bpm = 60f / this._beats[index2]._beatLength; + } + this.AverageBpm = this.CalcMedianBpm(this._beats); + for(int index2 = 0; index2 < this._beats.Count; ++index2) + this._beats[index2]._bpm = this.AverageBpm; + } + + public void UpdateEnergiesFromLines(string[] lines) + { + int index1 = 0; + int index2 = 0; + this._beats[0]._magnitude = 0.0f; + do + { + float val1 = -1f; + float val2 = -1f; + Format.FloatsFromCsvLine(lines[index1], out val1, out val2); + while((double)val1 >= (double)this._beats[index2 + 1]._triggerTime) + { + ++index2; + this._beats[index2]._magnitude = 0.0f; + if(index2 == this._beats.Count - 1) + break; + } + this._beats[index2]._magnitude += val2; + ++index1; + } + while(index1 != lines.Length && index2 != this._beats.Count - 1); + } + + public void CreateFromLines(string[] lines) + { + this._beats = new List(); + int index1 = 0; + for(int index2 = 0; index2 < lines.Length; ++index2) + { + List stringList = new List((IEnumerable)lines[index2].Split('\t')); + float triggerTime = float.Parse(stringList[0]); + int beatInBar = int.Parse(stringList[1]); + this._beats.Add(new BeatInfo(index1, triggerTime, -1f, -1f, -1f, beatInBar, false)); + ++index1; + } + for(int index2 = 0; index2 < this._beats.Count - 1; ++index2) + { + this._beats[index2]._beatLength = this._beats[index2 + 1]._triggerTime - this._beats[index2]._triggerTime; + this._beats[index2]._bpm = 60f / this._beats[index2]._beatLength; + } + if(this._beats.Count > 0) + { + this.AverageBpm = this.CalcMedianBpm(this._beats); + } + else + { + App.logger.Debug("MadMom failure"); + this.AverageBpm = 120f; + } + for(int index2 = 0; index2 < this._beats.Count; ++index2) + this._beats[index2]._bpm = this.AverageBpm; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureBase.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureBase.cs new file mode 100644 index 0000000..3eb6fc4 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureBase.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class BeatStructureBase + { + + public event EventHandler _buildCompleteEvent; + + protected bool _isBuilt; + + public virtual List Beats => (List)null; + + [JsonProperty("AverageBpm")] + public float AverageBpm { get; protected set; } + + [JsonProperty("_isBuilt")] + public bool IsBuilt => this._isBuilt; + + public virtual void Build() + { + } + + public virtual BeatInfo GetBeatInInterval(float fromTime, float toTime) + { + foreach(BeatInfo beat in this.Beats) + { + if((double)beat._triggerTime >= (double)fromTime && (double)beat._triggerTime < (double)toTime) + return beat; + } + return (BeatInfo)null; + } + + protected float CalcAverageBpm(List beatList) + { + float num = 0.0f; + foreach(BeatInfo beat in this.Beats) + num += beat._bpm; + return num / (float)this.Beats.Count; + } + + protected void RaiseBuildCompleteEvent() + { + _buildCompleteEvent?.Invoke(this, EventArgs.Empty); + } + + public override string ToString() + { + string str = ""; + foreach(BeatInfo beat in this.Beats) + str += beat.ToString(); + return str; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureMadmom.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureMadmom.cs new file mode 100644 index 0000000..77bb05c --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/BeatStructureMadmom.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class BeatStructureMadmom : BeatStructureBase + { + [JsonProperty("_barList")] + public BarList _barList; + [JsonProperty("_segmentList")] + public SegmentList _segmentList; + [JsonProperty("")] + public int _dataVersion = BeatStructureMadmom.DATA_VERSION; + public static int DATA_VERSION = 3; + [JsonProperty("_beatList")] + public BeatList _beatList; + private const float MAX_SPEED_BPM = 190f; + private const float MIN_SPEED_BPM = 90f; + private UserSongClip _userSong; + private MadmomProcess _madmomProcess; + private VampProcess _vampProcessSegments; + private VampProcess _vampProcessEnergies; + + public override List Beats => this._beatList._beats; + + public BeatStructureMadmom() + { + } + + public BeatStructureMadmom( + UserSongClip userSong, + MadmomProcess madmomProcess, + VampProcess vampProcessSegments, + VampProcess vampProcessEnergies) + { + this._madmomProcess = madmomProcess; + this._userSong = userSong; + this._vampProcessSegments = vampProcessSegments; + this._vampProcessEnergies = vampProcessEnergies; + this._isBuilt = false; + } + + public override void Build() => this._madmomProcess.Apply(this._userSong.wavPath, OnBeatsTracked); + + private void OnBeatsTracked() + { + this._beatList = new BeatList(); + this._beatList.CreateFromLines(this._madmomProcess._resultEntries); + this._barList = new BarList(); + this._barList.CreateFromBeats(this._beatList._beats); + this._barList.RemoveDeviants(2f); + this._barList.FillEmptyRegions(8f, this._userSong.trackData.duration); + if((double)this._barList._bars[0].Bpm > 190.0) + this._barList.HalfSpeed(); + else if((double)this._barList._bars[0].Bpm < 90.0) + this._barList.DoubleSpeed(this._beatList); + this._beatList.CreateFromBars(this._barList); + this.AverageBpm = this._beatList.AverageBpm; + // ISSUE: method pointer + this._vampProcessEnergies.Apply("vamp:bbc-vamp-plugins:bbc-energy", this._userSong.wavPath, OnEnergiesTracked); + } + + private void OnEnergiesTracked() + { + this._beatList.UpdateEnergiesFromLines(this._vampProcessEnergies._resultEntries); + this._barList.UpdateEnergies(this.Beats); + // ISSUE: method pointer + this._vampProcessSegments.Apply("vamp:qm-vamp-plugins:qm-segmenter", this._userSong.wavPath,OnSegmentsTracked); + } + + private void OnSegmentsTracked() + { + this._segmentList = new SegmentList(this._vampProcessSegments._resultEntries, this.Beats, this._barList); + App.logger.Debug(this._segmentList.ToString()); + List segments = this._segmentList._segments; + for(int index1 = 0; index1 < segments.Count; ++index1) + { + for(int index2 = 0; index2 < segments[index1]._numBeats; ++index2) + this.Beats[segments[index1]._startBeatIndex + index2]._segment = segments[index1]; + } + this.OnBuildComplete(); } + + private void OnBuildComplete() + { + this._isBuilt = true; + RaiseBuildCompleteEvent(); + } + } + } diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJob.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJob.cs new file mode 100644 index 0000000..97f8d4c --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJob.cs @@ -0,0 +1,14 @@ +using System; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class FFmpegJob + { + public Action _onFinished; + public string _inputPath; + public string _outputPath; + public string _message; + + public virtual string GetCommand() => ""; + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJobConvert.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJobConvert.cs new file mode 100644 index 0000000..4eeb967 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegJobConvert.cs @@ -0,0 +1,25 @@ +using System; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class FFmpegJobConvert : FFmpegJob + { + public FFmpegJobConvert(string inputPath, string outputPath, Action onFinished) + { + this._inputPath = inputPath; + this._outputPath = outputPath; + this._onFinished = onFinished; + } + + public override string GetCommand() => string.Join(" ", new string[7] + { + "-y", + "-i", + "\"" + this._inputPath + "\"", + "-ar 44100 ", + "-acodec pcm_s16le ", + "-nostdin -loglevel error", + "\"" + this._outputPath + "\"" + }); + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs new file mode 100644 index 0000000..e687949 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/FFmpegQueue.cs @@ -0,0 +1,70 @@ +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Threading; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.Helpers; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class FFmpegQueue + { + public static FFmpegQueue instance = instance == null ? new FFmpegQueue() : instance; + + public void Queue(FFmpegJob job) => RunFFMPEG(job); + + public static string binaryPath => Path.Combine(Paths.StreamingAssetsPath, @"ffmpeg\bin\ffmpeg.exe"); + + private void RunFFMPEG(FFmpegJob job) + { + Directory.CreateDirectory(Paths.TrackDataFolder(LocationMode.PlayerData)); + App.logger.Debug(("FFMPEG start: " + FFmpegQueue.binaryPath + " " + job.GetCommand())); + CancellationTokenSource doneCts = new CancellationTokenSource(); + var done = doneCts.Token; + string exepath = FFmpegQueue.binaryPath; + string command = job.GetCommand(); + new Thread((ThreadStart)(() => + { + string output = ""; + Process process = new Process(); + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.FileName = exepath; + process.StartInfo.Arguments = command; + process.OutputDataReceived += (DataReceivedEventHandler)((s, e) => output += e.Data); + process.ErrorDataReceived += (DataReceivedEventHandler)((s, e) => output += e.Data); + process.Start(); + process.WaitForExit(); + process.Close(); + job._message = output; + doneCts.Cancel(); + })).Start(); + while(!done.IsCancellationRequested) { } + job._onFinished.Invoke(job); + App.logger.Debug("FFMEG done"); + } + + public bool ExtractDurationFromFFmpegOutput(string output, out int minutes, out int seconds) + { + minutes = -1; + seconds = -1; + string str1 = "Duration: "; + string str2 = "xx:xx:xx.xx"; + int startIndex = output.IndexOf(str1) + str1.Length; + string[] strArray = output.Substring(startIndex, str2.Length).Split(':', '.'); + if(strArray == null || strArray.Length != 4) + return false; + int[] numArray = new int[strArray.Length]; + for(int index = 0; index < strArray.Length; ++index) + { + if(!int.TryParse(strArray[index], out numArray[index])) + return false; + } + minutes = numArray[0] * 60 + numArray[1]; + seconds = numArray[2]; + return true; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/MadmomProcess.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/MadmomProcess.cs new file mode 100644 index 0000000..7e68cf5 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/MadmomProcess.cs @@ -0,0 +1,100 @@ + +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Threading; +using BoxVR_Playlist_Manager.Helpers; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class MadmomProcess + { + public static string madmonOutputPath; + public string[] _resultEntries; + public string _output; + private const bool SHOW_WINDOW = false; + private string processPath; + private string outputFileName; + + private string BinaryPath => Path.Combine(Paths.StreamingAssetsPath, @"DBNDownBeatTracker\DBNDownBeatTracker.exe"); + + public MadmomProcess() + { + madmonOutputPath = Path.Combine(Paths.PersistentDataPath, "Temp"); + this.processPath = this.BinaryPath; + if(Directory.Exists(MadmomProcess.madmonOutputPath)) + { + return; + } + Directory.CreateDirectory(MadmomProcess.madmonOutputPath); + } + + private string CreateCommandArgs(string musicPath, string outputPath) => string.Format("--beats_per_bar 4 single \"{0}\" -o \"{1}\" -j 1", (object)musicPath, (object)outputPath); + + public void Apply(string musicPath, Action successAction) + { + ProcessMadmom(musicPath, successAction); + } + + private void ProcessMadmom(string musicPath, Action successAction) + { + outputFileName = Path.GetFileNameWithoutExtension(musicPath) + ".madmom.txt"; + string resultOutPath = Path.Combine(MadmomProcess.madmonOutputPath, Path.GetFileNameWithoutExtension(musicPath) + ".madmom.txt"); + string ffmpegPath = Path.GetDirectoryName(FFmpegQueue.binaryPath); + string arguments = this.CreateCommandArgs(musicPath, resultOutPath); + string path = MadmomProcess.madmonOutputPath + this.outputFileName; + File.Delete(resultOutPath); + File.Delete(path); + bool isProcessComplete = false; + App.logger.Debug(arguments); + Process process = (Process)null; + new Thread((ThreadStart)(() => + { + App.logger.Debug("Madmom started"); + process = new Process() + { + StartInfo = { + FileName = this.processPath, + UseShellExecute = false, + EnvironmentVariables = { + { + "PROGRESS_PATH", + MadmomProcess.madmonOutputPath + this.outputFileName + } + } + } + }; + process.StartInfo.EnvironmentVariables["PATH"] = process.StartInfo.EnvironmentVariables["PATH"] + ";" + ffmpegPath; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.Arguments = arguments; + isProcessComplete = false; + process.Start(); + process.PriorityClass = ProcessPriorityClass.BelowNormal; + process.WaitForExit(); + isProcessComplete = true; + })).Start(); + App.logger.Debug("Waiting for madmom"); + while(!isProcessComplete){ } + while(!process.HasExited){ } + int exitCode = process.ExitCode; + process.Close(); + App.logger.Debug("Madmom finished : error code= " + (object)exitCode); + this._resultEntries = new string[0]; + if(File.Exists(resultOutPath)) + { + this._resultEntries = File.ReadAllLines(resultOutPath); + } + + if(exitCode == -1 || this._resultEntries.Length == 0) + { + throw new System.Exception("Process error"); + } + else + { + successAction.Invoke(); + } + File.Delete(resultOutPath); + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/PlaylistManager.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/PlaylistManager.cs new file mode 100644 index 0000000..bb5b7c6 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/PlaylistManager.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Collections; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Models; +using BoxVR_Playlist_Manager.Helpers; +using BoxVR_Playlist_Manager.FitXr.Enums; +using Newtonsoft.Json; +using BoxVR_Playlist_Manager.FitXr.Utility; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class PlaylistManager + { + public static int MAX_NUM_ENTRIES = 20; + public static PlaylistManager instance = instance == null ? new PlaylistManager() : instance; + private string playlistsPath; + public List workoutPlaylists; + private List playlistQueue; + private bool isProcessing; + + public event PlaylistManager.OnTrackAdditionComplete onTrackAdditionComplete; + + public PlaylistManager() + { + Directory.CreateDirectory(Paths.RootDataFolder(LocationMode.PlayerData)); + Directory.CreateDirectory(Paths.WorkoutDefinitionFolder(LocationMode.PlayerData, Game.BoxVR)); + Directory.CreateDirectory(Paths.TrackDataFolder(LocationMode.PlayerData)); + Directory.CreateDirectory(Paths.DefinitionsFolder(LocationMode.PlayerData)); + Directory.CreateDirectory(Paths.ImgDataFolder(LocationMode.PlayerData)); + this.playlistsPath = Paths.WorkoutDefinitionFolder(LocationMode.PlayerData, Game.BoxVR); + if(Directory.Exists(this.playlistsPath)) + return; + Directory.CreateDirectory(this.playlistsPath); + } + + + public List GetAllWorkoutPlaylists() => this.workoutPlaylists; + + public List ReturnAllPlaylistsFromDisk() => !Directory.Exists(this.playlistsPath) ? new List() : new List((IEnumerable)Tools.IO.GetVisibleFiles(this.playlistsPath, "*.play")); + + public void LoadWorkoutPlaylists() + { + this.workoutPlaylists = this.LoadAllWorkoutPlaylistsFromDisk(); + LoadPlaylistTrackDefinition(); + foreach(var playlist in workoutPlaylists) + { + playlist.CalcTotalLength(); + } + } + + private void LoadPlaylistTrackDefinition() + { + while(TrackDataManager.instance == null) { } + while(!TrackDataManager.instance.trackListLoaded) { } + for(int index1 = 0; index1 < this.workoutPlaylists.Count; ++index1) + { + for(int index2 = 0; index2 < this.workoutPlaylists[index1].songs.Count; ++index2) + { + this.workoutPlaylists[index1].songs[index2].trackDefinition = new TrackDefinition(); + this.workoutPlaylists[index1].songs[index2].trackDefinition.LoadTrackDefinition(this.workoutPlaylists[index1].songs[index2].trackDataName, LocationMode.PlayerData); + } + } + } + + public List LoadAllWorkoutPlaylistsFromDisk() + { + List workoutPlaylistList = new List(); + if(Directory.Exists(this.playlistsPath)) + { + foreach(string visibleFile in Tools.IO.GetVisibleFiles(this.playlistsPath, "*.workoutplaylist.txt")) + { + WorkoutPlaylist workoutPlaylist = new WorkoutPlaylist(); + string jsonString = File.ReadAllText(visibleFile); + workoutPlaylist.LoadFromJSON(jsonString); + workoutPlaylistList.Add(workoutPlaylist); + } + } + return workoutPlaylistList; + } + + public void Saveplaylists() + { + + for(int index = 0; index < this.workoutPlaylists.Count; ++index) + this.ExportPlaylistJson(this.workoutPlaylists[index]); + } + + public void ExportPlaylistJson(WorkoutPlaylist playlist) + { + playlist.CalcTotalLength(); + if(playlist.definition.originalName != playlist.definition.workoutName) + { + DeleteJsonFile(playlist.definition.originalName); + } + File.WriteAllText(Path.Combine(this.playlistsPath, playlist.definition.workoutName + ".workoutplaylist.txt"), JsonConvert.SerializeObject(playlist)); + } + + public string PlaylistJsonFromName(string playlistName) + { + string str1 = ""; + string str2 = Path.Combine(this.playlistsPath, playlistName + ".play"); + if((!File.Exists(str2) ? 0 : (new FileInfo(str2).Length > 0L ? 1 : 0)) != 0) + str1 = File.ReadAllText(str2); + return str1; + } + + public void DeletePlaylist(string playlistName) + { + WorkoutPlaylist playlistByName = this.GetPlaylistByName(playlistName); + if(playlistByName == null) + return; + this.DeletePlaylist(playlistByName); + } + + public void DeletePlaylist(WorkoutPlaylist playlist) + { + this.DeleteJsonFile(playlist.definition.workoutName); + this.workoutPlaylists.Remove(playlist); + //this.Saveplaylists(); + } + + public List ReturnPlaylistTrackDefinitions( + WorkoutPlaylist playlist) + { + List trackDefinitionList = new List(); + if(playlist == null || playlist.songs == null) + return (List)null; + for(int index = 0; index < playlist.songs.Count; ++index) + { + playlist.songs[index].trackDefinition = TrackDataManager.instance.GetTrackDefinition(playlist.songs[index].trackDataName); + trackDefinitionList.Add(playlist.songs[index].trackDefinition); + } + return trackDefinitionList; + } + + public void PlaylistEditName(WorkoutPlaylist playlist, string newName) + { + string workoutName = playlist.definition.workoutName; + if(newName == workoutName) + return; + playlist.definition.workoutName = newName; + this.ExportPlaylistJson(playlist); + this.DeleteJsonFile(workoutName); + } + + private void DeleteJsonFile(string workoutName) => File.Delete(Path.Combine(this.playlistsPath, workoutName + ".workoutplaylist.txt")); + + public SongDefinition PlaylistAddEntry(WorkoutPlaylist playlist, string fileName, LocationMode location) + { + if(this.playlistQueue == null) + this.playlistQueue = new List(); + PlaylistManager.PlaylistToAdd playlistToAdd = new PlaylistManager.PlaylistToAdd() + { + playlistToAdd = playlist, + songToAdd = new SongDefinition() + }; + playlistToAdd.songToAdd.trackDefinition = new TrackDefinition(); + playlistToAdd.originalFilePath = fileName; + playlistToAdd.location = location; + this.playlistQueue.Add(playlistToAdd); + if(this.isProcessing) + throw new Exception("Can't batch add yet"); + this.PlaylistAddProcess(); + return playlistToAdd.songToAdd; + } + + public void PlaylistAddProcess() + { + this.isProcessing = true; + string originalFilePath = this.playlistQueue[0].originalFilePath; + LocationMode location = this.playlistQueue[0].location; + App.logger.Debug("Location mode = " + location.ToString()); + var trackHash = MD5.MD5Sum(Path.GetFileNameWithoutExtension(originalFilePath)); + if(this.playlistQueue[0].songToAdd.trackDefinition.LoadTrackDefinition(trackHash, location)) + { + this.playlistQueue[0].songToAdd.trackDataName = this.playlistQueue[0].songToAdd.trackDefinition.trackId.trackId; + this.playlistQueue[0].playlistToAdd.AddSong(this.playlistQueue[0].songToAdd); + this.ExportPlaylistJson(this.playlistQueue[0].playlistToAdd); + this.WaitingOver(); + } + else + { + this.playlistQueue[0].songToAdd.trackDefinition.trackData = new TrackData(); + this.playlistQueue[0].songToAdd.trackDefinition.trackData.PopulateTrackDataFromAudioFile(originalFilePath, location, Game.BoxVR); + WaitForTrackData(); + } + } + + private void WaitForTrackData() + { + while(this.playlistQueue[0].songToAdd.trackDefinition.trackData.trackDataState == TrackDataState.Loading) { } + if(this.playlistQueue[0].songToAdd.trackDefinition.trackData.trackDataState == TrackDataState.Ready) + { + this.playlistQueue[0].songToAdd.trackDataName = this.playlistQueue[0].songToAdd.trackDefinition.trackData.trackId.trackId; + this.playlistQueue[0].songToAdd.trackDefinition.LoadTrackDefinition(playlistQueue[0].songToAdd.trackDataName, playlistQueue[0].location); + this.playlistQueue[0].playlistToAdd.AddSong(this.playlistQueue[0].songToAdd); + this.ExportPlaylistJson(this.playlistQueue[0].playlistToAdd); + } + else + App.logger.Debug("TrackData failed"); + this.WaitingOver(); + } + + private void WaitingOver() + { + this.playlistQueue.RemoveAt(0); + this.isProcessing = false; + if(this.playlistQueue.Count > 0) + { + this.PlaylistAddProcess(); + } + else + { + if(this.onTrackAdditionComplete == null) + return; + this.onTrackAdditionComplete(); + this.onTrackAdditionComplete = (PlaylistManager.OnTrackAdditionComplete)null; + } + } + + public int ParsingQueueSize() => this.playlistQueue == null ? 0 : this.playlistQueue.Count; + + public void PlayistRemoveEntry(WorkoutPlaylist playlist, string filePath) + { + string withoutExtension = Path.GetFileNameWithoutExtension(filePath); + for(int index = 0; index < playlist.songs.Count; ++index) + { + if(playlist.songs[index].trackDataName == withoutExtension) + { + playlist.RemoveSong(index); + this.ExportPlaylistJson(playlist); + break; + } + } + } + + public WorkoutPlaylist AddNewPlaylist(string name = "") + { + if(this.workoutPlaylists == null) + this.workoutPlaylists = new List(); + if(name == "") + name = this.GeneratePlaylistName(); + WorkoutPlaylist workoutPlaylist = new WorkoutPlaylist() + { + definition = new WorkoutInfo() + }; + workoutPlaylist.definition.workoutName = name; + workoutPlaylist.definition.originalName = name; + workoutPlaylist.definition.workoutType = WorkoutType.BoxVR_Playlist; + this.workoutPlaylists.Add(workoutPlaylist); + return workoutPlaylist; + } + + private string GeneratePlaylistName() + { + string str = "playlist"; + string id = str; + int num = 1; + while(this.IsIdInPlaylists(id)) + { + id = str + " " + num.ToString(); + ++num; + } + return id; + } + + private bool IsIdInPlaylists(string id) + { + foreach(WorkoutPlaylist workoutPlaylist in this.workoutPlaylists) + { + if(workoutPlaylist.definition.workoutName == id) + return true; + } + return false; + } + + public WorkoutPlaylist GetPlaylistByName(string name) + { + if(this.workoutPlaylists != null) + { + for(int index = 0; index < this.workoutPlaylists.Count; ++index) + { + if(this.workoutPlaylists[index].definition.workoutName == name) + return this.workoutPlaylists[index]; + } + } + return (WorkoutPlaylist)null; + } + + public void ClearOrphanedFile(string playlistName) + { + File.Delete(Path.Combine(this.playlistsPath, playlistName + ".play")); + } + + public void ShufflePlaylistTracks(WorkoutPlaylist playlist) => playlist.songs.Shuffle(); + + public List GetAllOldSaves() + { + string path = Path.Combine(Paths.PersistentDataPath, "Playlists"); + return !Directory.Exists(path) ? new List() : new List((IEnumerable)Tools.IO.GetVisibleFiles(path, "*.playlist.txt")); + } + + public List ReturnSongPathsFromOldSavePath(string SaveFilePath) + { + List stringList = new List(); + foreach(string readAllLine in File.ReadAllLines(SaveFilePath)) + { + if(File.Exists(readAllLine)) + stringList.Add(readAllLine); + } + return stringList; + } + + private void ConvertLegacyPlaylistsToGen3() + { + } + + public class PlaylistToAdd + { + public WorkoutPlaylist playlistToAdd; + public SongDefinition songToAdd; + public string originalFilePath; + public LocationMode location; + } + + public delegate void OnTrackAdditionComplete(); + } +} \ No newline at end of file diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/Segment.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/Segment.cs new file mode 100644 index 0000000..2ef7813 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/Segment.cs @@ -0,0 +1,42 @@ +using System.Text; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class Segment + { + public float _startTime; + public int _startBeatIndex; + public float _length; + public int _numBeats; + public float _averageEnergy; + public int _index; + public Segment.EnergyLevel _energyLevel; + + public Segment(float startTime, float length) + { + this._startTime = startTime; + this._length = length; + } + + public float endTime => this._startTime + this._length; + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append("Segment:"); + stringBuilder.AppendFormat("[{0, 6:F2} Start]", (object)this._startTime); + stringBuilder.AppendFormat("[{0, 6:F2} Energy]", (object)this._averageEnergy); + stringBuilder.AppendFormat("[{0, 6:F2} Range]", (object)this._energyLevel.ToString()); + return stringBuilder.ToString(); + } + + public enum EnergyLevel + { + UltraLow, + Low, + Medium, + High, + Size, + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/SegmentList.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/SegmentList.cs new file mode 100644 index 0000000..cc432b2 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/SegmentList.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class SegmentList + { + public List _segments; + + public SegmentList(string[] csvLines, List beats, BarList bars) + { + this._segments = new List(); + foreach(string csvLine in csvLines) + { + List stringList = new List((IEnumerable)csvLine.Split(',')); + int num = stringList.Count - 1; + if(num < 4) + App.logger.Error(("Not enough entries in line " + csvLine)); + this._segments.Add(new Segment(float.Parse(stringList[num - 3]), float.Parse(stringList[num - 2]))); + } + this.AlignSegmentsWithBeats(ref this._segments, beats, bars); + this.CalculateSegmentEnergies(ref this._segments, beats); + } + + private void CalculateSegmentEnergies(ref List segments, List beats) + { + for(int index1 = 0; index1 < segments.Count; ++index1) + { + float num1 = 0.0f; + for(int index2 = 0; index2 < segments[index1]._numBeats; ++index2) + { + int index3 = segments[index1]._startBeatIndex + index2; + num1 += beats[index3]._magnitude; + } + float num2 = num1 / (float)segments[index1]._numBeats; + segments[index1]._averageEnergy = num2; + } + float[] numArray1 = new float[4] + { + 0.1f, + 0.3f, + 0.3f, + 0.3f + }; + int[] numArray2 = new int[4]; + List segmentList = new List((IEnumerable)segments); + segmentList.Sort((Comparison)((x, y) => (int)Mathf.Sign(x._averageEnergy - y._averageEnergy))); + int num3 = this._segments[this._segments.Count - 1]._startBeatIndex + this._segments[this._segments.Count - 1]._numBeats; + int num4 = 0; + for(int index = 0; index < 4; ++index) + { + if(index == 3) + { + numArray2[index] = num3 - num4; + } + else + { + numArray2[index] = (int)((double)num3 * (double)numArray1[index]); + num4 += numArray2[index]; + } + } + int index4 = 0; + int[] numArray3 = new int[4] { 0, 0, 0, 0 }; + for(int index1 = 0; index1 < segmentList.Count; ++index1) + { + segmentList[index1]._energyLevel = (Segment.EnergyLevel)index4; + numArray3[index4] += segmentList[index1]._numBeats; + if(numArray3[index4] > numArray2[index4]) + { + ++index4; + numArray3[index4] = 0; + } + } + } + + private int FindLargestNeighbourEnergyDelta(List bars, int centerBarIndex, int range) + { + float[] numArray = new float[2 * (range + 1)]; + for(int index1 = -range - 1; index1 <= range; ++index1) + { + numArray[index1 + range + 1] = float.MinValue; + int index2 = index1 + centerBarIndex; + if(index2 < bars.Count && index2 >= 0) + numArray[index1 + range + 1] = bars[index2]._avgEnergy; + } + int length = 2 * range + 1; + float[] array = new float[length]; + for(int index = 0; index < length; ++index) + array[index] = Mathf.Abs(numArray[index + 1] - numArray[index]); + float num = ((IEnumerable)array).Max(); + return Array.IndexOf(array, num) - range; + } + + private void AlignSegmentsWithBeats( + ref List segments, + List beats, + BarList bars) + { + for(int index1 = 0; index1 < segments.Count; ++index1) + { + int closestBar = bars.FindClosestBar(segments[index1]._startTime, beats); + int neighbourEnergyDelta = this.FindLargestNeighbourEnergyDelta(bars._bars, closestBar, 2); + int index2 = Mathf.Clamp(closestBar + neighbourEnergyDelta, 0, bars._bars.Count - 1); + int beatIndex = bars._bars[index2]._beatIndex; + segments[index1]._startBeatIndex = beatIndex; + } + List segmentList = new List(); + List intList = new List(); + foreach(Segment segment in this._segments) + { + if(intList.Contains(segment._startBeatIndex)) + segmentList.Add(segment); + else + intList.Add(segment._startBeatIndex); + } + foreach(Segment segment in segmentList) + this._segments.Remove(segment); + for(int index = 0; index < segments.Count; ++index) + { + int num = (index < segments.Count - 1 ? segments[index + 1]._startBeatIndex : beats.Count) - segments[index]._startBeatIndex; + segments[index]._numBeats = num; + } + } + + public override string ToString() + { + StringBuilder stringBuilder = new StringBuilder(); + foreach(Segment segment in this._segments) + stringBuilder.AppendLine(segment.ToString()); + return stringBuilder.ToString(); + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/SongParser.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/SongParser.cs new file mode 100644 index 0000000..f138860 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/SongParser.cs @@ -0,0 +1,85 @@ +using System; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.FitXr.Models; +using NLog; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class SongParser + { + private static Logger _log = LogManager.GetLogger(nameof(SongParser)); + + public static SongParser instance = instance == null ? new SongParser() : instance; + public MadmomProcess _madmomProcess; + public VampProcess _vampProcessEnergies; + public VampProcess _vampProcessSegments; + public BeatStructureBase beatStructure; + private UserSongClip songClip; + private string passedPath; + private LocationMode passedlocationMode; + private bool busy; + + public event SongParser.OnParsingComplete onParsingComplete; + + public SongParser() + { + _madmomProcess = new MadmomProcess() { }; + _vampProcessEnergies = new VampProcess(); + _vampProcessSegments = new VampProcess(); + } + + /// + /// Entry to processing tasks + /// + /// Original audio track + /// File path to the .wav track + /// + /// + public bool CreateSongClipFromAudioClip( + ATL.Track audioTrack, + string wavPath, + LocationMode locationMode) + { + if(busy) + { + _log.Debug("Parsing a track already"); + return false; + } + busy = true; + passedPath = wavPath; + passedlocationMode = locationMode; + WavLoaded(audioTrack); + return true; + } + + private void WavLoaded(ATL.Track clip) + { + songClip = new UserSongClip(clip); + + //Set filepath to .wav file + songClip.wavPath = passedPath; + ParseSong(); + } + + public void ParseSong() + { + this.beatStructure = (BeatStructureBase)new BeatStructureMadmom(this.songClip, this._madmomProcess, this._vampProcessEnergies, this._vampProcessSegments); + this.beatStructure._buildCompleteEvent += OnAnalysisComplete; + this.beatStructure.Build(); + } + + private void OnAnalysisComplete(object sender, EventArgs args) + { + this.busy = false; + this.beatStructure._buildCompleteEvent -= OnAnalysisComplete; + _log.Debug("Treack analysis complete"); + TrackData trackData = TrackDataManager.instance.NewTrackData(songClip, (BeatStructureMadmom)this.beatStructure, this.passedlocationMode); + _log.Debug(trackData.originalTrackName); + onParsingComplete?.Invoke(trackData); + } + + public delegate void OnParsingComplete(TrackData trackData); + + public delegate void OnParsingFailed(string error); + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/TrackDataManager.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/TrackDataManager.cs new file mode 100644 index 0000000..bca656a --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/TrackDataManager.cs @@ -0,0 +1,381 @@ +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.FitXr.Models; +using BoxVR_Playlist_Manager.Helpers; +using Newtonsoft.Json; +using NLog; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + class TrackDataManager + { + private static Logger _log = LogManager.GetLogger(nameof(TrackDataManager)); + public static TrackDataManager instance = instance == null ? new TrackDataManager() : instance; + private ATL.Track audioTrack; + public string currentPath; + private Game currentGameType; + private List myWorkoutTrackDataList; + private List trackDefinitionsResources; + private List trackDefinitionsEditor; + private List trackDefinitionsPlayer; + private List trackDefinitionsDLC; + private string originalFileNameBackup; + private bool trackDataLoadComplete; + public bool trackListLoaded => this.trackDataLoadComplete; + + public event TrackDataManager.OnTrackDataComplete onTrackDataComplete; + + public TrackDataManager() + { + LoadTrackDefinitionLists(); + } + + public string GetLocationpathPath( + TrackId trackId, + LocationMode locationMode, + Game gameType) + { + string str = Paths.TrackDataFolder(locationMode); + switch(locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + if(!Directory.Exists(Paths.RootDataFolder(locationMode))) + Directory.CreateDirectory(Paths.RootDataFolder(locationMode)); + str = str + trackId.trackId + ".trackdata.txt"; + break; + case LocationMode.Workouts: + case LocationMode.Downloadable: + case LocationMode.MyWorkout: + str = str + trackId.trackId + ".trackdata"; + break; + } + return str; + } + + public bool DoesTrackDataExist(string filePath, LocationMode locationMode, Game gameType) + { + TrackId trackId = new TrackId(filePath); + string locationpathPath = this.GetLocationpathPath(trackId, locationMode, gameType); + + return System.IO.File.Exists(locationpathPath) && new FileInfo(locationpathPath).Length > 0L; + } + + public bool DoesTrackDataExistByID( + TrackId trackId, + LocationMode locationMode, + Game gameType) + { + string locationpathPath = this.GetLocationpathPath(trackId, locationMode, gameType); + return System.IO.File.Exists(locationpathPath) && new FileInfo(locationpathPath).Length > 0L; + } + + public bool DoesFileExist(TrackId trackId, string originalFilePath) + { + string path = Path.Combine(MadmomProcess.madmonOutputPath, trackId.trackId + ".madmom.txt"); + bool flag1 = false; + if(System.IO.File.Exists(path)) + flag1 = true; + string locationpathPath = this.GetLocationpathPath(trackId, LocationMode.PlayerData, Game.BoxVR); + bool flag2 = false; + if((!System.IO.File.Exists(locationpathPath) ? 0 : (new FileInfo(locationpathPath).Length > 0L ? 1 : 0)) != 0) + flag2 = true; + if(flag1 && !flag2) + { + this.LegacyToTrackData(trackId.trackId, originalFilePath); + this.ClearLegacyFiles(trackId.trackId); + return true; + } + return flag2; + } + + public TrackData LegacyToTrackData(string trackMD5, string originalFilePath) + { + TrackData trackData = new TrackData(); + var file = new ATL.Track(originalFilePath); + trackData.originalTrackName = file.Title; + trackData.trackId = new TrackId(); + trackData.trackId.trackId = trackMD5; + trackData.duration = (float)file.Duration; + trackData.locationMode = LocationMode.PlayerData; + trackData.originalFilePath = originalFilePath; + if(string.IsNullOrEmpty(trackData.originalTrackName)) + trackData.originalTrackName = Path.GetFileNameWithoutExtension(originalFilePath); + BeatStructureMadmom beatStructure = this.ReadBeatStructureFromFile(trackMD5); + trackData.bpm = beatStructure.AverageBpm; + trackData.beatStrucureJSON = this.BeatStructureToJSON(beatStructure); + this.SaveTrackData(trackData); + return trackData; + } + + public TrackData NewTrackData( + UserSongClip clip, + BeatStructureMadmom bsm, + LocationMode locationMode) + { + TrackData trackData = new TrackData(); + trackData.originalFilePath = clip.originalFilePath; + trackData.originalTrackName = clip.trackData.originalTrackName; + trackData.originalArtist = clip.trackData.originalArtist; + trackData.trackId = new TrackId(clip.originalFilePath); + trackData.bpm = bsm._beatList.AverageBpm; + trackData.beatStrucureJSON = BeatStructureToJSON(bsm); + trackData.duration = clip.trackData.duration; + trackData.locationMode = locationMode; + trackData.firstBeatOffset = bsm.Beats[0]._triggerTime; + SaveTrackData(trackData); + TrackDefinition trackDefinition = new TrackDefinition(); + trackDefinition.locationMode = locationMode; + trackDefinition.trackId = trackData.trackId; + trackDefinition.tagLibTitle = trackData.originalTrackName; + trackDefinition.tagLibArtist = trackData.originalArtist; + trackDefinition.duration = trackData.duration; + trackDefinition.bpm = trackData.bpm; + trackDefinition.trackData = trackData; + trackDefinition.SaveTrackDefinition(); + this.trackDefinitionsPlayer.Add(trackDefinition); + return trackData; + } + + public void PopulateTrackDataFromAudioFile(string filePath, LocationMode locationMode, string originalFilePath, bool converted = false) + { + App.logger.Debug("Loading " + filePath); + string extension = Path.GetExtension(filePath); + if(extension == ".wav") + { + string filename = Path.GetFileNameWithoutExtension(filePath); + //Should never happen, but if we have a wav with a non md5 name, make it md5 + if(!converted) + { + TrackId trackId = new TrackId(filePath); + filename = trackId.trackId; + } + string str = Paths.WavDataFolder(locationMode) + filename + ".wav"; + if(!System.IO.File.Exists(str) && filePath != str) + { + System.IO.File.Copy(filePath, str); + } + LoadWav(str, locationMode, originalFilePath); + } + else + { + if(!(extension == ".mp3") && !(extension == ".m4a") && !(extension == ".ogg")) + { + return; + } + + TrackId trackId = new TrackId(filePath); + string outputPath = Paths.WavDataFolder(locationMode) + trackId.trackId + ".wav"; + //Convert to WAV + FFmpegQueue.instance.Queue((FFmpegJob)new FFmpegJobConvert(filePath, outputPath, OnConvertedToWav)); + } + } + + private void OnConvertedToWav(FFmpegJob job) + { + App.logger.Debug("FFMPEG complete: " + job._outputPath); + LocationMode locationMode = LocationMode.PlayerData; + this.originalFileNameBackup = Path.GetFileNameWithoutExtension(job._inputPath); + this.PopulateTrackDataFromAudioFile(job._outputPath, locationMode, job._inputPath, true); + } + + public void WavLoaded(ATL.Track track, string wavPath, LocationMode locationMode) + { + _log.Debug("Wav Loaded"); + this.audioTrack = track; + SongParser.instance.onParsingComplete += new SongParser.OnParsingComplete(OnParsingComplete); + SongParser.instance.CreateSongClipFromAudioClip(audioTrack, wavPath, locationMode); + } + + private void OnParsingComplete(TrackData trackdata) + { + SongParser.instance.onParsingComplete -= new SongParser.OnParsingComplete(this.OnParsingComplete); + _log.Debug("Track data created"); + onTrackDataComplete?.Invoke(trackdata); + } + + public void ClearLegacyFiles(string trackMD5) + { + string path1 = Path.Combine(MadmomProcess.madmonOutputPath, trackMD5 + ".madmom.txt"); + if(System.IO.File.Exists(path1)) + System.IO.File.Delete(path1); + string path2 = Path.Combine(MadmomProcess.madmonOutputPath, trackMD5 + ".beatstructuremadmom.txt"); + if(!System.IO.File.Exists(path2)) + return; + System.IO.File.Delete(path2); + } + + public string GetMadmomTextFromFilename(string trackMD5) + { + string str1 = ""; + string str2 = Path.Combine(MadmomProcess.madmonOutputPath, trackMD5 + ".madmom.txt"); + if((!System.IO.File.Exists(str2) ? 0 : (new FileInfo(str2).Length > 0L ? 1 : 0)) != 0) + str1 = System.IO.File.ReadAllText(str2); + return str1; + } + + public string GetBeatStructureXMLFromFilename(string trackMD5) + { + string str1 = ""; + string str2 = Path.Combine(MadmomProcess.madmonOutputPath, trackMD5 + ".beatstructuremadmom.txt"); + if((!System.IO.File.Exists(str2) ? 0 : (new FileInfo(str2).Length > 0L ? 1 : 0)) != 0) + str1 = System.IO.File.ReadAllText(str2); + return str1; + } + + public void SaveTrackData(TrackData trackData) => System.IO.File.WriteAllText(this.GetLocationpathPath(trackData.trackId, trackData.locationMode, Game.BoxVR), JsonConvert.SerializeObject(trackData)); + + public string TrackDataJsonFromFileName(TrackId trackId, LocationMode locationMode) => this.TrackDataJsonFromFilePath(this.GetLocationpathPath(trackId, locationMode, Game.BoxVR)); + + public TrackData TrackDataFromJson(string json) => JsonConvert.DeserializeObject(json); + + public string TrackDataJsonFromFilePath(string filePath) + { + string str = ""; + if((!System.IO.File.Exists(filePath) ? 0 : (new FileInfo(filePath).Length > 0L ? 1 : 0)) != 0) + str = System.IO.File.ReadAllText(filePath); + return str; + } + public string BeatStructureToJSON(BeatStructureMadmom beatStructure) => JsonConvert.SerializeObject(beatStructure); + + public BeatStructureMadmom ReadBeatStructureFromFile(string trackMD5) + { + string inputUri = Path.Combine(MadmomProcess.madmonOutputPath, trackMD5 + ".beatstructuremadmom.txt"); + using(XmlReader reader = XmlReader.Create(inputUri)) + return (BeatStructureMadmom)new DataContractSerializer(typeof(BeatStructureMadmom)).ReadObject(reader); + } + + public BeatStructureMadmom ReadBeatStructureFromJSON(string json) => JsonConvert.DeserializeObject(json); + + public void LoadWav(string wavPath, LocationMode locationMode, string originalFileName) + { + var atlTrack = new ATL.Track(originalFileName); + WavLoaded(atlTrack, wavPath, locationMode); + } + + public void LoadTrackDefinitionLists() + { + this.trackDataLoadComplete = false; + this.trackDefinitionsResources = new List(); + this.trackDefinitionsPlayer = new List(); + this.trackDefinitionsEditor = new List(); + this.myWorkoutTrackDataList = new List(); + this.trackDefinitionsDLC = new List(); + string str = Paths.DefinitionsFolder(LocationMode.Workouts); + int count; + string path1 = Paths.DefinitionsFolder(LocationMode.PlayerData); + string[] fileNames; + if(Directory.Exists(path1)) + { + fileNames = Directory.GetFiles(path1, "*.wdef.txt"); + for(count = 0; count < fileNames.Length; ++count) + { + TrackDefinition trackDefinition = JsonConvert.DeserializeObject(System.IO.File.ReadAllText(fileNames[count])); + if(this.GetTrackDefinition(trackDefinition.trackId.trackId) == null) + { + trackDefinition.locationMode = LocationMode.PlayerData; + this.trackDefinitionsPlayer.Add(trackDefinition); + } + } + fileNames = (string[])null; + } + string path2 = Paths.DefinitionsFolder(LocationMode.Editor); + if(Directory.Exists(path2)) + { + fileNames = Directory.GetFiles(path2, "*.wdef.txt"); + for(count = 0; count < fileNames.Length; ++count) + { + TrackDefinition trackDefinition = JsonConvert.DeserializeObject(System.IO.File.ReadAllText(fileNames[count])); + if(this.GetTrackDefinition(trackDefinition.trackId.trackId) == null) + this.trackDefinitionsEditor.Add(trackDefinition); + trackDefinition.locationMode = LocationMode.Editor; + } + fileNames = (string[])null; + } + this.trackDataLoadComplete = true; + } + + + public List GetTrackDefinitionList() + { + List trackDefinitionList = new List(); + trackDefinitionList.AddRange((IEnumerable)this.trackDefinitionsResources); + trackDefinitionList.AddRange((IEnumerable)this.trackDefinitionsPlayer); + trackDefinitionList.AddRange((IEnumerable)this.trackDefinitionsEditor); + trackDefinitionList.AddRange((IEnumerable)this.trackDefinitionsDLC); + return trackDefinitionList; + } + + public void UpdateDLCTrackDefinitionList(List newList) => this.trackDefinitionsDLC = newList; + + public TrackDefinition GetTrackDefinition(string trackId) + { + TrackDefinition trackDefinition = (TrackDefinition)null; + for(int index = 0; index < this.trackDefinitionsResources.Count; ++index) + { + if(this.trackDefinitionsResources[index].trackId.trackId == trackId) + { + trackDefinition = this.trackDefinitionsResources[index]; + break; + } + } + if(trackDefinition == null) + { + for(int index = 0; index < this.trackDefinitionsPlayer.Count; ++index) + { + if(this.trackDefinitionsPlayer[index].trackId.trackId == trackId) + { + trackDefinition = this.trackDefinitionsPlayer[index]; + break; + } + } + } + if(trackDefinition == null) + { + for(int index = 0; index < this.trackDefinitionsEditor.Count; ++index) + { + if(this.trackDefinitionsEditor[index].trackId.trackId == trackId) + { + trackDefinition = this.trackDefinitionsEditor[index]; + break; + } + } + } + if(trackDefinition == null) + { + for(int index = 0; index < this.trackDefinitionsDLC.Count; ++index) + { + if(this.trackDefinitionsDLC[index].trackId.trackId == trackId) + { + trackDefinition = this.trackDefinitionsDLC[index]; + break; + } + } + } + return trackDefinition; + } + + public TrackData GetTrackDataByID(string trackDataId) + { + TrackDefinition trackDefinition = this.GetTrackDefinition(trackDataId); + if(trackDefinition == null) + return (TrackData)null; + trackDefinition.LoadTrackData(); + return trackDefinition.trackData; + } + + public delegate void OnWavLoaded(ATL.Track track, string filePath, LocationMode locationMode); + + public delegate void OnWavLoadFailed(string error); + + public delegate void OnTrackDataComplete(TrackData trackData); + + public delegate void OnTrackDataFailed(string error); + + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/UserSongClip.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/UserSongClip.cs new file mode 100644 index 0000000..698afe3 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/UserSongClip.cs @@ -0,0 +1,36 @@ +using System; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Models; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class UserSongClip + { + public Action onClipCreated; + public string wavPath; + public TrackId trackId; + public string originalFileName; + public string originalFilePath; + public TrackData trackData; + + public UserSongClip() + { + } + + public UserSongClip(ATL.Track track) + { + trackId = new TrackId() { trackId = Path.GetFileNameWithoutExtension(track.Path) }; + wavPath = track.Path; + originalFilePath = track.Path; + originalFileName = Path.GetFileNameWithoutExtension(track.Path); + trackData = new TrackData() + { + originalArtist = track.Artist, + duration = (float)track.Duration, + originalTrackName = track.Title, + trackId = trackId, + originalFilePath = track.Path + }; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/BeatStructure/VampProcess.cs b/BOXVR Playlist Manager/FitXr/BeatStructure/VampProcess.cs new file mode 100644 index 0000000..99f6bd6 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/BeatStructure/VampProcess.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.IO; +using System.Text; +using BoxVR_Playlist_Manager.Helpers; + +namespace BoxVR_Playlist_Manager.FitXr.BeatStructure +{ + public class VampProcess + { + public string[] _resultEntries; + public StringBuilder output; + private Process _process; + private const bool SHOW_WINDOW = false; + private char APO_LITERAL; + private string outputPath; + private string vampToolPath; + private string vampPluginPath; + private bool isProcessComplete; + + public VampProcess() + { + this.output = new StringBuilder(); + this.outputPath = Path.Combine(MadmomProcess.madmonOutputPath, "vampOutput.txt"); + this.vampToolPath = Path.Combine(Paths.StreamingAssetsPath, @"Vamp\sonic-annotator-v1.5-win32\start.bat"); + this.vampPluginPath = Path.Combine(Paths.StreamingAssetsPath, @"Vamp\Vamp Plugins"); + this.isProcessComplete = true; + } + + public void Apply( + string pluginId, + string musicPath, + Action onFinished) + { + ApplyCoroutine(pluginId, musicPath, onFinished); + } + + public void ApplyCoroutine( + string pluginId, + string musicPath, + Action onFinished) + { + VampProcess vampProcess = this; + File.Delete(vampProcess.outputPath); + string str = $" -d {pluginId} \"{musicPath}\" -w csv --csv-force --csv-one-file \"{vampProcess.outputPath}\""; + vampProcess._process = new Process(); + vampProcess._process.StartInfo.FileName = vampProcess.vampToolPath; + vampProcess._process.StartInfo.RedirectStandardOutput = true; + vampProcess._process.StartInfo.UseShellExecute = false; + vampProcess._process.StartInfo.CreateNoWindow = true; + vampProcess._process.StartInfo.EnvironmentVariables["VAMP_PATH"] = vampProcess.vampPluginPath; + vampProcess._process.StartInfo.Arguments = str; + vampProcess._process.EnableRaisingEvents = true; + // ISSUE: reference to a compiler-generated method + //vampProcess._process.Exited += new EventHandler(vampProcess.\u003CApplyCoroutine\u003Eb__12_0); + vampProcess.isProcessComplete = false; + vampProcess._process.Start(); + while(!vampProcess.isProcessComplete && !vampProcess._process.HasExited) { } + if((vampProcess._process.ExitCode != 0 ? 0 : (File.Exists(vampProcess.outputPath) ? 1 : 0)) != 0) + { + vampProcess._resultEntries = File.ReadAllLines(vampProcess.outputPath); + vampProcess._process.Close(); + onFinished.Invoke(); + } + else + { + vampProcess._process.Close(); + throw new Exception("PROCESOR_ANALYSISERROR: There was an error analyzing the music file."); + } + } + + private void OnDestroy() + { + if(this.isProcessComplete) + return; + this._process.Kill(); + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/AudioClipStatus.cs b/BOXVR Playlist Manager/FitXr/Enums/AudioClipStatus.cs new file mode 100644 index 0000000..df12b0c --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/AudioClipStatus.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + public enum AudioClipStatus + { + Pending, + Loading, + Failed, + Loaded, + WaitForOgg + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/Game.cs b/BOXVR Playlist Manager/FitXr/Enums/Game.cs new file mode 100644 index 0000000..27bc081 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/Game.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + public enum Game + { + BoxVR + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/LocationMode.cs b/BOXVR Playlist Manager/FitXr/Enums/LocationMode.cs new file mode 100644 index 0000000..3401cd4 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/LocationMode.cs @@ -0,0 +1,11 @@ +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + public enum LocationMode + { + PlayerData, + Workouts, + Editor, + Downloadable, + MyWorkout, + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/TrackDataState.cs b/BOXVR Playlist Manager/FitXr/Enums/TrackDataState.cs new file mode 100644 index 0000000..d998499 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/TrackDataState.cs @@ -0,0 +1,10 @@ +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + public enum TrackDataState + { + Idle, + Loading, + Ready, + Failed, + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/TrackGenre.cs b/BOXVR Playlist Manager/FitXr/Enums/TrackGenre.cs new file mode 100644 index 0000000..6c7cf3b --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/TrackGenre.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + [Flags] + public enum TrackGenre + { + None = 0, + Pop = 1, + Rock = 2, + Electronic = 4, + HipHop = 8, + } +} diff --git a/BOXVR Playlist Manager/FitXr/Enums/WorkoutType.cs b/BOXVR Playlist Manager/FitXr/Enums/WorkoutType.cs new file mode 100644 index 0000000..e0baad9 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Enums/WorkoutType.cs @@ -0,0 +1,8 @@ +namespace BoxVR_Playlist_Manager.FitXr.Enums +{ + public enum WorkoutType + { + BoxVR_Workout, + BoxVR_Playlist, + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/MusicActionSerializable.cs b/BOXVR Playlist Manager/FitXr/Models/MusicActionSerializable.cs new file mode 100644 index 0000000..f792871 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/MusicActionSerializable.cs @@ -0,0 +1,10 @@ +using BoxVR_Playlist_Manager.FitXr.MusicActions; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class MusicActionSerializable + { + public MusicActionType musicActionType; + public string musicActionJSON; + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/SerialisedActionList.cs b/BOXVR Playlist Manager/FitXr/Models/SerialisedActionList.cs new file mode 100644 index 0000000..3ee9f39 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/SerialisedActionList.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class MusicActionListSerializable + { + [JsonProperty("actionList")] + public List actionList { get; set; } = new List(); + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/SongDefinition.cs b/BOXVR Playlist Manager/FitXr/Models/SongDefinition.cs new file mode 100644 index 0000000..1414d36 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/SongDefinition.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using BoxVR_Playlist_Manager.FitXr.BeatStructure; +using BoxVR_Playlist_Manager.FitXr.MusicActions; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class SongDefinition + { + public SongDefinition() + { + serialisedActionList = new MusicActionListSerializable(); + } + + [JsonProperty("trackDataName")] + public string trackDataName { get; set; } + + //From my minor testing this has always contained an empty list + [JsonProperty("serialisedActionList")] + public MusicActionListSerializable serialisedActionList { get; set; } + + [JsonIgnore] + public List musicActionList; + [JsonIgnore] + public TrackDefinition trackDefinition { get; set; } + + public void EditMoveAction(MoveChannel channel, float beatNumber, MoveType moveType) + { + App.logger.Debug("Editing " + (object)channel + "/" + (object)beatNumber + " = " + (object)moveType); + MusicActionMoveCue musicActionMoveCue = (MusicActionMoveCue)null; + int index1 = -1; + for(int index2 = 0; index2 < this.musicActionList.Count; ++index2) + { + if(this.musicActionList[index2] is MusicActionMoveCue) + { + MusicActionMoveCue musicAction = (MusicActionMoveCue)this.musicActionList[index2]; + if((double)musicAction.beatNumber == (double)beatNumber && musicAction.moveAction.moveChannel == channel) + { + index1 = index2; + musicActionMoveCue = musicAction; + break; + } + if(index1 == 0 && (double)this.musicActionList[index2].beatNumber > (double)beatNumber) + index1 = index2; + } + } + if(moveType == MoveType.None) + { + if(index1 != -1) + { + App.logger.Debug("Deleting entry at " + (object)index1); + this.musicActionList.RemoveAt(index1); + } + } + else + { + if(musicActionMoveCue == null) + { + musicActionMoveCue = new MusicActionMoveCue(new MoveAction(moveType, channel), beatNumber, 0.0); + if(index1 > 0) + this.musicActionList.Insert(index1, (MusicAction)musicActionMoveCue); + else + this.musicActionList.Add((MusicAction)musicActionMoveCue); + } + App.logger.Debug("modified cue"); + musicActionMoveCue.moveAction.moveType = moveType; + } + this.RecalculateCueTiming(); + } + + public void RecalculateCueTiming() + { + if(this.trackDefinition == null) + this.trackDefinition = TrackDataManager.instance.GetTrackDefinition(this.trackDataName); + this.musicActionList.Sort((Comparison)((x, y) => x.beatNumber.CompareTo(y.beatNumber))); + float num = 60f / this.trackDefinition.bpm; + for(int index = 0; index < this.musicActionList.Count; ++index) + { + if(this.musicActionList[index] is MusicActionAudio) + this.musicActionList[index].startTime = 0.0; + if(this.musicActionList[index] is MusicActionBPM) + this.musicActionList[index].startTime = 0.0; + if(this.musicActionList[index] is MusicActionMoveCue) + { + MusicActionMoveCue musicAction = (MusicActionMoveCue)this.musicActionList[index]; + musicAction.startTime = (double)num * (double)musicAction.beatNumber + (double)this.trackDefinition.firstBeatStartDelay; + } + } + } + + public MusicActionAudio GetFirstAudioAction() + { + if(this.musicActionList == null) + return (MusicActionAudio)null; + for(int index = 0; index < this.musicActionList.Count; ++index) + { + if(this.musicActionList[index] is MusicActionAudio) + return (MusicActionAudio)this.musicActionList[index]; + } + return (MusicActionAudio)null; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/TrackBase.cs b/BOXVR Playlist Manager/FitXr/Models/TrackBase.cs new file mode 100644 index 0000000..b0cea4e --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/TrackBase.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class TrackBase + { + public TrackBase() + { + + } + [JsonProperty("trackId")] + public TrackId trackId { get; set; } + [JsonProperty("duration")] + public float duration { get; set; } + [JsonProperty("bpm")] + public float bpm { get; set; } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/TrackData.cs b/BOXVR Playlist Manager/FitXr/Models/TrackData.cs new file mode 100644 index 0000000..3785009 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/TrackData.cs @@ -0,0 +1,132 @@ +using System.IO; +using BoxVR_Playlist_Manager.FitXr.BeatStructure; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.Helpers; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class TrackData : TrackBase + { + public TrackData() + { + + } + + + [JsonProperty("originalFilePath")] + public string originalFilePath { get; set; } + [JsonProperty("originalTrackName")] + public string originalTrackName { get; set; } + [JsonProperty("originalArtist")] + public string originalArtist { get; set; } + [JsonProperty("firstBeatOffset")] + public float firstBeatOffset { get; set; } + [JsonProperty("locationMode")] + public LocationMode locationMode { get; set; } + [JsonProperty("beatStrucureJSON")] + public string beatStrucureJSON { get; set; } + + [JsonIgnore] + public BeatStructureMadmom beatStructure; + [JsonIgnore] + public TrackDataState trackDataState; + + public event TrackData.OnTrackDataReady onTrackDataReady; + + public void LoadTrackData(string trackId, LocationMode locationMode) + { + string str = ""; + switch(locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + string path = Paths.TrackDataFolder(locationMode) + trackId + ".trackdata.txt"; + if(File.Exists(path)) + { + str = File.ReadAllText(path); + } + break; + } + if(str != "") + { + TrackData trackData = JsonConvert.DeserializeObject(str); + this.originalTrackName = trackData.originalTrackName; + this.trackId = trackData.trackId; + this.duration = trackData.duration; + this.bpm = trackData.bpm; + this.locationMode = locationMode; + this.beatStrucureJSON = trackData.beatStrucureJSON; + this.originalFilePath = trackData.originalFilePath; + this.originalArtist = trackData.originalArtist; + this.LoadBeatStructure(); + if(this.onTrackDataReady != null) + this.onTrackDataReady(this); + this.trackDataState = TrackDataState.Ready; + } + else + { + this.trackDataState = TrackDataState.Failed; + } + } + + public void LoadBeatStructure() + { + this.beatStructure = TrackDataManager.instance.ReadBeatStructureFromJSON(this.beatStrucureJSON); + } + + public string WavFilePath() + { + string str = ""; + switch(this.locationMode) + { + case LocationMode.PlayerData: + str = MadmomProcess.madmonOutputPath + this.trackId.trackId + ".wav"; + break; + case LocationMode.Workouts: + case LocationMode.MyWorkout: + str = this.trackId.trackId.ToString(); + break; + case LocationMode.Editor: + str = Paths.WavDataFolder(this.locationMode) + "/" + this.trackId.trackId + ".wav"; + break; + } + return str; + } + + public void PopulateTrackDataFromAudioFile( + string filePath, + LocationMode locationMode, + Game gameType) + { + this.trackDataState = TrackDataState.Loading; + TrackDataManager.instance.onTrackDataComplete += new TrackDataManager.OnTrackDataComplete(OnTrackDataCreated); + if(TrackDataManager.instance.DoesTrackDataExist(filePath, locationMode, gameType)) + { + this.LoadTrackData(new TrackId(filePath).trackId, locationMode); + } + else + { + TrackDataManager.instance.PopulateTrackDataFromAudioFile(filePath, locationMode, filePath); + } + } + + public void OnTrackDataCreated(TrackData trackData) + { + TrackDataManager.instance.onTrackDataComplete -= new TrackDataManager.OnTrackDataComplete(this.OnTrackDataCreated); + this.originalFilePath = trackData.originalFilePath; + this.originalTrackName = trackData.originalTrackName; + this.originalArtist = trackData.originalArtist; + this.trackId = trackData.trackId; + this.duration = trackData.duration; + this.bpm = trackData.bpm; + this.locationMode = trackData.locationMode; + this.beatStrucureJSON = trackData.beatStrucureJSON; + this.trackDataState = TrackDataState.Ready; + onTrackDataReady?.Invoke(this); + } + + + public delegate void OnTrackDataReady(TrackData trackData); + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/TrackDefinition.cs b/BOXVR Playlist Manager/FitXr/Models/TrackDefinition.cs new file mode 100644 index 0000000..f423e61 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/TrackDefinition.cs @@ -0,0 +1,135 @@ +using System; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.Helpers; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class TrackDefinition : TrackBase + { + public TrackDefinition() => this.audioClipStatus = AudioClipStatus.Pending; + + + [JsonProperty("firstBeatStartDelay")] + public double firstBeatStartDelay { get; set; } + [JsonProperty("tagLibArtist")] + public string tagLibArtist { get; set; } + [JsonProperty("tagLibTitle")] + public string tagLibTitle { get; set; } + [JsonProperty("genreMask")] + public TrackGenre genreMask { get; set; } + [JsonProperty("audioClipStatus")] + public AudioClipStatus audioClipStatus { get; set; } + + [JsonIgnore] + public LocationMode locationMode; + + [JsonIgnore] + public byte[] waveTextureData; + [JsonIgnore] + public TrackData trackData; + + [JsonIgnore] + public TimeSpan DurationTimeSpan => TimeSpan.FromSeconds(duration); + + + public void LoadTrackData() + { + if(this.trackData != null) + return; + this.trackData = new TrackData(); + this.trackData.LoadTrackData(this.trackId.trackId, this.locationMode); + } + + public bool LoadTrackDefinition(string trackHash, LocationMode mode) + { + bool flag = false; + this.locationMode = mode; + + string str1 = Paths.DefinitionsFolder(mode) + trackHash + ".wdef"; + string str2 = ""; + switch(this.locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + string path = str1 + ".txt"; + if(System.IO.File.Exists(path)) + { + str2 = System.IO.File.ReadAllText(path); + break; + } + break; + //TODO double cheeck as i think it needs .txt + case LocationMode.Workouts: + case LocationMode.MyWorkout: + str2 = System.IO.File.ReadAllText(str1); + break; + } + if(str2 != "") + { + //TrackDefinition trackDefinition = (TrackDefinition)JsonUtility.FromJson(str2); + var trackDefinition = JsonConvert.DeserializeObject(str2); + flag = true; + this.trackId = trackDefinition.trackId; + this.firstBeatStartDelay = trackDefinition.firstBeatStartDelay; + this.tagLibTitle = trackDefinition.tagLibTitle; + this.tagLibArtist = trackDefinition.tagLibArtist; + this.duration = trackDefinition.duration; + this.bpm = trackDefinition.bpm; + } + return flag; + } + + public bool SaveTrackDefinition() + { + if(this.locationMode == LocationMode.MyWorkout || this.locationMode == LocationMode.Workouts) + { + App.logger.Error("Cant save to resourses you potatoe head !!!!"); + return false; + } + Directory.CreateDirectory(Paths.DefinitionsFolder(this.locationMode)); + //string json = JsonUtility.ToJson((object)this, true); + string json = JsonConvert.SerializeObject(this); + File.WriteAllText(Paths.DefinitionsFolder(this.locationMode) + this.trackId.trackId + ".wdef.txt", json); + return true; + } + + //public IEnumerator GenerateWavImageData(int numSamples = 131071) + //{ + // if(this.audioClipStatus != AudioClipStatus.Loaded) + // yield return (object)this.LoadAudioClip(); + // while(this.audioClipStatus != AudioClipStatus.Loaded || this.audioClipStatus != AudioClipStatus.Failed) + // yield return (object)null; + // if(Object.op_Implicit((Object)this.audioClip)) + // { + // Debug.Log((object)"generating wave image data"); + // this.waveTextureData = new byte[numSamples]; + // float[] audioSamples = new float[this.audioClip.get_samples() * this.audioClip.get_channels()]; + // this.audioClip.LoadAudioData(); + // while(this.audioClip.get_loadState() != 2) + // yield return (object)null; + // this.audioClip.GetData(audioSamples, 0); + // double num1 = (double)(audioSamples.Length / this.waveTextureData.Length); + // for(int index1 = 0; index1 < this.waveTextureData.Length; ++index1) + // { + // double num2 = (double)index1 * num1 - num1 / 2.0; + // double num3 = num2 + num1; + // float num4 = 0.0f; + // for(int index2 = (int)num2; index2 < (int)num3; ++index2) + // { + // if(index2 > 0 && index2 < audioSamples.Length && (double)Mathf.Abs(audioSamples[index2]) > (double)num4) + // num4 = Mathf.Abs(audioSamples[index2]); + // } + // this.waveTextureData[index1] = (byte)((double)num4 * 256.0); + // } + // if(this.locationMode == LocationMode.Editor) + // { + // File.WriteAllBytes(Paths.ImgDataFolder(this.locationMode) + this.trackId.trackId + ".imgdata.bytes", this.waveTextureData); + // Debug.Log((object)"Written wave image data"); + // } + // audioSamples = (float[])null; + // } + //} + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/TrackId.cs b/BOXVR Playlist Manager/FitXr/Models/TrackId.cs new file mode 100644 index 0000000..116c212 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/TrackId.cs @@ -0,0 +1,23 @@ +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Utility; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class TrackId + { + public TrackId() + { + } + public TrackId(string fullFilePath) + { + if(fullFilePath.Contains("/TrackData/")) + this.trackId = Path.GetFileNameWithoutExtension(fullFilePath); + else + this.trackId = MD5.MD5Sum(Path.GetFileNameWithoutExtension(fullFilePath)); + } + + [JsonProperty("trackId")] + public string trackId { get; set; } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/WorkoutId.cs b/BOXVR Playlist Manager/FitXr/Models/WorkoutId.cs new file mode 100644 index 0000000..8458f6e --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/WorkoutId.cs @@ -0,0 +1,38 @@ +using System; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public struct WorkoutId : IEquatable + { + public string _id; + + public WorkoutId(string id) => this._id = id; + + public static bool operator ==(WorkoutId obj1, WorkoutId obj2) + { + if((ValueType)obj1 == (ValueType)obj2) + return true; + return (ValueType)obj1 != null && (ValueType)obj2 != null && obj1._id == obj2._id; + } + + public static bool operator !=(WorkoutId obj1, WorkoutId obj2) => !(obj1 == obj2); + + public bool Equals(WorkoutId other) + { + if((ValueType)other == null) + return false; + return (ValueType)this == (ValueType)other || this._id.Equals(other._id); + } + + public override bool Equals(object obj) + { + if(obj == null) + return false; + if((ValueType)this == obj) + return true; + return obj.GetType() == this.GetType() && this.Equals((WorkoutId)obj); + } + + public override int GetHashCode() => this._id.GetHashCode(); + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/WorkoutInfo.cs b/BOXVR Playlist Manager/FitXr/Models/WorkoutInfo.cs new file mode 100644 index 0000000..2141015 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/WorkoutInfo.cs @@ -0,0 +1,57 @@ +using BoxVR_Playlist_Manager.FitXr.Enums; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class WorkoutInfo + { + [JsonProperty("workoutName")] + public string workoutName { get; set; } + [JsonProperty("game")] + public Game game { get; set; } + [JsonProperty("workoutStyle")] + public int workoutStyle { get; set; } + [JsonProperty("authorName")] + public string authorName { get; set; } + [JsonProperty("trackGenre")] + public TrackGenre trackGenre { get; set; } + [JsonProperty("leaderboardId")] + public string leaderboardId { get; set; } = "undefined"; + [JsonProperty("sonyLeaderboardId")] + public int sonyLeaderboardId { get; set; } = -1; + [JsonProperty("workoutId")] + public string workoutId { get; set; } + [JsonProperty("hasSquats")] + public bool hasSquats { get; set; } + [JsonProperty("hasJumps")] + public bool hasJumps { get; set; } + [JsonProperty("workoutType")] + public WorkoutType workoutType { get; set; } + [JsonProperty("duration")] + public float duration { get; set; } + [JsonIgnore] + public string originalName { get; set; } + + public WorkoutInfo() + { + + } + + public WorkoutInfo(WorkoutInfo workoutInfo) + { + workoutName = workoutInfo.workoutName; + originalName = workoutInfo.originalName ?? workoutInfo.workoutName; + game = workoutInfo.game; + workoutStyle = workoutInfo.workoutStyle; + authorName = workoutInfo.authorName; + trackGenre = workoutInfo.trackGenre; + leaderboardId = workoutInfo.leaderboardId; + sonyLeaderboardId = workoutInfo.sonyLeaderboardId; + workoutId = workoutInfo.workoutId; + hasSquats = workoutInfo.hasSquats; + hasJumps = workoutInfo.hasJumps; + workoutType = workoutInfo.workoutType; + duration = workoutInfo.duration; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Models/WorkoutPlaylist.cs b/BOXVR Playlist Manager/FitXr/Models/WorkoutPlaylist.cs new file mode 100644 index 0000000..d76d39c --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Models/WorkoutPlaylist.cs @@ -0,0 +1,162 @@ +using System.Collections.Generic; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.BeatStructure; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.FitXr.MusicActions; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr.Models +{ + public class WorkoutPlaylist + { + public WorkoutPlaylist(WorkoutPlaylist playlist) + { + definition = new WorkoutInfo(playlist.definition); + + songs = new List(playlist.songs); + } + + public WorkoutPlaylist() + { + definition = new WorkoutInfo(); + songs = new List(); + } + + [JsonProperty("definition")] + public WorkoutInfo definition { get; set; } + + [JsonProperty("songs")] + public List songs { get; set; } + + public WorkoutId workoutId => new WorkoutId(this.definition.workoutName.Replace("_", "").Replace(" ", "")); + + public void SaveToPath(string path) + { + if(!Directory.Exists(path)) + { + App.logger.Debug("Path failure"); + } + else + { + for(int index = 0; index < this.songs.Count; ++index) + { + if(this.songs[index].musicActionList != null) + { + this.songs[index].serialisedActionList = new MusicActionListSerializable(); + this.songs[index].serialisedActionList.actionList = MusicActionListSerializer.Instance.BuildSerializableMusicActionList(this.songs[index].musicActionList); + } + } + File.WriteAllText(path + this.definition.workoutName + ".workoutplaylist.txt", JsonConvert.SerializeObject(this)); + } + } + + public void Save(LocationMode locationMode) + { + this.PreSave(); + WorkoutDefinitionManager.instance.SaveWorkout(this, locationMode); + } + + private void PreSave() + { + this.definition.hasSquats = false; + this.definition.trackGenre = TrackGenre.None; + this.definition.duration = 0.0f; + label_9: + for(int index1 = 0; index1 < this.songs.Count; ++index1) + { + this.definition.trackGenre |= this.songs[index1].trackDefinition.genreMask; + this.definition.duration += this.songs[index1].trackDefinition.duration; + if(!this.definition.hasSquats) + { + for(int index2 = 0; index2 < this.songs[index1].musicActionList.Count; ++index2) + { + if(this.songs[index1].musicActionList[index2] is MusicActionMoveCue) + { + switch(((MusicActionMoveCue)this.songs[index1].musicActionList[index2]).moveAction.moveType) + { + case MoveType.Boxing_Dodge: + case MoveType.Boxing_Squat: + this.definition.hasSquats = true; + goto label_9; + default: + continue; + } + } + } + } + } + } + + public void LoadFromJSON(string jsonString) + { + if(string.IsNullOrEmpty(jsonString)) + return; + WorkoutPlaylist workoutPlaylist = JsonConvert.DeserializeObject(jsonString); + if(workoutPlaylist == null) + return; + this.definition = workoutPlaylist.definition; + this.songs = workoutPlaylist.songs; + for(int index = 0; index < this.songs.Count; ++index) + this.songs[index].musicActionList = MusicActionListSerializer.instance.ReadSerializedActionList(this.songs[index].serialisedActionList); + } + + public void LoadFromPath(string path) => this.LoadFromJSON(this.WorkoutJsonFromPath(path)); + + public string WorkoutJsonFromPath(string workoutPath) + { + string str = ""; + if((!File.Exists(workoutPath) ? 0 : (new FileInfo(workoutPath).Length > 0L ? 1 : 0)) != 0) + str = File.ReadAllText(workoutPath); + return str; + } + + public SongDefinition AddSong(TrackId trackId) + { + if(this.songs == null) + this.songs = new List(); + SongDefinition songDefinition = new SongDefinition(); + songDefinition.trackDataName = trackId.trackId; + this.songs.Add(songDefinition); + return songDefinition; + } + + public void AddSong(SongDefinition song) + { + if(this.songs == null) + this.songs = new List(); + this.songs.Add(song); + } + + public void RemoveSong(int index) + { + string trackDataName = this.songs[index].trackDataName; + this.songs.RemoveAt(index); + } + + public bool RemoveSong(SongDefinition song) + { + return songs.Remove(song); + } + + public float CalcTotalLength() + { + float num = 0.0f; + if(this.songs != null) + { + for(int index = 0; index < this.songs.Count; ++index) + { + if(this.songs[index].trackDefinition != null) + { + num += this.songs[index].trackDefinition.duration; + } + else + { + this.songs[index].trackDefinition = TrackDataManager.instance.GetTrackDefinition(this.songs[index].trackDataName); + } + } + } + this.definition.duration = num; + return num; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActionListSerializer.cs b/BOXVR Playlist Manager/FitXr/MusicActionListSerializer.cs new file mode 100644 index 0000000..25df024 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActionListSerializer.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BoxVR_Playlist_Manager.FitXr.Models; +using BoxVR_Playlist_Manager.FitXr.MusicActions; +using BoxVR_Playlist_Manager.Helpers; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr +{ + public class MusicActionListSerializer + { + public static MusicActionListSerializer instance = instance == null ? new MusicActionListSerializer() : instance; + + public static MusicActionListSerializer Instance => instance; + + public void SaveWorkoutActionSequence( + List musicActions, + string workoutName, + string difficulty = "") + { + string str = Paths.PersistentDataPath + "/Playlists/Workouts/"; + string path = Path.Combine(str, workoutName + "_" + difficulty + ".actions"); + if(difficulty == "") + { + str = Paths.PersistentDataPath + "/Workouts/"; + path = Path.Combine(str, workoutName + ".actions"); + } + if(!Directory.Exists(str)) + Directory.CreateDirectory(str); + List actionSerializableList = this.BuildSerializableMusicActionList(musicActions); + string json = JsonConvert.SerializeObject(new MusicActionListSerializable() + { + actionList = actionSerializableList + }); + File.WriteAllText(path, json); + } + + public List LoadWorkoutActionSequence( + string workoutName, + string difficulty = "") + { + List musicActionList = new List(); + string str = Paths.PersistentDataPath + "/Playlists/Workouts/" + workoutName + "_" + difficulty + ".actions"; + if(difficulty == "") + str = Paths.PersistentDataPath + "/Workouts/" + workoutName + ".actions"; + return (!File.Exists(str) ? 0 : (new FileInfo(str).Length > 0L ? 1 : 0)) != 0 ? this.ReadSerializedActionList(JsonConvert.DeserializeObject(File.ReadAllText(str))) : (List)null; + } + + public List ReadSerializedActionList( + MusicActionListSerializable listWrapper) + { + List musicActionList = new List(); + if(listWrapper == null) + { + return musicActionList; + } + for(int index = 0; index < listWrapper.actionList.Count; ++index) + { + switch(listWrapper.actionList[index].musicActionType) + { + case MusicActionType.MoveCue: + MusicActionMoveCue musicActionMoveCue = JsonConvert.DeserializeObject(listWrapper.actionList[index].musicActionJSON); + musicActionMoveCue.musicActiontype = MusicActionType.MoveCue; + musicActionList.Add((MusicAction)musicActionMoveCue); + break; + case MusicActionType.Message: + MusicActionMessage musicActionMessage = JsonConvert.DeserializeObject(listWrapper.actionList[index].musicActionJSON); + musicActionMessage.musicActiontype = MusicActionType.Message; + musicActionList.Add((MusicAction)musicActionMessage); + break; + case MusicActionType.BPM: + MusicActionBPM musicActionBpm = JsonConvert.DeserializeObject(listWrapper.actionList[index].musicActionJSON); + musicActionBpm.musicActiontype = MusicActionType.BPM; + musicActionList.Add((MusicAction)musicActionBpm); + break; + case MusicActionType.Audio: + MusicActionAudio musicActionAudio = JsonConvert.DeserializeObject(listWrapper.actionList[index].musicActionJSON); + musicActionAudio.musicActiontype = MusicActionType.Audio; + musicActionList.Add((MusicAction)musicActionAudio); + break; + } + } + return musicActionList; + } + + public List BuildSerializableMusicActionList( + List musicActions) + { + List actionSerializableList = new List(); + for(int index = 0; index < musicActions.Count; ++index) + { + MusicActionSerializable actionSerializable = new MusicActionSerializable(); + if(musicActions[index] is MusicActionMoveCue) + actionSerializable.musicActionType = MusicActionType.MoveCue; + else if(musicActions[index] is MusicActionMessage) + actionSerializable.musicActionType = MusicActionType.Message; + else if(musicActions[index] is MusicActionBPM) + actionSerializable.musicActionType = MusicActionType.BPM; + else if(musicActions[index] is MusicActionAudio) + actionSerializable.musicActionType = MusicActionType.Audio; + actionSerializable.musicActionJSON = this.SerializeMusicAction(musicActions[index], actionSerializable.musicActionType); + actionSerializableList.Add(actionSerializable); + } + return actionSerializableList; + } + + private string SerializeMusicAction(MusicAction musicAction, MusicActionType musicActionType) + { + string str = ""; + switch(musicActionType) + { + case MusicActionType.MoveCue: + str = JsonConvert.SerializeObject((MusicActionMoveCue)musicAction); + break; + case MusicActionType.Message: + str = JsonConvert.SerializeObject((MusicActionMessage)musicAction); + break; + case MusicActionType.BPM: + str = JsonConvert.SerializeObject((MusicActionBPM)musicAction); + break; + case MusicActionType.Audio: + str = JsonConvert.SerializeObject((MusicActionAudio)musicAction); + break; + } + return str; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MessagePosition.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MessagePosition.cs new file mode 100644 index 0000000..dd1becf --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MessagePosition.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public enum MessagePosition + { + CENTER, + LEFT, + RIGHT, + BOTH, + NONE, + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MoveAction.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MoveAction.cs new file mode 100644 index 0000000..ea565af --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MoveAction.cs @@ -0,0 +1,18 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MoveAction + { + public MoveType moveType; + public MoveChannel moveChannel; + public int sequenceBeat; + public float actionOffset; + + public MoveAction(MoveType type, MoveChannel channel) + { + this.moveType = type; + this.moveChannel = channel; + } + + public override string ToString() => this.moveChannel.ToString() + "_" + this.moveType.ToString(); + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MoveChannel.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MoveChannel.cs new file mode 100644 index 0000000..cd398ce --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MoveChannel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public enum MoveChannel + { + BoxVR_Front, + BoxVR_Front_Low, + BoxVR_Center, + BoxVR_Center_Low, + BoxVR_Back, + BoxVR_Back_Low, + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MoveType.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MoveType.cs new file mode 100644 index 0000000..054f466 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MoveType.cs @@ -0,0 +1,15 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public enum MoveType + { + None = 0, + Rest = 1, + StanceChange = 2, + Boxing_Block = 100, // 0x00000064 + Boxing_Jab = 101, // 0x00000065 + Boxing_Hook = 102, // 0x00000066 + Boxing_UpperCut = 103, // 0x00000067 + Boxing_Dodge = 104, // 0x00000068 + Boxing_Squat = 105, // 0x00000069 + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicAction.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicAction.cs new file mode 100644 index 0000000..1783194 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicAction.cs @@ -0,0 +1,13 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MusicAction + { + public double startTime; + public float beatNumber; + public MusicActionType musicActiontype; + + public virtual void Perform() + { + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionAudio.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionAudio.cs new file mode 100644 index 0000000..6859389 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionAudio.cs @@ -0,0 +1,20 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MusicActionAudio : MusicAction + { + public string wavFilePath; + public string trackId; + + public MusicActionAudio(string wavPath, string trackid, double startOffset) + { + this.wavFilePath = wavPath; + this.startTime = startOffset; + this.trackId = trackid; + this.musicActiontype = MusicActionType.Audio; + } + + public override void Perform() + { + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionBPM.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionBPM.cs new file mode 100644 index 0000000..5895407 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionBPM.cs @@ -0,0 +1,14 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MusicActionBPM : MusicAction + { + public float segmentBPM; + + public MusicActionBPM(float trackBPM, double offsetOnArrival) + { + this.segmentBPM = trackBPM; + this.startTime = offsetOnArrival; + this.musicActiontype = MusicActionType.BPM; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMessage.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMessage.cs new file mode 100644 index 0000000..1cafbf7 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMessage.cs @@ -0,0 +1,18 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MusicActionMessage : MusicAction + { + public MessagePosition position; + public string message; + + public MusicActionMessage( + string message, + MessagePosition position, + double displayTime) + { + this.message = message; + this.position = position; + this.startTime = displayTime; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMoveCue.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMoveCue.cs new file mode 100644 index 0000000..c345c8b --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionMoveCue.cs @@ -0,0 +1,17 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public class MusicActionMoveCue : MusicAction + { + public MoveAction moveAction; + + public MusicActionMoveCue(MoveAction action, float beat, double time) + { + this.moveAction = action; + this.startTime = time; + this.beatNumber = beat; + this.musicActiontype = MusicActionType.MoveCue; + } + + //public override void Perform() => MoveCueFactoryBoxVR.instance.Spawn(this.moveAction); + } +} diff --git a/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionType.cs b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionType.cs new file mode 100644 index 0000000..e9ad089 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/MusicActions/MusicActionType.cs @@ -0,0 +1,10 @@ +namespace BoxVR_Playlist_Manager.FitXr.MusicActions +{ + public enum MusicActionType + { + MoveCue, + Message, + BPM, + Audio, + } +} diff --git a/BOXVR Playlist Manager/FitXr/ReadOnlyDictionary.cs b/BOXVR Playlist Manager/FitXr/ReadOnlyDictionary.cs new file mode 100644 index 0000000..a4358e9 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/ReadOnlyDictionary.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace BoxVR_Playlist_Manager.FitXr +{ + [Serializable] + public class ReadOnlyDictionary : + IDictionary, + ICollection>, + IEnumerable>, + IEnumerable + { + private readonly IDictionary _dictionary; + + public ReadOnlyDictionary() => this._dictionary = (IDictionary)new Dictionary(); + + public ReadOnlyDictionary(IDictionary dictionary) => this._dictionary = dictionary; + + void IDictionary.Add(TKey key, TValue value) => throw ReadOnlyDictionary.ReadOnlyException(); + + public bool ContainsKey(TKey key) => this._dictionary.ContainsKey(key); + + public ICollection Keys => this._dictionary.Keys; + + bool IDictionary.Remove(TKey key) => throw ReadOnlyDictionary.ReadOnlyException(); + + public bool TryGetValue(TKey key, out TValue value) => this._dictionary.TryGetValue(key, out value); + + public ICollection Values => this._dictionary.Values; + + public TValue this[TKey key] => this._dictionary[key]; + + TValue IDictionary.this[TKey key] + { + get => this[key]; + set => throw ReadOnlyDictionary.ReadOnlyException(); + } + + void ICollection>.Add( + KeyValuePair item) + { + throw ReadOnlyDictionary.ReadOnlyException(); + } + + void ICollection>.Clear() => throw ReadOnlyDictionary.ReadOnlyException(); + + public bool Contains(KeyValuePair item) => this._dictionary.Contains(item); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => this._dictionary.CopyTo(array, arrayIndex); + + public int Count => this._dictionary.Count; + + public bool IsReadOnly => true; + + bool ICollection>.Remove( + KeyValuePair item) + { + throw ReadOnlyDictionary.ReadOnlyException(); + } + + public IEnumerator> GetEnumerator() => this._dictionary.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => (IEnumerator)this.GetEnumerator(); + + private static Exception ReadOnlyException() => (Exception)new NotSupportedException("This dictionary is read-only"); + } +} diff --git a/BOXVR Playlist Manager/FitXr/Tools/Format.cs b/BOXVR Playlist Manager/FitXr/Tools/Format.cs new file mode 100644 index 0000000..2e2829d --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Tools/Format.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BoxVR_Playlist_Manager.FitXr.Tools +{ + public class Format + { + public static string SecondsToString(float value) => string.Format("{0:0}m:{1:00}s", (object)Math.Floor(value / 60f), (object)(value % 60f)); + + public static void FloatsFromCsvLine(string entry, out float val1, out float val2) + { + string[] strArray = entry.Split(','); + float.TryParse(strArray[1], out val1); + float.TryParse(strArray[2], out val2); + } + + public static IEnumerable> CreateSortedDictionary( + ReadOnlyDictionary unsortedDic) + { + return unsortedDic.OrderBy, int>((Func, int>)(pair => pair.Key)).Take>(unsortedDic.Count); + } + + public static int[] Long2DoubleInt(long a) => new int[2] + { + (int) (a & (long) uint.MaxValue), + (int) (a >> 32) + }; + + public static long DoubleIntToLong(int[] a) => (long)a[1] << 32 | (long)(uint)a[0]; + + public static float LbsToKg(float lbs) => lbs * 0.453592f; + + public static float CmToFeet(float cm) => cm * 0.0328084f; + + public static float FeetToInch(float feet) => feet * 12f; + + public static DateTime StartOfWeek(DateTime dt, DayOfWeek startOfWeek) + { + int num = (7 + (dt.DayOfWeek - startOfWeek)) % 7; + return dt.AddDays((double)(-1 * num)).Date; + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Tools/IO.cs b/BOXVR Playlist Manager/FitXr/Tools/IO.cs new file mode 100644 index 0000000..54e7fee --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Tools/IO.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BoxVR_Playlist_Manager.FitXr.Tools +{ + public class IO + { + public static void WriteStringToDisk(string filename, string content) + { + if(File.Exists(filename)) + File.Delete(filename); + StreamWriter text = File.CreateText(filename); + text.Write(content); + text.Close(); + } + + public static void DeleteFilesWithExtension(string directory, string extension) + { + foreach(FileInfo fileInfo in ((IEnumerable)new DirectoryInfo(directory).GetFiles("*." + extension)).Where((Func)(p => p.Extension == "." + extension)).ToArray()) + { + try + { + fileInfo.Attributes = FileAttributes.Normal; + File.Delete(fileInfo.FullName); + } + catch + { + } + } + } + + public static string[] GetVisibleFiles(string path, string fileFilter) + { + List stringList = new List(); + DirectoryInfo directoryInfo = new DirectoryInfo(path); + string str = fileFilter; + char[] chArray = new char[1] { ';' }; + foreach(string searchPattern in str.Split(chArray)) + { + foreach(FileInfo fileInfo in ((IEnumerable)directoryInfo.GetFiles(searchPattern)).Where((Func)(f => (f.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden))) + stringList.Add(fileInfo.FullName); + } + stringList.Sort(); + return stringList.ToArray(); + } + + public static string[] GetDrives() => Directory.GetLogicalDrives(); + + public static string[] GetVisibleDirectories(string path) + { + string[] directories = Directory.GetDirectories(path); + List stringList = new List(); + foreach(string path2 in directories) + { + string path1 = Path.Combine(path, path2); + if((new DirectoryInfo(path1).Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) + stringList.Add(path1); + } + return ((IEnumerable)directories).ToArray(); + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Utility/MD5.cs b/BOXVR Playlist Manager/FitXr/Utility/MD5.cs new file mode 100644 index 0000000..4f8a536 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Utility/MD5.cs @@ -0,0 +1,20 @@ +using System; +using System.Text; +using System.Security.Cryptography; + +namespace BoxVR_Playlist_Manager.FitXr.Utility +{ + public sealed class MD5 + { + public static string MD5Sum(string strToEncrypt) => MD5.Md5Sum(strToEncrypt); + + public static string Md5Sum(string strToEncrypt) + { + byte[] hash = new MD5CryptoServiceProvider().ComputeHash(new UTF8Encoding().GetBytes(strToEncrypt)); + string str = ""; + for(int index = 0; index < hash.Length; ++index) + str += Convert.ToString(hash[index], 16).PadLeft(2, '0'); + return str.PadLeft(32, '0'); + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/Utility/ShuffleList.cs b/BOXVR Playlist Manager/FitXr/Utility/ShuffleList.cs new file mode 100644 index 0000000..1df9902 --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/Utility/ShuffleList.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace BoxVR_Playlist_Manager.FitXr.Utility +{ + public static class ShuffleList + { + public static void Shuffle(this IList list) + { + Random random = new Random(); + int count = list.Count; + while(count > 1) + { + --count; + int index = random.Next(count + 1); + T obj = list[index]; + list[index] = list[count]; + list[count] = obj; + } + } + } +} diff --git a/BOXVR Playlist Manager/FitXr/WorkoutDefinitionManager.cs b/BOXVR Playlist Manager/FitXr/WorkoutDefinitionManager.cs new file mode 100644 index 0000000..459261e --- /dev/null +++ b/BOXVR Playlist Manager/FitXr/WorkoutDefinitionManager.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Enums; +using BoxVR_Playlist_Manager.FitXr.Models; +using BoxVR_Playlist_Manager.Helpers; +using Newtonsoft.Json; + +namespace BoxVR_Playlist_Manager.FitXr +{ + public class WorkoutDefinitionManager + { + public static WorkoutDefinitionManager instance = instance == null ? new WorkoutDefinitionManager() : instance; + private Game currentGameType; + private string currentpath; + public bool workoutsLoaded; + + public List workoutList { get; private set; } + + public WorkoutDefinitionManager() + { + if(MusicActionListSerializer.instance == null) + return; + } + + public void LoadWorkoutPlaylists(LocationMode loadingMode, Game gameType, bool additive = false) + { + if(additive) + { + if(this.workoutList == null) + this.workoutList = new List(); + } + else + this.workoutList = new List(); + this.currentGameType = gameType; + this.workoutsLoaded = false; + this.SetCurrentPath(loadingMode); + switch(loadingMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + LoadFromDataPath(); + break; + default: + throw new Exception($"{currentGameType} NOT SUPPORTED"); + } + } + + public void UpdateWorkoutList(List newWorkouts) + { + for(int index = 0; index < newWorkouts.Count; ++index) + { + if(!this.workoutList.Contains(newWorkouts[index])) + this.workoutList.Add(newWorkouts[index]); + } + this.workoutList.Sort((Comparison)((p1, p2) => p1.definition.duration.CompareTo(p2.definition.duration))); + } + + private void SetCurrentPath(LocationMode locationMode) => this.currentpath = Paths.WorkoutDefinitionFolder(locationMode, Game.BoxVR); + + private void LoadFromDataPath() + { + if(Directory.Exists(this.currentpath)) + { + foreach(FileSystemInfo file in new DirectoryInfo(this.currentpath).GetFiles("*.txt")) + { + WorkoutPlaylist workoutPlaylist = new WorkoutPlaylist(); + string jsonString = File.ReadAllText(file.FullName); + workoutPlaylist.LoadFromJSON(jsonString); + if(!this.workoutList.Contains(workoutPlaylist)) + this.workoutList.Add(workoutPlaylist); + } + this.workoutList.Sort((Comparison)((p1, p2) => p1.definition.duration.CompareTo(p2.definition.duration))); + } + this.workoutsLoaded = true; + } + + public void SaveWorkout(WorkoutPlaylist workout, LocationMode savingMode) + { + string str = Paths.WorkoutDefinitionFolder(savingMode, workout.definition.game); + for(int index = 0; index < workout.songs.Count; ++index) + { + workout.songs[index].serialisedActionList = new MusicActionListSerializable(); + workout.songs[index].serialisedActionList.actionList = MusicActionListSerializer.Instance.BuildSerializableMusicActionList(workout.songs[index].musicActionList); + } + this.currentpath = str + "/" + workout.definition.workoutName + ".workoutplaylist.txt"; + File.WriteAllText(this.currentpath, JsonConvert.SerializeObject(workout)); + } + + public WorkoutPlaylist GetWorkoutByIDOrName(string nameOrID) + { + if(this.workoutList == null) + { + App.logger.Debug("Workout List not loaded"); + return (WorkoutPlaylist)null; + } + for(int index = 0; index < this.workoutList.Count; ++index) + { + if(nameOrID == this.workoutList[index].workoutId._id || nameOrID == this.workoutList[index].definition.workoutName) + return this.workoutList[index]; + } + return (WorkoutPlaylist)null; + } + } +} diff --git a/BOXVR Playlist Manager/Helpers/NotifyingObject.cs b/BOXVR Playlist Manager/Helpers/NotifyingObject.cs new file mode 100644 index 0000000..ada419b --- /dev/null +++ b/BOXVR Playlist Manager/Helpers/NotifyingObject.cs @@ -0,0 +1,65 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Threading; + +namespace BoxVR_Playlist_Manager.Helpers +{ + public class NotifyingObject : INotifyPropertyChanged + { + protected Dispatcher _dispatcher; + public NotifyingObject(Dispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + /// + /// Multicast event for property change notifications. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Checks if a property already matches a desired value. Sets the property and + /// notifies listeners only when necessary. + /// + /// Type of the property. + /// Reference to a property with both getter and setter. + /// Desired value for the property. + /// + /// Name of the property used to notify listeners. This + /// value is optional and can be provided automatically when invoked from compilers that + /// support CallerMemberName. + /// + /// + /// True if the value was changed, false if the existing value matched the + /// desired value. + /// + protected bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if(Equals(storage, value)) + { + return false; + } + + storage = value; + _dispatcher.Invoke(() => this.OnPropertyChanged(propertyName)); + return true; + } + + /// + /// Notifies listeners that a property value has changed. + /// + /// + /// Name of the property used to notify listeners. This + /// value is optional and can be provided automatically when invoked from compilers + /// that support . + /// + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChangedEventHandler eventHandler = this.PropertyChanged; + if(eventHandler != null) + { + eventHandler(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/BOXVR Playlist Manager/Helpers/Paths.cs b/BOXVR Playlist Manager/Helpers/Paths.cs new file mode 100644 index 0000000..a31f012 --- /dev/null +++ b/BOXVR Playlist Manager/Helpers/Paths.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using BoxVR_Playlist_Manager.FitXr.Enums; + +namespace BoxVR_Playlist_Manager.Helpers +{ + public class Paths + { + private static string _applicationPath => Environment.ExpandEnvironmentVariables(Properties.Settings.Default.BoxVRExePath); + private static string _persistentDataPath => Environment.ExpandEnvironmentVariables(Properties.Settings.Default.BoxVRAppDataPath); + + public static string StreamingAssetsPath => Path.Combine(_applicationPath, "BoxVR_Data", "StreamingAssets"); + + public static string PersistentDataPath => _persistentDataPath; + + public static string ApplicationPath => _applicationPath; + + public static string RootDataFolder(LocationMode locationMode) + { + string str = ""; + switch(locationMode) + { + case LocationMode.PlayerData: + str = _persistentDataPath + "/Playlists"; + break; + case LocationMode.Workouts: + case LocationMode.Downloadable: + case LocationMode.MyWorkout: + App.logger.Error("No root path for these locations"); + break; + case LocationMode.Editor: + str = _persistentDataPath + "/WorkoutEditor"; + break; + } + return str.Replace("/", "\\"); + } + + + public static string WorkoutDefinitionFolder(LocationMode locationMode, Game gameType) + { + string str = ""; + switch(locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + str = Paths.RootDataFolder(locationMode) + "/WorkoutPlaylists/" + gameType.ToString(); + break; + case LocationMode.Workouts: + str = "WorkoutData/WorkoutPlaylists/" + gameType.ToString(); + break; + } + return str.Replace("/", "\\"); + } + + public static string TrackDataFolder(LocationMode locationMode) + { + string str = ""; + switch(locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + str = Paths.RootDataFolder(locationMode) + "/TrackData/"; + break; + case LocationMode.Workouts: + case LocationMode.MyWorkout: + str = "WorkoutData/TrackData/"; + break; + } + return str.Replace("/", "\\"); + } + + public static string WavDataFolder(LocationMode locationMode) => Paths.TrackDataFolder(locationMode); + + public static string ImgDataFolder(LocationMode locationMode) => Paths.TrackDataFolder(locationMode); + + public static string DefinitionsFolder(LocationMode locationMode) + { + string str = ""; + switch(locationMode) + { + case LocationMode.PlayerData: + case LocationMode.Editor: + str = Paths.RootDataFolder(locationMode) + "/TrackDefinitions/"; + break; + case LocationMode.Workouts: + case LocationMode.MyWorkout: + str = "WorkoutData/TrackDefinitions/"; + break; + } + return str.Replace("/", "\\"); + } + } +} diff --git a/BOXVR Playlist Manager/IsEnabledColorConverter.cs b/BOXVR Playlist Manager/IsEnabledColorConverter.cs index 02b20c6..1d69a23 100644 --- a/BOXVR Playlist Manager/IsEnabledColorConverter.cs +++ b/BOXVR Playlist Manager/IsEnabledColorConverter.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Data; namespace BoxVR_Playlist_Manager diff --git a/BOXVR Playlist Manager/MainWindow.xaml b/BOXVR Playlist Manager/MainWindow.xaml index 57bbd11..78bdca6 100644 --- a/BOXVR Playlist Manager/MainWindow.xaml +++ b/BOXVR Playlist Manager/MainWindow.xaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:BoxVR_Playlist_Manager" mc:Ignorable="d" - Title="BOXVR Playlist Manager" Height="450" Width="800"> + Title="BOXVR Playlist Manager" Height="450" Width="1000"> - M444.788 291.1l42.616 24.599c4.867 2.809 7.126 8.618 5.459 13.985-11.07 35.642-29.97 67.842-54.689 94.586a12.016 12.016 0 0 1-14.832 2.254l-42.584-24.595a191.577 191.577 0 0 1-60.759 35.13v49.182a12.01 12.01 0 0 1-9.377 11.718c-34.956 7.85-72.499 8.256-109.219.007-5.49-1.233-9.403-6.096-9.403-11.723v-49.184a191.555 191.555 0 0 1-60.759-35.13l-42.584 24.595a12.016 12.016 0 0 1-14.832-2.254c-24.718-26.744-43.619-58.944-54.689-94.586-1.667-5.366.592-11.175 5.459-13.985L67.212 291.1a193.48 193.48 0 0 1 0-70.199l-42.616-24.599c-4.867-2.809-7.126-8.618-5.459-13.985 11.07-35.642 29.97-67.842 54.689-94.586a12.016 12.016 0 0 1 14.832-2.254l42.584 24.595a191.577 191.577 0 0 1 60.759-35.13V25.759a12.01 12.01 0 0 1 9.377-11.718c34.956-7.85 72.499-8.256 109.219-.007 5.49 1.233 9.403 6.096 9.403 11.723v49.184a191.555 191.555 0 0 1 60.759 35.13l42.584-24.595a12.016 12.016 0 0 1 14.832 2.254c24.718 26.744 43.619 58.944 54.689 94.586 1.667 5.366-.592 11.175-5.459 13.985L444.788 220.9a193.485 193.485 0 0 1 0 70.2zM336 256c0-44.112-35.888-80-80-80s-80 35.888-80 80 35.888 80 80 80 80-35.888 80-80z @@ -35,8 +34,9 @@ M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z M16 288c-8.84 0-16 7.16-16 16v32c0 8.84 7.16 16 16 16h112v-64H16zm336-152V0H152c-13.3 0-24 10.7-24 24v264h127.99v-65.18c0-14.28 17.29-21.41 27.36-11.27l95.7 96.43c6.6 6.65 6.6 17.39 0 24.04l-95.7 96.42c-10.06 10.14-27.36 3.01-27.36-11.27V352H128v136c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H376c-13.2 0-24-10.8-24-24zm153-31L407.1 7c-4.5-4.5-10.6-7-17-7H384v128h128v-6.1c0-6.3-2.5-12.4-7-16.9z M0 84V56c0-13.3 10.7-24 24-24h112l9.4-18.7c4-8.2 12.3-13.3 21.4-13.3h114.3c9.1 0 17.4 5.1 21.5 13.3L312 32h112c13.3 0 24 10.7 24 24v28c0 6.6-5.4 12-12 12H12C5.4 96 0 90.6 0 84zm416 56v324c0 26.5-21.5 48-48 48H80c-26.5 0-48-21.5-48-48V140c0-6.6 5.4-12 12-12h360c6.6 0 12 5.4 12 12zm-272 68c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208zm96 0c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208zm96 0c0-8.8-7.2-16-16-16s-16 7.2-16 16v224c0 8.8 7.2 16 16 16s16-7.2 16-16V208z + - + @@ -63,12 +63,12 @@ - + - - + + - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - @@ -179,7 +192,7 @@ -