Skip to main content

Playing Audio

WASM-4's sound system has 4 independent channels. Each channel is dedicated to a different type of audio waveform.

  • 2 pulse wave (square wave) channels. The classic chiptune sound.
  • 1 triangle wave channel. A softer sound, great for bass.
  • 1 noise channel. A harsh sound, for percussion and effects.
info

WASM-4's sound system is closely inspired by the architecture of the Nintendo NES and Gameboy.

Playing a Tone#

The tone() function is used to play a tone with a given frequency on a given channel.

tone (frequency, duration, volume, flags)

  • Frequency is the "pitch", measured in hertz.
  • Durations are measured in frames, 1/60ths of a second.
  • Volume ranges from 0 (silent) to 100 (full volume).
  • Flags sets the channel (0-3), duty cycle (0-3) and panning (0-2).

For example, to play a one second (60 frames) tone of 262 Hz (middle C) on the first pulse wave channel:

w4.tone(262, 60, 100, w4.TONE_PULSE1);

Duty Cycle#

The duty cycle of the pulse channels can be controlled with additional flags:

FlagDuty Cycle
TONE_MODE112.5% (default)
TONE_MODE225%
TONE_MODE350%
TONE_MODE475%

For example, to play at 50% duty cycle (square wave):

w4.tone(262, 60, 100, w4.TONE_PULSE1 | w4.TONE_MODE3);

Frequency Slide#

We can actually pass two different frequencies to tone(). The high 16 bits of the frequency parameter is used for a second frequency. If non-zero, it specifies the frequency to slide to over the duration of the tone.

For example, to slide the tone starting from 262 Hz and up to 523 Hz:

w4.tone(262 | (523 << 16), 60, 100, w4.TONE_PULSE1);

Volume#

There are two volume levels that can be passed to the tone function:

VolumeShiftDescription
Sustain0The main volume used for the sustain duration.
Peak8Optional, the peak volume reached by the attack duration.

If the peak volume is not set (or is set to 0), it defaults to 100.

ADSR Envelope#

ADSR describes how the volume changes over time, and has 4 time components:

TimeShiftDescription
Attack24The time it takes to initially ramp up from 0 volume to peak volume.
Decay16The time taken to ramp down from peak volume to the sustain volume.
Sustain0The time to hold the tone steady at the sustain volume.
Release8The time to ramp back down to 0 volume.

These times are all measured in frames (1/60th of a second), and can be packed into the duration parameter.

For example, to play a tone that sustains for one second and releases over half a second (30 frames):

w4.tone(262, 60 | (30 << 8), 100, w4.TONE_PULSE1);

Panning#

Tones can be panned either center (default), far left (TONE_PAN_LEFT), or far right (TONE_PAN_RIGHT), similar to on a gameboy.

w4.tone(262, 60, 100, w4.TONE_PULSE1 | w4.TONE_PAN_LEFT);

Note Mode#

By enabling Note Mode with the TONE_NOTE_MODE flag, tone will use MIDI note numbers rather than frequencies. This results in more accurate pitches when playing musical notes.

You can read more about how this works in the tone(...) documentation.

Here's the same example as before, now playing middle-C using the MIDI note number 60:

w4.tone(60, 60, 100, w4.TONE_PULSE1 | w4.TONE_NOTE_MODE);

Calculating Flags#

Setting ADSR flags require the use of various bitwise and bitshift operations. This can be a little confusing to understand.

These functions can be used to understand how they are calculated:

function toneFrequency(freq1: i32 = 0, freq2: i32 = 0): u32 {    return freq1 | (freq2 << 16);}
function toneDuration(attack: i32 = 0, decay: i32 = 0, sustain: i32 = 0, release: i32 = 0): u32 {    return (attack << 24) | (decay << 16) | sustain | (release << 8);}
function toneVolume(peak: i32 = 0, volume: i32 = 0): u32 {    return (peak << 8) | volume;}
function toneFlags(channel: i32 = 0, mode: i32 = 0, pan: i32 = 0): u32 {    return channel | (mode << 2) | (pan << 4);}

Sound Tool#

The sound demo and sound test are a great way to quickly experiment with different sounds and find values to plug into your game: