Search code examples
c#xamarintimer

Correct way to implement a metronome in Xamarin


I'm quite new in Xamarin and currently developping a music-related app. One of it's functionality is a metronome, having the bpm as an input. The metronome is set ON/OFF by the press of a single button, and it ticks with a sound using the SimpleAudioPlayer plugin.

Below is my implementation, but the feeling is off : there is some lag, which, well, is not ideal for a metronome.

        // OFF by default
        bool _MetronomeOn = false;
        async private void btnMetronome_Clicked(object sender, EventArgs e)
        {

            _MetronomeOn = !_MetronomeOn;

            // While Metronome is On
            while (_MetronomeOn)
            {
                // Get the bpm from ui
                double bpm;
                if (!Double.TryParse(entryMetronome.Text, out bpm))
                    bpm = 90;

                double secInterval = 60 / bpm;
                var player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
                player.Load("metronome.mp3");

                if (_MetronomeOn)
                {
                    // NOTE : Added Xam.Plugin.SimpleAudioPlayer nugget package 
                    player.Play();

                    await Task.Delay(TimeSpan.FromSeconds(secInterval));
                }
            }
        }

I am not sure what causes this... I think it can be the while implementation, the plugin loading maybe being heavy on the ressource...

Any help would be appreciated :)

EDIT : Thanks to Jason, my first step was to load the mp3 once and not during each loop. I tried at first to push the var player globally (outside any function) but VS 2022 didn't want me to and I was too much a newbie to find the correct type. But I did :) :

I set up the player globally and loaded the mp3 once in the OnAppearing() method :

    public partial class MainPage : ContentPage
    {
        // NOTE : Added Xam.Plugin.SimpleAudioPlayer nugget package to the Android Solution
        // The sound itself is loaded (not played) in OnAppearing()
        Plugin.SimpleAudioPlayer.ISimpleAudioPlayer player;

        public MainPage()
        {
            InitializeComponent();
        }

        // When the main view is launched and Shuffle Button animation
        protected override void OnAppearing()
        {
            base.OnAppearing();

            // Load the mp3. It must be in :
            // Android : Asset folder and build action is AndroidAsset
            // iOS :  Resource folder and build action is BundleResource
            player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
            player.Load("metronome.mp3");
            ...
        }
        
        ...
    }

This had a better result. Next step is implementing the correct timer, I'm working on it.


Solution

  • And here is the full code, thanks to you all. Here's what I changed :

    • Loaded the mp3 just once by putting Plugin.SimpleAudioPlayer.ISimpleAudioPlayer player; as a global variable and setting its parameters and loading the mp3 in the OnAppearing() method
    • Added a System.Timers.Timer to handled the timer correctly. I dropped the _MetronomeOn variable as the aTimer.Enabled property let me know if it was running or not. It now works like a charm. No more ugly whiles !

    Thanks a bunch !

    namespace TestMetro
    {
        public partial class MainPage : ContentPage
        {
    
            private static System.Timers.Timer aTimer;
            // NOTE : Added Xam.Plugin.SimpleAudioPlayer nugget package to the Android Solution
            // The sound itself is loaded (not played) in OnAppearing()
            Plugin.SimpleAudioPlayer.ISimpleAudioPlayer player;
            public MainPage()
            {
                InitializeComponent();
            }
    
            protected override void OnAppearing()
            {
                base.OnAppearing();
    
                // Load the mp3. It must be in :
                // Android : Asset folder and build action is AndroidAsset
                // iOS :  Resource folder and build action is BundleResource
                player = Plugin.SimpleAudioPlayer.CrossSimpleAudioPlayer.Current;
                player.Load("metronome.mp3");
    
                // Initiate the timer -- tick rate to be set below
                aTimer = new System.Timers.Timer();
                // Hook up the Elapsed event for the timer. 
                aTimer.Elapsed += OnMetronomeTick;
                aTimer.AutoReset = true;
                aTimer.Enabled = false;
            }
    
            private void OnMetronomeTick(object sender, ElapsedEventArgs e)
            {
                txtBPM.Text = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff",CultureInfo.InvariantCulture); 
                player.Play();
            }
    
            // REMOVED _MetronomeOn Var
            private void btnClick_Clicked(object sender, EventArgs e)
            {
                
                // Get the bpm from ui
                double bpm;
                if (!Double.TryParse(txtBPM.Text, out bpm))
                    bpm = 90;
    
                double secInterval = (60 * 1000 / bpm);
                aTimer.Interval = secInterval;
                if (aTimer.Enabled)
                    aTimer.Stop();
                else
                    aTimer.Start();
            }
        }
    }