Skip to content

Artefact2/libxm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

libxm

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.

Disclaimer

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 used xm_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.

Building

  • 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 or cmake-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)
    

Size

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.

Examples

  • 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 by libxmize) 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):

Known inaccuracies

  • Set channel panning (E8y; not in base FT2) is supported for XM/MOD
    • Can be disabled via EFFECT_SET_CHANNEL_PANNING
  • 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
  • (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.

Tests

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

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.

About

A small XM (Fasttracker II Extended Module) player library.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 7