Measure Tempo

DrumScript estimates the tempo (BPM) of any audio file using a spectral onset envelope approach.

Under the hood, ds.detect_tempo() works by:

  1. Computing an onset strength envelope — a curve that peaks wherever a drum hit occurs.

  2. Building a tempogram — a matrix that measures how periodic (i.e. “beat-like”) the onset envelope is at every possible BPM.

  3. Summing the tempogram energy across time, then picking the BPM with the strongest peak — but only within a plausible musical range (60–240 BPM) to avoid artifacts.

If the audio is shorter than 1 second, tempo estimation is unreliable, so DrumScript defaults to 120 BPM.

(Note: We are using a copyright-free synthetic track for this demonstration.)


from IPython.display import Audio, display

import drumscript as ds
# 1. Load audio
audio_file_path = "audio/test_track_1.wav"
audio_file = ds.load_audio(audio_file_path)
print("Audio loaded!")
display(Audio(audio_file_path))
/home/runner/work/DrumScript/DrumScript/drumscript/audio_processor/audio_loader.py:51: UserWarning: PySoundFile failed. Trying audioread instead.
  audio_data, sample_rate = librosa.load(
/home/runner/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:184: FutureWarning: librosa.core.audio.__audioread_load
	Deprecated as of librosa version 0.10.0.
	It will be removed in librosa version 1.0.
  y, sr_native = __audioread_load(path, offset, duration, dtype)
Error loading audio file audio/test_track_1.wav: 
---------------------------------------------------------------------------
LibsndfileError                           Traceback (most recent call last)
File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:176, in load(path, sr, mono, offset, duration, dtype, res_type)
    175 try:
--> 176     y, sr_native = __soundfile_load(path, offset, duration, dtype)
    178 except sf.SoundFileRuntimeError as exc:
    179     # If soundfile failed, try audioread instead

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:209, in __soundfile_load(path, offset, duration, dtype)
    207 else:
    208     # Otherwise, create the soundfile object
--> 209     context = sf.SoundFile(path)
    211 with context as sf_desc:

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/soundfile.py:690, in SoundFile.__init__(self, file, mode, samplerate, channels, subtype, endian, format, closefd, compression_level, bitrate_mode)
    688 self._info = _create_info_struct(file, mode, samplerate, channels,
    689                                  format, subtype, endian)
--> 690 self._file = self._open(file, mode_int, closefd)
    691 if set(mode).issuperset('r+') and self.seekable():
    692     # Move write position to 0 (like in Python file objects)

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/soundfile.py:1265, in SoundFile._open(self, file, mode_int, closefd)
   1264     err = _snd.sf_error(file_ptr)
-> 1265     raise LibsndfileError(err, prefix="Error opening {0!r}: ".format(self.name))
   1266 if mode_int == _snd.SFM_WRITE:
   1267     # Due to a bug in libsndfile version <= 1.0.25, frames != 0
   1268     # when opening a named pipe in SFM_WRITE mode.
   1269     # See http://github.com/erikd/libsndfile/issues/77.

LibsndfileError: Error opening 'audio/test_track_1.wav': Format not recognised.

During handling of the above exception, another exception occurred:

NoBackendError                            Traceback (most recent call last)
Cell In[2], line 3
      1 # 1. Load audio
      2 audio_file_path = "audio/test_track_1.wav"
----> 3 audio_file = ds.load_audio(audio_file_path)
      4 print("Audio loaded!")
      5 display(Audio(audio_file_path))

File ~/work/DrumScript/DrumScript/drumscript/audio_processor/audio_loader.py:51, in load_audio(audio_path, sr)
     49 # sample_rate = SAMPLE_RATE
     50 try:
---> 51     audio_data, sample_rate = librosa.load(
     52         audio_path, sr=sr
     53     )  # The librosa.load_audio() fct handles wide variety of audio formats, including .mp3, .wav, .flac, .ogg, etc.
     54     # return audio_data, sr
     55     return audio_data, sample_rate

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:184, in load(path, sr, mono, offset, duration, dtype, res_type)
    180 if isinstance(path, (str, pathlib.PurePath)):
    181     warnings.warn(
    182         "PySoundFile failed. Trying audioread instead.", stacklevel=2
    183     )
--> 184     y, sr_native = __audioread_load(path, offset, duration, dtype)
    185 else:
    186     raise exc

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/decorator/__init__.py:247, in decorate.<locals>.fun(*args, **kw)
    245 if not kwsyntax:
    246     args, kw = fix(args, kw, sig)
--> 247 return caller(func, *(extras + args), **kw)

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/util/decorators.py:63, in deprecated.<locals>.__wrapper(func, *args, **kwargs)
     54 """Warn the user, and then proceed."""
     55 warnings.warn(
     56     "{:s}.{:s}\n\tDeprecated as of librosa version {:s}."
     57     "\n\tIt will be removed in librosa version {:s}.".format(
   (...)     61     stacklevel=3,  # Would be 2, but the decorator adds a level
     62 )
---> 63 return func(*args, **kwargs)

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/librosa/core/audio.py:240, in __audioread_load(path, offset, duration, dtype)
    237     reader = path
    238 else:
    239     # If the input was not an audioread object, try to open it
--> 240     reader = audioread.audio_open(path)
    242 with reader as input_file:
    243     sr_native = input_file.samplerate

File ~/work/DrumScript/DrumScript/.venv/lib/python3.12/site-packages/audioread/__init__.py:131, in audio_open(path, backends)
    128         pass
    130 # All backends failed!
--> 131 raise NoBackendError()

NoBackendError: 
# 2. Detect tempo
bpm = ds.detect_tempo(audio_file_path)

print(f"Detected Tempo: {bpm:.2f} BPM")
Detected Tempo: 76.00 BPM