Keep Your Running Cadence With a Garmin Music Playlist
I've been running more this year, and one of the aspects I've been working on is running at a consistently higher cadence. Most experts recommend running at a pace of 170-180 strides per minute, but it can be rough maintaining that over time! And while some fitness devices have metronomes that can help you keep pace, I've found that listening to music with a matching tempo.
Christopher McDougall recommends the Rock Lobster approach - at 180 beats per minute, it's a suggested cadence for many runners. But my wife hates that song, my cadence is a little lower, and I certainly can't listen to it for an entire running session!
There are plenty of online playlists that target a specific running cadence, but I don't want to subscribe to a service just to keep a tempo! I've got plenty of MP3s I've collected over the years, why can't I use those?
Ahead of a half-marathon I had signed up for, I wrote a script that does just that. Give it a folder of music files, and it'll find all of them within a tempo range, and create a playlist for you to drop onto a compatible device. I use a Garmin watch, but it should work for other devices as well.
The script to generate cadence playlists is available here.
Measuring Tempo
I tried a few approaches to grab BPM data for my files. My first hope was to pull the data from an authoritative source such as MusicBrainz, but almost none of the songs in my library had a tagged tempo value.
I also tried bpm-tools, which is available on most Linux distributions. This pulled back a cadence metric, but when spot-checking a few files, I noticed the data was pretty inaccurate; about 50% of the files deviated significantly from the calculated tempo. This was made worse by the lack of a confidence metric, so I couldn't apply a low-pass filter to remove obvious errors.
I ended up using the deeprhythm library, which uses a convolutional neural network to detect tempo. It performed really well, both to quickly detect thousands of songs, and also run accurately. After spot-checking a few songs, I decided it was accurate enough for my usage, and wrote up a script to process my library.
{"tempo": 91.0, "confidence": 0.65030837059021, "file": "/mnt/data/music/beets-library/Carly Rae Jepsen/E•MO•TION/12 - When I Needed You.mp3"}
{"tempo": 111.0, "confidence": 0.7165337204933167, "file": "/mnt/data/music/beets-library/Carly Rae Jepsen/E•MO•TION/06 - Boy Problems.mp3"}
{"tempo": 145.0, "confidence": 0.6137411594390869, "file": "/mnt/data/music/beets-library/Carly Rae Jepsen/E•MO•TION/15 - Favourite Colour.mp3"}
{"tempo": 112.0, "confidence": 0.9051607251167297, "file": "/mnt/data/music/beets-library/Carly Rae Jepsen/E•MO•TION/13 - Black Heart.mp3"}
{"tempo": 118.0, "confidence": 0.7597530484199524, "file": "/mnt/data/music/beets-library/Carly Rae Jepsen/E•MO•TION/01 - Run Away With Me.mp3"}
Garmin watches can load a playlist in M3U format, so the script builds a simple playlist, and consolidates everything into a folder you can drag & drop onto a device.
0:/MUSIC/cadence/03 - Star Quality.m4a
0:/MUSIC/cadence/04 - Lucifer's Jigsaw.mp3
0:/MUSIC/cadence/03 - Brothaz.m4a
0:/MUSIC/cadence/02 - Such Great Heights.m4a
0:/MUSIC/cadence/11 - Paper Lanterns.mp3
0:/MUSIC/cadence/DSP - Le Weeknd de Nemo.mp3
0:/MUSIC/cadence/28 - The Room Where It Happens.m4a
uv is neat
I've struggled running Python apps in the past, especially if they pulled in dependencies.
As chaotic as the JavaScript ecosystem is, using a library is (usually) as easy as an npm install
.
With Python, I've never been able to make heads nor tails of pip, pipx, requirements.txt, poetry, virtualenv, and the other tools in the ecosystem.
A coworker pointed me toward Astral's uv, which claims to be the One Tool needed to manage the chaos. And at least for this case, it works great!
I especially like its support for inline dependencies (PEP 723), which lets me specify all my project's dependencies with the script itself:
#!/usr/bin/env python3
# /// script
# dependencies = [
# "click"
# "deeprhythm"
# ]
# ///
import click
from deeprhythm import DeepRhythmPredictor
import os
# ...
With this, I can simply uv run <script>.py
, and it'll create a virtual environment, download required dependencies (or use a local cache), and execute the script.
matt@ORTHO ~/mus> uv run bpm.py --dir /mnt/c/Users/Matt/Music/
⠹ Preparing packages... (38/53)
⠦ Preparing packages... (43/53)
nvidia-cuda-nvrtc-cu12 ------------------------------ 70.92 MiB/83.96 MiB
triton ------------------------------ 71.99 MiB/148.33 MiB
nvidia-cufft-cu12 ------------------------------ 71.36 MiB/184.17 MiB
nvidia-cusolver-cu12 ------------------------------ 71.01 MiB/255.11 MiB
nvidia-cusparselt-cu12 ------------------------------ 71.12 MiB/273.89 MiB
nvidia-cusparse-cu12 ------------------------------ 70.68 MiB/274.86 MiB
nvidia-nccl-cu12 ------------------------------ 70.77 MiB/307.43 MiB
nvidia-cublas-cu12 ------------------------------ 70.95 MiB/566.81 MiB
nvidia-cudnn-cu12 ------------------------------ 70.77 MiB/674.02 MiB
torch ------------------------------ 72.24 MiB/846.92 MiB
It does feel a little ridiculous pulling down PyTorch and a gigabyte of dependencies for a hundred-line shell script, but at least it's fast and easy!
uv also lets you execute scripts from a URL, so you can run this fom a command-line and begin processing:
uv run https://gist.githubusercontent.com/mattdsteele/082fd77c3e65faa1332a36962c11da78/raw/bbe07ad1fe8737153a49362cbe70f91d51a75fb8/cadence-playlist.py
Maybe tempo isn't the only factor I should consider
I had a fun time with the playlist, and was able to keep a decent cadence going. You can even see where a song change with a significant delta occurred.
With nothing but tempo to drive the playlist, I got a real eclectic mix in my ears. Some of the artists I experienced on the run:
- Rancid (ok!)
- Audioslave (nice)
- The Lonely Island (uh..)
- Hamilton soundtrack (still slaps!)
- Jack Johnson (..I can't believe it captured BPM for an acoustic guitar)
- Death Cab for Cutie (maybe not the right context to feel emo)
- Brother Ali (..but this came at the right time)
- Norah Jones (oh boy)
- Jamiroquai (this made me virtually insane)
So yeah, maybe I should have looked through the songs in the playlist first. Or only pull in certain genres by querying beets (a very cool tool, but probably a whole other post of its own).
It might not have been the ideal soundtrack for a PR (my actual finishing time was pretty middling), but it at least wasn't as bad as this playlist.
- Keep Your Running Cadence With a Garmin Music Playlist - 2025
- Thank Java for Better JavaScript Dates - 2025
- This blog's comments are powered by Webmentions - 2025
- From Gatsby to Eleventy - 2024
- Mutation Testing with StrykerJS - 2024
- My favorite water bottles - 2024
- Web Push is almost usable with iOS 17 - 2023
- I wired up my bike's GPS to order me pizza during a gravel race 🍕 - 2023
- Running a Playwright script on AWS Lambda - 2023
- Overly Complicate Starting Your Vehicle, Using Home Assistant, an ESP8266, and a Spare Car Fob - 2023
- Generating Custom OpenGraph Cards with Gatsby and the 11ty Screenshot Service - 2023
- Keeping your Fediverse followers when moving Owncast servers - 2023
- Running Owncast with Hardware Acceleration on a Raspberry Pi 4 - 2023
- Vite is Neat - 2023
- Serverless Bike Tracking with a SPOT Tracker, AWS Location + Friends - 2022
- Advent of Code as Soulcraft - 2021
- Gravel Worlds - Long Voyage - 2021
- Making a Raspberry Pi Photo Booth with Pibooth, NeoPixels, and Giant Buttons - 2021
- 3 Things I Learned Trying out the JavaScript Temporal API - 2021
- Green Screen Zwifting with retroreflective fabric - 2021
- Getting the PPPoE Credentials from your C4000XG without calling CenturyLink - 2020
- Setting up a Livestream with Owncast - 2020
- Indieweb Livestreaming your Wedding with Owncast - 2020
- Maybe mashups in 2020 are just Jamstack sites - 2020
- Secret Strava - 2020
- <bt-device> and Renderless Web Components - 2020
- Building Fast, Tiny GitHub Actions with Go and Docker - 2019
- Why I Built Blumhouse to Purge My Twitter History - 2019
- Toolchainless - 2019
- Web Components Aren't Weird Anymore - 2018
- JavaScript Gardening with the Particle Photon - 2018
- The Neverending Side Project - 2018
- Light Up Your Bike with Web Bluetooth and StencilJS - 2017
- Every Java Developer Should Learn TypeScript - 2017
- Web Bluetooth Is Your New Squeeze - 2017
- Building Custom Elements That Work With AngularJS 1.x and Angular - 2016
- How to Run a Board Game Library At Your Conference After-Party - 2016
- The Languages Which Almost Became CSS (Recap) - 2016
- Upgrading to Angular 2 using ngUpgrade - 2016
- Reacting to Heart Rate and Bike Sensors With RxJS - 2016
- Driving an LED Light Strip with Heart Rate and Bike Sensors - 2015
- Bringing ArnoldC to JavaScript - 2015
- Source Maps in 2015 - 2015
- We're hosting a JavaScript conference! - 2015
- Building a Handcrafted IR Blaster for Arduino-Powered Bike Lights - 2014
- Responsive Images using <picture> and srcset/sizes - 2014
- Responsive Web Design Workshop at Interface - 2014
- Hardware is the new Geocities - 2014
- Go(lang) for Broke - 2013
- Clickbait HTTP Status Codes - 2013
- A fresh coat of paint - 2013
- Exploring the Device APIs - 2013
- Diving into the Device API - 2013
- Unit Testing JavaScript when you're Afraid of JavaScript - 2013
- What Zelda Taught Me About Front End Engineering - 2012
- Unit Testing in JavaScript with Jasmine - 2012
- Town Tester - How well does your city unit test? - 2012
- Zen and the Art of TDD - 2012
- Lessons Learned from the first Hack Omaha - 2012
- I have a cameo in The Clean Coder - 2011
- Test-driven source code formatting - 2011
- Scramble Squares puzzle game solver - 2010
- Omaha Public Library card symbology - 2010
- Now this is a story all about how my bike got flipped, turned upside down - 2009
- The King of Kong - A Fistful of Inaccuracies - 2008
- Inelegant code affects your reputation - 2008
- Unintended consequences of the Internet - 2007
- Screen scraping Google Spreadsheets exported as HTML - 2007
- UI designs in Leopard that I really hope grow on me - 2007
- The Thatcher Illusion - 2007