go-zikmu is a pure-Go library for loading, replaying, and streaming tracker modules.
It currently supports:
MODS3MXMIT
The package is designed for software playback in Go applications. You can use it to:
- detect and load tracker modules from any
io.ReaderAt - inspect module metadata
- render interleaved
float32PCM samples - control playback with play, pause, stop, reset, seek, and volume
- expose an
io.ReadSeekerstream for audio backends - plug directly into Ebiten through the
ebitenaudiohelper package
Use the module path from go.mod:
go get github.com/olivierh59500/go-zikmuUse the Go toolchain version declared in go.mod.
package main
import (
"fmt"
"os"
"github.com/olivierh59500/go-zikmu"
)
func main() {
f, err := os.Open("music.xm")
if err != nil {
panic(err)
}
defer f.Close()
info, err := f.Stat()
if err != nil {
panic(err)
}
module, err := zikmu.Load(f, info.Size())
if err != nil {
panic(err)
}
fmt.Printf(
"%s | format=%s | channels=%d | patterns=%d\n",
module.Metadata.Title,
module.Metadata.Format,
module.Metadata.Channels,
module.Metadata.Patterns,
)
}Load auto-detects the module format. If you only want detection, use zikmu.Detect.
cfg := zikmu.DefaultConfig()
player, err := zikmu.NewPlayer(module, cfg)
if err != nil {
panic(err)
}
buffer := make([]float32, cfg.BufferSamples*cfg.Channels)
written, err := player.Render(buffer)
if err != nil {
panic(err)
}
pcm := buffer[:written]
_ = pcmRender writes interleaved float32 PCM samples in the usual [-1.0, 1.0] range. The default config uses:
44100Hz2output channels- interpolation enabled
Custom configs currently support 1 or 2 output channels.
The player API also exposes:
Play()/Pause()Stop()/Reset()Seek(time.Duration)Position()SetVolume(float64)/Volume()Stream() io.ReadSeeker
Stream() produces little-endian float32 PCM, which is useful for backends that pull audio data.
The repository includes an ebitenaudio package that wraps a zikmu.Player as an Ebiten audio player.
package main
import (
"time"
"github.com/hajimehoshi/ebiten/v2/audio"
"github.com/olivierh59500/go-zikmu/ebitenaudio"
)
ctx := audio.NewContext(cfg.SampleRate)
ebitenPlayer, err := ebitenaudio.NewPlayer(ctx, player, ebitenaudio.Options{
BufferSize: 100 * time.Millisecond,
AutoPlay: true,
})
if err != nil {
panic(err)
}
defer ebitenPlayer.Close()The Ebiten audio context sample rate must match zikmu.Config.SampleRate.
A runnable example is available in examples/ebiten-minimal:
go run ./examples/ebiten-minimal /path/to/module.itKeyboard controls in the example:
Space: play/pauseR: resetS: stopLeft/Right: seek backward or forward by 5 secondsUp/Down: volume
Unknown files return a typed error, so you can use errors.Is:
if errors.Is(err, zikmu.ErrUnsupportedFormat) {
// not a supported tracker module
}The package also exposes *zikmu.Error, which carries:
- an error code
- the operation name
- the detected format
- a byte offset when available
Run the full test suite:
go test ./...Run benchmarks:
go test -bench . ./...The repository also contains:
examples/ebiten-minimalfor manual playback testingcmd/zikmu-compare-libmikmodfor renderer comparisons against a libmikmod-based reference helpertools/for the small C utilities used by the comparison workflow
MIT. See LICENSE.
NewScreamTracker3(data, ScreamTracker3Options{SampleRate: 48000, Interpolation: true}) selects the integer Scream Tracker compatibility mixer.
Fill renders interleaved int16 stereo without callback allocations;
PositionAt maps an audible sample frame to its order, row, tracker frame and
order-separator flags. Its marker history is bounded. StartOrder starts at a
specific order; PackedPatterns supports the pattern encoding in FC soundtracks.
Ordinary S3M files leave PackedPatterns false. The default NewPlayer behavior
and supported formats are unchanged.
The compatibility core is distributed under the GNU General Public License in
internal/st3/LICENSE; the surrounding library retains
its existing license. No soundtrack assets are included with the core.
For exports or native-length looping, players returned by NewPlayer implement
FiniteRenderer. Its RenderUntilEnd method returns the final complete PCM
frames with io.EOF at the song's native end, without a silent tail. Reset then
Play restarts deterministically. Existing Render callers retain their
continuous-stream behavior; intentional tracker loops remain continuous.