smlab
Generate StepMania dance-single (4-panel DDR) charts from an audio file, using models trained on a
corpus of human-authored simfiles.
This is not a random step generator. The audio is separated into drums, bass, other and vocals, and the chart follows what is actually playing: a model decides where the steps go and a second decides which panels they use, both reading the same encoding of the music.
smlab generate song.ogg -o /path/to/Pack -T "Song Title" -A "Artist"
That writes /path/to/Pack/Song Title/ containing Song Title.ssc and a copy of the audio as
Song Title.ogg. --format sm and --format dwi write the older formats instead.
Only .ssc carries everything the generator works out. .sm has no per-chart tags, so the groove
radar and the chart hash go unwritten, and .dwi additionally cannot indicate a mine, a lift, or a
roll: mines and lifts are dropped and a roll becomes an ordinary freeze.
The chart and offset weights are not bundled: together they run to over 150 MB. They are looked
for on the machine before anything is fetched, so a system package can install them into
/usr/share/smlab/ and nothing is ever downloaded. Otherwise they are pulled from the GitHub
release once, verified against digests shipped in the wheel, and kept in ~/.local/share/smlab/.
smlab weights prints the whole search order. Pass -c to point at a local checkpoints/
directory, or set SMLAB_WEIGHTS_DIR to name another one.
Everything that does not need a model works without them: parsing, timing estimation, playability analysis, chart drawing.
Automatically generated fields
| Field | How it is chosen |
|---|---|
#BPMS |
Folded onset envelope over a 25 s excerpt. Override with --bpm, or --bpm-multiplier 2 when detection lands an octave low. |
#OFFSET |
A model that reads four frequency bands folded onto the bar and picks which of 96 positions holds the downbeat. Override with --offset. |
#TITLE, #ARTIST, #GENRE |
Read from the audio tags when not given. MP3, Ogg, FLAC, M4A, and Opus. |
#SAMPLESTART |
Predicted by a model that scores every measure and picks one. |
#SAMPLELENGTH |
Fixed at 15 s, which 83% of the corpus uses. |
#CREDIT |
The current username. |
| Steps | Placement and selection models, decoded under physical constraints. |
Accuracy
None of these numbers come from the training split. The offset figures cover the songs whose tempo was recovered correctly.
| Task | Result | Previous heuristic |
|---|---|---|
| Tempo within 0.5 BPM | 87.7% | 78.3% |
| Offset within 30 ms | 58.1% | 46.2% |
| Offset, median error | 20.5 ms | 45.5 ms |
| Offset a clean half-beat out | 2.2% | 12.9% |
| Tempo and offset both right | 50.9% of all songs | — |
The median offset error of 20.5 ms is one phase bin, so it is limited by the model's resolution rather than its judgement.
Check the detected timing before trusting a chart. Roughly one song in eight gets the wrong
tempo, and for those the grid is wrong however good the offset is. The confidence figure printed
alongside the detection is a usable signal: values near 1.0 mean the winning tempo barely beat the
runner-up.
Two knobs exist for the common failures:
--bpm-multiplier 2when the tempo comes out halved,0.5when doubled.--latency 0.060when every chart is consistently early or late on your setup, since playback latency is a property of the machine rather than the song.SMLAB_LATENCYsets it once.
Difficulty and rating scales
A rating means nothing without knowing its scale, so --scale picks one: 10 for classic DDR,
15 for In The Groove, 20 for X-era and later (the default). The same number differs sharply
across them — a nine is 4.33 notes per second in ITG, 4.10 on the classic scale, and 3.12 on the
modern one.
smlab generate song.ogg -D Hard -D "Challenge:16" --scale 20
Each difficulty takes an optional rating after a colon; -m sets one for any that do not. Because
the classic scale saturates — corpus charts labelled ten run anywhere from 3.5 to 6.9 notes per
second — --nps bypasses the rating and states the note rate outright.
Keyboard charts versus pad charts
--style decides what the generator is allowed to write, and the same analysis is available for
existing files through smlab analyze.
feet— danceable with two feet. Nothing needs more than two panels at once, no panel repeats faster than a foot can retap, and the note rate stays within human stamina.hands— additionally allows the three and four panel chords common in In The Groove.keyboard— no physical limits, and a separate density scale, because a keyboard chart at a given rating runs roughly twice as dense as a pad chart carrying the same number.
The classifier decides by three separate measures: whether a two-foot assignment exists at all (a dynamic program over foot positions), whether any row needs more than two panels, and whether the note rate over a one-second and a ten-second window exceeds what a dancer sustains. Validated against the corpus, it rates the keyboard megapacks 26.2% keyboard-only against 1.5% for arcade rips, and finds hand-chords in 10.9% of In The Groove charts against 0.0% for arcade.
Chart image
smlab generate song.ogg --image # while generating
smlab image "Song/Song.sm" # from an existing simfile
Pictures go in .images/ inside the song folder, one per chart. PNG by default, --svg for vector.
Timing conventions
Read out of the StepMania source rather than assumed, because a sign error is silent: the chart still loads and plays, merely off-beat forever.
| Tag | Meaning |
|---|---|
#OFFSET (.sm/.ssc) |
Beat 0 occurs at -OFFSET seconds into the audio. |
#GAP (.dwi) |
Whole milliseconds until beat 0, so OFFSET = -GAP / 1000. |
A song whose tempo moves needs more than one #BPMS entry. smlab drift song.mp3 measures the
wander and prints the whole set of tempo segments needed to hold the grid on the music, as a command
line to paste back; --warp SECONDS:BPM on generate places one marker, repeatably, and --warp
with no value fits them for you. That is the Ableton warp workflow: the tool shows the drift, you
decide what to do about it. Freezes are not inserted.
smlab drift song.mp3 --image beats.png draws the whole song in rows with the grid over it, bars
numbered and beats ticked, so drift is visible rather than argued about. Pass --offset when you
know where beat zero is; the automatic answer is read from an onset envelope, which peaks after the
attack rather than on it.
Warping is experimental. Fitting is deliberately reluctant, because where a beat is measured depends on what is playing: percussion entering shifts the measured phase without moving the beat, so even audio rendered at a mathematically exact tempo can look like it warps. A boundary therefore has to shift the grid by more than the tolerance, and a tempo that departs and comes straight back is treated as an artefact rather than as music. An abrupt change is located only to within about six seconds, and nothing in the first six seconds can be placed at all.
Retraining
Only needed to change the models. Separating the corpus into stems is by far the slow part; training the chart model itself takes about 85 minutes on a consumer GPU, and the offset model about 70 seconds.
Separation is an optional dependency, because it pulls in a second copy of torch's ecosystem:
pip install 'smlab[stems]'
smlab scan ~/.project-outfox/Songs -w 16 # index the corpus and its timing
smlab stems # separate and build the training features
smlab vocab -c cache/stems # collect the note-row patterns
smlab train # the chart model
The offset model reads its own smaller cache of onset envelopes and needs no separation:
smlab envelopes && smlab train-offset
Pass -c checkpoints to generate to use locally trained models instead of downloaded ones, and
smlab publish to attach them, with their digests, to a GitHub release.
Development
uv sync --all-groups --all-extras
After making changes:
uv run ruff format . && uv run ruff check . && uv run mypy smlab && uv run pytest
torch is taken from PyPI, whose Linux wheels already carry CUDA, so a machine with an NVIDIA driver uses the GPU without any extra configuration. To build against something else — ROCm, a different CUDA release, or CPU only - install that variant over the top:
uv pip install --torch-backend=rocm6.4 torch
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smlab-0.0.1.tar.gz.
File metadata
- Download URL: smlab-0.0.1.tar.gz
- Upload date:
- Size: 1.6 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f00287e8dfd3f8ecb3fe6ddd92f4c1c96dffe7002b7e53b4d87557b3ecda978
|
|
| MD5 |
ddc3b9a0bffc88d92374fcd78f098af2
|
|
| BLAKE2b-256 |
92825c68af42221c9f96801e3e133e58398c1803b933f3a3eff07dfa596dff94
|
Provenance
The following attestation bundles were made for smlab-0.0.1.tar.gz:
Publisher:
publish.yml on Tatsh/smlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smlab-0.0.1.tar.gz -
Subject digest:
7f00287e8dfd3f8ecb3fe6ddd92f4c1c96dffe7002b7e53b4d87557b3ecda978 - Sigstore transparency entry: 2660643253
- Sigstore integration time:
-
Permalink:
Tatsh/smlab@584c04365d7b42b11361c73ec812b91af192e61f -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/Tatsh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@584c04365d7b42b11361c73ec812b91af192e61f -
Trigger Event:
push
-
Statement type:
File details
Details for the file smlab-0.0.1-py3-none-any.whl.
File metadata
- Download URL: smlab-0.0.1-py3-none-any.whl
- Upload date:
- Size: 1.5 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3794566e174656b037dc45be9280fb2227df2912e0ee781ab76c744bc7e3eb90
|
|
| MD5 |
96e5f3a08500bf16ca7dbad36a979299
|
|
| BLAKE2b-256 |
56fc32e179246ce7cf51c7a75e0ec9b2f3cb859f93e00322d0d2a43fcf58e23a
|
Provenance
The following attestation bundles were made for smlab-0.0.1-py3-none-any.whl:
Publisher:
publish.yml on Tatsh/smlab
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smlab-0.0.1-py3-none-any.whl -
Subject digest:
3794566e174656b037dc45be9280fb2227df2912e0ee781ab76c744bc7e3eb90 - Sigstore transparency entry: 2660643294
- Sigstore integration time:
-
Permalink:
Tatsh/smlab@584c04365d7b42b11361c73ec812b91af192e61f -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/Tatsh
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@584c04365d7b42b11361c73ec812b91af192e61f -
Trigger Event:
push
-
Statement type: