using RhythmBase.Adofai.Utils; using RhythmBase.Exceptions; using System.Diagnostics.CodeAnalysis; namespace RhythmBase.Adofai.Components { /// /// Represents a beat in the ADLevel. /// public struct ADBeat : IComparable, IEquatable { internal readonly ADLevel? baseLevel => _calculator?.Collection; /// /// Gets or sets the beat only value. /// public readonly float BeatOnly { get => _beat + 1f; set { } } /// /// Gets or sets the time span. /// public readonly TimeSpan TimeSpan { get => _timeSpan; set { } } /// /// Initializes a new instance of the struct with a specified beat. /// /// The beat value. public ADBeat(float beat) { this = default; _beat = beat; _isBeatLoaded = true; } /// /// Initializes a new instance of the struct with a specified time span. /// /// The time span value. public ADBeat(TimeSpan timeSpan) { this = default; _timeSpan = timeSpan; _isTimeSpanLoaded = true; } /// /// Initializes a new instance of the struct with a specified calculator and beat. /// /// The beat calculator. /// The beat value. public ADBeat(ADBeatCalculator calculator, float beat) { this = default; _calculator = calculator; _beat = beat; _isBeatLoaded = true; } /// /// Initializes a new instance of the struct with a specified calculator and time span. /// /// The beat calculator. /// The time span value. /// Thrown when the time span is less than zero. public ADBeat(ADBeatCalculator calculator, TimeSpan timeSpan) { this = default; if (timeSpan < TimeSpan.Zero) { throw new OverflowException(string.Format("The time must not be less than zero, but {0} is given", timeSpan)); } _calculator = calculator; _timeSpan = timeSpan; _isTimeSpanLoaded = true; } /// /// Construct a beat of the 1st beat from the calculator /// /// Specified calculator. /// The first beat tied to the level. public static ADBeat Default(ADBeatCalculator calculator) { ADBeat Default = new(calculator, 1f); return Default; } /// /// Determine if two beats come from the same level /// /// A beat. /// Another beat. /// If true, an exception will be thrown when two beats do not come from the same level. /// public static bool FromSameLevel(ADBeat a, ADBeat b, bool @throw = false) { bool flag = a.baseLevel.Equals(b.baseLevel); bool FromSameLevel; if (flag) { FromSameLevel = true; } else { if (@throw) { throw new RhythmBaseException("Beats must come from the same ADLevel."); } FromSameLevel = false; } return FromSameLevel; } /// /// Determine if two beats are from the same level. ///
/// If any of them does not come from any level, it will also return true. ///
/// A beat. /// Another beat. /// If true, an exception will be thrown when two beats do not come from the same level. /// public static bool FromSameLevelOrNull(ADBeat a, ADBeat b, bool @throw = false) => a.baseLevel == null || b.baseLevel == null || FromSameLevel(a, b, @throw); public readonly bool FromSameLevel(ADBeat b, bool @throw = false) => FromSameLevel(this, b, @throw); /// /// Determine if two beats are from the same level. ///
/// If any of them does not come from any level, it will also return true. ///
/// Another beat. /// If true, an exception will be thrown when two beats do not come from the same level. /// public readonly bool FromSameLevelOrNull(ADBeat b, bool @throw = false) => baseLevel == null || b.baseLevel == null || FromSameLevel(b, @throw); /// /// Returns a new instance of unbinding the level. /// /// A new instance of unbinding the level. public readonly ADBeat WithoutBinding() { ADBeat result = this; result._calculator = null; return result; } private readonly void IfNullThrowException() { if (IsEmpty) { throw new InvalidRDBeatException(); } } /// /// Refresh the cache. /// public void ResetCache() { float i = BeatOnly; _isTimeSpanLoaded = false; } internal void ResetBPM() { _isBeatLoaded = true; _isTimeSpanLoaded = false; _isBpmLoaded = false; } internal void ResetCPB() => _isBeatLoaded = true; /// /// Gets a value indicating whether this instance is empty. /// /// /// true if this instance is empty; otherwise, false. /// public readonly bool IsEmpty { get { return _calculator == null || (!_isBeatLoaded && !_isTimeSpanLoaded); } } /// public static ADBeat operator +(ADBeat a, float b) { ADBeat result = new(a._calculator, a.BeatOnly + b); return result; } /// public static ADBeat operator +(ADBeat a, TimeSpan b) { ADBeat result = new(a._calculator, a.TimeSpan + b); return result; } /// public static ADBeat operator -(ADBeat a, float b) { ADBeat result = new(a._calculator, a.BeatOnly - b); return result; } /// public static ADBeat operator -(ADBeat a, TimeSpan b) { ADBeat result = new(a._calculator, a.TimeSpan - b); return result; } /// public static bool operator >(ADBeat a, ADBeat b) => FromSameLevel(a, b, true) && a.BeatOnly > b.BeatOnly; /// public static bool operator <(ADBeat a, ADBeat b) => FromSameLevel(a, b, true) && a.BeatOnly < b.BeatOnly; /// public static bool operator >=(ADBeat a, ADBeat b) => FromSameLevel(a, b, true) && a.BeatOnly >= b.BeatOnly; /// public static bool operator <=(ADBeat a, ADBeat b) => FromSameLevel(a, b, true) && a.BeatOnly <= b.BeatOnly; /// public static bool operator ==(ADBeat a, ADBeat b) => (FromSameLevel(a, b, true) && a._beat == b._beat) || (a._isTimeSpanLoaded && b._isTimeSpanLoaded && a._timeSpan == b._timeSpan) || a.BeatOnly == b.BeatOnly; /// public static bool operator !=(ADBeat a, ADBeat b) => !(a == b); /// public readonly int CompareTo(ADBeat other) => checked((int)Math.Round((double)unchecked(_beat - other._beat))); /// public override readonly string ToString() => string.Format("[{0}]", BeatOnly); /// public override readonly bool Equals([NotNull] object obj) => obj.GetType() == typeof(ADBeat) && Equals((obj != null) ? ((ADBeat)obj) : default); /// public readonly bool Equals(ADBeat other) => this == other; /// public override readonly int GetHashCode() => HashCode.Combine(BeatOnly, baseLevel); internal ADBeatCalculator? _calculator; private bool _isBeatLoaded; private bool _isTimeSpanLoaded; private bool _isBpmLoaded; private float _beat; private TimeSpan _timeSpan; private float _bpm; } }