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 = [];
}
}