A metronome that follows a whole piece, not just a single time signature. Handles tempo changes, odd meters, rehearsal marks, repeats, ritardando, accelerando, and practice loops — all defined in a simple text format.
Live: https://togry.github.io/metronome/
npm install
npm run dev # local dev server at http://localhost:5173
npm run build # produces dist/index.html — single self-contained fileThe built dist/index.html can be opened directly in a browser with no
server required.
The simplest use: list the bars you want to drill, with no end marker. The score loops back to m.1 continuously until you stop it.
1| 4/4, 1/4=120
2| 7/8 (2+2+3)
3| 4/4
4| 5/4
That's it — four bars cycling forever. Add a time signature, tempo, or grouping only on lines where something changes.
Each line describes what changes at a given measure:
measure_number separator [rehearsal_mark] time_signature (grouping) 1/note=BPM
The separator between the measure number and the content determines the
barline type (see Repeat notation below). The simplest separator is |
or :, meaning a normal barline.
All fields except the measure number and separator are optional.
| Separator | Meaning |
|---|---|
| or : |
Normal barline |
|: |
Open repeat |
:| |
Close repeat |
|:| |
Single-measure repeat |
|| |
Double barline / section boundary (last one ends the score) |
||: |
Double barline + open repeat |
:|| |
Close repeat + double barline |
1|: 4/4, 1/4=90 open repeat at m.1
8:| [A] close repeat; m.1-8 plays twice; rehearsal mark A
9|: $ 7/8 (2+2+3) open repeat; segno ($) at m.9
16:|| [B] close repeat; double barline; rehearsal mark B
17|: 4/4 open new repeat
24:| @ close repeat; coda jump point (@) for D.S.
25| [C] 1/4=120 new section, faster
32| DS al Coda jump to $, and on return bail out at @
33| Coda coda section starts here
36|| end of score
Putting the measure number in brackets is equivalent to supplying an explicit rehearsal mark with that number as the label:
[12]| 4/4, 1/4=120 same as: 12| [12] 4/4, 1/4=120
Standard signatures like 4/4, 3/4, 6/8, 2/2 work as expected.
For compound meters (6/8, 12/8) the primary beat is the dotted quarter automatically.
For odd meters, specify how beats are grouped within the measure:
| Signature | Grouping | Primary beats |
|---|---|---|
7/8 |
(2+2+3) |
3 beats: ♩♩♩. |
5/8 |
(2+3) |
2 beats: ♩ ♩. |
8/8 |
(3+3+2) |
3 beats: ♩. ♩. ♩ |
Groupings are remembered per time signature. If you write 7/8 (2+2+3) at
measure 4 and bare 7/8 at measure 19, measure 19 automatically inherits
(2+2+3).
Written as 1/note=BPM, e.g. 1/4=120 means quarter note = 120 bpm.
A dotted note value can be written with a period: 1/4.=60 means dotted
quarter = 60 bpm, the natural way to express compound meter tempos.
The tempo stays in effect until the next tempo marking.
Add rit or accel with a target tempo to begin a smooth tempo curve.
The curve spans to the next explicit tempo mark or a tempo.
1| 4/4 1/4=160
3| rit 1/4=60 rit from m.3; arrive at 60 BPM
7| 1/4=60 accel 1/4=160 new base tempo, then accelerate
12| 1/4=160 arrival; accel spreads across m.7-11
Use a tempo to snap back to the tempo that was in effect before the rit/accel:
4| rit 1/4=60
5| a tempo restores the pre-rit BPM
Tempo is interpolated smoothly on every beat, not just once per bar.
The score is pre-expanded at parse time into a flat playback sequence.
Simple repeats — |: opens a repeat section, :| closes it. The section
plays twice.
Single-measure repeat — |:| on a single line repeats just that measure.
D.C. / D.S. al Fine / al Coda
| Directive | Meaning |
|---|---|
DC al Fine |
Jump to m.1; stop when Fine is reached |
DC al Coda |
Jump to m.1; at @ jump to the Coda section |
DS al Fine |
Jump to $ (segno); stop at Fine |
DS al Coda |
Jump to $; at @ jump to the Coda section |
End of score — the last || or matched :| ends playback. Omit it
entirely for a practice loop that repeats from m.1.
Anything from # or // to the end of a line is a comment. Blank lines
are ignored.
# Symphony No. 5 — rehearsal score
1|: 4/4, 1/4=120
16:|| [A] # section A ends here
- ▶ / ◼ — play and stop
- COUNT IN — checkbox to enable a count-in; choose 2, 3, or 4 beats of quarters or eighths
- SUBDIVIDE — primary beats only, or subdivided to 4ths / 8ths / 16ths / 32nds (sub-clicks added only where the beat divides evenly)
- TEMPO slider — 10–150% of written tempo; actual BPM shown next to slider
- BT — Bluetooth latency offset (0–500 ms); compensates for wireless headphone delay so clicks and visual flashes align with what you hear
- ☀ / 🌙 — toggle between dark and daylight colour palettes
| Colour | Meaning |
|---|---|
| 🔴 Red | Measure downbeat |
| 🟡 Amber | Primary beat |
| 🔵 Cyan | Subdivision click |
| Key | Action |
|---|---|
Space |
Play / Stop |
← / → |
Move cursor one measure |
Shift+→ |
Start a loop from cursor, or extend existing loop end |
Shift+← |
Shrink loop end, or start a loop ending before cursor |
Arrow keys and Space are ignored while typing in the score editor.
Shows the full piece with rehearsal marks, time signature changes, and barline markers. Scrolls horizontally for long pieces.
Desktop: click to set cursor · drag to define loop · shift-click to set loop end · playhead tracks position in real time.
Mobile: tap to set cursor · double-tap then drag to define loop · two-finger drag to scroll.
When a loop region is defined, only measures within that region play and repeat. Repeat sections fully inside the loop are honoured (play twice per cycle). The loop end is inclusive.
Click any tile to set the cursor. Active measure highlighted in gold; loop
region in orange. Tiles show time signature, grouping, and structural
markers ($, @, directives).
The built dist/index.html is a single self-contained file — no assets
folder, no server needed.
GitHub Pages (this repo): pushing to main triggers a GitHub Actions
workflow that runs npm run build and deploys dist/ automatically.
Netlify: drag dist/index.html onto app.netlify.com/drop.
Anywhere else: copy dist/index.html to any static file host.
src/
parser.js score text → measures[], seq[], warnings
beatModel.js beat patterns, tempo math (pure functions)
timeline.js timeline event list, loop seq bounds
constants.js palettes, subdivision options, example scores
Metronome.jsx top-level component: all state, scheduler, layout
main.jsx React entry point
components/
ScorePanel.jsx score editor, clear/paste/parse buttons
HelpModal.jsx in-app help overlay
Timeline.jsx timeline strip with markers and playhead
MeasureGrid.jsx measure tile grid