Package github.com/matjam/bunyip/audio/tracker
audio/tracker
Package tracker loads and plays tracker music: ProTracker MOD (4 to 32 channels), ScreamTracker 3 S3M, FastTracker 2 XM and Impulse Tracker IT. Player implements audio.Stream, so a module plays through the mixer as any other voice does. While it plays, call Seek to move to a song position and row, read the Position, and Mute or Solo pattern channels. The four formats share one Module model. Module.Format and the flags the loaders set select the format-specific behaviour (period tables, slide units, effect semantics).
To load a module from bytes, call Load, which detects the format; LoadMOD, LoadS3M, LoadXM and LoadIT each read one format. A Module carries the song's name, orders, patterns, samples and instruments, so a game can show what is playing or drive visuals from the pattern data. NewPlayer renders it at the mixer's rate. The player reports its position (order and row) for syncing gameplay to the music, and it accepts seeks and per-channel mute and solo. Playback is deterministic, so a module renders the same bytes on every platform.
Index
- Constants
- type AutoVibrato
- type Cell
- type EnvPoint
- type Envelope
- type Format
- type Instrument
- type LoopType
- type Module
- type NNA
- type Pattern
- type Player
func NewPlayer(m *Module, rate int) *Playerfunc (p *Player) Channels() intfunc (p *Player) Finished() boolfunc (p *Player) Length() intfunc (p *Player) Mute(channel int, mute bool)func (p *Player) Muted(channel int) boolfunc (p *Player) Position() (order, row int)func (p *Player) Read(out []float32) intfunc (p *Player) Rows(order int) intfunc (p *Player) Seek(order, row int)func (p *Player) Solo(channel int, solo bool)func (p *Player) Soloed(channel int) bool
- type Sample
Constants
const (
NoteNone = -1
NoteOff = 1000 // key off: release envelopes, then fade
NoteCut = 1001 // stop immediately
NoteFade = 1002 // start fading without releasing sustain
)
Special note values.
Types
type AutoVibrato source
type AutoVibrato struct {
Type int // 0 sine, 1 ramp, 2 square, 3 random
Sweep int // ticks to reach full depth
Depth int // depth in the source format's units
Rate int // oscillator increment per tick in source-format units
}
AutoVibrato is instrument-level vibrato (XM instruments, IT samples).
type Cell source
type Cell struct {
Note int // NoteNone, NoteOff, NoteCut, NoteFade, or a note index
Instrument int // 0 none
VolCmd volCmd // decoded volume-column command; internal command vocabulary
VolParam int // volume-column command argument
Effect effect // decoded effect command; internal command vocabulary
Param byte // effect command argument
}
Cell is one channel's entry in a row.
type EnvPoint source
type EnvPoint struct {
Tick int // time in tracker ticks
Value float32 // envelope value in the ranges described by EnvPoint
}
EnvPoint is one node: Tick and a value in 0..64 for volume, -32..32 for panning and pitch (IT stores signed values; XM pan is 0..64 shifted).
type Envelope source
type Envelope struct {
Points []EnvPoint // nodes in increasing tick order
Enabled bool // apply the envelope during playback
Sustain bool // hold or loop sustain points before key off
Loop bool // loop between LoopStart and LoopEnd
SustainStart int // point indices
SustainEnd int // last sustain point index
LoopStart int // first loop point index
LoopEnd int // last loop point index
}
Envelope is a volume, panning or pitch envelope.
type Format source
type Format int
Format selects the period and effect semantics.
type Instrument source
type Instrument struct {
Name string // instrument display name
SampleMap [120]int // note -> sample index, -1 none
NoteMap [120]int // note -> note actually played (IT); identity elsewhere
VolEnv Envelope // volume envelope
PanEnv Envelope // panning envelope
PitchEnv Envelope // pitch or filter envelope
PitchIsFilter bool // IT: the pitch envelope drives the filter cutoff instead
Fadeout int // subtracted from a 65536-scale fade each tick after key off
NNA NNA // action for the previous note when a new note begins
DCT int // duplicate check type: 0 off, 1 note, 2 sample, 3 instrument
DCA int // duplicate check action: 0 cut, 1 note off, 2 fade
GlobalVolume int // 0..128 (IT); 128 elsewhere
Pan float32 // default pan
HasPan bool // apply Pan when this instrument starts a note
FilterCutoff int // IT: 0..127, -1 unset
FilterResonance int // IT: 0..127, -1 unset
}
Instrument maps notes to samples and shapes them with envelopes.
type Module source
type Module struct {
Title string // display title from the file
Channels int // pattern channels, excluding NNA background voices
Samples []Sample // decoded samples referenced by instruments and cells
Instruments []Instrument // empty for sample-only formats (MOD, S3M)
Patterns []Pattern // pattern data indexed by Orders
Orders []int // pattern index per song position
Restart int // song position to loop back to
Speed int // initial ticks per row
Tempo int // initial BPM
Pan []float32 // initial pan per channel, -1 left to +1 right
ChannelVol []int // 0..64 per channel; nil means 64
Format Format // selects the source format's playback semantics
GlobalVolume int // 0..128
MixVolume int // 0..128: the file's master/mixing volume; per-channel gain follows it
LinearSlides bool // XM/IT: slides move pitch by fractions of a semitone
OldEffects bool // IT "old effects" flag
CompatGxx bool // IT: Gxx shares memory with Exx/Fxx
}
Module is a song in memory, in a form shared by every loader.
LoadIT source
func LoadIT(data []byte) (*Module, error)
LoadIT parses an Impulse Tracker module, including compressed samples.
LoadMOD source
func LoadMOD(data []byte) (*Module, error)
LoadMOD parses a ProTracker-family module.
type Pattern source
type Pattern struct {
Rows [][]Cell // row index, then channel index
}
Pattern is rows of cells, one cell per channel.
type Player source
type Player struct {
Loop bool // restart at Module.Restart on reaching the order list's end
// AmigaFilter enables the ProTracker LED low-pass filter (E00/E01).
// Off by default: most players and listeners expect the unfiltered mix.
AmigaFilter bool
// Cubic switches sample interpolation from linear to four-point
// Hermite, which rounds off the aliasing that makes chip samples sound
// harsher than in other players. Off by default to match the classic
// sound.
Cubic bool
// contains filtered or unexported fields
}
Player renders a Module to stereo float32 at a fixed rate. It implements
audio.Stream. Read runs on the mixer's thread once the player is
playing; Seek, Position, Mute and Solo take the same lock, so the game
loop can call them while it plays. Set Loop, AmigaFilter and Cubic
before playing.
Keep the Module and its referenced slices immutable while a player
uses them. The player retains them rather than copying the song.
Play a module through the engine's mixer. Player is an audio.Stream,
so it plays like any other music.
Example
package main
import (
"os"
"github.com/matjam/bunyip/audio"
"github.com/matjam/bunyip/audio/tracker"
)
func main() {
data, err := os.ReadFile("song.xm")
if err != nil {
return
}
mod, err := tracker.Load(data) // MOD, S3M, XM or IT, told apart by content
if err != nil {
return
}
mixer := audio.NewMixer(48000)
player := tracker.NewPlayer(mod, mixer.Rate())
player.Loop = true
mixer.PlayStream(player, audio.PlayOptions{Volume: 0.8})
}
NewPlayer source
func NewPlayer(m *Module, rate int) *Player
NewPlayer prepares a module for playback at rate Hz. Use a positive rate and a non-nil module returned by a loader. For a hand-built module, Pan must contain Channels entries, and ChannelVol must either be nil or contain Channels entries; pattern and sample indices must follow the Module model. Construction does not validate every field or return an error.
Channels source
func (p *Player) Channels() int
Channels is the number of pattern channels, the range Mute and Solo accept.
Finished source
func (p *Player) Finished() bool
Finished reports whether a non-looping song has ended.
Length source
func (p *Player) Length() int
Length is the number of song positions in the order list, the range Seek accepts.
Mute source
func (p *Player) Mute(channel int, mute bool)
Mute silences a pattern channel while the song plays on, as a tracker's channel mute does; notes it starts still run their course silently, so unmuting is seamless. Channels outside the module are ignored.
Position source
func (p *Player) Position() (order, row int)
Position reports the current song position (an index into the order list) and the row within its pattern.
Read source
func (p *Player) Read(out []float32) int
Read fills out and returns the frames written (audio.Stream).
Rows source
func (p *Player) Rows(order int) int
Rows is the number of rows in the pattern at a song position, or 0 for a position outside the song or one that names no pattern.
Seek source
func (p *Player) Seek(order, row int)
Seek jumps to a song position and row and plays on from there, cutting whatever was sounding. Order is clamped to the song and row to the pattern; a position that names no pattern moves on to the next that does. Speed, tempo and global volume stay as they are, so a song that changes them along the way plays from the new place at whatever was in force. Seeking a finished song starts it again.
type Sample source
type Sample struct {
Name string // sample display name
Data []float32 // mono sample frames, nominally -1..1
LoopStart int // first frame of the normal loop
LoopEnd int // exclusive end frame of the normal loop
Loop LoopType // normal loop behavior
SusLoopStart int // IT sustain loop, active until key off
SusLoopEnd int // exclusive end frame of the sustain loop
SusLoop LoopType // sustain loop behavior before key off
Volume int // 0..64
GlobalVolume int // 0..64 (IT); 64 elsewhere
Finetune float32 // semitones added to every note
C4Speed int // playback rate of the reference note (S3M/IT)
RelativeNote int // XM: semitones added to every note
Pan float32 // default pan, -1..1
HasPan bool // whether Pan applies on note start
Vibrato AutoVibrato // sample auto-vibrato settings
}
Sample is one instrument's PCM data, mono, -1..1.
Source files
bench_test.go denormal_test.go effects.go envelope.go example_test.go fuzz_test.go golden_test.go it.go itdecompress.go mod.go module.go perf_bench_test.go player.go render.go review_test.go row.go roweffects.go s3m.go tables.go tick.go tracker_test.go xm.go