Fretwork is an analysis tool to calculate difficulty values for Full Band (Guitar / Bass / Keys / Drums / Vocals) from chart & midi files (Guitar Hero / Rock Band / Clone Hero / YARG) using metrics derived directly from the charted notes (See Methodology.md for details)
Explainer video with some historical context
Libraries required: pandas, numpy, tqdm, mido, matplotlib, and openpyxl
To use the tool setup config and run these in order:
- Build - Scan a library, save everything into a cache file, & create a backup of original difficulties
- Analyze - Turn Build's cache into an .xlsx spreadsheet including song metadata and calculated metrics for every song/instrument combo.
Optionally, applies calculated difficulty to
song.inifiles for use in-game, or restores them back to their originals from the backup - Render - Output a PNG graph of metrics over time for one or more song/instrument combos based on a retrieval code from the spreadsheet
- 1. Setting up Config
- 2. Building a cache
- 3. Analyzing a cache
- 4. Rendering song graphs
- 5. Fixes/Extension Ideas
- License
Before running anything, open config.py and check these values:
| Setting | What it does | Example |
|---|---|---|
SEARCH_PATH |
Sets the folder to cache/analyze | r"C:\Users\[user]\Documents\Clone Hero\Songs" |
HEADER |
A short name for the library, becomes the prefix on every output file | "Library" |
DIFF_WRITE_MODE |
Change from None to allow Analyze to write/restore your song.ini files |
"CalcTier" |
SEARCH_PATH - this tool has only been tested on windows devices, but should work on Mac/Linux with OS-correct file paths.
HEADER is how Build defines a cache of song data, and how Analyze & Render search that cache. If you keep multiple libraries, give each one its own HEADER.
All outputs are named: {header}_{kind}_{timestamp}.{ext}
ex. Library_cache_08052026-0330.pkl, Library_metrics_08052026-0330.xlsx.
Render appearance settings
Under RENDER_DEFAULT and RENDER_THEMES, you can tweak how render.py's PNGs look:
mode:"dark"or"light"to set overall color theme- adjust hex value colors
build.py walks SEARCH_PATH, finds every song.ini, notes.chart, and notes.mid, reads them, and writes one cache file containing every song's note timing and metadata. A CSV containing per instrument original difficulties is also saved.
By default this will run on the SEARCH_PATH & HEADER set in the config.
If you ran a prior version, you will need to rebuild your cache with the addition of Drums & Vocals
Outputs in /caches folder:
{header}_cache_{timestamp}.pkl: The main output used by Analyze and Render{header}_errors_{timestamp}.csv: Only generated if some songs failed to parse, this lists which file failed and why (e.g. missing valid instruments, corrupt midi file){header}_BackupData.csv: A backup that stores all difficulties that were found at the time of building
Cache files are Python pickles - loading one can run code, so only load caches you built yourself for safety.
Optional arguments:
--search-path: scan a different folder than the one inconfig.py--header: name this run something other thanconfig.header
analyze.py loads the most recent cache for your config's HEADER, computes difficulty metrics for every song/instrument/selected level combo, and writes a .xlsx spreadsheet. This is the main output for browsing the library.
Optionally, analyze.py can also update each instrument's song.ini diff_* tag for use in-game. You can also restore all of them to the original assigned value. This option runs via args or DIFF_WRITE_MODE in the config.
Outputs in /metrics folder:
An .xlsx spreadsheet named {header}_metrics_{timestamp}.xlsx with:
- One tab per instrument group that has data in the cache, filterable by
E/M/H/Xlevels - Retrieval codes - an 8-digit song hash plus level(
E/M/H/X) and instrument (G/C/R/B/K/D/V), ex.04821993XGfor an Expert Guitar song - Metadata - Song Title, Artist, Level, Type (Instrument), Charter, Release/Source, Difficulty (song.ini diff tags)
- D scores & updated Remap/CalcTier numbers
Using XLSX_LEVELS in the config you can adjust the mix of Easy/Medium/Hard/Expert you want in the sheet.
The raw formula components are dropped by default but they can be included as hidden columns by using EXTRA_METRICS = True in the config.
Full D formula, Remap tables, & CalcTier detail in Methodology.md
In the metrics spreadsheet / render header, you'll see D translated two ways:
- RemapDiff (0–6): A manual grouping, calibrated to roughly match the percentage of official releases across the tiers. Capped at 6.
- CalcTier: A continuous, log-scaled tiering calculation. This value is not capped, so tiers can extend well past 6 as songs get harder.
Optional arguments:
--header: analyze a different library's most recent cache--cache: point at a specific cache file, instead of most recent for the header (a bare filename is looked up incaches/)--diff-mode:CalcTier,RemapDiff,Restore, orNone(not case sensitive)CalcTier/RemapDiffwrites selected value into every song's owndiff_*tag, per instrumentRestorereturns every instrument'sdiff_*values back to its{header}_BackupData.csvoriginal, throws errors for songs moved/deletedNoneleaves everysong.inialone for this run, even ifDIFF_WRITE_MODE/DIFF_WRITE_OVERRIDESwould write
--xlsx-levels: which EMHX levels go in the spreadsheet for this run, e.g.X,EX,EMHX, orALL(default:XLSX_LEVELSin the config). This only filters rows -song.iniwrites use the Expert anchor either way
Per-instrument exceptions: DIFF_WRITE_OVERRIDES in the config lets individual instruments use a different mode than --diff-mode/DIFF_WRITE_MODE, or skip writing.
- Overrides still apply when
DIFF_WRITE_MODEisNone- only the listed instruments are written Restorealways restores every instrument and ignores overrides- An unknown mode or instrument key stops Analyze before anything runs
How writes stay safe:
- The spreadsheet is saved first,
song.inifiles are written last - A value is only written where
{header}_BackupData.csvalready holds that song/instrument's original, anything else is reported asnot backed up (skipped) - Values that match the write aren't rewritten, so repeat runs report them as
unchanged - Band is only written for songs with a Band row (2+ core instruments with an Expert D score)
Upgrading from an earlier version: Build now fills blank backup cells (e.g. the
diff_vocalscolumn added with Vocals) from the currentsong.ini. If you already wrote Vocals difficulties with an earlier version, those written values will be captured as the "original". Restore first with the old version before rebuilding.
Note: After updating song.ini data, you MUST SCAN SONGS for the new metadata to work.
python render.py [retrieval code]
render.py draws one PNG graph of difficulty over time for a specific song/instrument/level combo, using its retrieval code.
Make sure the header in config matches the spreadsheet/library you are rendering from.
You can render several at once, any mix of instruments and levels:
python render.py 04821993EG 71620045MB 09933120HD 23859937XV
Or from a text file, one code per line:
python render.py --codes-file picks.txt
Outputs:
One PNG per code, named {code}_{Artist} - {Song}.png, showing:
5 Fret
- D - overall difficulty over time
- Notes - note density per second
- Variability - how much the fret pattern is changing per second
Drums
- D - overall difficulty over time
- Hands - Hand note density per second
- Travel - how much movement across the pads is happening per second
- Kicks - Kick note density per second
Vocals
- D - overall difficulty over time
- Pitch - Pitch movement density per second
- Syllables - Sung notes + Talkies per second
- Percussion - Percussion hit density per second (only rendered if present)
Graphs are available in light or dark mode depending on the config.
Optional arguments:
--header/--cache: pick which library/cache to pull from--out-dir: where to save the PNGs (defaults torender_dirinconfig.py)
Fixes:
- Midi files misbehaving - possibly parser drift / file corrruption/truncation?
Extension Ideas:
- Vocal harmonies (
HARM1-HARM3) - Pro Instruments
- Scoring by totals (as opposed to average), type of notes (singles by type/state, chords by type)
- D by section + Section names for renders
- Including strum/hopo/tap state by note in the cache
- Actually doing something with note state once it exists
- Star Power Difficulty (how hard are SP phrases to hit?)
- Rhythm changes/variability possibly easier than pattern recognition?
- Pattern recognition (chords, trills, runs, zigs, quads, quints, anchoring, etc) / Ngrams
- A strain-based difficulty metric splitting strum vs fret
- DDR Groove Radar style scoring (probably tied to patterns)
MIT - see LICENSE for details.
