Simple script to count symbols in mscz, mscx & musicxml files
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-28 09:35:09 +02:00
scorecount v2.3.0 2026-09-28 09:35:09 +02:00
tests v2.3.0 2026-09-28 09:35:09 +02:00
.gitignore v2.0.0 2026-09-28 09:22:16 +02:00
CHANGELOG.md v2.3.0 2026-09-28 09:35:09 +02:00
count_symbols.py v2.0.0 2026-09-28 09:22:16 +02:00
README.md v2.3.0 2026-09-28 09:35:09 +02:00
requirements.txt v2.0.0 2026-09-28 09:22:16 +02:00

count_symbols

Counts notational elements in MusicXML scores (notes, articulations, dynamics, lyrics, etc) and turns them into weighted points. Intended for pricing engraving and transcription work per element rather than per page or per hour.

Install

pip install -r requirements.txt

Requires Python 3.8+.

Input

MusicXML only: .musicxml, .xml, .mxl. Export from your notation program first (MuseScore: File > Export > MusicXML; Sibelius: File > Export > MusicXML). Native .mscz, .mscx and .sib files are rejected with a hint.

Usage

python3 count_symbols.py score.musicxml
python3 count_symbols.py *.musicxml
python3 count_symbols.py -l score.musicxml              # list parts with points
python3 count_symbols.py score.musicxml -p Clarinet     # one part
python3 count_symbols.py score.musicxml -p 2 -m 20-40   # part 2, bars 20-40
python3 count_symbols.py score.musicxml --rate 2 --currency CZK   # with price

Results are printed to the terminal. Add --csv to also write a <name>_report.csv next to each input (with -p / -m the filter is added to the file name so reports do not overwrite each other), or --out PATH to choose the file.

Options

-p, --part PART     count only this part: 1-based index, id (P1) or name
                    (exact, then substring); repeat for several parts
-m, --measures R    count only this range: 20-40, 20-, -40 or 25 (inclusive,
                    uses the printed measure numbers)
-l, --list-parts    list parts with point totals and exit
--no-system         do not add the first part's system-level items to other
                    parts (see below)
--rate AMOUNT       price per point, e.g. 0.35 or 0,35; prints a price
--currency CODE     currency for --rate, e.g. EUR or CZK (required with --rate)
--decimals N        decimal places in the price (default depends on currency)
--csv               also write a CSV report next to each input
--out PATH          write the CSV report to PATH (implies --csv; single
                    input file only)
-v, --verbose       also print a per-item breakdown
-q, --quiet         print only the grand total
--version

Multi-staff instruments (piano) are one part; both staves are counted. Exit code is 1 if any file failed.

Pricing

--rate is the price per point and --currency is required with it, so a price is never shown with a guessed currency. Price is points x rate, computed in exact decimal arithmetic and rounded half up (24.675 becomes 24.68) to the currency's usual number of decimals: 0 for CZK, HUF, JPY, KRW, ISK, CLP, VND; 2 for EUR, USD, GBP, PLN, CHF and anything not listed. --decimals N overrides it. The rate accepts a decimal point or comma.

To avoid retyping, set defaults in your shell profile:

export SCORECOUNT_RATE=2
export SCORECOUNT_CURRENCY=CZK

Price appears after the point total, in the -l part list (one price per part), in the multi-file summary, and as a PRICE row in the CSV. The price for several files is computed from their total points, not from the sum of the rounded per-file prices.

System-level items in full scores

MuseScore (and other programs) write system-level items, meaning rehearsal marks, tempo, segno/coda/D.S. and volta endings, into the top part only. A part exported on its own gets them, so counting one part out of a full score would undercount it.

With -p (and in -l), every part other than the first therefore also gets the first part's system-level items that it does not already have at the same measure, so each part is counted as if exported on its own. The added amount is printed, and shown as ... (system, from part 1) rows in -v and the CSV. Use --no-system to turn this off. Without -p the whole score is counted once, as is.

System-level items are recognised by system="only-top" on a direction, by <sound> attributes such as segno, dalsegno, coda or tempo, and by volta endings. Limitation: if a part has its own item with the same type in the same measure as a system item, the system item is treated as already present.

What is counted

Weights live in scorecount/rules.py and can be edited there.

  • note (0.5): notes and rests
  • symbol (1): articulations, ornaments, technical marks, slurs, ties, tuplets, fermatas, accidentals, clefs, key/time signatures, hairpins, octave lines, pedal, segno/coda, repeat and other special barlines, volta endings
  • text (2): dynamics, expression / tempo text, metronome marks, rehearsal marks, chord symbols, lyric syllables

Spanning symbols are counted once, at their start. Plain barlines and the automatic final barline are not counted. Any element in a place where symbols live but not listed in rules.py is reported as "not counted" so gaps are visible.

CSV output

With --csv / --out, columns are: category, label, count, weight, points, then category totals, a grand total, and per-part totals when more than one part is counted.

Tests

python3 -m unittest discover -s tests

Layout

count_symbols.py        entry point
scorecount/
  rules.py              weights and element vocabulary
  loader.py             reading MusicXML, part and range selection
  counter.py            counting logic
  report.py             CSV and console output
  cli.py                argument handling
tests/