____ _
/ ___|__ _ ___ ___ __ _ __| | ___
| | / _` / __|/ __/ _` |/ _` |/ _ \
| |__| (_| \__ \ (_| (_| | (_| | __/
\____\__,_|___/\___\__,_|\__,_|\___|
Drop in any audio file — Cascade analyses it, generates a beatmap, and you start playing.
↓ Install · Quick start · Controls · Modifiers · Practice · How it works · Contribute
Cascade is a five-lane rhythm game that runs entirely in your terminal. Drop
in any .mp3 / .flac / .ogg / .wav and it will analyse the audio,
detect onsets and beat, and generate beatmaps for four difficulties — all
offline, with no server round-trip.
Because it's built on ratatui and rodio, it works in any
truecolor-capable terminal; modern terminals that support the kitty keyboard
protocol (Kitty, WezTerm, foot, Alacritty 0.13+) additionally unlock proper
hold-note release detection.
- Works with your library — import any audio file, or a whole folder recursively; ID3/Vorbis tags are read automatically
- Smart beatmaps — spectral-flux onset detection with sample-window-corrected timing, fractional BPM with half/double-tempo protection, onset-aligned beat grid, per-difficulty density, pitch-contour-aware lane placement for melodic onsets
- Chords + holds + slides — up to 3-note chords, sustained holds, and slide notes that transition between lanes (Hard / Expert)
- Rich visuals — half-block rendering with sub-pixel note motion, particle physics, a starfield that warps into streaks when the song gets loud, vignette, live spectrum bars, beat-synced receptor glow, chromatic-aberration flash on Perfect hits, a live early/late hit-error meter, waveform song-progress preview in the HUD, tear-free synchronized terminal output
- Song preview — song select plays the hottest 20 seconds of the highlighted track, with a tiny live level meter next to it
- Star ratings — every difficulty gets a ★ rating from peak note density, jacks, holds and slides, shown in song select and
cascade song - Synthesized hit feedback — every judgement has its own click; menu navigation has its own sound
- Modifiers — Hidden, Flashlight, Sudden Death, Perfect Only. Each combo gets its own best-score slot
- Drain mode — optional survival variant where health bleeds over time; only Perfects meaningfully restore it
- Practice mode — loop any section at any speed from 0.25× to 2.0×, perfect for drilling that one run you keep bailing on
- Replay ghosts — every scored run records timing events; race an old run with
cascade replay <run-id> - Results breakdown — timing-spread histogram, accuracy-over-the-song strip, early/late bias, roughest section, and miss-heavy lane immediately after a run
- Song packs — bundle multiple
.cscdshare packages into one.cpack - Achievements — 12 unlockables for combos, full clears, mod runs
- Stats dashboard — aggregate play history with a 30-day accuracy sparkline and activity heatmap, both in the UI and via
cascade stats - Themes — five built-in palettes (Classic / Neon / Mono / Sunset / Ocean); switch instantly from Settings
- Best scores — persisted per song / difficulty / mod combo
- Calibration — built-in metronome calibrator removes audio latency drift
- Terminal-native — no Electron, no GPU, ~10 MB binary
brew install glebmatz/cascade/cascadecurl --proto '=https' --tlsv1.2 -LsSf https://github.com/glebmatz/cascade/releases/latest/download/cascade-installer.sh | shirm https://github.com/glebmatz/cascade/releases/latest/download/cascade-installer.ps1 | iex# Full compile from source
cargo install cascade-rhythm
# Prebuilt binary via cargo-binstall (much faster)
cargo binstall cascade-rhythmgit clone https://github.com/glebmatz/cascade
cd cascade
cargo build --release
./target/release/cascade# 1. Import a song. ID3 tags are read automatically; beatmaps for all
# 4 difficulties are generated. Pass a directory to batch-import every
# supported audio file under it.
cascade add ~/Music/my-favorite-song.mp3
cascade add ~/Music/ # batch, recursive
# 2. Check what's imported (with best scores per difficulty).
cascade list
# 3. Launch straight into gameplay.
cascade play my-favorite-song --hard
# 4. Push yourself with modifiers. Each (difficulty + mod combo) gets
# its own best-score slot.
cascade play my-favorite-song --hard --mods hd,sd
# Fix a typo in tags later:
cascade rename my-favorite-song --title "Numb" --artist "Linkin Park"
# Detailed stats for one song (all difficulties + mod combos):
cascade song my-favorite-song
# Track unlocks:
cascade achievements
# Aggregate play stats (top songs, 30-day accuracy sparkline, activity heatmap):
cascade stats
# Replay against a previous run:
cascade history
cascade replay 662f0f00-0004
# …or just run `cascade` for the full interactive UI.
cascadeThe first thing you should do on a new machine is calibrate. Audio output has 20–80 ms of platform-dependent latency and the game needs to know how much to compensate.
Main menu → Settings → Calibrate Audio
Tap Space on the beat for ~16 beats. The game takes the IQR-trimmed
median of your timing errors and stores it as offset_ms. Takes 15 seconds
once, and every hit after that will feel honest. The offset shifts both
judging and the note highway, so notes reach the hit line exactly when you
hear them.
Upgrading from 0.9 or older? Recalibrate once: input is now timestamped the moment it arrives instead of once per frame, and the calibrator measures against the same audio clock as gameplay.
| Key | Action |
|---|---|
| D F Space J K | Lanes 1 – 5 (hold for hold notes) |
| Esc | Pause |
| Q | Quit to song select (while paused) |
| Key | Action |
|---|---|
| ↑ / ↓ or K / J | Move cursor |
| Enter | Confirm |
| Esc | Back |
| Tab | Cycle difficulty (song select) |
| s | Cycle sort: Title / Artist / Recently added / BPM (song select) |
| m | Toggle modifiers overlay: Hidden / Flashlight / Sudden Death / Perfect Only |
| p | Toggle practice overlay: loop a section, adjust speed |
| / | Search (song select) |
| r | Rename selected song (song select) |
| i | Import audio file (song select) |
| x | Delete song (song select) |
Cascade degrades gracefully across terminals. The table below notes the minimum level you need for each feature.
| Terminal | Truecolor | Kitty kbd protocol | Hold notes |
|---|---|---|---|
| Kitty | ✅ | ✅ | ✅ |
| WezTerm | ✅ | ✅ | ✅ |
| foot | ✅ | ✅ | ✅ |
| Alacritty 0.13+ | ✅ | ✅ | ✅ |
| iTerm2 | ✅ | ❌ | |
| Terminal.app | ✅ (Sonoma+) | ❌ | |
| Windows Terminal | ✅ | 🔄 (partial) | |
| tmux | passthrough | passthrough | depends on host |
If hold notes are important to you and you're on macOS, Kitty and WezTerm are both free and excellent.
Config lives at ~/.cascade/config.toml — edit directly, or use the
in-game Settings screen.
[gameplay]
scroll_speed = 1.0 # 0.5 – 2.0
difficulty = "hard"
health_enabled = true
drain_mode = false # continuous health drain; Perfects restore
holds_enabled = true
[keys]
lanes = ["d", "f", " ", "j", "k"]
[audio]
volume = 0.8
offset_ms = 0 # set by calibrator
[display]
theme = "classic" # classic / neon / mono / sunset / oceanImported songs + generated beatmaps + best scores + play history live under
~/.cascade/songs/, ~/.cascade/scores.json, ~/.cascade/achievements.json,
and ~/.cascade/play_history.json.
Press m in song select to toggle modifiers, or pass --mods on the
CLI. Each (difficulty + mods) combination gets its own best-score slot, so a
clean Hard run and a Hard + Hidden + Sudden Death run are tracked separately.
| Code | Name | Effect |
|---|---|---|
hd |
Hidden | Notes appear only close to the hit zone. |
fl |
Flashlight | Highway is dark except a narrow band around the hit zone. |
sd |
Sudden Death | The first miss ends the run immediately. |
po |
Perfect Only | Anything below Perfect counts as a Miss. |
cascade play my-favorite-song --hard --mods hd,flStuck on one part of a song? Practice mode loops a section of your choice at a speed of your choice. Score and achievements are not saved while practising — it's for learning, not for scoreboards.
# Loop 1:30 → 2:00 at 70% speed until it clicks.
cascade play my-favorite-song --section 1:30-2:00 --speed 0.7
# Full song, slowed down.
cascade play my-favorite-song --speed 0.5
# Any difficulty still applies.
cascade play my-favorite-song --hard --section 1:30-2:00In song select, press p to open the practice overlay. Tab cycles fields, ←/→ nudges speed by 0.05, C clears, Enter applies, Esc closes. The practice badge stays visible in song select so you don't forget it's on, and mirrors in the top HUD during gameplay.
Practice mode silently disables modifiers for the run — by design, distractions are not what you want while learning a pattern.
- Slowing down lowers the pitch (
rodiodoesn't time-stretch). - The loop seek produces a tiny (~50 ms) audible gap — fine for learning, not designed for perfect-timing play.
Twelve unlockable achievements ship with the game — full combos, grade
milestones, and clearing songs with each modifier. The unlock list lives at
~/.cascade/achievements.json and you can see your progress with:
cascade achievementsFive palettes ship with the game and recolor lane backgrounds, notes, hit bursts, judgement splashes, and hit particles.
| Slug | Vibe |
|---|---|
classic |
Default bright rainbow (red / green / yellow / blue / purple) |
neon |
Cyberpunk — hot pink, cyan, electric violet |
mono |
Grayscale, minimal, focused |
sunset |
Warm gradient of crimson / orange / amber / rose |
ocean |
Cool aqua / seafoam / deep blue / indigo |
Pick one in Main menu → Settings → Theme. D/F cycles
backward, J/K forward, Enter also advances.
Changes apply instantly — no restart — and persist to
~/.cascade/config.toml under display.theme.
Drop a .toml file into ~/.cascade/themes/ and it shows up next to the
built-ins in the Settings cycle. Built-in slugs (classic, neon, mono,
sunset, ocean) are reserved — conflicting slugs are ignored. Each color
is an [r, g, b] triple in 0..=255.
# ~/.cascade/themes/cherry.toml
slug = "cherry"
name = "Cherry Blossom"
# 5 lanes, in order: D / F / Space / J / K
lane_colors = [
[250, 200, 210],
[255, 170, 190],
[240, 150, 180],
[200, 120, 170],
[160, 90, 150],
]
# Warm tint added to the highway as combo rises.
combo_heat = [255, 150, 180]
# Splash colors for Perfect / Great / Good / Miss.
judgement = [
[255, 220, 230],
[230, 180, 200],
[180, 150, 170],
[200, 80, 120],
]
# Hit-particle colors for Perfect / Great / Good.
particle = [
[255, 220, 230],
[230, 180, 200],
[200, 160, 180],
]Check that your files are valid with:
cascade themesIt lists built-ins + user themes and reports per-file issues (bad TOML, wrong array length, slug conflicts, duplicates).
Every non-practice run is logged to ~/.cascade/play_history.json. Runs now
include replay timing events: note index, lane, input time, calibrated offset,
judgement, and event kind. Open the
Stats entry from the main menu, or run:
cascade statsYou get:
- Totals — lifetime plays, time played, notes hit.
- Top 5 songs — ranked by play count; title updates with renames.
- Per-difficulty breakdown — plays, best accuracy, best score, average accuracy.
- 30-day accuracy sparkline — daily average accuracy for the last 30 days rendered as unicode block glyphs. Blanks are days with no plays.
- 30-day activity heatmap — play count per day in five density levels
(
· ░ ▒ ▓ █). - Achievement progress — unlocked out of total.
Practice runs are excluded — they are for learning, not for stats. Replay events make the file scale with note count, but typical libraries still stay small enough to load instantly.
List recent runs with replay ids:
cascade historyThen race one of them as a ghost:
cascade replay 662f0f00-0004The ghost is drawn as small cross markers on the highway using the prior run's actual input timestamps, so early and late hits drift around the live notes instead of snapping to the beatmap. The Results screen also draws a timing-spread histogram (±120 ms, colored by judgement window) and a per-section accuracy strip across the whole song, alongside timing bias, early/late counts, the lane with the most misses, and the roughest 15-second window.
- Novelty: short-time FFT with Hann window (2048 / 512 hop), per-band log-magnitude with running-max whitening, half-wave-rectified spectral flux summed across 8 logarithmic bands. Each frame is timestamped at the hop where new audio entered the window, not at the window start, so onsets land on the sound instead of ~40 ms ahead of it.
- Peak picking: 95th-percentile normalization; local max within ±50 ms;
must exceed
median + 1.5 × MADover a ±200 ms window. - BPM: autocorrelation of the smoothed novelty signal in the 60 – 200 BPM window with a log-tempo prior centred at 130 BPM (keeps 140 – 180 BPM tracks from halving), then refined to a fractional value with parabolic interpolation on a peak at 2 – 8× the beat lag.
- Beat phase: comb cross-correlation at the estimated BPM, then nudged by the median offset of nearby onsets so the grid sits on the music.
- Notes: snapped to the phase-aligned grid only when an onset is already within 12 ms of a grid line (otherwise the onset time is kept, so a slightly-off grid can't drag notes off the beat), filtered per-difficulty by strength; lane chosen from the dominant band with repeat-avoidance hysteresis; per-peak secondary bands (≥50 % of top flux) can produce 2- or 3-note chords on harder difficulties.
- Holds: if the dominant band's whitened energy stays ≥75 % of its peak level for ≥1.5 beats, the note becomes a hold of that duration.
No labeled training data, no downloads, all deterministic. Timing is guarded
by synthetic click-track and kick/hat regression tests in
tests/generator_timing_test.rs. Beatmaps from older generator versions are
regenerated automatically the first time a song is played.
Contributions are very welcome — see CONTRIBUTING.md for the workflow, code style, and testing conventions.
Good places to start:
- Issues tagged
good first issueandhelp wanted - New particle effects, judgement animations, menu screens
- Better onset detection (e.g. CNN-free drum transcription) — the
generator is self-contained in
src/beatmap/generator.rs - Extra terminals validated and added to the compatibility table
Cascade does not bundle audio — but you can share the beatmaps + metadata plus a download link to the original file. The recipient ends up with a fully playable song after one command:
# Author side. The URL is remembered in metadata.json so you only set it once.
cascade add ~/Music/song.mp3 --source-url https://example.com/song.mp3
# Pack everything (metadata + 4 beatmaps + sha256 of the audio) into one file.
cascade export my-song -o my-song.cscd
# Recipient side. Audio is fetched from the URL and hash-verified.
cascade import my-song.cscdIf the package has no source_url, cascade import still installs the
beatmaps and tells you which audio filename to drop into the song folder
manually. If a source_url is present but the SHA-256 doesn't match, the
downloaded file is renamed to audio.mp3.mismatch and the import fails
loudly — beatmap timings are derived from the original file, so a different
encoding will misalign every note.
You can also import directly from a URL without packing:
cascade add https://example.com/song.mp3For multiple songs, export a pack:
cascade pack export "Starter Pack" song-one song-two song-three -o starter.cpackInstall it on another machine:
cascade pack import starter.cpackA .cpack is just a JSON bundle of normal Cascade share packages. Audio is
still fetched per song from each package's source_url, or reported as missing
when no URL is recorded.
Cascade itself does not host any audio. Use this with files you have the right to redistribute (your own work, public-domain tracks, Creative Commons, etc.).
- Online song sharing (upload beatmap JSON, not audio)
- Replay ghosts and run breakdowns
- Multi-song share packs
- Multiplayer via terminal-to-terminal
- Note editor for hand-tuning generated maps
See CHANGELOG.md for release history.
Cascade is dual-licensed under either of
- Apache License, Version 2.0 — LICENSE-APACHE
- MIT license — LICENSE-MIT
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.