maxmod

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


Configuration

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.


Tutorial

  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
      else:
        module f
    

    Note

    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:

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

    Note

    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:

    type
      Sample = enum
        sfxCoin
        sfxJump
        sfxShoot
      Module = enum
        modSpacecat
        modSubway
    
  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:
      keyPoll()
    
      if keyHit(kiA):
        # play sound effect
        maxmod.effect(sfxShoot)
    
      # mix one frame of audio
      maxmod.frame()
    
      VBlankIntrWait()
    

Types

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
  • mmPlayLoop
  • mmPlayOnce
type MmMixMode = enum
  • mmMix8kHz8121 Hz
  • mmMix10kHz10512 Hz
  • mmMix13kHz13379 Hz
  • mmMix16kHz15768 Hz
  • mmMix18kHz18157 Hz
  • mmMix21kHz21024 Hz
  • mmMix27kHz26758 Hz
  • mmMix31kHz31536 Hz
type MmSoundEffect = object

    Field    

    Type    

    Description    

id uint32

sample ID (defined in soundbank header)

rate uint16 handle*: MmSfxHandle

sound handle

volume uint8

volume, 0..255

panning uint8

panning, 0..255

type MmGbaSystem = object

    Field    

    Type    

mixingMode MmMixMode
modChannelCount uint32
mixChannelCount uint32
moduleChannels pointer
activeChannels pointer
mixingChannels pointer
mixingMemory pointer
waveMemory pointer
soundbank MmSoundbankPtr

Variables

let soundbankBin: MmSoundbankPtr

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


Constants

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

Procedures

proc init(soundbank: MmSoundbankPtr, channels: uint)

Initialize Maxmod with default settings.

Soundbank:

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)

Channels:

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.

Function:

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:

ID of module to play.

Mode:

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)

Jumps to the pattern at the given index in the song’s pattern order table, and starts playing that pattern from the beginning.

Position:

New position in the song.

proc getPosition(): uint

Get playback position (i.e. the index of the current pattern).

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:

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

proc activeSub(): bool

Returns true if a jingle is actively playing.

proc setModuleVolume(volume: FixedN[10])

Set volume scaler for music.

Volume:

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.

Volume:

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.

Tempo:

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

proc setModulePitch(pitch: FixedN[10])

Set pitch of playback.

Pitch:

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.

Parameters:

Id:

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.

Parameters:

Sound:

Sound effect attributes.

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

Set the volume of a sound effect.

Parameters:

Handle:

Sound effect handle.

Volume:

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.

Parameters:

Handle:

Sound effect handle.

Panning:

0.0 ..< 1.0 = left .. right

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

Set the playback rate of an effect.

Parameters:

Handle:

Sound effect handle.

Rate:

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

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

Scale the playback rate of an effect.

Parameters:

Handle:

Sound effect handle.

Factor:

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

proc cancel(handle: MmSfxHandle)

Stop sound effect.

Parameters:

Handle:

Sound effect handle.

proc release(handle: MmSfxHandle)

Release sound effect (invalidate handle and allow interruption)

Parameters:

Handle:

Sound effect handle.

proc active(): bool

Returns true if module is playing.

proc setEffectsVolume(volume: FixedN[8])

Set master volume scale for effect playback.

Parameters:

Volume:

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.