A small XM (Fasttracker II Extended Module) player library. Main features:
-
Small and hackable: many features can be disabled at compile-time, or are optimized out by the compiler if not used. It is easy to bundle libxm in your game, demo, intro, etc.
-
Fairly portable. Minimal dependencies (just libm). No memory allocations. Big-endian compatible (tested on s390x).
-
Reasonable accuracy compared to Fasttracker 2. Deviations from FT2 playback, that aren't obviously bugs in FT2, are also libxm bugs. If you have a module that plays incorrectly, please test it in FT2/FT2clone and open an issue!
-
Can load most XM/MOD/S3M files, however playback accuracy of non-XM is best-effort.
-
Timing functions for synchronising against specific instruments, samples or channels.
-
Samples can be loaded and altered at run-time, making it possible to use libxm with softsynths or other real-time signal processors.
Written in C23 and released under the WTFPL license, version 2.
Libxm comes without any warranty, to the extent permitted by applicable law. In particular,
-
Load untrusted modules at your own risk. While precautions are taken in the loading/parsing code, bugs are likely and a maliciously crafted module file could cause arbitrary code execution, data loss or worse;
-
Load modules with
xm_create_context_from_libxm()
if and only if you usedxm_context_to_libxm()
yourself, as there are no safety checks at all in the loading code. This function is meant for sizecoding, the use case being statically embedding known modules in games, demos, intros and such.
-
Build the library:
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -Bbuild -Ssrc make -C build
-
Build a specific example:
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -Bbuild-FOO -Sexamples/FOO make -C build-FOO
-
To build a shared library and link dynamically, use
cmake -DBUILD_SHARED_LIBS=ON
. -
To see a list of build options, use
cmake -L
orcmake-gui
. -
To use libxm in your program, put these lines in the
CMakeLists.txt
of your project, then#include <xm.h>
:add_subdirectory(/path/to/libxm/src libxm_build) target_link_libraries(my_stuff PRIVATE xm)
libxmtoau
can be compiled (with all playback features enabled) and
crushed to about 4293 bytes (GCC 15.1, x86_64-linux-gnu).
cmake -DCMAKE_BUILD_TYPE=MinSizeRel -DXM_VERBOSE=OFF -DXM_LIBXM_DELTA_SAMPLES=OFF -DXM_LINEAR_INTERPOLATION=OFF -DXM_RAMPING=OFF -DXM_STRINGS=OFF -DXM_TIMING_FUNCTIONS=OFF -DXM_MUTING_FUNCTIONS=OFF -DXM_SAMPLE_TYPE=float -DXM_SAMPLE_RATE=44100 -Bbuild-libxmize -Sexamples/libxmize
make -C build-libxmize libxmtoau
strip -R .eh_frame_hdr -R .eh_frame build-libxmize/libxmtoau
xzcrush build-libxmize/libxmtoau
If you are using libxm to play a single module (like in a demo/intro), disable
features as suggested by libxmize --analyze
to save a few more bytes.
For example, libxmize --analyze mindrmr.xm
suggests -DXM_DISABLED_EFFECTS=0xFFFFD9FBFFDE68E1 -DXM_DISABLED_VOLUME_EFFECTS=0x0CC0 -DXM_DISABLED_FEATURES=0x120037FFFC77EE04 -DXM_PANNING_TYPE=8
. We only want to play the module once and quit, so we can use -DXM_LOOPING_TYPE=1
. Compiling with these new flags, the resulting binary is crushed to 2678 bytes.
-
libxm.js is a very simple XM player/visualiser that runs in a browser (emscripten port).
-
xmgl
is a simple music visualiser that uses OpenGL, GLFW and JACK for very precise audio synchronisation. See a demo here: https://www.youtube.com/watch?v=SR-fSa7J698 -
xmprocdemo
: a simple non-interactive demo that plays back a single module with procedurally generated samples. Somewhat optimized for size. (Dream Candy by Drozerix, public domain. Thank you Drozerix for the great music!) -
libxmize
converts a.xm
module to the libxm format. It is highly non-portable and is meant for static linking and sizecoding (loading code is much shorter and libxm format compresses better). -
libxmtoau
reads standard input (a file generated bylibxmize
) and writes a .AU file to standard output. Somewhat optimized for size, see size section above. You can test it with, for example,libxmize file.xm | libxmtoau | mpv -
.
Here are some interesting modules, most showcase unusual or advanced tracking techniques (and thus are a good indicator of a player's accuracy):
- Cerror - Expatarimental
- Lamb - Among the stars
- Raina - Cyberculosis
- Raina - Slumberjack
- Strobe - One for all
- Strobe - Paralysicical death
- Set channel panning (E8y; not in base FT2) is supported for XM/MOD
- Can be disabled via
EFFECT_SET_CHANNEL_PANNING
- Can be disabled via
- Glissando control (E3y) with Amiga frequencies is not yet supported
- Arpeggios after pitch slides with Amiga frequencies are subtly incorrect
- Amiga filter toggle (E0y) is not supported, and is unlikely to be
- Invert loop / funk repeat (EFy) is not supported, and is unlikely to be
- Period wraparound after a long slide down (with eg, 2xx) is not accurate
- Global volume effects (Gxx/Hxy) are subtly incorrect
- Tone portamento (3xx/Mx) does not "lock" its direction
- Arpeggio (0xy) does not reset vibrato offset (Vy) when Spd=1
- (MOD only) Sample offset (9xx) beyond sample loop end will cut the note
- Can be manually toggled with
FEATURE_ACCURATE_SAMPLE_OFFSET_EFFECT
- Can be manually toggled with
- (S3M only) Sxy has incorrect memory semantics
- (S3M only) Note cut (SCy), finetune (S2y), tone portamento (Gxx), waveform control (S3y/S4y) effects are implemented incorrectly
- (S3M only) Old stereo control (SAy) might not behave correctly
- (S3M only) 16 bit samples are supported (not in base ST3)
- (S3M only) Stereo samples are not supported (not in base ST3)
To report more, please open an issue.
Some test XM files are in the tests
directory.
A few tests can be automatically run (failing tests marked XXX are not regressions, but bugs/inaccuracies that have yet to be fixed):
cmake -Bbuild-tests -Stests
make -C build-tests all test
Other tests require manual checking, see the table below.
Test | Status | Tested against | Extras
--------------------------------+----------------+------------------------+------------------------------------------------
autovibrato-triggers.xm | MOSTLY | FT2clone 1.94 | Should sound identical. Use a spectrogram as it is very hard to hear subtle changes in pitch.
pattern-loop-quirk.xm | PASS | MilkyTracker | Should play the same notes at the same time.
pos_jump.xm | PASS | Milkytracker, OpenMPT | Only one beep should be heard.
ramping.xm | PASS | FT2clone 1.94 | If XM_RAMPING is ON, output should be mostly frame for frame identical.
waveform-control-autovibrato.xm | PASS | FT2clone 1.94 | Should sound identical. Patterns 0 and 1 should also sound identical. Use a spectrogram as it is very hard to hear subtle changes in pitch.
waveform-control-combo.xm | PASS | FT2clone 1.94 | Should sound identical.
waveform-control-tremolo.xm | PASS | FT2clone 1.94 | Should sound identical.
waveform-control-tremolo.s3m | FAIL | Scream Tracker 3.21 | Should sound identical (except random waveform bits).
waveform-control-vibrato.xm | PASS | FT2clone 1.94 | Should sound identical.
Thanks to:
-
Thunder [email protected], for writing the
modfil10.txt
file; -
Matti "ccr" Hamalainen [email protected], for writing the
xm-form.txt
file; -
Mr.H of Triton and Guru and Alfred of Sahara Surfers, for writing the specification of XM 1.04 files;
-
All the MilkyTracker contributors, for the thorough documentation of effects;
-
Vladimir Kameñar, for writing
The Unofficial XM File Format Specification
; -
All the people that helped on
#milkytracker
IRC; -
All the libxm contributors.