# PokeyMAX audio diagnostic (`pmaudio.xex`)

A standalone Atari 8-bit program that detects which audio devices a
**PokeyMAX** core exposes and plays a short, identifiable sound on each one
in turn. Written in plain C (cc65) so it also serves as a worked example of
driving the PokeyMAX register map from 6502 C.

## Build

```
./build.sh          # needs cc65 (cl65) on PATH
```

produces `pmaudio.xex` — a standard Atari binary you can load from SpartaDOS,
boot from an SIO/SD device, or run under Altirra.

## What it does

1. Probes for PokeyMAX (writes `0x3F` to `$D20C`, expects ID `1` back).
2. Reads the **CAPABILITY** register `$D211` and prints what is installed:
   POKEY count (mono / stereo / quad), SID, PSG, COVOX, SAMPLE, FLASH, plus
   the core version string from `$D214`.
3. Waits for **START**, then walks the devices, with a status line naming
   each as it sounds:
   - **POKEY** left (voice 1) then right (voice 2), then a chord across all
     four register banks (see *stereo vs quad* below).
   - **SID1** (left) and **SID2** (right) — triangle notes.
   - **PSG1** and **PSG2** — tone on channel A.
   - **COVOX** — an 8-bit DAC triangle ramp.
   - **SAMPLE** — one period of a triangle synthesised into the 42 KiB block
     RAM and loop-played on channel 1 via DMA.
4. Any key repeats; **ESC** quits and restores the audio state.

Devices that the capability register says are absent are skipped.

## Two design points worth reading

### Shadow registers

The Atari OS keeps RAM shadows of the two POKEY control registers it uses for
interrupts:

| Shadow | Address | Mirrors |
|--------|---------|---------|
| `POKMSK` | `$0010` | `IRQEN` (`$D20E`, write-only) |
| `SSKCTL` | `$0232` | `SKCTL` (`$D20F`) |

The OS keyboard/SIO/timer IRQ handler re-writes `IRQEN` from `POKMSK`, and the
stage-2 VBI re-writes `SKCTL` from `SSKCTL`. If you poke those hardware
registers directly without updating the shadows, the next interrupt simply
stamps the OS values back over yours.

`AUDF*`, `AUDC*`, and `AUDCTL` have **no** OS shadow, so direct writes to them
stick — that is what the tone code does. The program only ever changes
`IRQEN`/`SKCTL` through their shadows, and it saves/restores the full audio
state (`POKMSK`, `SSKCTL`, `AUDCTL`) around the test so the OS is left exactly
as it was found. It deliberately avoids `SEI`/`CLI` from C; instead it keeps
the OS interrupts running and just doesn't fight them.

### You can't hear stereo vs quad by playing voices in turn

If you play POKEY voices 1→2→3→4 one at a time:

- **Mono** core: `$D210–$D2FF` are shadows of POKEY1, so all four come out the
  same single channel — four identical beeps.
- **Stereo** core: POKEY3/4 are address shadows of POKEY1/2, and POKEY1/3 → left,
  POKEY2/4 → right. Sequentially you hear L, R, L, R.
- **Quad** core: voices are independent, but played sequentially you *still*
  hear L, R, L, R.

So stereo and quad are **indistinguishable** when voices play one at a time.
The only audible difference appears when voices 1 and 3 (and 2 and 4) sound
**simultaneously at different pitches**:

- **Stereo**: writing "voice 3" overwrites voice 1 (it's the same hardware), so
  the left channel carries a *single* pitch. Likewise right.
- **Quad**: voices 1 and 3 are independent and **sum** on the left, 2 and 4 on
  the right — so each side carries a *two-note chord*.

The POKEY test therefore writes four different dividers to the four register
banks at once. On a quad core you hear a richer 2+2 chord; on stereo each side
collapses to one pitch. And because ears can be fooled, the program *also*
reports the configuration directly from `$D211`.

## Register reference used

From the PokeyMAX developer guide v1.30:

- POKEY1 `$D200`, POKEY2 `$D210`, POKEY3 `$D220`, POKEY4 `$D230`
- Config bank: write `0x3F` to `$D20C`, then `$D210–$D21F` are config regs
  (`MODE $D210`, `CAPABILITY $D211`, `VERSION $D214`, …); write `0x00` to restore
- SID1 `$D240` (left), SID2 `$D260` (right)
- PSG1 `$D2A0`, PSG2 `$D2B0` (registers directly memory-mapped, R0..RF)
- COVOX `$D280–$D283` (CH1/CH4 left, CH2/CH3 right)
- SAMPLE `$D284–$D293` (block-RAM DMA player)

## Notes / things you may want to tweak

- **Tone pitches** are chosen only to be *distinct*, not musically exact. The
  `DIV_*` constants assume the 64 kHz POKEY clock with `AUDCTL = 0`.
- **SID/PSG note values** are likewise approximate; adjust `freq`/`period`
  arguments for specific notes (SID clock ≈ 1 MHz; PSG clock selectable via
  `PSGMODE $D215`).
- **Memory map caveat**: COVOX lives at `$D280` only in the stereo+covox /
  quad+covox layouts. On a mono core the `$D210+` space is all POKEY1 shadow,
  so the capability bits gate those tests — but if you have an unusual
  `RESTRICT` (`$D217`) setup, check the active memory map in the guide.
- The screen output writes ATASCII→internal codes straight into the GR.0
  screen RAM (`SAVMSC $0058`) to keep the example free of conio/editor
  dependencies.
