System Overview
Harmonicon is a rhythm game for diatonic and chromatic harmonica, written in Rust on top of the Bevy game engine (version 0.19). The player plays a real harmonica into a microphone; the game listens, detects the pitch being played in real time, and scores it against a scrolling chart — the same core loop as any note-highway rhythm game (Guitar Hero, Clone Hero, osu!), except the “controller” is an acoustic instrument and a live audio pipeline instead of a button press.
The workspace shape
Harmonicon is a Cargo workspace (edition = "2024"): fourteen
library crates under crates/, a root package holding the binaries and
the composition root, and one crate above the root for Android. The
layering is not a convention — it is the dependency graph, and Cargo
refuses to express a cycle, so an illegal import doesn’t compile.
That is the whole point of the split, and what
Module Boundaries and Dependency Rules
covers in depth.
src/lib.rs is the composition root: its run() assembles every plugin
and nothing else. It exists because Android never calls a main — it
loads a shared object and calls android_main (see
Android) — so both entry points had to become thin
wrappers around one shared function. src/main.rs (the game itself) and
everything in src/bin/ (hole-editor, note-editor, note-bench,
gen_synthetic_dataset — small developer tools, described in
Testing Strategy and the Song Editor chapter) go through it. Assembly only, never
logic.
The crates
A crate may depend only on ones earlier in this list, and peers may not depend on each other.
Bevy-free layers. Their whole dependency tree is
serde/serde_json/midly, which is why their tests run in seconds
rather than after an engine link. Keeping harmonicon-core free of Bevy
is the single most valuable property of the split; anything needing
Resource/Component/App belongs a level up.
harmonicon-core— music theory, chart types, scoring math, pitch/MIDI conversion, pitch→hole resolution, the harmonica synth, WAV, grid snapping, and the generated training drills.harmonicon-score— reading foreign score files (MIDI, Guitar Pro 3–7, MuseScore, MusicXML) behind oneScoreFiletrait, and converting a track onto a harmonica.harmonicon-dsp— the five pitch detectors (FFT/YIN/pYIN/MPM/NMF) and their windowing.
Shared vocabulary, Bevy-aware but feature-agnostic:
harmonicon-audio—cpalcapture, the ECS wrapper overharmonicon-dsp, waveform analysis.harmonicon-platform— asset discovery and the~/Harmoniconwatcher, settings, localization, theme, responsive layout.harmonicon-song— chart/manifest loading, MIDI-backed songs, and the lessons data layer (manifests, prerequisite graph, unit gates, progress).harmonicon-app— theAppState/MenuPagestate machine, routing flags, player profile.harmonicon-ui—dialogs(buttons, comboboxes, file dialogs, tooltips, scroll areas, the circle-of-fifths and 12-bar-grid teaching widgets), the Bravura notation staff, the spectrogram. Intentionally has no idea what a “song” or a “lesson” is.
Features:
harmonicon-gameplay— the clock, judging, 2D/3D highways, HUD overlays, adaptive difficulty, the Bending Trainer, call-and-response. Also whereAppState::Playing’s schedule is assembled for everyGameplayMode— see the composition-root discussion in Module Boundaries.harmonicon-jam/harmonicon-editor— Jam Session and the Song Editor. Siblings: neither imports the other.harmonicon-menu— the page state machine, routing, shared menu chrome, and the guided tutorial tour.harmonicon-lessons— the lesson tree/reader UI and the pure tree layout.harmonicon-bench— the pitch-detection benchmark and dataset generator (dev tooling).
No re-export facades. A call site names the crate it depends on
(harmonicon_core::chart, harmonicon_gameplay::gameplay::…), so every
dependency is visible where it’s taken. Re-exporting a moved module under
its old path was tried and deliberately removed: it hid which crate code
came from and let modules reach for things casually.
Per-crate architecture notes live in crates/<name>/CLAUDE.md — the
load-bearing facts for one subsystem, alongside its code rather than all
in one place.
The engine and its major dependencies
Beyond Bevy itself, a handful of dependencies carry specific, non-interchangeable responsibilities worth knowing about up front — each gets its own discussion in the chapter its responsibility belongs to:
| Crate | Role | Discussed in |
|---|---|---|
bevy 0.20 | ECS, rendering, UI, audio playback, asset system | throughout |
cpal | Cross-platform microphone capture | Audio Pipeline |
rustfft | FFT for pitch detection and the spectrogram | Audio Pipeline |
midly | MIDI file parsing | Chart Format, Jam Session |
serde_json / jsonschema | Chart/theme/lesson JSON parsing and schema validation | Chart Format |
fluent / fluent_content / fluent-langneg | Fluent-based localization (the Bevy asset layer on top is ours, in localization::ftl) | Localization and Theming |
figment | Layered settings-file loading | Persistence |
notify-debouncer-full | Filesystem watching for ~/Harmonicon | Persistence |
rodio (decode-only) | OGG/WAV waveform pre-analysis | Chart Format |
Where to go next
If you’re orienting yourself for the first time, read The Plugin Architecture and Application States and Modes next — together they describe the skeleton every other chapter’s subsystem hangs off of.