For my MMBasic vector game I needed two things at once: background music and sound effects that don’t trip over each other. On the CMM2 you get there with MOD files and fixed audio channels, and the trick is to know up front which effect lives on which channel.

One MOD file for music, one as a sample library

A MOD file is more than a single song. It carries patterns plus a whole sample library, and every sample is addressable individually. I built two MOD files: one with the background music, one that just exists to hold the sound effects as samples.

The game has one of them loaded at any time. With music off, I load the effects MOD instead so the samples are still available.

CONST music_file$ = "game_music.mod"
CONST sounds_file$ = "game_sounds.mod"

IF music_on THEN
  PLAY MODFILE music_file$
ELSE
  PLAY MODFILE sounds_file$
END IF

Naming sample slots and channels

What’s worked well: pin sample numbers and channel numbers down with constants at the top of the program. They’re then in one place and the rest of the code reads almost like commentary.

CONST SND_LASER       = 12
CONST SND_TELEPORT    = 13
CONST SND_UFO_SHOT    = 15
CONST SND_EXPLODE_S   = 16
CONST SND_EXPLODE_M   = 17
CONST SND_EXPLODE_L   = 18
CONST SND_THRUST      = 19

CONST SND_CH_LASER   = 1
CONST SND_CH_THRUST  = 2
CONST SND_CH_EXPLODE = 3
CONST SND_CH_EVENT   = 4

Channels are the secret sauce. A sample fired on a channel that’s already busy replaces the previous one. For some effects that’s exactly what you want, for others it’s catastrophic.

Playing an effect

PLAY MODSAMPLE sample, channel triggers a sample on a channel. If the channel is busy it gets cut off.

PLAY MODSAMPLE SND_LASER,    SND_CH_LASER
PLAY MODSAMPLE SND_THRUST,   SND_CH_THRUST
PLAY MODSAMPLE SND_EXPLODE_M, SND_CH_EXPLODE

Shots, thrust, and explosions each live on their own channel. So a shot doesn’t kill the thrust loop, an explosion doesn’t drop the laser. That matters most for the thrust sound, which has to keep playing as long as the player is accelerating.

On a single shared event channel like SND_CH_EVENT I park sounds where it’s fine for one to interrupt another, like teleport or UFO alarm. By the time a second one fires, the first has usually faded out anyway.

Volume and muting

I adjust master volume with PLAY VOLUME, the two values are left and right:

DIM INTEGER volume
volume = 80
PLAY VOLUME volume, volume

When swapping between the music MOD and the effects MOD, PLAY STOP is mandatory, otherwise the two streams collide:

SUB ToggleMusic()
  music_on = 1 - music_on
  PLAY STOP
  IF music_on THEN
    PLAY MODFILE music_file$
  ELSE
    PLAY MODFILE sounds_file$
  END IF
END SUB

A MOD file is a song and a sample library in one; splitting it into two MODs gives you very flexible audio at minimal RAM cost. But without strict channel discipline the sounds cut each other off, and the constant block at the top of the program was the cheapest investment in saving hours of debugging. PLAY MODSAMPLE is also low-latency since no WAV file gets pulled from disk per call. The two-MOD pattern with fixed channels keeps my game running steadily with no audio dropouts.