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:
Computing an onset strength envelope — a curve that peaks wherever a drum hit occurs.
Building a tempogram — a matrix that measures how periodic (i.e. “beat-like”) the onset envelope is at every possible BPM.
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