XMrsPlayer — a safe, no_std soundtracker music player
XMrsPlayer plays real soundtracker modules, on desktops and on tiny embedded targets alike. The crate is 100% safe Rust (#![forbid(unsafe_code)]) and builds on no_std; it reuses the data structures defined by the companion xmrs crate.
It does not aim for bit-exact compatibility with every tracker ever written — that target is unreachable given the historical and ever-shifting nature of the demo scene — but it aims to be close enough to amaze you. :-)
Supported formats
| Format | Status |
|---|---|
| Amiga Module (MOD) | Works |
| FastTracker XM | Works |
| ScreamTracker S3M | Works |
| Impulse Tracker IT | WIP — loader mostly done, playback in progress |
| SID (C64) | Experimental — reverse-engineered from Rob Hubbard's 6510 player |
A word on SID: tracks and effects can already be extracted from several of Hubbard's hits and played back through the generic module engine. One thing is still needed to go further — working per-track to reconstruct patterns (some tracks change length, which defeats a naive pattern builder), The module engine is already generic enough; the rest is a matter of time (and motivated contributors — Rust speakers welcome!).
FM instruments are supported by the underlying xmrs data structures and are ready for future formats.
Using it as a library
Minimal example (desktop, std):
use *;
use *;
let bytes = read?;
let module = load?;
// Default voice pool capacity (256, matching schism's MAX_VOICES) —
// see `XmrsPlayer::new_with_voice_pool_capacity` if you need more
// (NNA-heavy modules) or fewer (embedded targets).
let mut player = new;
player.set_amplification; // unity by default; raise for more presence
player.set_max_loop_count; // 0 = loop forever
// The player is an Iterator<Item = f32> producing interleaved L/R samples.
for sample in player.by_ref.take
See the examples/ directory for complete players built on cpal and rodio.
For modules with very heavy NNA polyphony (the Another Life-class
files that stack hundreds of NNA = Continue voices), you can raise
the pool capacity at construction:
let mut player = new_with_voice_pool_capacity;
The default is sized to match how the original trackers behaved — go above it only when you have evidence the song needs it.
The player also exposes a subscription API — hook into row/tick changes with PlayerObserver, into the produced audio stream with MixObserver / ChannelsObserver (VU-meters, WAV recorders, visualisers), or into MIDI events emitted by IT Zxx macros with MidiObserver (bridge to external synths or hardware). See OBSERVERS.md.
Cargo features
xmrsplayer has opt-in features so you only pay for what you use.
Default: ["std", "import"] — the common desktop/server case.
Runtime
| Feature | Effect |
|---|---|
std |
Enable the standard library and forward std + float-helpers to xmrs. The latter exposes f32 ↔ Q conversions on every fixed-point type, which desktop editors / GUIs use for level-meters and numeric inputs. Embedded users skip both with default-features = false. |
demo |
Everything needed to build the example CLI players: pulls in std, import, clap, console, cpal, hound, rodio. |
The whole gain / pitch / LFO / filter / sample chain is integer
Q-format, end-to-end. No libm, no micromath, no num-traits. The
crate compiles no_std out of the box without picking a math
backend — there's nothing to pick.
Format importers (propagated to xmrs)
| Feature | Format |
|---|---|
import |
Umbrella — all of the below |
import_mod |
Amiga MOD |
import_xm |
FastTracker XM |
import_s3m |
ScreamTracker S3M |
import_it |
Impulse Tracker IT |
import_sid |
C64 SID |
Build examples:
# Tiny no_std build, XM only
# Embedded build with multiple importers
# Desktop build (default)
Installing the CLI player
The demo feature ships a standalone CLI player (xmrsplayer) built on cpal.
From crates.io:
From a local checkout:
Run it directly from the git repo:
Typical invocation:
Interactive keys while playing: Space to pause, ←/→ to move, Enter/i for info, Esc or q to quit.
Design notes
- Safety first. Both this crate and its dependency
xmrsare#![forbid(unsafe_code)]. - Dependencies are lean, and gated behind features — this matters not only for embedded.
- Concert pitch by default. Notes resolve to MIDI-aligned frequencies (A-4 = 440 Hz exactly), so tracker output mixes cleanly with any 440-Hz-aligned source. The legacy FT2 / ProTracker reference values (≈ −2 cents in linear mode, ≈ +17 cents in Amiga mode) are still available in
xmrs::fixed::tablesfor bit-numerical comparison against reference players. - FT2/XM quirks are format-driven. Setting
module.format = ModuleFormat::Xmactivates the canonical FT2 reference behaviour (arpeggio LUT,E60pattern-loop bug, arpeggio period clamp, portamento-down signed-overflow,K00-eats-note, vol-colBxadvancing vibrato). There is no separateft2_quirksflag — the format is the switch. - Embedded-friendly. The code should be progressively degradable to older processors through successive optimizations, and it would be a lot of fun to see what LLVM can do with it on float-less architectures like the Z80, the MOS 6502 family, or on the 68k target.
Contributing
The project was written with extension and sharing in mind — feel free to use it for your own projects. If you do, an issue on the codeberg repository mentioning it would be lovely (but not mandatory).
License
MIT — see LICENSE.