xmrsplayer 0.11.1

XMrsPlayer is a safe no-std soundtracker music player
Documentation

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

cargo add xmrsplayer

Minimal example (desktop, std):

use xmrs::prelude::*;
use xmrsplayer::prelude::*;

let bytes = std::fs::read("song.xm")?;
let module = Module::load(&bytes)?;

// 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 = XmrsPlayer::new(&module, 48_000.0, /* song */ 0);
player.set_amplification(1.0);        // unity by default; raise for more presence
player.set_max_loop_count(1);         // 0 = loop forever

// The player is an Iterator<Item = f32> producing interleaved L/R samples.
for sample in player.by_ref().take(48_000 * 2) {
    // hand `sample` to your audio backend (cpal, rodio, hound, ...)
}

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 = XmrsPlayer::new_with_voice_pool_capacity(
    &module, 48_000.0, /* song */ 0, /* capacity */ 1024
);

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
cargo build --no-default-features --features "import_xm" --release

# Embedded build with multiple importers
cargo build --no-default-features --features "import_mod import_xm" --release

# Desktop build (default)
cargo build --release

Installing the CLI player

The demo feature ships a standalone CLI player (xmrsplayer) built on cpal.

From crates.io:

cargo install xmrsplayer --features demo

From a local checkout:

cargo install --path . --features demo

Run it directly from the git repo:

cargo run --features demo --release -- --help

Typical invocation:

xmrsplayer -f song.xm                 # play
xmrsplayer -f song.xm -o song.wav     # render to WAV
xmrsplayer -f song.xm -a 0.25 -l 1    # one loop, amplification 0.25

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 xmrs are #![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::tables for bit-numerical comparison against reference players.
  • FT2/XM quirks are format-driven. Setting module.format = ModuleFormat::Xm activates the canonical FT2 reference behaviour (arpeggio LUT, E60 pattern-loop bug, arpeggio period clamp, portamento-down signed-overflow, K00-eats-note, vol-col Bx advancing vibrato). There is no separate ft2_quirks flag — 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.