Skip to content
GattoDev edited this page Jun 10, 2026 · 2 revisions

APU

The APU handles audio. It generates sound in real time by synthesizing waveforms sample by sample and feeding them into Godot's AudioStreamGenerator. The design is loosely based on the NES APU — two pulse channels, a triangle channel, and a noise channel.

Sample rate is 44,100 Hz. Buffer length is 50ms.


Channels

Pulse 1 & Pulse 2

Square/pulse waves. The core of the sound. A duty cycle controls what fraction of each cycle is "on" vs "off" — when it's on the wave outputs +1.0, when it's off it outputs -1.0.

duty 0.5  → classic square wave, full and buzzy
duty 0.25 → thinner, more NES-sounding
duty 0.125 → very thin, almost clicking

Each channel has its own frequency, volume, and duty cycle. Pulse 1 & Pulse 2 start disabled.

Triangle

A smoother wave than pulse — no harsh edges, sounds rounder. Good for bass lines or melody that needs to sit back in the mix. The math is just an absolute value applied to a sawtooth, which makes the output linearly ramp up and then back down.

Starts disabled by default.

Noise

Pure white noise — a completely random sample every frame. Useful for percussion, explosions, static, anything percussive or textural. Very simple internally: just randf_range(-1.0, 1.0).

Starts disabled by default.


Mixing

Each frame the APU:

  1. Asks Godot how many audio samples it needs
  2. For each sample, sums the output of every enabled channel, each scaled by its volume
  3. Clamps the result to [-1.0, 1.0] so it doesn't clip
  4. Pushes it out as a stereo frame — same signal on both sides
  5. Advances the internal time clock by 1.0 / 44100

Enabling channels

Set these directly on the APU object:

apu.pulse1_enabled = true
apu.pulse2_enabled = true
apu.triangle_enabled = true
apu.noise_enabled = false

Setting properties directly

You can just set properties on the APU directly:

apu.pulse1_freq = 440.0
apu.pulse1_volume = 0.2
apu.pulse1_duty = 0.5

apu.pulse2_freq = 220.0 apu.triangle_freq = 110.0


The register interface

The APU also has a fake hardware register system if you want to feel like you're writing for real hardware:

apu.write_register("PULSE1_FREQ", 440.0)
apu.write_register("PULSE1_VOL", 0.15)
apu.read_register("PULSE1_FREQ")  # → 440.0

It does the same thing as setting properties directly — it's just an alternate interface. write_register updates both the register map and the actual property.

Available registers:

Register What it controls
PULSE1_FREQ Pulse 1 frequency in Hz
PULSE1_VOL Pulse 1 volume (0.0–1.0)
PULSE1_DUTY Pulse 1 duty cycle (0.0–1.0)
PULSE2_FREQ Pulse 2 frequency in Hz
PULSE2_VOL Pulse 2 volume (0.0–1.0)
PULSE2_DUTY Pulse 2 duty cycle (0.0–1.0)
TRI_FREQ Triangle frequency in Hz
TRI_VOL Triangle volume (0.0–1.0)
NOISE_VOL Noise volume (0.0–1.0)

Note: noise_enabled isn't in the register map — toggle that directly on the APU.

Clone this wiki locally