Skip to main content

smlab

Python versions PyPI - Version GitHub tag (with filter) License GitHub commits since latest release (by SemVer including pre-releases) CodeQL QA Tests Coverage Status Dependabot Documentation Status mypy uv pytest Ruff Downloads Stargazers pre-commit.ci status Prettier

@Tatsh Buy Me A Coffee Libera.Chat Mastodon Follow Patreon

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 2 when the tempo comes out halved, 0.5 when doubled.
  • --latency 0.060 when every chart is consistently early or late on your setup, since playback latency is a property of the machine rather than the song. SMLAB_LATENCY sets 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

smlab-0.0.1.tar.gz (1.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smlab-0.0.1-py3-none-any.whl (1.5 MB view details)

Uploaded Python 3

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

Hashes for smlab-0.0.1.tar.gz
Algorithm Hash digest
SHA256 7f00287e8dfd3f8ecb3fe6ddd92f4c1c96dffe7002b7e53b4d87557b3ecda978
MD5 ddc3b9a0bffc88d92374fcd78f098af2
BLAKE2b-256 92825c68af42221c9f96801e3e133e58398c1803b933f3a3eff07dfa596dff94

See more details on using hashes here.

Provenance

The following attestation bundles were made for smlab-0.0.1.tar.gz:

Publisher: publish.yml on Tatsh/smlab

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for smlab-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3794566e174656b037dc45be9280fb2227df2912e0ee781ab76c744bc7e3eb90
MD5 96e5f3a08500bf16ca7dbad36a979299
BLAKE2b-256 56fc32e179246ce7cf51c7a75e0ec9b2f3cb859f93e00322d0d2a43fcf58e23a

See more details on using hashes here.

Provenance

The following attestation bundles were made for smlab-0.0.1-py3-none-any.whl:

Publisher: publish.yml on Tatsh/smlab

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page