Building A Metronome In Python

Jie Huang
8 min readApr 1, 2022

--

Photo by Rachel Loughman on Unsplash

Slow and steady. These are the most common words you heard from almost all tutors when you get started to learn guitar. As for me, keeping a steady rhythm is not that easy. Fortunately, practicing with a metronome is a great way to develop a sense of rhythm. Therefore, I built a metronome application based on Python.

In this post, I’d like to share the building process of this application. If you love music and happened to want a metronome on your computer, you can download it from the Github page here. And if you want to build it by yourself, you can find the step-by-step Python code in this article here.

What features do we expect?

The principal feature of a metronome is to produce an audible click at a fixed tempo that can be set by the user, typically in beats per minute (bpm).¹ Besides, the options of time signatures (4/4, 3/4, 6/8, etc.) are also needed. The metronome app should play strong or weak beats according to different time signatures. Furthermore, I noticed another two practical functions that a large number of people desire. The first one is to show tempo markings, which are usually written as Italian words that correspond with tempos. For example, Allegro means fast and is a tempo between 120 bpm and 156 bpm. Tempo marking leaves freedom for you to choose the tempo in that range.

Some tempo markings

And another feature is tempo estimation achieved by tapping several beats continuously. This function is useful when you just want to use the metronome at your own pace rather than tune it to a ‘cold’ number. Moreover, we can also tap the beats along with a song to get its tempo. Last but not least, to make it run on Windows, macOS, and Linux, a cross-platform app is needed.

Photo by Pawel Czerwinski on Unsplash

How to create this app?

Good looks and a handy user interface make our lives easy. As the application we are going to build is not that complicated, I choose python Tkinter to build the user interface. But before that, let’s make the building blocks first. We need to know how to use Python to play sound, determine tempo markings, and estimate the tempo. Let’s conquer them one by one.

Photo by Angelina Litvin on Unsplash

Playing click sounds

The click sound is the core of a metronome. Despite there being many ways to make a click sound, I chose to use the simplest way. I use the simpleaudio package to load and play click sound files. This package is not installed by default. So you can install it very easily by usingpip install simpleaudio. If everything goes well, you can hear the click sound by running this code.

If so, congratulations! Using five lines of Python code, you built a metronome app at the tempo of 120 bpm. To make it more interesting we can add a weak click sound like the following.

We count the number of beats, choose to play strong or weak click sound according to the count, and set the counter back to zero every four beats. By running this program, you can hear the strong beat that reminds you it is the first beat in this measure. Actually, you just set the metronome on the mode of 4/4 time signature, which means there are four quarter notes in each measure. Here we use a quarter note that lasts for 0.5 seconds to represent the value of a unit beat. What about the mode of 6/8 time signature at the tempo of 120 bpm?

As you can see, to realize the 6/8 time signature, we set the counter back to zero at every six beats, and decrease the interval to 0.25 seconds, so that we instruct our program that there are six eighth notes in each measure. We now capture the ability to change the tempo and the options of time signature. Let’s move on to the tempo markings.

Determining tempo markings

Given a tempo, we want to know the corresponding marking.

To avoid overlap of tempo ranges, I only store some tempo markings in a dictionary. As you can see from the outputs, this way leads to the flaw of some tempos that do not correspond to markings. Very likely, there are other solutions to this problem, but now I turn a blind eye to it for the purpose of simplicity.

Tempo estimation

To estimate the tempo, a trigger event like a key pressed should be bound to the program, which we will show in the part of building the user interface later. Every time we tap the T key on the keyboard, the method time.time() is called. This method would return the time as a floating-point number expressed in seconds since the epoch, January 1, 1970, 00:00:00 (UTC). By taping T continuously, we collect the tapping time to a list and calculate the mean interval between each tap, so that we can estimate the tempo.

As for the long list, we only use the last 6 elements for the purpose of real-time estimation. Until now, we completed the most of core functions. What we left is the user interface. Let’s take a break, listen to a song, and have a cup of coffee. ☕️

Photo by David van Dijk on Unsplash

Creating the user interface for the metronome

The user interface of this metronome

This simple user interface is built with Tkinter. There are three frames, which contain time signature options, tempo information, and click counter respectively. Radio buttons are used for the selection of time signatures; Two stacked labels in the middle frame are for displaying the current tempo and its marking, and a scale at the bottom is used to adjust the tempo. And the right frame only contains a label to show a big click counter.

Make GUI using Tkinter

Although it seems that we have written a lot of codes here, this implementation only determines what this app looks like. To complete the app, we need to connect all the previous functions to the widgets.

Play sounds repeatedly under Tkinter

It’s a little bit different to play a click sound repeatedly under the Tkinter framework. You may have noticed the last line of code window.mainloop() , which tells you there is a loop already in our program so that we can not use another infinite while loop. However, we can use a timer to achieve the same effect as an infinite loop. We set a timer, let Tkinter play the click sound when the time is up, and repeat this process.

The Tkinter integer variable ts_mode is used to store the selection of time signatures. By calling ts_mode.get() we can get the current selection of radio buttons. Then we pass it as the index of the time signatures represented as a dictionary by using time_signature[ts_mode.get()] , then select the time signature using the index of -1 .

According to the tempo and time signature, we calculate the interval of two adjacent clicks in milliseconds then convert the value into an integer using int((60/tempo) * (4/time_signature[-1])*1000). At the end of the function play , we set a interval_ms milliseconds timer to call the function again. In this way, we can play the click sounds repeatedly under Tkinter.

Play sounds based on time signatures

To play the different sound effects, we add constraints in the play function use if statement and trace the ts_mode variable using ts_mode.trace('w', update_time_signature). Once it is changed, the function will be triggered, and then the click counter and interval will be updated. In this way, we are able to play according to the time signature.

Adjust tempo using the scale

The scale is used to change the tempo. When the scale value changes, we update the interval in a new function called update that is connected to the scale command. By running the program, you can change the tempo by sliding the scale.

Show labels of tempo, marking, and click counter

As you have noticed, the last program does not make the label of tempo, and its marking change as you slide the scale. To display the labels correctly, we just need to add a few lines in the updatefunction.

Besides, to show the big click counter correctly, we just add one line in the function of play .

Play and pause

We can’t leave the metronome play forever. To start and pause the click sounds, we can use window.bind('<Key>', key_pressed) to bind a keypressed function to the main window. When the space key is pressed, we change the valued boolean variable ONusing not operation so that we can switch between on and off. In the play function, a if statement is used to determine whether or not to turn on the click sounds based on the value of ON.

Tempo estimation

To estimate the tempo, in key_pressed function, we call tap_estimate when t is pressed on the keyboard. In tap_estimate function, we collect the time of this tap to time_list, then using the list to calculate the interval.

Arrow key to changing the tempo

For easier time adjustment, we add the arrow shortcuts. The arrow Up and Down are for faster adjustment with step 10, while the arrow Left and Right are for normal adjustment with step 1.

M to loop through modes of time signature

We can even add a shortcut to loop through the time signatures. This is easy to achieve by using a % operation.

Congratulations! You made it. 🎉

Photo by Joe Caione on Unsplash

Summary

Actually, this metronome only contains about 100 lines of code although we seem to write much more than that. The reason for this is that I tried to break each function into a stand-alone working part so that you can run each python script separately.

This is the first time I write on Medium, and hopefully, I can bring some value to this wonderful platform, and build connections with people all over the world. I hope you can give me some feedback about this metronome. Do you think it is user-friendly? Do you like the design? What other features do you expect? It would be wonderful if you can offer me some advice so that I can make it better in the next update.

I’m Jack, a young man who dreams to master guitar and loves programming. Thanks for your reading. See you on the next journey.

--

--

Jie Huang

I enjoy writing code about music and physics in my free time.