NAME
OpenMPT::Bindings — Raku bindings for libopenmpt (module music rendering)
SYNOPSIS
use OpenMPT::Bindings::Module;
use OpenMPT::Bindings::Types;
my $mod = OpenMPT::Bindings::Module.from-file('song.xm');
# Metadata
say $mod.title;
say $mod.duration;
# Render S16_LE interleaved stereo (ready-to-write bytes)
my ($frames, $blob) = $mod.read-interleaved-stereo(48000, 1024);
# Float stereo (separate Blob channels)
my ($count, $left, $right) = $mod.read-float-stereo(44100, 512);
# Seek
$mod.set-position(10.5);
# Library class methods
say OpenMPT::Bindings::Module.library-version;
say OpenMPT::Bindings::Module.is-extension-supported('xm');
DESCRIPTION
OpenMPT::Bindings provides low-level and high-level Raku bindings for
libopenmpt, a library for decoding tracked music
files (MOD, XM, S3M, IT, and many more).
The bindings wrap the C API — all ~90 exported functions are available. The
OpenMPT::Bindings::Module class wraps the opaque openmpt_module* handle
and provides idiomatic Raku access to audio rendering, metadata, playback
control, CTL parameters, and file probing. OpenMPT::Bindings::ModuleExt
adds the libopenmpt extension API (interactive playback, pattern
visualization).
REQUIREMENTS
- libopenmpt (shared library, version 0.7+)
- Raku (6.d or later)
Some features (current-sequence) require libopenmpt 0.9+ and gracefully
return -1 on older versions.
The library is not bundled — you must install libopenmpt via your
system package manager:
# Debian/Ubuntu
apt install libopenmpt0t64
# Fedora
dnf install libopenmpt
# Arch
pacman -S libopenmpt
# macOS (Homebrew)
brew install libopenmpt
# Windows
Download the libopenmpt DLL bundle from
https://lib.openmpt.org/libopenmpt/download/ and place these files in
a directory on your PATH (e.g. C:\Windows\System32):
- libopenmpt.dll
- openmpt-mpg123.dll
- openmpt-ogg.dll
- openmpt-vorbis.dll
- openmpt-zlib.dll
If you place the DLLs in a custom directory, set the
C<LIBOPENMPT_DIR> environment variable to that path before loading any
bindings. This also works on Linux/macOS for AppImage, .app bundles,
etc.
INSTALLATION
zef install OpenMPT::Bindings
USAGE
Loading modules
# From file
my $xm = OpenMPT::Bindings::Module.from-file('track.xm');
# From memory (Blob)
my $data = 'track.it'.IO.slurp(:bin);
my $it = OpenMPT::Bindings::Module.from-blob($data);
Construction with initial CTL parameters
my $mod = OpenMPT::Bindings::Module.from-file('track.xm',
ctls => { 'play.tempo_factor' => 2.0 });
say $xm.title;
say $xm.artist;
say $xm.type; # "xm", "mod", "s3m", "it"
say $xm.type-long; # "FastTracker 2 v1.04"
say $xm.module-message; # song message / comments
say $xm.tracker; # tracker that created the file
say $xm.date; # creation date
say $xm.originaltype;
# All available metadata keys
.say for $xm.metadata-keys;
Audio rendering
All render methods return Blob buffers of raw bytes. Every call returns
($frames-returned, $buf...).
# Float stereo (separate buffers)
my ($frames, $left, $right) = $xm.read-float-stereo(44100, 1024);
# Float interleaved stereo
my ($frames, $buf) = $xm.read-interleaved-float-stereo(48000, 512);
# Float mono
my ($frames, $buf) = $xm.read-float-mono(44100, 256);
# Float quad (4-channel surround)
my ($frames, $fl, $fr, $rl, $rr) = $xm.read-float-quad(44100, 512);
# Float interleaved quad
my ($frames, $buf) = $xm.read-interleaved-float-quad(44100, 512);
# Int16 stereo
my ($frames, $left, $right) = $xm.read-stereo(44100, 1024);
# Int16 interleaved stereo
my ($frames, $buf) = $xm.read-interleaved-stereo(44100, 512);
# Int16 mono
my ($frames, $buf) = $xm.read-mono(44100, 256);
# Int16 quad
my ($frames, $fl, $fr, $rl, $rr) = $xm.read-quad(44100, 512);
# Int16 interleaved quad
my ($frames, $buf) = $xm.read-interleaved-quad(44100, 512);
Playback control
$xm.select-subsong(0);
$xm.set-repeat-count(-1); # -1 = forever, 0 = once, 1 = twice
$xm.set-position(15.5); # seek to 15.5 seconds (Numeric)
$xm.set-position-order-row(3, 0); # seek to order 3, row 0
Position and duration
say $xm.duration;
say $xm.position;
say $xm.time-at-position(0, 0);
Render parameters
$xm.set-mastergain(0); # millibel
$xm.set-stereo-separation(100); # percent
say $xm.interpolation-filter-length;
say $xm.volume-ramping-strength;
Playback status
say $xm.current-bpm;
say $xm.current-speed;
say $xm.current-tempo;
say $xm.current-order;
say $xm.current-pattern;
say $xm.current-row;
say $xm.current-playing-channels;
say $xm.current-sequence; # 0.9+; returns -1 on older libopenmpt
# Per-channel VU meters
say $xm.current-channel-vu-mono(0);
say $xm.current-channel-vu-left(0);
say $xm.current-channel-vu-right(0);
say $xm.current-channel-vu-rear-left(2);
say $xm.current-channel-vu-rear-right(2);
say $xm.num-subsongs;
say $xm.num-channels;
say $xm.num-orders;
say $xm.num-patterns;
say $xm.num-instruments;
say $xm.num-samples;
say $xm.restart-order(0);
say $xm.restart-row(0);
# Names
say $xm.subsong-name(0);
say $xm.channel-name(0);
say $xm.order-name(0);
say $xm.pattern-name(0);
say $xm.instrument-name(0);
say $xm.sample-name(0);
Pattern / order access
say $xm.order-pattern(2); # pattern index at order 2
say $xm.is-order-skip(3); # Bool — skip entry?
say $xm.is-order-stop(3); # Bool — stop entry?
say $xm.is-pattern-skip(1); # Bool — skip item?
say $xm.is-pattern-stop(1); # Bool — stop item?
say $xm.pattern-num-rows(0); # row count
say $xm.pattern-rows-per-beat(0);
say $xm.pattern-rows-per-measure(0);
say $xm.get-pattern-cell(0, 0, 0, OPENMPT_MODULE_COMMAND_NOTE);
say $xm.format-pattern-cell(0, 0, 0, OPENMPT_MODULE_COMMAND_NOTE);
say $xm.highlight-pattern-cell(0, 0, 0, OPENMPT_MODULE_COMMAND_NOTE);
CTL parameters
# Get (type dispatch via :type parameter)
say $xm.get-ctl('dither'); # Str (text)
say $xm.get-ctl('play.tempo_factor', :type(Numeric)); # Num
say $xm.get-ctl('dither', :type(Int)); # Int
say $xm.get-ctl('dither', :type(Bool)); # Bool
# Set (auto-dispatch via Raku type)
$xm.set-ctl('play.tempo_factor', 2.0); # Numeric → set_floatingpoint
$xm.set-ctl('dither', 0); # Int → set_integer
$xm.set-ctl('dither', False); # Bool → set_boolean
$xm.set-ctl('load.skip_plugin', '1'); # Str → set_text
# List all supported CTL keys
.say for $xm.ctl-keys;
Library class methods
say OpenMPT::Bindings::Module.library-version; # uint32
say OpenMPT::Bindings::Module.core-version; # uint32
say OpenMPT::Bindings::Module.library-string('url'); # Str
my @exts = OpenMPT::Bindings::Module.supported-extensions;
say OpenMPT::Bindings::Module.is-extension-supported('xm'); # Bool
File probing
my $data = 'unknown.bin'.IO.slurp(:bin);
my $result = OpenMPT::Bindings::Module.probe-file-header($data, $data.bytes);
# Compare with enum constants
if $result == OPENMPT_PROBE_FILE_HEADER_RESULT_SUCCESS {
say "Recognised!";
}
# Probe without knowing the file size
my $partial = OpenMPT::Bindings::Module.probe-file-header-without-filesize(
$data.subbuf(0, 64));
Extension API (ModuleExt)
The ModuleExt class wraps openmpt_module_ext* and provides interactive
playback and pattern visualization interfaces.
use OpenMPT::Bindings::ModuleExt;
my $ext = OpenMPT::Bindings::ModuleExt.from-file('track.xm');
say $ext.module.title; # access the underlying Module
# Interactive interface
$ext.set-current-speed(2); # 2x speed
$ext.set-tempo-factor(1.5);
$ext.set-pitch-factor(0.8);
$ext.set-global-volume(0.75);
$ext.set-channel-volume(0, 0.5);
$ext.set-channel-mute(1, True);
$ext.play-note(0, 60, 0.8, 0.5); # instrument, note, volume, panning
$ext.stop-note(0);
# Interactive2 interface
$ext.note-off(0);
$ext.note-fade(0);
$ext.set-channel-panning(0, 0.3);
$ext.get-channel-panning(0);
$ext.set-note-finetune(0, 0.05);
# Interactive3 interface
$ext.set-current-tempo2(180);
# Pattern visualization
$ext.get-pattern-row-channel-volume-effect-type(0, 0, 0);
$ext.get-pattern-row-channel-effect-type(0, 0, 0);
Error handling
use X::OpenMPT::Bindings;
try {
OpenMPT::Bindings::Module.from-blob(Buf.new(0xFF, 0xFE).Blob);
CATCH {
when X::OpenMPT::Bindings {
say .function; # "openmpt_module_create_from_memory2"
say .code; # error code (int32)
say .message; # human-readable error string
}
}
}
EXAMPLES
Two example scripts are included:
Real-time playback
raku -I lib examples/play-module.raku track.xm
Audio backends (select via --backend):
pulse — Linux (default) — libpulse-simple FFI (low-latency)alsa — Linux — native ALSA FFI via snd_pcm_writeiwaveout — Windows — winmm FFI
The pulse backend uses pa_simple_write which blocks at the server
buffer boundary, providing jitter-free playback without timing hacks.
If PulseAudio is unavailable it falls back to alsa.
File conversion
raku -I lib examples/convert-module.raku track.xm
Outputs a 16-bit stereo WAV file named track.wav by default.
Supported formats (select via --format):
wav — native (RIFF header + raw PCM)mp3 — pipes to ffmpeg (libmp3lame)flac — pipes to ffmpeg (flac)ogg — pipes to ffmpeg (libvorbis)opus — pipes to ffmpeg (libopus)
Common options:
# Cap duration and pick sample rate
raku -I lib examples/convert-module.raku --seconds=30 --rate=44100 track.it
# Batch-convert with quiet progress
raku -I lib examples/convert-module.raku --format=flac --output-dir=/tmp *.xm --quiet
# Explicit output path, subsong selection, gain/tempo
# --gain takes decibels (e.g. -2.0 = -2 dB, internally converted to millibel)
raku -I lib examples/convert-module.raku --subsong=1 --gain=-2 --tempo=1.5 --output=out.wav track.xm
The --force flag applies to all output formats: without it, the tool
refuses to overwrite an existing file. ffmpeg-based formats would silently
overwrite on their own; the Raku-side guard catches them early instead.
Compressed formats (mp3, flac, ogg, opus) require ffmpeg on PATH.
SEE ALSO
AUTHOR
Sasha Abbott sashaa@disroot.org
COPYRIGHT AND LICENSE
This software is dedicated to the public domain under the
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication.
To the extent possible under law, the author has waived all copyright
and related or neighboring rights to this work.