Search code examples
c#timing

Some kind of Metronome logic inaccuracy - C#


I'm trying to implement some kind of Metronome (tap tempo) logic that for every click of a key, calculate the interval between each click, to measure an average BPM (beats per minute)

for example, if I click on a key every second, I expect the BPM to be 60.

i introduced some code that uses this with clicks, and I noticed some kind of delay, the average was 62,

I thought that my manual clicking was not right, so I introduced a Timer object that will "click" every second, and still, am getting 62 and not 60 bpm

why is that? how can it be more accurate?

here is my code:

sorry its a spaghetti one, I tried it just for fun

public class Program
{
    static Stopwatch sw = new Stopwatch();
    static List<double> nums = new List<double>();

    private static void TimerOnElapsed(object sender, ElapsedEventArgs e)
    {
        EvaluateTime(sw, nums);
        sw.Restart();
    }

    // Driver program
    public static void Main()
    {
        
        Console.WriteLine("App is ready");
        Timer timer = new Timer();
        timer.Interval = 1000;
        timer.Elapsed += TimerOnElapsed;
        timer.AutoReset = true;
        sw.Restart();
        timer.Start();
        while (true)
        {
            Console.WriteLine(sw.Elapsed.TotalSeconds);
            var x = Console.ReadKey();

            if (x.Key != ConsoleKey.B)
            {
                EvaluateTime(sw, nums);
            }

            sw.Restart();
            if (x.Key == ConsoleKey.S)
            {
                return;
            }
        }
    }
    
    private static void EvaluateTime(Stopwatch sw, List<double> nums)
    {
        nums.Add(sw.Elapsed.TotalSeconds);
        Console.Clear();
        Console.WriteLine($"Average: {Math.Round(60 / (nums.Sum() / nums.Count), 2)}");
    }
}

Solution

  • I found that the overhead of restarting the stopwatch was causing the delay,

    I rather just use the time of the stopwatch and calculate the interval between each click

    by checking the elapsed seconds minus the sum of all previous elements

    the final code looks like this:

     public class Program
    {
        static readonly Stopwatch sw = new Stopwatch();
        static readonly List<double> nums = new List<double>();
    
        // Driver program
        public static void Main()
        {
            Console.WriteLine("App is ready");
            while (true)
            {
                Console.WriteLine(sw.Elapsed.TotalSeconds);
                var x = Console.ReadKey();
                if (!sw.IsRunning) sw.Start();
                else EvaluateTime(sw, nums);
    
                switch (x.Key)
                {
                    case ConsoleKey.S:
                        return;
                    case ConsoleKey.R:
                        sw.Reset();
                        nums.Clear();
                        Console.WriteLine("waiting for input");
                        break;
                }
            }
        }
    
        private static void EvaluateTime(Stopwatch sw, List<double> nums)
        {
            Console.WriteLine(
                $"{Math.Round(sw.Elapsed.TotalSeconds, 2)} - {Math.Round(nums.Sum(), 2)} = {Math.Round(sw.Elapsed.TotalSeconds, 2) - Math.Round(nums.Sum(), 2)}");
    
        nums.Add(Math.Round(sw.Elapsed.TotalSeconds - nums.Sum(), 2));
        Console.WriteLine($"The Average Is ====> {Math.Round(60 / (nums.Sum() / nums.Count), 2)}");
    }
    

    I tried to use the timer again, now the results are more consistent:

    App is ready
    0
    1 - 0 = 1
    The Average Is ====> 60
    2 - 1 = 1
    The Average Is ====> 60
    3 - 2 = 1
    The Average Is ====> 60
    4 - 3 = 1
    The Average Is ====> 60
    5 - 4 = 1
    The Average Is ====> 60
    6 - 5 = 1
    The Average Is ====> 60
    6.99 - 6 = 0.9900000000000002
    The Average Is ====> 60.09
    8.01 - 6.99 = 1.0199999999999996 // my guess for this inconsitency is the increasing size of the sum calculation, but this is a small difference
    The Average Is ====> 59.93
    8.99 - 8.01 = 0.9800000000000004
    The Average Is ====> 60.07