TI SN76489 Sound Generator

3 tone channels + 1 noise channel. Mixed on-chip. Mono output. Not present on the Electron (Electron uses its own ULA for sound).

The chip lives on the System VIA’s slow peripheral bus (Port A) with /WE driven by addressable-latch line 0. The CPU does not address it directly — every write is a multi-step dance through the System VIA. See system-via for the slow-bus general protocol.

Clock + internal reference

Per master-arm Ch 1: the SN76489 receives a 4 MHz reference clock from central timing on all BBC machines. The chip’s internal divider is ÷16, giving an effective 250 kHz internal reference. Output frequency for tone channels = reference ÷ 2 ÷ N = 125_000 ÷ N Hz where N is the 10-bit divider.

⚠ A widely-cited scarybeast blog post says the BBC’s input clock is 2 MHz with ÷8 internal divide. That’s incorrect — the Master ARM and BBC service manuals are unambiguous that it’s 4 MHz ÷ 16. Both descriptions arrive at the same 250 kHz reference, so the audible behaviour is identical, but the chip-input fact matters for hardware-level analysis (e.g. expected periodic glitches).

Downstream analog chain (BBC)

The SN76489 output is not driven directly to the speaker. Per scarybeast-sn76489-sampled (oscilloscope-traced):

  1. LM324N quad op-amp — likely contains an ~8 kHz low-pass (an 8 kHz square wave from the chip emerges as a near-sine, characteristic of a filter rolling off in that band).
  2. LM386N-1 audio power amplifier.
  3. Post-amp DC-normalisation network to keep the speaker happy (“normalizes the oscillation around the positive voltage point”).

Signal levels: at the chip output, a 125 kHz square is 720 mV peak-to-peak, centred ~3.3 V (not symmetric around 0). This asymmetric centring is the key enabler of the sampled-sound PCM trick — each attenuator setting shifts the mid-point DC offset, which the downstream low-pass filter extracts.

The ~8 kHz LM324N rolloff also imposes a soft anti-aliasing limit on PCM playback through this chip — sample rates much above 16 kHz produce diminishing audible bandwidth despite the chip’s 250 kHz internal capability.

Why sample playback actually works (the voltage-decay mechanism)

Per smspower-sn76489:

“Wherever a voltage (output) is artificially held away from zero, there will be leakage and the actual output will decay towards zero at a rate proportional to the offset from zero.”

“If the tone register value is zero, the constant offset output will just decay to zero. However, whenever the volume of the output is changed, the constant offset is restored. This allows speech effects.”

So the sample-playback chain is:

  1. Period set to 1 (per scarybeasts) emits a 125 kHz ultrasonic square wave.
  2. The LM324N low-pass strips the ~125 kHz carrier and the LM386N analog chain integrates the per-volume DC offset.
  3. Volume writes refresh that DC offset before the leakage current can drag it back to 0.
  4. Rapid volume writes therefore play out as a PCM sequence on the speaker — the chip + analog chain is doing the digital-to-analog conversion essentially by accident.

The 62.5 kHz / 16 µs cadence used by scarybeasts (sampled-sound) is fast enough that the volume DC has no time to decay measurably between writes.

Channels

Chip nameBBC channel (BASIC SOUND)Use
Tone 1channel 1tone
Tone 2channel 2tone
Tone 3channel 3tone
Noisechannel 0white / periodic noise

Internal register addressing (3-bit field)

R2 R1 R0Register
0 0 0Tone 3 frequency
0 0 1Tone 3 volume
0 1 0Tone 2 frequency
0 1 1Tone 2 volume
1 0 0Tone 1 frequency
1 0 1Tone 1 volume
1 1 0Noise control
1 1 1Noise volume

The 3-bit register address is embedded in the high nibble of every “latch” byte sent to the chip.

Tone frequency

Per channel: 10-bit divider N.

frequency = 4_000_000 / (32 × N)
          = 125_000 / N    Hz

Examples:

  • N = 1 → 125 kHz (out of audible range; effectively silent).
  • N = 239 → ~523 Hz (middle C).
  • N = 1023 → ~122 Hz.
  • N = 0 → behaviour disputed across sources, see “Period 0/1 disputed behaviour” below.

Period 0/1 disputed behaviour

For the discrete TI SN76489AN that ships in the BBC, two sources disagree:

  • SMSPower: “If the register value is zero or one then the output is a constant value of +1. This is often used for sample playback on the SN76489.” Maxim hedges this may be Sega-specific.
  • scarybeasts (2020) measured on real BBC: period = 1 produces a clean 125 kHz square wave, not a constant DC. 720 mV peak-to-peak, centred ~3.3 V.

Both views reconcile to the same audible result for sample playback: the downstream LM324N (~8 kHz low-pass) strips the 125 kHz carrier and leaves only the volume-derived DC envelope on the speaker. See sampled-sound for the full sample-playback discussion.

For ordinary tone-generation use: don’t write period 0 — behaviour undocumented for the BBC’s specific chip. Use period 1 only if you intend sample playback (where it’s the canonical ultrasonic-carrier setup).

Tone registers update immediately on each byte

Per smspower-sn76489: when writing a two-byte tone update (latch byte → data byte), the register updates after the latch byte too, not just after the data byte completes. So between the two writes the tone briefly holds a glitched value (low 4 bits new, high 6 bits stale). At ~16 µs between writes this glitch is audible on slow tones. For PCM playback timing this matters.

The latched-channel state is sticky — after a tone latch + data, the chip is still latched on that channel; further data bytes update the same channel’s high 6 bits with no re-latch. Useful for smooth frequency sweeps.

Volume / attenuation (4 bits)

A3 A2 A1 A0AttenuationLinear amplitude (16-bit ref)
00000 dB (max — chip’s “loudest”)32767
0001-2 dB26028
0010-4 dB20675
0011-6 dB16422
0100-8 dB13045
0101-10 dB10362
0110-12 dB8231
0111-14 dB6568
1000-16 dB5193
1001-18 dB4125
1010-20 dB3277
1011-22 dB2603
1100-24 dB2067
1101-26 dB1642
1110-28 dB1304
1111OFF (silent)0 (hard-clamped)

Linear amplitudes per smspower-sn76489 (calibrated against SMS1 / Mega Drive). Each step is exactly −2 dB (so multiplier = 10^(−0.1) = 0.79432823 between successive levels). Index 15 is hard-clamped to 0 regardless — the chip enforces a true mute, not just −30 dB.

The non-linear (logarithmic) response matters for sample playback: equal increments in the volume register produce unequal increments in audible level. Per-instrument LUTs (as in sampled-sound) compensate.

BBC convention is inverted: in OSWORD &07 amplitude, &FF = chip-volume 15 = max, &00 = chip-volume 0 = silent. MOS does the conversion.

Noise channel

The “noise channel” isn’t a fourth oscillator — it’s a 15-bit Linear Feedback Shift Register (LFSR) clocked by a counter (like tone channels), with the output bit driven by the LFSR’s bit 0. The LFSR width and tap pattern are chip-variant specific; the BBC’s discrete SN76489AN uses 15 bits with a specific tap mask documented below.

Noise control register (3 bits)

Written via either a latch byte (%1110dddd — channel 3, tone/noise = 0) or a data byte while channel 3 is the latched channel. Only the low 3 bits matter:

BitField
2FB: 0 = “periodic noise” (1-bit feedback), 1 = white noise
1, 0NF1, NF0: counter reset value (= noise base rate)

Counter reset values per NF1/NF0 (per smspower-sn76489)

NF1 NF0Counter resetResulting toggle rate at 4 MHz / div-16
000x10 (16)reference / 32 ≈ 7813 Hz
010x20 (32)reference / 64 ≈ 3906 Hz
100x40 (64)reference / 128 ≈ 1953 Hz
11Tone 2’s current frequency”modulated noise” — sweep with tone 2

NF=11 lets you sweep noise pitch dynamically by varying tone 2’s register — useful for laser/spaceship effects. Note tone 2 doesn’t need to be audible (set its volume to 15/silent) for this to work; the LFSR clocks off its counter regardless of whether the chip’s mixer hears it.

The LFSR — bit-level behaviour

The LFSR clocks on every other counter-zero event (the underlying counter toggles a flip-flop and the LFSR advances on 0 → 1 transitions). When it advances:

  1. The current LFSR is shifted right by 1 bit.
  2. The new top bit comes from the feedback function.
  3. The bit that falls off the right is the noise output sent to the mixer.

The feedback function depends on FB and on the chip variant:

ModeFunction
White (FB=1), BBC SN76489ANnew_top = XOR of bit 0 and bit 1 (taps = $0003)
White (FB=1), SMS / GG / Genesisnew_top = XOR of bit 0 and bit 3 (taps = $0009) — and they use 16 bits
White (FB=1), Tandy 1000new_top = XOR of bit 0 and bit 4 (taps = $0011)
“Periodic” (FB=0), all variantsnew_top = bit 0 (i.e. bit 0 is fed back to itself)

For the BBC’s 15-bit LFSR with white-noise taps $0003 and self-feedback periodic, the maximum white-noise period is 2¹⁵ − 1 = 32767 bits — enough to sound aperiodic to the ear.

”Periodic noise” is a 1/15 duty cycle, not actual noise

When you write the noise register, the LFSR is reset to 0100_0000_0000_0000 (only the top bit set, on a 15-bit LFSR — analogous bit on the 16-bit chip). In periodic mode (FB=0), the LFSR self-feeds — that single 1 circulates around the 15 bits forever, producing a 1/15 duty-cycle pulse train.

Per smspower-sn76489:

“Note that this ‘periodic noise’, as it is called in the original chip’s documentation, is in fact not periodic noise as it is defined elsewhere (white noise with a configurable periodicity); it is a duty cycle modifier.”

So on BBC, “periodic noise” is functionally a square wave with very narrow positive pulses. The pulse repetition frequency is (counter-toggle-rate) / 15. With NF=00 (counter = 0x10), that’s roughly 7813 / 15 ≈ 521 Hz. With NF=11 driven by tone 2, you can dial the pulse rate from ~7.6 Hz to ~7.8 kHz.

This explains the BBC SOUND channel-0 “wheee” sweep effects: setting tone 2 to a sweeping frequency with noise channel in periodic mode gives a melodic-pitched pulse train, not a noisy hiss.

White noise spectrum

In FB=1 (white) mode, the LFSR steps through (close to) the maximal-length sequence — all 32767 non-zero states for the BBC’s 15-bit variant. The output is a binary pseudo-random sequence at the counter toggle rate. Spectrum: roughly flat from DC to the toggle rate, with a slight 1/f tilt and notches at multiples of the LFSR period.

For the BBC’s three counter-reset choices (NF=00/01/10), this gives three distinct “noise colours”:

  • NF=00 (toggle ~7813 Hz): bright/hissy (“white noise” in the colloquial sense)
  • NF=01 (toggle ~3906 Hz): medium
  • NF=10 (toggle ~1953 Hz): low/rumbly

Worked LFSR step (BBC, white noise)

// 15-bit LFSR, BBC SN76489AN, white noise (FB=1, tap mask $0003)
uint16_t lfsr;  // initialised to 0x4000 (bit 14 = 1) on noise-register write
 
uint8_t step_lfsr_white() {
    uint8_t output = lfsr & 1;
    uint8_t feedback = parity(lfsr & 0x0003);  // XOR of bit 0 and bit 1
    lfsr = (lfsr >> 1) | (feedback << 14);     // shift right, new top bit
    return output;
}
 
uint8_t step_lfsr_periodic() {
    uint8_t output = lfsr & 1;
    lfsr = (lfsr >> 1) | (output << 14);       // bit 0 fed back to top
    return output;
}

Where parity() is XOR-reduction of the masked bits.

Output inversion caveat

Per SMSPower: some chip variants output the LFSR in inverted form, which can confuse reverse-engineering. For BBC purposes the John Kortink data on which the BBC’s $0003 mask is based is canonical (credited in the SMSPower page history).

Byte formats

The chip distinguishes commands by bit 7:

Latch byte — bit 7 = 1

Latches a register address and carries 4 low data bits:

bit:  7   6   5   4   3   2   1   0
      1   R2  R1  R0  d3  d2  d1  d0

Used for:

  • Frequency first byte: d3..d0 = F3..F0 (low 4 bits of frequency).
  • Volume: d3..d0 = A3..A0 (attenuation).
  • Noise control: d3..d0 = X, FB, NF1, NF0 (bit 3 don’t care).

Data byte — bit 7 = 0

Extends a previous frequency latch with 6 high bits (top bit don’t care, plus 5 frequency bits — actually 6, see chart):

bit:  7   6   5   4   3   2   1   0
      0   X   F9  F8  F7  F6  F5  F4

Sequence for setting a 10-bit frequency on tone 1:

1. Latch byte: 1 100 d3..d0      ; bits 100 = tone 1 freq, low 4 bits
2. Data byte:  0 X F9..F4         ; high 6 bits of same channel

After this, you can send additional data bytes (each 0 X F9..F4) to update just the high bits of the same channel — no re-latch needed. Useful for smooth frequency sweeps.

Direct-write sequence (the slow-bus dance)

SEI                      ; own the slow bus; MOS uses it for keyboard scan etc.
 
LDA #&FF : STA &FE43     ; DDRA = all output
LDA #byte_to_send
STA &FE41                ; latch byte on Port A
 
; Pulse /WE low via System VIA addressable latch line 0
; Encoding: PB0-2 = line number (000 = line 0), PB3 = value (0 = low)
LDA #&00 : STA &FE40
 
; Hold low ≥ 8 µs — the chip latches on /WE high, but needs setup time
LDX #4
.delay   DEX : BNE delay  ; ~10 µs at 2 MHz
 
; Pulse /WE high
LDA #&08 : STA &FE40     ; PB3 = 1 → line 0 = 1
 
CLI

Bypasses MOS’s sound buffers entirely — direct chip write, no envelope, no queueing, no flow control. Useful for:

  • Sample playback (write a stream of volume bytes at audio rate — 8 kHz pulse-width-modulation through the volume register).
  • Custom music engines that want sub-frame timing or non-standard envelopes.
  • Sound effects that need to start in <1 ms (vs the 100 Hz MOS service rate = up to 10 ms latency).

For Tube-safe writes, use OSBYTE &97 (write SHEILA) — slower but works through the Tube.

Persistent-/WE pattern (high-rate sample playback)

Per stardot-sn76489-sampled t=30838 (hoglet + scarybeasts, 2025), the chip will accept successive writes with /WE held continuously low — you don’t need to pulse the strobe per write. This halves the slow-bus work and enables sustained 62.5 kHz writes (one byte every 16 µs at 2 MHz CPU), which is what makes scarybeasts’ software-mixed multi-channel sample playback possible:

SEI                        ; own the slow bus for entire playback duration
LDA #&FF : STA &FE43       ; DDRA = output
 
; Hold /WE LOW once and never release it
LDA #&00 : STA &FE40       ; addressable latch line 0 = 0 (= /WE low)
 
; Now the inner loop is just STA &FE41 + cycle-padding:
.loop
    LDA samples,X
    STA &FE41              ; chip latches at ~9 µs into each 16 µs window
    ; ... pad to 32 cycles total per iteration ...
    INX
    BNE loop
 
; After playback, restore /WE high before releasing the slow bus
LDA #&08 : STA &FE40       ; latch line 0 = 1
CLI

The 16 µs cadence is the SN76489’s internal sampling rate; provided the bus value is stable when the chip samples (the “9 µs alignment” — bus must settle in the first ~9 µs of each 16 µs window), the byte is accepted. See sampled-sound for the multi-channel mixer that lives in the remaining 18-20 cycles per write.

⚠ This pattern means the MOS slow-bus arbitration is completely bypassed for the duration of playback — keyboard scan, ADC, RTC access, sound queue, and speech (Model B) all go silent. You must SEI for the entire playback duration and restore MOS state on exit.

Performance reference

  • One direct write: ~30 cycles (SEI + 4×STA + delay loop + CLI). At 2 MHz = 15 µs.
  • MOS OSWORD &07 (SOUND): hundreds of cycles dispatch + buffer enqueue. Latency to chip = up to 10 ms (next 100 Hz tick).
  • A typical “rapid effect” pattern: precompute the volume envelope as a byte sequence, set up a timer-driven IRQ (User VIA T1 — via-timers) that writes the next byte to the volume register on each tick.

See also

  • system-via — Slow peripheral bus + addressable latch.
  • sound — MOS-level SOUND/ENVELOPE interface.
  • buffers — Channel buffers 4-7 used by MOS sound.

This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.