Skip to content

Music and sound

#include "chili/audio.hpp"

A chiptune player for songs written as text, and short sound effects. It is the player from Snek, made shared.

Channels

Channel Constant Plays
0 LEAD melody: square wave, 25% duty
1 BASS bass: triangle wave
2 KICK_CHANNEL kick drum: sine with a fast pitch drop
3 NOISE snare and hi-hat: noise
4-7 SFX1-SFX4 sound effects

There is no volume control: the console's system menu owns volume. Games offer music on/off and sound on/off.

Set up

void init() {
  chili::audio::init();
  chili::audio::set_music(save.data.music);
  chili::audio::set_sound(save.data.sound);
}

void update(uint32_t time) {
  chili::audio::update(time);
}

Songs

A song is three tracks of steps. One step is a 16th note, step_ms long.

const chili::audio::Song title_song = {
  140,   // step_ms
  true,  // loop
  // lead
  "A4 - - - C5 - E5 - D5 - C5 - B4 - C5 - "
  "A4 - - - F4 - A4 - C5 - - - . . . . ",
  // bass
  "A2 - - - E3 - - - A3 - - - E3 - - - "
  "F2 - - - C3 - - - F3 - - - C3 - - - ",
  // drums
  "K . . . H . . . S . . . H . . H "
  "K . . . H . . . S . . . H . S S ",
};

chili::audio::play(title_song);

Note format

Steps are separated by spaces.

Token Means Example
Note starts a note: letter A-G, optional #, octave 0-9 (4 if left out) C4, F#5, G
- holds the previous note C4 - - - is one long C
. rest: releases the note C4 . C4 .
K kick drum (drum track) K . . .
S snare . . S .
H hi-hat H H H H
  • A4 is 440 Hz. Octave 4 starts at middle C (C4, 262 Hz).
  • A track can be empty (""). The song is as long as its longest track; shorter tracks rest at the end.
  • Anything else reads as a rest.

Keep tracks lined up

Write 16 steps per line, one bar of 4/4. Lines that line up across the three tracks are much easier to fix.

Control

Function Does
play(const Song &) Starts a song from the top. Keep the Song alive (a global const).
stop() Stops. current() becomes nullptr.
pause(bool) Pauses and resumes, for a pause menu.
finished() True when a one-shot song (loop = false) has ended, or nothing plays.
current() The song playing, or nullptr.
set_tempo(float scale) Step length scale: 0.8 plays 25% faster. play() resets it to 1.

Speed the music up with the game, like Snek does:

float progress = float(start_ms - step_ms()) / float(start_ms - min_ms);
chili::audio::set_tempo(1.0f - 0.2f * progress);

Play a jingle, then move on when it ends:

chili::audio::play(fanfare);   // loop = false
// later, in update():
if(chili::audio::finished()) scenes.go(results);

Sound effects

An effect is a list of up to 8 steps, each a frequency for some milliseconds, on one channel.

using namespace blit;
namespace audio = chili::audio;

//                              channel      waveform        volume  pulse   release steps
const audio::Sfx sfx_eat     = {audio::SFX1, Waveform::SQUARE, 0x4000, 0x4000, 50, 4, {{523, 30}, {659, 30}, {784, 30}, {1047, 70}}};
const audio::Sfx sfx_menu    = {audio::SFX2, Waveform::SQUARE, 0x2800, 0x7fff, 20, 1, {{880, 30}}};
const audio::Sfx sfx_die     = {audio::SFX1, Waveform::SAW,    0x4800, 0x7fff, 120, 3, {{440, 60}, {330, 60}, {220, 160}}, 1};
//                                                                                                       priority ^

audio::play_sfx(sfx_eat);
audio::play_sfx(sfx_eat, 1.5f);  // pitch up: for combos
Field Means
channel SFX1 to SFX4. Put game sounds and UI sounds on different channels so they do not cut each other off.
waveform blit::Waveform::SQUARE, SAW, TRIANGLE, SINE or NOISE.
volume 0 to 0xffff.
pulse_width Square duty: 0x7fff is 50%, 0x4000 25%.
release_ms Fade-out after the last step.
count, steps How many steps, and the steps ({freq_hz, ms}).
priority Optional, default 0. A playing effect is only cut off by one of the same or higher priority. Give the death sound 1 so a menu blip cannot cut it off.

Settings

Function Does
set_music(bool) Music on/off. The song keeps time while off, so finished() still works.
set_sound(bool) Effects on/off.
music_on(), sound_on() The current settings.

Wire them to toggles in the menu:

chili::Menu::toggle("MUSIC", save.data.music, [] { chili::audio::set_music(save.data.music); save.store(); }),