LIBXMP(3)

๐Ÿ’– LIBXMP(3) โ€” Extended Module Player ๐Ÿ’–

okay so bestie, welcome to libxmp, the tracker module player library that's literally the moment. ๐Ÿ’…

โœจ NAME โœจ

libxmp โ€” A tracker module player library. That's the whole vibe. ๐ŸŽ€

๐ŸŒธ Introduction ๐ŸŒธ

Libxmp is a module player library supporting many mainstream and obscure module formats including Protracker MOD, Scream Tracker III S3M and Impulse Tracker IT. (Yes, all the classics, no cap.) Libxmp loads the module and renders the sound as linear PCM samples in a buffer at rate and format specified by the user, one frame at a time (standard modules usually play at 50 frames per second). ๐ŸŽถ

Possible applications for libxmp include stand-alone module players, module player plugins for other players, module information extractors, background music replayers for games and other applications, module-to-mp3 renderers, etc. It's giving versatile. โœจ

๐Ÿง  Concepts ๐Ÿง 

  • Player context: Most libxmp functions require a handle that identifies the module player context. Each context is independent and multiple contexts can be defined simultaneously. (So you can have multiple players going at once, iconic.)
  • Sequence: Each group of positions in the order list that loops over itself, also known as "subsong". Most modules have only one sequence, but some modules, especially modules used in games can have multiple sequences. "Hidden patterns" outside the main song are also listed as extra sequences, certain module authors such as Skaven commonly place extra patterns at the end of the module. (Skaven said "bonus tracks", fr fr.)
  • State: [Added in libxmp 4.2] The player can be in one of three possible states: unloaded, loaded, or playing. The player is in unloaded state after context creation, changing to other states when a module is loaded or played. (Main character energy: unloaded, loaded, playing. Periodt.)
  • External sample mixer: [Added in libxmp 4.2] Special sound channels can be reserved using xmp_start_smix() to play module instruments or external samples. This is useful when libxmp is used to provide background music to games or other applications where sound effects can be played in response to events or user actions. (SFX and BGM together? Obsessed.)
  • Amiga mixer: [Added in libxmp 4.4] Certain formats may use special mixers modeled after the original hardware used to play the format, providing more realistic sound at the expense of CPU usage. Currently Amiga formats such as Protracker can use a mixer modeled after the Amiga 500, with or without the led filter. (Retro vibes, CPU says ouch, worth it.)

โœจ A simple example โœจ

This example loads a module, plays it at 44.1kHz and writes it to a raw sound file:

#include <stdio.h>
#include <stdlib.h>
#include <xmp.h>

int main(int argc, char **argv)
{
    xmp_context c;
    struct xmp_frame_info mi;
    FILE *f;

    /* The output raw file */
    f = fopen("out.raw", "wb");
    if (f == NULL) {
        fprintf(stderr, "can't open output file\n");
        exit(EXIT_FAILURE);
    }

    /* Create the player context */
    c = xmp_create_context();

    xmp_set_player(c, XMP_PLAYER_DEFPAN, 50);

    /* Load our module */
    if (xmp_load_module(c, argv[1]) != 0) {
        fprintf(stderr, "can't load module\n");
        exit(EXIT_FAILURE);
    }

    /* Play the module */
    xmp_start_player(c, 44100, 0);
    while (xmp_play_frame(c) == 0) {
        xmp_get_frame_info(c, &mi);

        if (mi.loop_count > 0)    /* exit before looping */
            break;

        fwrite(mi.buffer, mi.buffer_size, 1, f);  /* write audio data */
    }
    xmp_end_player(c);
    xmp_release_module(c);        /* unload module */
    xmp_free_context(c);          /* destroy the player context */

    fclose(f);

    exit(EXIT_SUCCESS);
}

A player context can load and play a single module at a time. Multiple contexts can be defined if needed. (One module per context, but you can stack contexts like they're limited edition drops.)

Use xmp_test_module() to check if the file is a valid module and retrieve the module name and type. Use xmp_load_module() to load the module to memory. These two calls return 0 on success or <0 in case of error. Error codes are:

-XMP_ERROR_INTERNAL   /* Internal error */
-XMP_ERROR_FORMAT     /* Unsupported module format */
-XMP_ERROR_LOAD       /* Error loading file */
-XMP_ERROR_DEPACK     /* Error depacking file */
-XMP_ERROR_SYSTEM     /* System error */
-XMP_ERROR_STATE      /* Incorrect player state */

If a system error occurs, the specific error is set in errno. (So check that errno, bestie, it's telling you the tea.)

Parameters to xmp_start_player() are the sampling rate (up to 768kHz) and a bitmapped integer holding one or more of the following mixer flags:

XMP_FORMAT_8BIT       /* Mix to 8-bit instead of 16 */
XMP_FORMAT_UNSIGNED   /* Mix to unsigned samples */
XMP_FORMAT_MONO       /* Mix to mono instead of stereo */
XMP_FORMAT_32BIT      /* Mix to 32-bit integer instead of 16 */

After xmp_start_player() is called, each call to xmp_play_frame() will render an audio frame. Call xmp_get_frame_info() to retrieve the buffer address and size. xmp_play_frame() returns 0 on success or -1 if replay should stop. (It said "I'm done", respect it.)

Use xmp_end_player(), xmp_release_module() and xmp_free_context() to release memory and end replay. (Clean up after yourself, queen.)

๐ŸŽง SDL example ๐ŸŽง

To use libxmp with SDL, just provide a callback function that renders module data. The module will play when SDL_PauseAudio(0) is called:

#include <SDL/SDL.h>
#include <xmp.h>

static void fill_audio(void *udata, unsigned char *stream, int len)
{
    xmp_play_buffer(udata, stream, len, 0);
}

int sound_init(xmp_context ctx, int sampling_rate, int channels)
{
    SDL_AudioSpec a;

    a.freq = sampling_rate;
    a.format = (AUDIO_S16);
    a.channels = channels;
    a.samples = 2048;
    a.callback = fill_audio;
    a.userdata = ctx;

    if (SDL_OpenAudio(&a, NULL) < 0) {
            fprintf(stderr, "%s\n", SDL_GetError());
            return -1;
    }
}

int main(int argc, char **argv)
{
    xmp_context ctx;

    if ((ctx = xmp_create_context()) == NULL)
            return 1;

    sound_init(ctx, 44100, 2);
    xmp_set_player(ctx, XMP_PLAYER_DEFPAN, 50);
    xmp_load_module(ctx, argv[1]);
    xmp_start_player(ctx, 44100, 0);

    SDL_PauseAudio(0);

    sleep(10); /* Do something important here */

    SDL_PauseAudio(1);

    xmp_end_player(ctx);
    xmp_release_module(ctx);
    xmp_free_context(ctx);

    SDL_CloseAudio();
    return 0;
}

SDL callbacks run in a separate thread, so don't forget to protect sections that manipulate module data with SDL_LockAudio() and SDL_UnlockAudio(). (Thread safety is not a want, it's a need. ๐Ÿ’…)

๐Ÿ’… API reference ๐Ÿ’…

๐Ÿ’– Version and player information ๐Ÿ’–

const char *xmp_version

A string containing the library version, such as "4.0.0". (Cute, right?)

const unsigned int xmp_vercode

The library version encoded in a integer value. Bits 23-16 contain the major version number, bits 15-8 contain the minor version number, and bits 7-0 contain the release number. (Bit math, but make it fashion.)

int xmp_syserrno()

[Added in libxmp 4.5] Use to retrieve errno if you received -XMP_ERROR_SYSTEM from an xmp function call. Useful if either libxmp or its client is statically linked to libc.

Returns: System errno.

const char *const *xmp_get_format_list()

Query the list of supported module formats.

Returns:

  • a NULL-terminated read-only array of strings containing the names of all supported module formats. (So many formats, so little time. ๐Ÿ”ฅ)

๐Ÿฆ‹ Context creation ๐Ÿฆ‹

xmp_context xmp_create_context()

Create a new player context and return an opaque handle to be used in subsequent accesses to this context.

Returns:

  • the player context handle. (Your new baby. ๐Ÿ‘‘)

void xmp_free_context(xmp_context c)

Destroy a player context previously created using xmp_create_context().

Parameters:

  • c โ€” the player context handle. (Say bye, bestie.)

๐ŸŒˆ Module loading ๐ŸŒˆ

int xmp_test_module(char *path, struct xmp_test_info *test_info)

Test if a file is a valid module. Testing a file does not affect the current player context or any currently loaded module.

Parameters:

  • path โ€” pathname of the module to test.
  • test_info โ€” NULL, or a pointer to a structure used to retrieve the module title and format if the file is a valid module. struct xmp_test_info is defined as:
struct xmp_test_info {
    char name[XMP_NAME_SIZE];      /* Module title */
    char type[XMP_NAME_SIZE];      /* Module format */
};

Returns:

  • 0 if the file is a valid module, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_DEPACK if the file is compressed and uncompression failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).

int xmp_test_module_from_memory(const void *mem, long size, struct xmp_test_info *test_info)

[Added in libxmp 4.5] Test if a memory buffer is a valid module. Testing memory does not affect the current player context or any currently loaded module.

Parameters:

  • mem โ€” a pointer to the module file image in memory. Multi-file modules or compressed modules can't be tested in memory. (So no zip files from RAM, okay?)
  • size โ€” the size of the module.
  • test_info โ€” NULL, or a pointer to a structure used to retrieve the module title and format if the memory buffer is a valid module. struct xmp_test_info is defined as:
struct xmp_test_info {
    char name[XMP_NAME_SIZE];      /* Module title */
    char type[XMP_NAME_SIZE];      /* Module format */
};

Returns:

  • 0 if the memory buffer is a valid module, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).

int xmp_test_module_from_file(FILE *f, struct xmp_test_info *test_info)

[Added in libxmp 4.5] Test if a module from a stream is a valid module. Testing streams does not affect the current player context or any currently loaded module.

Parameters:

  • f โ€” the file stream. Compressed modules that need an external depacker can't be tested from a file stream. On return, the stream position is undefined. Caller is responsible for closing the file stream. (So close it yourself, don't leave it on read. ๐Ÿ™ˆ)
  • test_info โ€” NULL, or a pointer to a structure used to retrieve the module title and format if the memory buffer is a valid module. struct xmp_test_info is defined as:
struct xmp_test_info {
    char name[XMP_NAME_SIZE];      /* Module title */
    char type[XMP_NAME_SIZE];      /* Module format */
};

Returns:

0 if the stream is a valid module, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_DEPACK if the stream is compressed and uncompression failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


int xmp_test_module_from_callbacks(void *priv, struct xmp_callbacks callbacks, struct xmp_test_info *test_info)

[Added in libxmp 4.5] ok bestie this one checks whether a module coming from a custom stream is actually a valid module ๐Ÿ’… And listen โ€” testing custom streams does not touch the current player context or any module you've got loaded. Zero drama, no side effects, we love that for us.

Parameters:

  • priv โ€” pointer to the custom stream. Multi-file modules or compressed modules can't be tested using this function. This should not be NULL. ๐Ÿซถ
  • callbacks โ€” struct specifying stream callbacks for the custom stream. These callbacks should behave as close to fread/fseek/ftell/fclose as possible, and seek_func must be capable of seeking to SEEK_END. The close_func is optional, but all other functions must be provided. If a close_func is provided, the stream will be closed once testing has finished or upon returning an error code. struct xmp_callbacks is defined as:
struct xmp_callbacks {
    unsigned long (*read_func)(void *dest, unsigned long len,
                               unsigned long nmemb, void *priv);
    int           (*seek_func)(void *priv, long offset, int whence);
    long          (*tell_func)(void *priv);
    int           (*close_func)(void *priv);
};
  • test_info โ€” NULL, or a pointer to a structure used to retrieve the module title and format if the memory buffer is a valid module.

struct xmp_test_info is defined as:

struct xmp_test_info {
    char name[XMP_NAME_SIZE];      /* Module title */
    char type[XMP_NAME_SIZE];      /* Module format */
};

Returns:

0 if the custom stream is a valid module, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


int xmp_load_module(xmp_context c, char *path)

Load a module into the specified player context ๐Ÿ”ฅ (Certain player flags, such as XMP_PLAYER_SMPCTL and XMP_PLAYER_DEFPAN, must be set before loading the module, see xmp_set_player() for more information. So like, set your flags first, THEN load. Order matters, bestie, no cap.)

Parameters:

  • c โ€” the player context handle.
  • path โ€” pathname of the module to load.

Returns:

0 if successful, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_DEPACK if the file is compressed and uncompression failed, -XMP_ERROR_LOAD if the file format was recognized but the file loading failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


int xmp_load_module_from_memory(xmp_context c, const void *mem, long size)

[Added in libxmp 4.2] load a module straight from memory into the specified player context ๐Ÿงš memory's giving main character energy, we don't even need a file.

Parameters:

  • c โ€” the player context handle.
  • mem โ€” a pointer to the module file image in memory. Multi-file modules or compressed modules can't be loaded from memory. ๐Ÿ™ˆ
  • size โ€” the size of the module.

Returns:

0 if successful, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_LOAD if the file format was recognized but the file loading failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


int xmp_load_module_from_file(xmp_context c, FILE *f, long size)

[Added in libxmp 4.3] load a module from a stream into the specified player context ๐ŸŒธ

Parameters:

  • c โ€” the player context handle.
  • f โ€” the file stream. On return, the stream position is undefined. Caller is responsible for closing the file stream. (Yes you, you close it. It's your stream, your responsibility, periodt ๐Ÿ’…)
  • size โ€” the size of the module (ignored.)

Returns:

0 if successful, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_LOAD if the file format was recognized but the file loading failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


int xmp_load_module_from_callbacks(xmp_context c, void *priv, struct xmp_callbacks callbacks)

[Added in libxmp 4.5] load a module from a custom stream into the specified player context. it's giving custom, it's giving bespoke, it's giving exactly what you asked for โœจ

Parameters:

  • c โ€” the player context handle.
  • priv โ€” pointer to the custom stream. Multi-file modules or compressed modules can't be loaded using this function. This should not be NULL.
  • callbacks โ€” struct specifying stream callbacks for the custom stream. These callbacks should behave as close to fread/fseek/ftell/fclose as possible, and seek_func must be capable of seeking to SEEK_END. The close_func is optional, but all other functions must be provided. If a close_func is provided, the stream will be closed once loading has finished or upon returning an error code. struct xmp_callbacks is defined as:
struct xmp_callbacks {
    unsigned long (*read_func)(void *dest, unsigned long len,
                               unsigned long nmemb, void *priv);
    int           (*seek_func)(void *priv, long offset, int whence);
    long          (*tell_func)(void *priv);
    int           (*close_func)(void *priv);
};

Returns:

0 if successful, or a negative error code in case of error. Error codes can be -XMP_ERROR_FORMAT in case of an unrecognized file format, -XMP_ERROR_LOAD if the file format was recognized but the file loading failed, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).


void xmp_release_module(xmp_context c)

Release memory allocated by a module from the specified player context ๐ŸŽ€ like, let it go, babe, free the memory, it deserves peace.

Parameters:

  • c โ€” the player context handle.

void xmp_scan_module(xmp_context c)

Scan the loaded module for sequences and timing โฑ๏ธ Here's the tea: scanning is automatically performed by xmp_load_module(), so this function should be called only if:

  • xmp_set_player() is used to change player timing (with parameter XMP_PLAYER_VBLANK) in libxmp 4.0.2 or older, or
  • xmp_set_tempo_factor() is used to change the base tempo factor.

The scan is unaffected by the relative tempo factor set by xmp_set_tempo_factor_relative() ๐Ÿฆ‹ (that one's doing its own thing, minding its own business).

Parameters:

  • c โ€” the player context handle.

void xmp_get_module_info(xmp_context c, struct xmp_module_info *info)

Retrieve current module data ๐Ÿ’– the whole vibe, the whole lore, all of it.

Parameters:

  • c โ€” the player context handle.
  • info โ€” pointer to structure containing the module data. struct xmp_module_info is defined as follows:
struct xmp_module_info {
    unsigned char md5[16];          /* MD5 message digest */
    int vol_base;                   /* Volume scale */
    struct xmp_module *mod;         /* Pointer to module data */
    char *comment;                  /* Comment text, if any */
    int num_sequences;              /* Number of valid sequences */
    struct xmp_sequence *seq_data;  /* Pointer to sequence data */
};

Detailed module data is exposed in the mod field:

struct xmp_module {
    char name[XMP_NAME_SIZE];       /* Module title */
    char type[XMP_NAME_SIZE];       /* Module format */
    int pat;                        /* Number of patterns */
    int trk;                        /* Number of tracks */
    int chn;                        /* Tracks per pattern */
    int ins;                        /* Number of instruments */
    int smp;                        /* Number of samples */
    int spd;                        /* Initial speed */
    int bpm;                        /* Initial BPM */
    int len;                        /* Module length in patterns */
    int rst;                        /* Restart position */
    int gvl;                        /* Global volume */

    struct xmp_pattern **xxp;       /* Patterns */
    struct xmp_track **xxt;         /* Tracks */
    struct xmp_instrument *xxi;     /* Instruments */
    struct xmp_sample *xxs;         /* Samples */
    struct xmp_channel xxc[64];     /* Channel info */
    unsigned char xxo[XMP_MAX_MOD_LENGTH];  /* Orders */
};

See the header file for more information about pattern and instrument data ๐Ÿ“š (fr fr, go read it, the header knows things).


Module playing ๐ŸŽถ

int xmp_start_player(xmp_context c, int rate, int format)

Start playing the currently loaded module ๐Ÿ”ฅ๐Ÿ”ฅ this is the moment, the drop, the whole point.

Parameters:

  • c โ€” the player context handle.
  • rate โ€” the sampling rate to use, in Hz (typically 44100). Valid values range from 8kHz to 768kHz. That's a huge range and honestly, iconic ๐Ÿ‘‘
  • flags โ€” bitmapped configurable player flags, one or more of the following:
  • XMP_FORMAT_8BIT โ€” mix to 8-bit instead of 16 ๐Ÿ’–
  • XMP_FORMAT_UNSIGNED โ€” mix to unsigned samples โœจ
  • XMP_FORMAT_MONO โ€” mix to mono instead of stereo ๐ŸŽง
  • XMP_FORMAT_32BIT โ€” mix to 32-bit integer instead of 16 ๐Ÿ”ฅ

Returns:

0 if it worked, or a negative error code if it did not. Bestie, the codes you might catch are -XMP_ERROR_INTERNAL when the player itself glitches out, -XMP_ERROR_INVALID when the sampling rate you passed is just not it, or -XMP_ERROR_SYSTEM when the system throws hands (and in that case the actual system error code gets dropped into errno).


๐ŸŽต int xmp_play_frame(xmp_context c)

Play exactly one frame of the module. Modules usually play at 50 frames per second, so yeah, that's the vibe. Then you use xmp_get_frame_info() to grab the buffer that's holding all that audio data ๐Ÿ‘‘

Parameters:

  • c โ€” the player context handle.

Returns:

0 if successful, -XMP_END if the module ended or got stopped, or -XMP_ERROR_STATE if the player is not in playing state. ๐Ÿ˜ญ


๐Ÿซถ int xmp_play_buffer(xmp_context c, void *buffer, int size, int loop)

[Added in libxmp 4.1] Fill the buffer with PCM data up to the size you asked for. It's a convenience function, and lowkey it just calls xmp_play_frame() internally to fill the buffer you hand it. ๐Ÿงš

Okay so important: don't call both xmp_play_frame() and xmp_play_buffer() in the same replay loop. Pick a lane, bestie. ๐Ÿ’…

If you don't need equally sized data chunks, xmp_play_frame() may give you better performance. Also ngl, silence gets tacked onto the end of a buffer if the module ends and no loop is going to be performed. ๐Ÿคท

Parameters:

  • c โ€” the player context handle.
  • buffer โ€” the buffer to fill with PCM data, or NULL to reset the internal state.
  • size โ€” the buffer size in bytes.
  • loop โ€” stop replay when the loop counter reaches this value, or 0 to disable loop checking.

Returns:

0 if successful, -XMP_END if the module was stopped or the loop counter got reached, or -XMP_ERROR_STATE if the player is not in playing state. โœจ


๐ŸŒธ void xmp_get_frame_info(xmp_context c, struct xmp_frame_info *info)

Retrieve the current frame data. The tea โ˜•

Parameters:

  • c โ€” the player context handle.
  • info โ€” pointer to the structure that holds the current frame data. struct xmp_frame_info is defined like this:
struct xmp_frame_info {           /* Current frame information */
    int pos;            /* Current position */
    int pattern;        /* Current pattern */
    int row;            /* Current row in pattern */
    int num_rows;       /* Number of rows in current pattern */
    int frame;          /* Current frame */
    int speed;          /* Current replay speed */
    int bpm;            /* Current bpm */
    int time;           /* Current module time in ms */
    int total_time;     /* Estimated replay time in ms*/
    int frame_time;     /* Frame replay time in us */
    void *buffer;       /* Pointer to sound buffer */
    int buffer_size;    /* Used buffer size */
    int total_size;     /* Total buffer size */
    int volume;         /* Current master volume */
    int loop_count;     /* Loop counter */
    int virt_channels;  /* Number of virtual channels */
    int virt_used;      /* Used virtual channels */
    int sequence;       /* Current sequence */

    struct xmp_channel_info {     /* Current channel information */
        unsigned int period;      /* Sample period */
        unsigned int position;    /* Sample position */
        short pitchbend;          /* Linear bend from base note*/
        unsigned char note;       /* Current base note number */
        unsigned char instrument; /* Current instrument number */
        unsigned char sample;     /* Current sample number */
        unsigned char volume;     /* Current volume */
        unsigned char pan;        /* Current stereo pan */
        unsigned char reserved;   /* Reserved */
        struct xmp_event event;   /* Current track event */
    } channel_info[XMP_MAX_CHANNELS];
};

So here's the move: call this function to grab sound buffer data after xmp_play_frame() has been called. The fields buffer and buffer_size hold the pointer to the sound buffer PCM data and how big it is. That buffer size will be no larger than XMP_MAX_FRAMESIZE โ€” and honestly it's typically way smaller than XMP_MAX_FRAMESIZE unless you're out here using extremely large sampling rates. ๐Ÿ’€

The fields time, total_time, and frame_time are all based on the base tempo factor that was set when the module was last scanned (peep xmp_set_tempo_factor() and xmp_scan_module()). ๐Ÿฆ‹


๐Ÿ‘‘ void xmp_end_player(xmp_context c)

End module replay and release the player memory. Say bye ๐Ÿ‘‹

Parameters:

  • c โ€” the player context handle.

๐ŸŽฎ Player control

โญ๏ธ int xmp_next_position(xmp_context c)

Skip replay to the start of the next position. If the module was stopped with xmp_stop_module(), this operation restarts the module at position 0. And if the module is already restarting at position 0, this operation does literally nothing. ๐Ÿ™ˆ

Real talk: libxmp functions that set the current position do not fully take effect until the next xmp_play_frame() or xmp_play_buffer() call. So don't be surprised when nothing happens immediately.

Parameters:

  • c โ€” the player context handle.

Returns:

The new position index, -1 if the module is restarting at position 0, or -XMP_ERROR_STATE if the player is not in playing state. โœจ


โฎ๏ธ int xmp_prev_position(xmp_context c)

Skip replay to the start of the previous position. If the module was stopped with xmp_stop_module(), or it's restarting at position 0, or the previous position belongs to a different sequence, this operation does nothing. Not even a little. ๐Ÿ’…

Same deal as before: functions that set the current position don't fully kick in until the next xmp_play_frame() or xmp_play_buffer() call.

Parameters:

  • c โ€” the player context handle.

Returns:

The new position index, or -XMP_ERROR_STATE if the player is not in playing state.


๐ŸŽฏ int xmp_set_position(xmp_context c, int pos)

Skip replay to the start of the position you picked. If the module was stopped with xmp_stop_module(), this operation will restart the module right at the destination position. Main character energy, honestly. ๐ŸŒŸ

Yep, same rule: setting the position doesn't fully take effect until the next xmp_play_frame() or xmp_play_buffer() call.

Parameters:

  • c โ€” the player context handle.
  • pos โ€” the position index to set.

Returns:

The new position index, -1 if the module is restarting at position 0, -XMP_ERROR_INVALID if the new position is invalid, or -XMP_ERROR_STATE if the player is not in playing state. ๐Ÿซถ


๐Ÿ“ int xmp_set_row(xmp_context c, int row)

[Added in libxmp 4.5] Skip replay to the given row. ๐ŸŽ€

Parameters:

  • c โ€” the player context handle.
  • row โ€” the row to set.

Returns:

The new row, -XMP_ERROR_INVALID if the new row is invalid, or -XMP_ERROR_STATE if the player is not in playing state.


โฑ๏ธ int xmp_set_tempo_factor(xmp_context c, double val)

[Added in libxmp 4.5] Modify the current base tempo multiplier. And this is lowkey important: this value is a property of the currently loaded module, not of the player. So the default tempo factor is 1.0 for most modules, 0.264 for MED/OctaMED tempo mode modules, 4.0 / rows_per_beat for MED/OctaMED BPM mode modules, and roughly 0.401373 for Farandole Composer modules. Different strokes for different folks, fr fr. ๐Ÿ’–

Heads up: this function does not recalculate the playback times returned by xmp_get_frame_info(). If you want those times recalculated, you gotta call xmp_scan_module() after setting the tempo factor. No shortcuts. ๐Ÿ˜ค

Wanna set a relative tempo factor instead? Go see xmp_set_tempo_factor_relative().

Parameters:

  • c โ€” the player context handle.
  • val โ€” the new multiplier.

Returns:

0 on success, -1 if the value is invalid, or -XMP_ERROR_STATE if the player is not in the playing state.


๐ŸŒ€ int xmp_set_tempo_factor_relative(xmp_context c, double val)

[Added in libxmp 4.7] Set a relative tempo multiplier. This value gets multiplied against the base tempo multiplier (the one set by loading a module or by calling xmp_set_tempo_factor()) when it's calculating the duration of the next tick. ๐Ÿงฎ

And no, this value does not affect the times reported by scanning the module, even if you call xmp_scan_module(). Sorry bestie. The default relative tempo factor value is 1.0, and it gets reset to that value every single time xmp_start_player() is called. ๐Ÿ”„

Relative tempo factors between 0.1 and 2.0 should work with the default base tempo multiplier, but more extreme values are not guaranteed to work. Proceed with caution, icon. โš ๏ธ

Wanna set the base tempo factor? That's xmp_set_tempo_factor()'s job.

Parameters:

  • c โ€” the player context handle.
  • val โ€” the new multiplier.

Returns:

0 on success, -1 if the value is invalid, or -XMP_ERROR_STATE if the player is not in the playing state. ๐ŸŒˆ


๐Ÿ” double xmp_get_tempo_factor(xmp_context c)

[Added in libxmp 4.7] Get the base tempo multiplier โ€” the one set by loading a module or after calling xmp_set_tempo_factor(). โœจ

Parameters:

  • c โ€” the player context handle.

Returns:

The current base time factor on success, or -1.0 * XMP_ERROR_STATE if the player is not in either the loaded or the playing states. ๐Ÿ˜ฌ


๐Ÿ”Ž double xmp_get_tempo_factor_relative(xmp_context c)

[Added in libxmp 4.7] Get the relative tempo multiplier โ€” the one set by xmp_start_player() or after calling xmp_set_tempo_factor_relative(). ๐ŸŒธ

Parameters:

  • c โ€” the player context handle.

Returns:

The current relative time factor on success, or -1.0 * XMP_ERROR_STATE if the player is not in the playing state. ๐Ÿ’œ


๐Ÿ›‘ void xmp_stop_module(xmp_context c)

Stop the currently playing module. That's it, that's the function. โœ‹

Parameters:

  • c โ€” the player context handle.

๐ŸŽ›๏ธ Player parameter setting ๐ŸŽ›๏ธ

  • void xmp_restart_module(xmp_context c) โ€” bestie, this one just yeets the currently playing module back to the start. ๐Ÿ”„

Heads up tho: the libxmp functions that set the current position don't fully kick in until the next xmp_play_frame() or xmp_play_buffer() call. Like, they're pending, okay? Don't @ me.

Parameters:

  • c โ€” the player context handle. ๐Ÿ’…
  • int xmp_seek_time(xmp_context c, int time) โ€” skips replay to the time you asked for. It sets the current position to the position closest to the specified time in the current sequence. This one is fast, but not particularly precise โ€” it does not process any frames to seek closer to the requested time. Want the accurate (but much slower) version? See xmp_seek_time_frame(). ๐Ÿฆ‹

Also: libxmp functions that set the current position do not fully take effect until the next xmp_play_frame() or xmp_play_buffer() call.

Parameters:

  • c โ€” the player context handle.
  • time โ€” time to seek in milliseconds.

Returns:

  • The new position index, or -XMP_ERROR_STATE if the player is not in playing state. ๐Ÿ˜ญ
  • int xmp_seek_time_frame(xmp_context c, int time) โ€” skips replay to the specified time too, but make it โœจpreciseโœจ. It sets the current position to the position closest to the specified time in the current sequence, then plays frames as-needed (via xmp_play_frame()) until the player is positioned at the frame containing the requested time. The caller can then render this frame with a subsequent call to xmp_play_frame() or xmp_play_buffer().
โš ๏ธ Warning: this function is computationally expensive. It is not guaranteed to call xmp_play_frame(), but it may call it numerous times. Your CPU is gonna be working overtime, no cap. ๐Ÿ’€

And again, the classic: libxmp functions that set the current position do not fully take effect until the next xmp_play_frame() or xmp_play_buffer() call.

Parameters:

  • c โ€” the player context handle.
  • time โ€” time to seek in milliseconds.

Returns:

  • The new position index, or -XMP_ERROR_STATE if the player is not in playing state.
  • int xmp_channel_mute(xmp_context c, int chn, int status) โ€” mutes or unmutes the channel you point at. ๐Ÿคซ

Parameters:

  • c โ€” the player context handle.
  • chn โ€” the channel to mute or unmute.
  • status โ€” 0 to mute channel, 1 to unmute, 2 the inverse of the current channel status, or -1 to query the current channel status.

Returns:

  • The previous channel status, or -XMP_ERROR_STATE if the player is not in playing state.
  • int xmp_channel_vol(xmp_context c, int chn, int vol) โ€” sets or retrieves the volume of the specified channel. ๐Ÿ”Š

Parameters:

  • c โ€” the player context handle.
  • chn โ€” the channel to set or get volume.
  • vol โ€” a value from 0-100 to set the channel volume, or -1 to retrieve the current volume.

Returns:

  • The previous channel volume, or -XMP_ERROR_STATE if the player is not in playing state.
  • void xmp_inject_event(xmp_context c, int chn, struct xmp_event *event) โ€” dynamically inserts a new event into a playing module. Like dropping a little surprise into the mix, live. ๐ŸŽ€

Parameters:

  • c โ€” the player context handle.
  • chn โ€” the channel to insert the new event.
  • event โ€” the event to insert. struct xmp_event is defined as:
struct xmp_event {
    unsigned char note;   /* Note number (0 means no note) */
    unsigned char ins;    /* Patch number */
    unsigned char vol;    /* Volume (0 to basevol) */
    unsigned char fxt;    /* Effect type */
    unsigned char fxp;    /* Effect parameter */
    unsigned char f2t;    /* Secondary effect type */
    unsigned char f2p;    /* Secondary effect parameter */
    unsigned char _flag;  /* Internal (reserved) flags */
};

๐ŸŽง Player parameter setting ๐ŸŽง

  • int xmp_set_instrument_path(xmp_context c, char *path) โ€” sets the path used to retrieve external instruments or samples. Some formats (such as Protracker song files, ST2 song files, and MED2) use this to read sample files from a different directory in the filesystem. ๐Ÿ—‚๏ธ

Parameters:

  • c โ€” the player context handle.
  • path โ€” the path to retrieve instrument files. A value of NULL will unset the instrument path. Prior to 4.6.1, this function crashes when path is NULL. ๐Ÿ’€ (yes really, be careful)

Returns:

  • 0 if the instrument path was correctly set, or -XMP_ERROR_SYSTEM in case of error (the system error code is set in errno).
  • int xmp_get_player(xmp_context c, int param) โ€” retrieves the current value of the specified player parameter. ๐Ÿ‘€

Parameters:

  • c โ€” the player context handle.
  • param โ€” player parameter to get. Valid parameters are:
XMP_PLAYER_AMP         /* Amplification factor */
XMP_PLAYER_MIX         /* Stereo mixing */
XMP_PLAYER_INTERP      /* Interpolation type */
XMP_PLAYER_DSP         /* DSP effect flags */
XMP_PLAYER_FLAGS       /* Player flags */
XMP_PLAYER_CFLAGS      /* Player flags for current module*/
XMP_PLAYER_SMPCTL      /* Control sample loading */
XMP_PLAYER_VOLUME      /* Player master volume */
XMP_PLAYER_STATE       /* Current player state (read only) */
XMP_PLAYER_SMIX_VOLUME /* SMIX Volume */
XMP_PLAYER_DEFPAN      /* Default pan separation */
XMP_PLAYER_MODE        /* Player personality */
XMP_PLAYER_MIXER_TYPE  /* Current mixer (read only) */
XMP_PLAYER_VOICES      /* Maximum number of mixer voices */

Valid states are:

XMP_STATE_UNLOADED     /* Context created */
XMP_STATE_LOADED       /* Module loaded */
XMP_STATE_PLAYING      /* Module playing */

Valid mixer types are:

XMP_MIXER_STANDARD      /* Standard mixer */
XMP_MIXER_A500          /* Amiga 500 */
XMP_MIXER_A500F         /* Amiga 500 with led filter */

See xmp_set_player for the rest of valid values for each parameter. ๐Ÿซถ

Returns:

  • The parameter value, or -XMP_ERROR_STATE if the parameter is not XMP_PLAYER_STATE and the player is not in playing state.
  • int xmp_set_player(xmp_context c, int param, int val) โ€” sets a player parameter to the value you want. โš™๏ธ

Parameters:

  • param โ€” player parameter to set. Valid parameters are:
XMP_PLAYER_AMP         /* Amplification factor */
XMP_PLAYER_MIX         /* Stereo mixing */
XMP_PLAYER_INTERP      /* Interpolation type */
XMP_PLAYER_DSP         /* DSP effect flags */
XMP_PLAYER_FLAGS       /* Player flags */
XMP_PLAYER_CFLAGS      /* Player flags for current module*/
XMP_PLAYER_SMPCTL      /* Control sample loading */
XMP_PLAYER_VOLUME      /* Player master volume */
XMP_PLAYER_SMIX_VOLUME /* SMIX Volume */
XMP_PLAYER_DEFPAN      /* Default pan separation */
XMP_PLAYER_MODE        /* Player personality */
XMP_PLAYER_VOICES      /* Maximum number of mixer voices */
  • val โ€” the value to set. Valid values depend on the parameter being set.

Valid values:

  • Amplification factor: ranges from 0 to 3. Default value is 1. ๐Ÿ”ฅ
  • Stereo mixing: percentual left/right channel separation. Default is 70.
  • Interpolation type: can be one of the following values:
XMP_INTERP_NEAREST  /* Nearest neighbor */
XMP_INTERP_LINEAR   /* Linear (default) */
XMP_INTERP_SPLINE   /* Cubic spline */
  • DSP effects flags: enable or disable DSP effects. Valid effects are:
XMP_DSP_LOWPASS     /* Lowpass filter effect */
XMP_DSP_ALL         /* All effects */
  • Player flags: tweakable player parameters. Valid flags are:
XMP_FLAGS_VBLANK    /* Use vblank timing */
XMP_FLAGS_FX9BUG    /* Emulate Protracker 2.x FX9 bug */
XMP_FLAGS_FIXLOOP   /* Make sample loop value / 2 */
XMP_FLAGS_A500      /* Use Paula mixer in Amiga modules */
  • [Added in libxmp 4.1] Player flags for current module: same flags as above but after applying module-specific quirks (if any). โœจ
  • [Added in libxmp 4.1] Sample control: Valid values are:
XMP_SMPCTL_SKIP     /* Don't load samples */
  • Disabling sample loading when loading a module allows allows computation of module duration without decompressing and loading large sample data, and is useful when duration information is needed for a module that won't be played immediately. This option must be specified before calling xmp_load_module(). ๐Ÿ‘‘
  • [Added in libxmp 4.2] Player volumes: Set the player master volume or the external sample mixer master volume. Valid values are 0 to 100.
  • [Added in libxmp 4.3] Default pan separation: percentual left/right pan separation in formats with only left and right channels. Default is 100%, but 50% is recommended for most audio playback situations. This option must be specified before calling xmp_load_module().
  • [Added in libxmp 4.4] Player personality: The player can be forced to emulate a specific tracker in cases where the module relies on a format quirk and tracker detection fails. Valid modes are:
                XMP_MODE_AUTO         /* Autodetect mode (default) */
                XMP_MODE_MOD          /* Play as a generic MOD player */
                XMP_MODE_NOISETRACKER /* Play using Noisetracker quirks */
                XMP_MODE_PROTRACKER   /* Play using Protracker 1/2 quirks */
                XMP_MODE_S3M          /* Play as a generic S3M player */
                XMP_MODE_ST3          /* Play using ST3 bug emulation */
                XMP_MODE_ST3GUS       /* Play using ST3+GUS quirks */
                XMP_MODE_XM           /* Play as a generic XM player */
                XMP_MODE_FT2          /* Play using FT2 bug emulation */
                XMP_MODE_IT           /* Play using IT quirks */
                XMP_MODE_ITSMP        /* Play using IT sample mode quirks */

Okay so by default, formats that are giving S3M energy โ€” like PTM or IMF โ€” get the S3M replayer (no Scream Tracker 3 quirks/bug emulation tho). And formats that are giving XM energy, like RTM and MDL? They get the XM replayer, no FT2 quirks/bug emulation. Lowkey sensible ๐Ÿ’…

Multichannel MOD files ride with the XM replayer. Scream Tracker 3 MOD files get the S3M replayer with ST3 quirks. S3M files get the most appropriate replayer based on the tracker that made them, and ST3 quirks and bugs are only turned on if it was actually made using ST3. XM files get FT2 bugs and quirks only if they were created using Fast Tracker II. No cap.

And modules made with OpenMPT? Bestie, they get played with all the bugs and quirks of the original trackers. Iconic.

  • [Added in libxmp 4.4] Maximum number of mixer voices: this is the max number of virtual channels that can be used to play the module. โš ๏ธ If you set it too high, modules with voice leaks can cause excessive CPU usage. Default is 128.

Returns:

0 if parameter was correctly set, -XMP_ERROR_INVALID if parameter or values are out of the valid ranges, or -XMP_ERROR_STATE if the player is not in playing state.


๐ŸŒˆ External sample mixer API ๐ŸŒˆ

Libxmp 4.2 ships a lil mini-API that lets you add sound effects to games and similar apps โ€” as long as you have a low latency sound system. It lets you play module instruments or external sample files in WAV format in response to whatever events you want. Obsessed fr fr ๐ŸŽ€

โœจ Example โœจ

This example uses SDL to load a module and a sound sample, plays the module as background music, and plays the sample when a key is pressed:

#include <SDL/SDL.h>
#include <xmp.h>

static void fill_audio(void *udata, unsigned char *stream, int len)
{
    xmp_play_buffer(udata, stream, len, 0);
}

int sound_init(xmp_context ctx, int sampling_rate, int channels)
{
    SDL_AudioSpec a;

    a.freq = sampling_rate;
    a.format = (AUDIO_S16);
    a.channels = channels;
    a.samples = 2048;
    a.callback = fill_audio;
    a.userdata = ctx;

    if (SDL_OpenAudio(&a, NULL) < 0) {
            fprintf(stderr, "%s\n", SDL_GetError());
            return -1;
    }
}

int video_init()
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "%s\n", SDL_GetError());
        return -1;
    }
    if (SDL_SetVideoMode(640, 480, 8, 0) == NULL) {
        fprintf(stderr, "%s\n", SDL_GetError());
        return -1;
    }
    atexit(SDL_Quit);
}

int main(int argc, char **argv)
{
    SDL_Event event;
    xmp_context ctx;

    if ((ctx = xmp_create_context()) == NULL)
            return 1;

    video_init();
    sound_init(ctx, 44100, 2);

    xmp_start_smix(ctx, 1, 1);
    xmp_smix_load_sample(ctx, 0, "blip.wav");

    xmp_set_player(ctx, XMP_PLAYER_DEFPAN, 50);
    xmp_load_module(ctx, "music.mod");
    xmp_start_player(ctx, 44100, 0);
    xmp_set_player(ctx, XMP_PLAYER_VOLUME, 40);

    SDL_PauseAudio(0);

    while (1) {
        if (SDL_WaitEvent(&event)) {
            if (event.type == SDL_KEYDOWN) {
                if (event.key.keysym.sym == SDLK_ESCAPE)
                    break;
                xmp_smix_play_sample(ctx, 0, 60, 64, 0);
            }
        }
    }

    SDL_PauseAudio(1);

    xmp_end_player(ctx);
    xmp_release_module(ctx);
    xmp_end_smix(ctx);
    xmp_free_context(ctx);

    SDL_CloseAudio();
    return 0;
}

๐Ÿงš SMIX API reference ๐Ÿงš

int xmp_start_smix(xmp_context c, int nch, int nsmp)

Boots up the external sample mixer subsystem with the number of reserved channels and samples you hand it.

Parameters:

  • c โ€” the player context handle.
  • nch โ€” number of reserved sound mixer channels (1 to 64).
  • nsmp โ€” number of external samples.

Returns:

0 if the external sample mixer system was correctly initialized, -XMP_ERROR_INVALID in case of invalid parameters, -XMP_ERROR_STATE if the player is already in playing state, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).

int xmp_smix_play_instrument(xmp_context c, int ins, int note, int vol, int chn)

Plays a note using an instrument from the module you currently have loaded, in one of the reserved sound mixer channels. ๐ŸŽต

Parameters:

  • c โ€” the player context handle.
  • ins โ€” the instrument to play.
  • note โ€” the note number to play (60 = middle C).
  • vol โ€” the volume to use (range: 0 to the maximum volume value used by the current module).
  • chn โ€” the reserved channel to use to play the instrument.

Returns:

0 if the instrument was correctly played, -XMP_ERROR_INVALID in case of invalid parameters, or -XMP_ERROR_STATE if the player is not in playing state.

int xmp_smix_play_sample(xmp_context c, int ins, int vol, int chn)

Plays an external sample file in one of the reserved sound channels. Heads up bestie โ€” the sample has to have been loaded already with xmp_smix_load_sample(). ๐Ÿ’–

Parameters:

  • c โ€” the player context handle.
  • ins โ€” the sample to play.
  • vol โ€” the volume to use (0 to the maximum volume value used by the current module.
  • chn โ€” the reserved channel to use to play the sample.

Returns:

0 if the sample was correctly played, -XMP_ERROR_INVALID in case of invalid parameters, or -XMP_ERROR_STATE if the player is not in playing state.

int xmp_smix_channel_pan(xmp_context c, int chn, int pan)

Sets the reserved channel pan value. Panning the vibe, literally. ๐Ÿฆ‹

Parameters:

  • c โ€” the player context handle.
  • chn โ€” the reserved channel number.
  • pan โ€” the pan value to set (0 to 255).

Returns:

0 if the pan value was set, -XMP_ERROR_INVALID in case of invalid parameters, or -XMP_ERROR_STATE if the player is not in playing state.

int xmp_smix_load_sample(xmp_context c, int num, char *path)

Loads a sound sample from a file. Samples should be in mono WAV (RIFF) format, okay?

Parameters:

  • c โ€” the player context handle.
  • num โ€” the slot number of the external sample to load.
  • path โ€” pathname of the file to load.

Returns:

0 if the sample was correctly loaded, -XMP_ERROR_INVALID if the sample slot number is invalid (not reserved using xmp_start_smix()), -XMP_ERROR_FORMAT if the file format is unsupported, or -XMP_ERROR_SYSTEM in case of system error (the system error code is set in errno).

int xmp_smix_release_sample(xmp_context c, int num)

Releases the memory that an external sample allocated in the specified player context. Freeing your faves so they don't haunt the heap ๐Ÿ™ˆ

Parameters:

  • c โ€” the player context handle.
  • num โ€” the sample slot number to release.

Returns:

0 if memory was correctly released, or -XMP_ERROR_INVALID if the sample slot number is invalid.

void xmp_end_smix(xmp_context c)

Deinitializes and releases the memory used by the external sample mixer subsystem. Say bye bye ๐Ÿ‘‹

Parameters:

  • c โ€” the player context handle.

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Claudio Matsuoka and Hipolito Carraro Jr.


And that's the whole page, bestie โ€” written by two absolute legends, and this particular copy is libxmp 4.7, dated July 2026, filed under LIBXMP(3). Slay, go make something noisy with it ๐Ÿ’…โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from