-
Notifications
You must be signed in to change notification settings - Fork 0
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.
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.
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.
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.
Each frame the APU:
- Asks Godot how many audio samples it needs
- For each sample, sums the output of every enabled channel, each scaled by its volume
- Clamps the result to
[-1.0, 1.0]so it doesn't clip - Pushes it out as a stereo frame — same signal on both sides
- Advances the internal time clock by
1.0 / 44100
Set these directly on the APU object:
apu.pulse1_enabled = true
apu.pulse2_enabled = true
apu.triangle_enabled = true
apu.noise_enabled = false
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 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.