Bindings for the Maxmod sound system by Mukunda Johnson. See the maxmod reference manual for more documentation.


The audio.nims file is used to add music and sfx to your project. This runs before your game is compiled, and generates two enums which are available to your game code: Sample for sound effects and Module for songs.

In this file, the following operations are available:

proc sample(name: string)#

Add a sound effect in .wav format, to be converted and added to the soundbank.

proc module(name: string)#

Add a song in .mod, .xm, .it or .s3m format, to be converted and added to the soundbank.

Since there are no other parameters to these procedures, you can write an audio.nims to convert new files automatically, as shown in the tutorial section below.


  1. Configure your audio.nims to automatically scan a directory in your project:

    # Add all music and sounds from the audio directory.
    for f in listFiles("audio").sorted:
      if f.endsWith(".wav"):
        sample f
        module f


    The list of files should be sorted, otherwise the files aren’t guaranteed to appear in the same order on different machines, so you lose reproducibility of your builds. Ok, tbh there never was any reproducibility but it’s nice to pretend. x)

  2. Add some music & sounds to the directory:

    ├── audio.nims
    ├── audio
    │   ├── coin.wav
    │   ├── jump.wav
    │   ├── shoot.wav
    │   ├── spacecat.xm
    │   └── subway.xm
    ├── config.nims
    └── game.nim


    Sounds must be .wav files, 8-bit, mono. 16000 Hz sample rate is usually adequate. Music must be tracker modules in .mod, .xm, .it or .s3m formats.

    This will cause the following types to be generated:

      Sample = enum
      Module = enum
  3. Import maxmod, initialise it, and start using the sfx and mod values in your code:

    import natu/[bios, irq, input, maxmod]
    # register maxmod VBlank handler
    irq.put(iiVBlank, maxmod.vblank)
    # init with 8 channels
    maxmod.init(soundbankBin, 8)
    # play music
    maxmod.start(modSpacecat, mmPlayLoop)
    while true:
      if keyHit(kiA):
        # play sound effect
      # mix one frame of audio


type Sample = enum#

Represents an audio sample in ROM, converted from a .wav file. Each converted sample will be given an enum value with the “sfx” prefix. For example bark.wav will become sfxBark.

type Module = enum#

Represents a song in ROM, converted from a .mod, .xm, .it or .s3m file. Each converted song will be given an enum value with the “mod” prefix. For example funky.xm will become modFunky.

type MmSoundbankPtr = distinct cstring#

Pointer to soundbank data

type MmSfxHandle = distinct uint16#
proc `==`(a, b: MmSfxHandle): bool#
type MmFnPtr = proc () {.nimcall.}#
type MmCallback = proc (msg: uint; param: uint): uint {.nimcall.}#
type MmPlaybackMode = enum#
type MmMixMode = enum#
type MmSoundEffect = object#
type MmGbaSystem = object#


let soundbankBin: MmSoundbankPtr#

Pointer to your game’s soundbank data. You should pass this to maxmod.init.


These are mostly useful for initialising Maxmod manually with a MmGbaSystem object.

const mmMixLen8kHz = 544#
const mmMixLen10kHz = 704#
const mmMixLen13kHz = 896#
const mmMixLen16kHz = 1056#
const mmMixLen18kHz = 1216#
const mmMixLen21kHz = 1408#
const mmMixLen27kHz = 1792#
const mmMixLen31kHz = 2112#
const mmSizeofModCh = 40#
const mmSizeofActCh = 28#
const mmSizeofMixCh = 24#


proc init(soundbank: MmSoundbankPtr, channels: uint)#

Initialize Maxmod with default settings.



Memory address of soundbank (in ROM). A soundbank file can be created with the Maxmod Utility (or generated by Natu when you run nim build)


Number of module/mixing channels to allocate. Must be greater or equal to the channel count in your modules.

For GBA, this function uses these default settings (and allocates memory): 16kHz mixing rate, channel buffers in EWRAM, wave buffer in EWRAM, and mixing buffer in IWRAM.

proc init(setup: ptr MmGbaSystem)#

Initialize system. Call once at startup.

proc vblank()#

This procedure must be linked directly to the VBlank IRQ, or be the very first thing that runs in your custom VBlank handler.

During this procedure, the sound DMA is reset. The timing is extremely critical, so make sure that it is not interrupted, otherwise garbage may be heard in the output.

proc setVBlankHandler(function: MmFnPtr)#

Install user vblank handler.



Pointer to your VBlank handler.

proc setEventHandler(handler: MmCallback)#

Install handler to receive song events.

Use this function to receive song events. Song events occur in two situations: One is by special pattern data in a module (which is triggered by SFx/EFx commands). The other occurs when a module finishes playback (in mmPlayOnce mode).

During the song event, Maxmod is in the middle of module processing. Avoid using any Maxmod related functions during your song event handler since they may cause problems in this situation.

proc frame()#

This is the main work routine that processes music and updates the sound output.

This function must be called every frame. If a call is missed, garbage will be heard in the output and module processing will be delayed.

proc start(id: Module, mode: MmPlaybackMode = mmPlayLoop)#

Start module playback.



ID of module to play.


Playback mode (mmPlayLoop or mmPlayOnce)

proc pause()#

Pause module playback, resume with maxmod.resume()

proc resume()#

Resume module playback, pause with maxmod.pause()

proc stop()#

Stop module playback. start again with maxmod.start().

proc setPosition(position: uint)#

Set playback position.



New position in the module sequence.

proc getPosition(): uint#

Get playback position.

proc active(): bool#

Returns true if module is playing.

proc jingle(id: Module)#

Play module as jingle. Jingles are limited to 4 channels only.



ID of module (defined in soundbank header)

proc activeSub(): bool#

Returns true if a jingle is actively playing.

proc setModuleVolume(volume: FixedN[10])#

Set volume scaler for music.



Multipler for the overall volume of the song, ranging from 0.0 .. 1.0 (silent .. normal).

proc setJingleVolume(volume: FixedN[10])#

Set volume scaler for jingles.



Multipler for the overall volume of the jingle, ranging from 0.0 .. 1.0 (silent .. normal).

proc setModuleTempo(tempo: FixedN[10])#

Set tempo of playback.



Multiplier for the overall tempo of the song, ranging from 0.5 .. 2.0

proc setModulePitch(pitch: FixedN[10])#

Set pitch of playback.


Multiplier for the overall pitch of the song, ranging from 0.5 .. 2.0.

proc playModule(address: pointer, mode: uint, layer: uint)#

Play direct MAS file

proc effect(id: Sample): MmSfxHandle#

Play a sound effect at its default frequency with full volume and centered panning.



Sound effect ID. (defined in output/soundbank.nim which is generated for your project)

proc effectEx(sound: ptr MmSoundEffect): MmSfxHandle#

Play a sound effect with all parameters.



Sound effect attributes.

proc setVolume(handle: MmSfxHandle, volume: FixedN[8])#

Set the volume of a sound effect.



Sound effect handle.


Effect volume ranging from 0.0 ..< 1.0 (underlying value from 0 .. 255)

proc setPanning(handle: MmSfxHandle, panning: FixedN[8])#

Set the panning of a sound effect.



Sound effect handle.


0.0 ..< 1.0 = left .. right

proc setRate(handle: MmSfxHandle, rate: FixedN[10])#

Set the playback rate of an effect.



Sound effect handle.


Absolute playback rate, ranging from 0.0 ..< 64.0.

proc scaleRate(handle: MmSfxHandle, factor: FixedN[10])#

Scale the playback rate of an effect.



Sound effect handle.


Amount by which to multiply the playback rate, ranging from 0.0 ..< 64.0.

proc cancel(handle: MmSfxHandle)#

Stop sound effect.



Sound effect handle.

proc release(handle: MmSfxHandle)#

Release sound effect (invalidate handle and allow interruption)



Sound effect handle.

proc active(): bool#

Returns true if module is playing.

proc setEffectsVolume(volume: FixedN[8])#

Set master volume scale for effect playback.



0.0 ..< 1.0 representing 0% to 100% volume.

proc cancelAllEffects()#

Stop all sound effects

Playback Events#

const mmcbSongMessage = 0x0000002A'u32#

This happens when Maxmod reads a SFx (or mod/xm EFx) effect from a module.

param will contain the number specified in the effect (e.g. 3 for EF3).

const mmcbSongFinished = 0x0000002B'u32#

This happens when a module has finished playing, which can happen if you passed mmPlayOnce to maxmod.start, or when playing a jingle.

param will be 0 if the main module has ended, or 1 if the sub module (jingle) has ended.