Rand Stats

OpenMPT::Bindings

zef:sasha

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

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 });

Metadata

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);

Module information

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):

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):

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.