my attempt at libmodplug bindings in python

New releases and general discussions.

Post » Sat Apr 18, 2009 1:46 am

Heya everybody.

In anticipation of support for Python returning to Construct 1.0, I was looking for creating some bindings for modplug.dll, aka libmodplug, a GPL mod replayer engine which is probably better than libmikmod (although maybe not as well-documented)..... I appreciate all the work Rich and Ashley did (especially granting my request for mod support in Construct), however, the solution which was implemented ended up being less than ideal for handling Impulse Tracker files :(

Since I am kinda new to python (and know little to no C++), getting this far was a pain in the butt, but I think using some specific tools (gccxml + xml2py from Python's ctypeslib), I got the beginnings of the proper bindings. This was all based on the source headers available with libmodplug, specifically, modplug.h. I'm not sure how to plug it into an audio output quite yet, but I'll let you guys know if I do! Here is what I have so far:
[code:ahony2oi]from ctypes import *

_libraries = {}
_libraries['C:\\python26\\scripts\\modplug\\modplug.dll'] = CDLL('C:\\python26\\scripts\\modplug\\modplug.dll')
STRING = c_char_p

class _ModPlugFile(Structure):
_ModPlugFile._fields_ = [
ModPlugFile = _ModPlugFile
ModPlug_Load = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_Load
ModPlug_Load.restype = POINTER(ModPlugFile)
ModPlug_Load.argtypes = [c_void_p, c_int]
ModPlug_Unload = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_Unload
ModPlug_Unload.restype = None
ModPlug_Unload.argtypes = [POINTER(ModPlugFile)]
ModPlug_Read = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_Read
ModPlug_Read.restype = c_int
ModPlug_Read.argtypes = [POINTER(ModPlugFile), c_void_p, c_int]
ModPlug_GetName = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_GetName
ModPlug_GetName.restype = STRING
ModPlug_GetName.argtypes = [POINTER(ModPlugFile)]
ModPlug_GetLength = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_GetLength
ModPlug_GetLength.restype = c_int
ModPlug_GetLength.argtypes = [POINTER(ModPlugFile)]
ModPlug_Seek = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_Seek
ModPlug_Seek.restype = None
ModPlug_Seek.argtypes = [POINTER(ModPlugFile), c_int]

# values for enumeration '_ModPlug_Flags'
_ModPlug_Flags = c_int # enum

# values for enumeration '_ModPlug_ResamplingMode'
_ModPlug_ResamplingMode = c_int # enum
class _ModPlug_Settings(Structure):
_ModPlug_Settings._fields_ = [
('mFlags', c_int),
('mChannels', c_int),
('mBits', c_int),
('mFrequency', c_int),
('mResamplingMode', c_int),
('mReverbDepth', c_int),
('mReverbDelay', c_int),
('mBassAmount', c_int),
('mBassRange', c_int),
('mSurroundDepth', c_int),
('mSurroundDelay', c_int),
('mLoopCount', c_int),
ModPlug_Settings = _ModPlug_Settings
ModPlug_GetSettings = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_GetSettings
ModPlug_GetSettings.restype = None
ModPlug_GetSettings.argtypes = [POINTER(ModPlug_Settings)]
ModPlug_SetSettings = _libraries['C:\\python26\\scripts\\modplug\\modplug.dll'].ModPlug_SetSettings
ModPlug_SetSettings.restype = None
ModPlug_SetSettings.argtypes = [POINTER(ModPlug_Settings)]
__all__ = ['_ModPlug_Flags', 'MODPLUG_RESAMPLE_LINEAR',
'MODPLUG_ENABLE_REVERB', '_ModPlug_Settings',
'ModPlug_Settings', 'ModPlug_SetSettings',
'_ModPlug_ResamplingMode', 'ModPlug_Read',
'ModPlug_Unload', 'MODPLUG_ENABLE_SURROUND', 'ModPlugFile',
'MODPLUG_RESAMPLE_FIR', 'ModPlug_GetLength',
'_ModPlugFile', 'MODPLUG_RESAMPLE_SPLINE', 'ModPlug_Seek',

You'll probably need to replace the hardcoded paths to a dynamic path, replacing with modplug.dll for windows, and for Linux. What's not finished yet is the code to call the proper function to play the song. I'm hoping someone smarter than me might be able to figure that out!

You can get Modplug.dll by downloading OpenJazz (an open source Jazz Jackrabbit clone). From there, this source file can presumably be called to load the functions. Whew....
Posts: 34
Reputation: 1,468

Post » Sat Apr 18, 2009 2:20 am

oh, for anyone who wants to help, it might be of assistance to post the corresponding C++ header file, which is much much much better commented:

* This source code is public domain.
* Authors: Kenton Varda <[email protected]> (C interface wrapper)


#ifdef __cplusplus
extern "C" {

struct _ModPlugFile;
typedef struct _ModPlugFile ModPlugFile;

/* Load a mod file. [data] should point to a block of memory containing the complete
* file, and [size] should be the size of that block.
* Return the loaded mod file on success, or NULL on failure. */
ModPlugFile* ModPlug_Load(const void* data, int size);
/* Unload a mod file. */
void ModPlug_Unload(ModPlugFile* file);

/* Read sample data into the buffer. Returns the number of bytes read. If the end
* of the mod has been reached, zero is returned. */
int ModPlug_Read(ModPlugFile* file, void* buffer, int size);

/* Get the name of the mod. The returned buffer is stored within the ModPlugFile
* structure and will remain valid until you unload the file. */
const char* ModPlug_GetName(ModPlugFile* file);

/* Get the length of the mod, in milliseconds. Note that this result is not always
* accurate, especially in the case of mods with loops. */
int ModPlug_GetLength(ModPlugFile* file);

/* Seek to a particular position in the song. Note that seeking and MODs don't mix very
* well. Some mods will be missing instruments for a short time after a seek, as ModPlug
* does not scan the sequence backwards to find out which instruments were supposed to be
* playing at that time. (Doing so would be difficult and not very reliable.) Also,
* note that seeking is not very exact in some mods -- especially those for which
* ModPlug_GetLength() does not report the full length. */
void ModPlug_Seek(ModPlugFile* file, int millisecond);

enum _ModPlug_Flags
MODPLUG_ENABLE_OVERSAMPLING = 1 << 0, /* Enable oversampling (*highly* recommended) */
MODPLUG_ENABLE_NOISE_REDUCTION = 1 << 1, /* Enable noise reduction */
MODPLUG_ENABLE_REVERB = 1 << 2, /* Enable reverb */
MODPLUG_ENABLE_MEGABASS = 1 << 3, /* Enable megabass */
MODPLUG_ENABLE_SURROUND = 1 << 4 /* Enable surround sound. */

enum _ModPlug_ResamplingMode
MODPLUG_RESAMPLE_NEAREST = 0, /* No interpolation (very fast, extremely bad sound quality) */
MODPLUG_RESAMPLE_LINEAR = 1, /* Linear interpolation (fast, good quality) */
MODPLUG_RESAMPLE_SPLINE = 2, /* Cubic spline interpolation (high quality) */
MODPLUG_RESAMPLE_FIR = 3 /* 8-tap fir filter (extremely high quality) */

typedef struct _ModPlug_Settings
int mFlags; /* One or more of the MODPLUG_ENABLE_* flags above, bitwise-OR'ed */

/* Note that ModPlug always decodes sound at 44100kHz, 32 bit, stereo and then
* down-mixes to the settings you choose. */
int mChannels; /* Number of channels - 1 for mono or 2 for stereo */
int mBits; /* Bits per sample - 8, 16, or 32 */
int mFrequency; /* Sampling rate - 11025, 22050, or 44100 */
int mResamplingMode; /* One of MODPLUG_RESAMPLE_*, above */

int mReverbDepth; /* Reverb level 0(quiet)-100(loud) */
int mReverbDelay; /* Reverb delay in ms, usually 40-200ms */
int mBassAmount; /* XBass level 0(quiet)-100(loud) */
int mBassRange; /* XBass cutoff in Hz 10-100 */
int mSurroundDepth; /* Surround level 0(quiet)-100(heavy) */
int mSurroundDelay; /* Surround delay in ms, usually 5-40ms */
int mLoopCount; /* Number of times to loop. Zero prevents looping.
-1 loops forever. */
} ModPlug_Settings;

/* Get and set the mod decoder settings. All options, except for channels, bits-per-sample,
* sampling rate, and loop count, will take effect immediately. Those options which don't
* take effect immediately will take effect the next time you load a mod. */
void ModPlug_GetSettings(ModPlug_Settings* settings);
void ModPlug_SetSettings(const ModPlug_Settings* settings);

#ifdef __cplusplus
} /* extern "C" */

Posts: 34
Reputation: 1,468

Post » Sat Apr 18, 2009 3:56 am

hmm, the work is still incomplete; it apparently relies on some types from sndfile.h, which I haven't been able to convert to python using gccxml yet due to errors. I was informed of this on another forum, so I guess more research is necessary before this can be made useful just yet...
Posts: 34
Reputation: 1,468

Post » Sat Apr 18, 2009 11:54 am

In my opinion, I guess you could find more help on actual programming forums..
Posts: 255
Reputation: 3,934

Post » Sun Apr 19, 2009 5:53 am

breakthrough :mrgreen:

It works!!!!! This uses python's standard wave library to output a mod file to wave. With proper access to a Construct sound buffer, the output could easily be routed to our games as well :lol:

However, I don't know why, but the writing function is pretty slow! It probably has to do with all the appending of sample_data in the read looping function. When the file's played in real-time, it probably won't be much of a problem. However, please give this script 20-30 seconds to process a 3-5 minute song before the wave file is written.

I guess I can say now this is my first functional Python program, I hope it is helpful to anyone here 3
Posts: 34
Reputation: 1,468

Return to Construct Classic Discussion

Who is online

Users browsing this forum: No registered users and 0 guests