using RhythmBase.Components; using RhythmBase.Events; using RhythmBase.Extensions; namespace RhythmBase.Utils { /// /// Beat calculator. /// public class BeatCalculator { internal BeatCalculator(RDLevel level) { Collection = level; Refresh(); } /// /// Refresh the cache. /// public void Refresh() { _BPMList = Collection.Where() .ToList(); _CPBList = Collection.Where() .ToList(); } /// /// Convert beat data. /// /// The 1-based bar. /// The 1-based beat of the bar. /// Total 1-based beats. public float BarBeatToBeatOnly(uint bar, float beat) => BarBeatToBeatOnly(bar, beat, _CPBList); /// /// Convert beat data. /// /// The 1-based bar. /// The 1-based beat of the bar. /// Total time span. public TimeSpan BarBeatToTimeSpan(uint bar, float beat) => BeatOnlyToTimeSpan(BarBeatToBeatOnly(bar, beat)); /// /// Convert beat data. /// /// Total 1-based beats. /// The 1-based bar and the 1-based beat of bar. public (uint bar, float beat) BeatOnlyToBarBeat(float beat) => BeatOnlyToBarBeat(beat, _CPBList); /// /// Convert beat data. /// /// Total 1-based beats. /// Total time span. public TimeSpan BeatOnlyToTimeSpan(float beat) => BeatOnlyToTimeSpan(beat, _BPMList); /// /// Convert beat data. /// /// Total time span. /// Total 1-based beats. public float TimeSpanToBeatOnly(TimeSpan timeSpan) => TimeSpanToBeatOnly(timeSpan, _BPMList); /// /// Convert beat data. /// /// Total time span. /// The 1-based bar and the 1-based beat of bar. public (uint bar, float beat) TimeSpanToBarBeat(TimeSpan timeSpan) => BeatOnlyToBarBeat(TimeSpanToBeatOnly(timeSpan)); private static float BarBeatToBeatOnly(uint bar, float beat, IEnumerable Collection) { (float BeatOnly, uint Bar, uint CPB) foreCPB = new(1f, 1U, 8U); SetCrotchetsPerBar? LastCPB = Collection.LastOrDefault((SetCrotchetsPerBar i) => i.Active && i.Beat.BarBeat.bar < bar); if (LastCPB != null) foreCPB = new(LastCPB.Beat.BeatOnly, LastCPB.Beat.BarBeat.bar, LastCPB.CrotchetsPerBar); return foreCPB.BeatOnly + (bar - foreCPB.Bar) * foreCPB.CPB + beat - 1f; } private static (uint bar, float beat) BeatOnlyToBarBeat(float beat, IEnumerable Collection) { (float BeatOnly, uint Bar, uint CPB) foreCPB = new(1f, 1U, 8U); SetCrotchetsPerBar? LastCPB = Collection.LastOrDefault((SetCrotchetsPerBar i) => i.Active && i.Beat.BeatOnly < beat); if (LastCPB != null) foreCPB = new(LastCPB.Beat.BeatOnly, LastCPB.Beat.BarBeat.bar, LastCPB.CrotchetsPerBar); (uint bar, float beat) result = ((uint)Math.Round(foreCPB.Bar + Math.Floor((double)((beat - foreCPB.BeatOnly) / foreCPB.CPB))), ((beat - foreCPB.BeatOnly) % foreCPB.CPB) + 1f); return result; } private static TimeSpan BeatOnlyToTimeSpan(float beatOnly, IEnumerable BPMCollection) { ValueTuple fore = new(1f, Utils.DefaultBPM); BaseBeatsPerMinute? foreBPM = BPMCollection.FirstOrDefault(); if (foreBPM != null) fore = new ValueTuple(foreBPM.Beat.BeatOnly, foreBPM.BeatsPerMinute); float resultMinute = 0f; foreach (BaseBeatsPerMinute item in BPMCollection) if (beatOnly > item.Beat.BeatOnly) { resultMinute += (item.Beat.BeatOnly - fore.Item1) / fore.Item2; fore = new ValueTuple(item.Beat.BeatOnly, item.BeatsPerMinute); } resultMinute += (beatOnly - fore.Item1) / fore.Item2; return TimeSpan.FromMinutes((double)resultMinute); } private static float TimeSpanToBeatOnly(TimeSpan timeSpan, IEnumerable BPMCollection) { ValueTuple fore = new(1f, Utils.DefaultBPM); BaseBeatsPerMinute? foreBPM = BPMCollection.FirstOrDefault(); if (foreBPM != null) fore = new ValueTuple(foreBPM.Beat.BeatOnly, foreBPM.BeatsPerMinute); float beatOnly = 1f; foreach (BaseBeatsPerMinute item in BPMCollection) if (timeSpan > BeatOnlyToTimeSpan(item.Beat.BeatOnly, BPMCollection)) beatOnly = (float)((double)beatOnly + (BeatOnlyToTimeSpan(item.Beat.BeatOnly, BPMCollection) - BeatOnlyToTimeSpan(fore.Item1, BPMCollection)).TotalMinutes * (double)fore.Item2); beatOnly = (float)((double)beatOnly + (timeSpan - BeatOnlyToTimeSpan(fore.Item1, BPMCollection)).TotalMinutes * (double)fore.Item2); return beatOnly; } /// /// Creates a beat instance. /// public RDBeat BeatOf(float beatOnly) => new(this, beatOnly); /// /// Creates a beat instance. /// public RDBeat BeatOf(uint bar, float beat) => new(this, bar, beat); /// /// Creates a beat instance. /// public RDBeat BeatOf(TimeSpan timeSpan) => new(this, timeSpan); /// /// Creates an interval between two beats. /// /// The first beat. /// The second beat. /// An RDRange representing the interval between the two beats. public RDRange IntervalOf(RDBeat beat1, RDBeat beat2) => new(new(this, beat1), new(this, beat2)); /// /// Creates an interval between two beats specified by bar and beat. /// /// The first beat specified by bar and beat. /// The second beat specified by bar and beat. /// An RDRange representing the interval between the two beats. public RDRange IntervalOf((uint bar, float beat) beat1, (uint bar, float beat) beat2) => IntervalOf(BeatOf(beat1.bar, beat1.beat), BeatOf(beat2.bar, beat2.beat)); /// /// Creates an interval between two beats specified by time spans. /// /// The first time span. /// The second time span. /// An RDRange representing the interval between the two time spans. public RDRange IntervalOf(TimeSpan timeSpan1, TimeSpan timeSpan2) => IntervalOf(BeatOf(timeSpan1), BeatOf(timeSpan2)); /// /// Calculate the BPM of the moment in which the beat is. /// public float BeatsPerMinuteOf(RDBeat beat) => _BPMList.LastOrDefault((BaseBeatsPerMinute i) => i.Beat < beat)?.BeatsPerMinute ?? Utils.DefaultBPM; /// /// Calculate the CPB of the moment in which the beat is. /// public float CrotchetsPerBarOf(RDBeat beat) => _CPBList.LastOrDefault((SetCrotchetsPerBar i) => i.Beat < beat)?.CrotchetsPerBar ?? 8; internal readonly RDLevel Collection; private List _BPMList = []; private List _CPBList = []; } }