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_infois defined as:
struct xmp_test_info {
char name[XMP_NAME_SIZE]; /* Module title */
char type[XMP_NAME_SIZE]; /* Module format */
};
Returns:
0if the file is a valid module, or a negative error code in case of error. Error codes can be-XMP_ERROR_FORMATin case of an unrecognized file format,-XMP_ERROR_DEPACKif the file is compressed and uncompression failed, or-XMP_ERROR_SYSTEMin case of system error (the system error code is set inerrno).
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_infois defined as:
struct xmp_test_info {
char name[XMP_NAME_SIZE]; /* Module title */
char type[XMP_NAME_SIZE]; /* Module format */
};
Returns:
0if the memory buffer is a valid module, or a negative error code in case of error. Error codes can be-XMP_ERROR_FORMATin case of an unrecognized file format or-XMP_ERROR_SYSTEMin case of system error (the system error code is set inerrno).
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_infois 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 beNULL. ๐ซถcallbacksโ struct specifying stream callbacks for the custom stream. These callbacks should behave as close tofread/fseek/ftell/fcloseas possible, andseek_funcmust be capable of seeking toSEEK_END. Theclose_funcis optional, but all other functions must be provided. If aclose_funcis provided, the stream will be closed once testing has finished or upon returning an error code.struct xmp_callbacksis 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 beNULL.callbacksโ struct specifying stream callbacks for the custom stream. These callbacks should behave as close tofread/fseek/ftell/fcloseas possible, andseek_funcmust be capable of seeking toSEEK_END. Theclose_funcis optional, but all other functions must be provided. If aclose_funcis provided, the stream will be closed once loading has finished or upon returning an error code.struct xmp_callbacksis 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 parameterXMP_PLAYER_VBLANK) in libxmp 4.0.2 or older, orxmp_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_infois 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 (typically44100). Valid values range from8kHzto768kHz. 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 bothxmp_play_frame()andxmp_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, orNULLto reset the internal state.sizeโ the buffer size in bytes.loopโ stop replay when the loop counter reaches this value, or0to 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_infois 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 nextxmp_play_frame()orxmp_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 nextxmp_play_frame()orxmp_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 nextxmp_play_frame()orxmp_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 byxmp_get_frame_info(). If you want those times recalculated, you gotta callxmp_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? Seexmp_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_STATEif 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 (viaxmp_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 toxmp_play_frame()orxmp_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_STATEif 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โ0to mute channel,1to unmute,2the inverse of the current channel status, or-1to query the current channel status.
Returns:
- The previous channel status, or
-XMP_ERROR_STATEif 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 from0-100to set the channel volume, or-1to retrieve the current volume.
Returns:
- The previous channel volume, or
-XMP_ERROR_STATEif 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_eventis 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 ofNULLwill unset the instrument path. Prior to 4.6.1, this function crashes whenpathisNULL. ๐ (yes really, be careful)
Returns:
0if the instrument path was correctly set, or-XMP_ERROR_SYSTEMin case of error (the system error code is set inerrno).
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_STATEif the parameter is notXMP_PLAYER_STATEand 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
0to3. Default value is1. ๐ฅ - 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
0to100. - [Added in libxmp 4.3] Default pan separation: percentual left/right pan separation in formats with only left and right channels. Default is
100%, but50%is recommended for most audio playback situations. This option must be specified before callingxmp_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:0to 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 (0to 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 (0to255).
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
- epel 10: libxmp-devel 4.7.3-1.el10_3 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- epel 9: libxmp-devel 4.7.3-1.el9 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- fedora 43: libxmp-devel 4.7.3-1.fc43 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- fedora 43: libxmp-devel 4.7.3-1.fc43 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- fedora 44: libxmp-devel 4.7.3-1.fc44 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- fedora 44: libxmp-devel 4.7.3-1.fc44 ยท 0BSD AND BSD-3-Clause AND ISC AND MIT AND LicenseRef-Fedora-Public-Domain
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms