How to Detect BPM of Currently Playing Music

I was training for a 10k, and to strengthen my feeble legs, I decided to run as close to 175 steps per minute as possible.

I needed 175 beats per minute (BPM) music.

I found some excellent songs on Spotify, but was unsure about the tempo, so I created a BPM detector.

BPM Detector

The detector uses three Python components cobbled together:

  • PyAudio to record the audio
  • PyAudioWPatch to be able to record sounds currently playing on the computer (to use a loopback output device instead of a microphone)
  • librosa to detect the BPM

Here is the code, which is also available on GitHub.

import pyaudiowpatch as pyaudio
import numpy as np
import librosa

RECORD_SECONDS = 10

with pyaudio.PyAudio() as p:
    wasapi_info = p.get_host_api_info_by_type(pyaudio.paWASAPI)
    default_speakers = p.get_device_info_by_index(wasapi_info["defaultOutputDevice"])

    if not default_speakers["isLoopbackDevice"]:
        for loopback in p.get_loopback_device_info_generator():
            if default_speakers["name"] in loopback["name"]:
                default_speakers = loopback
                break
        else:
            print("Default loopback output device not found 😭")
            exit()

    sampleRate = int(default_speakers['defaultSampleRate'])
    chunk = sampleRate * RECORD_SECONDS

    stream = p.open(format = pyaudio.paFloat32,
                    channels = 1,
                    rate = sampleRate,
                    input = True,
                    input_device_index = default_speakers['index'],
                    frames_per_buffer = chunk)

    print(f"Recording {RECORD_SECONDS} seconds from {default_speakers['index']}: {default_speakers['name']} 🎤")
    sound = stream.read(chunk)
    print("Recording complete 🎹")

    stream.stop_stream()
    stream.close()

    print("Analysing BPM 🎵")
    np_sound = np.frombuffer(sound, dtype=np.float32)
    tempo, beat_frames = librosa.beat.beat_track(y=np_sound, sr=sampleRate)
    if (tempo < 100):
        print(f'Estimated tempo: {round(tempo)} or {round(tempo * 2)} bpm')
    else:
        print(f'Estimated tempo: {round(tempo)} bpm')

The code records around 10 seconds of audio (which surprisingly takes 15 seconds) and shows the BPM.

Recording 10 seconds from 10: Speakers (3- High Definition Audio Device) [Loopback] 🎤
Recording complete 🎹
Analysing BPM 🎵
Estimated tempo: 144 bpm

I've tested its accuracy using online metronome, and the detector seems very precise.

With Spotify, however, I am getting mixed results, depending on the song.

Sometimes, it detects half BPM if some mid-beats are weaker, so I also show double the value if the detected tempo is lower than 100 BPM.

If you wonder how the 10k went, I was able to run close to 175 steps per minute for about 5k and then ran out of steam.

You can listen to my 175 BPM Spotify playlist here or run with it!