using Cryville.Common.Math; using System; namespace Cryville.Crtr.Extensions { public abstract class TimingModel { public double Time { get; protected set; } public double BeatTime { get; protected set; } public BeatTime FractionalBeatTime { get; protected set; } double m_bpm; public double BPM { get { return m_bpm; } set { m_bpm = value; } } public double BeatLength { get { return 60 / m_bpm; } set { m_bpm = 60 / value; } } public TimingModel(double offset) { Time = offset; FractionalBeatTime = new BeatTime(0, 0, 1); } } public class FractionalBeatTimeTimingModel : TimingModel { public FractionalBeatTimeTimingModel(double offset = 0) : base(offset) { } public void ForwardTo(BeatTime t) { if (t == FractionalBeatTime) return; if (BPM == 0) throw new InvalidOperationException("BPM not determined"); FractionalBeatTime = t; var nt = t.Decimal; Time += (nt - BeatTime) / BPM * 60; BeatTime = nt; } } public class BeatTimeTimingModel : TimingModel { public BeatTimeTimingModel(double offset = 0) : base(offset) { } public void ForwardTo(double t) { if (t == BeatTime) return; if (BPM == 0) throw new InvalidOperationException("BPM not determined"); Time += (t - BeatTime) / BPM * 60; BeatTime = t; FractionalBeatTime = ToBeatTime(t); } static BeatTime ToBeatTime(double beat, double error = 1e-4) { int i, n, d; FractionUtils.ToFraction(beat, error, out n, out d); i = n / d; n %= d; return new BeatTime(i, n, d); } } public class TimeTimingModel : TimingModel { public readonly double InputTimeAccuracy; public TimeTimingModel(double accuracy = 2e-3, double offset = 0) : base(offset) { if (accuracy <= 0) throw new ArgumentOutOfRangeException("accuracy"); InputTimeAccuracy = accuracy; } public void ForwardTo(double t) { if (t == Time) return; if (BPM == 0) throw new InvalidOperationException("BPM not determined"); BeatTime += (t - Time) * BPM / 60; int n, d; FractionUtils.ToFraction(BeatTime, Math.Min(1, InputTimeAccuracy * BPM / 60), out n, out d); FractionalBeatTime = new BeatTime(n, d); Time = t; } public void ForceSnap() { var alignedBeat = (int)Math.Round(BeatTime); BeatTime = alignedBeat; FractionalBeatTime = new BeatTime(alignedBeat, 1); } } }