Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .claude/skills/framebuffer-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ lifting:** a dual (page-flipped) framebuffer on the Atari ST side
blank; **~19 ms of compute every VBL** to draw your frame; chunked
drawing on the RP2040 (one byte per pixel) with the chunked → ST planar
conversion done for you in **~1 ms per VBL** (split across both cores);
**~6 kHz, 6-bit sampled sound** out the YM2149; and the Atari ST
sampled sound (8-bit 12.5 kHz through the DMA chip on an STE / Mega STE,
~6 kHz 6-bit out the YM2149 elsewhere); and the Atari ST
keyboard, mouse and joysticks handled on the RP2040, delivered to your app
as key events, mouse movement and stick states.

Expand Down Expand Up @@ -97,8 +98,11 @@ ikbd_joystick_t j; ikbd_read_joystick(1, &j); // j.state / j.pressed: IKBD_JOY_
// audio: audio.h
audio_play_loop(data, bytes); // loop a baked-in buffer, OR
audio_set_fill_callback(cb); // cb(buf,bytes) per VBL of samples, live
audio_play_pcm_loop(pcm, n, rate); // or signed 8-bit PCM at any rate
audio_set_pcm_callback(cb, AUDIO_DMA_RATE_HZ); // (either kind plays on both outputs)
audio_render_frame(); // every loop iteration: tops up the FIFO
// (80 ms of stall allowed, 120 ms latency)
// (80 ms of stall allowed; 120 ms latency
// on the YM, ~175 ms on the DMA chip)
```

**SD card:** the microSD is already mounted at boot
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
(`AUDIO_FIFO_SLICES`). When nothing is ready the last sample is held, never stale
sound, and `audioUnderruns` counts it.

### STE DMA sound

- On an STE or a Mega STE the sound plays through the DMA sound chip: 8-bit at 12,517 Hz, no
interrupt per sample. The ST copies each VBL's samples into a 2 KB ring in its RAM (below the
screen pages); Timer-B stays off, which gives the ST 1.6 ms of every frame back: a mouse moved
fast no longer costs frames there. A plain ST or Mega ST plays through the YM as before, from
the same firmware.
- Apps give 8-bit PCM at any rate (`audio_play_pcm_loop()`, `audio_set_pcm_callback()`) or YM
pairs as before; either plays on both outputs, converted on the RP. `audio_prefer_ym()` keeps
an STE on the YM; `tools/wav_to_ym4.py --mode pcm` writes PCM headers.
- The audio buffer in the cartridge window grows to 2 KB: `APP_FREE` starts at `$FA4980`
(about 14.4 KB).

### Mouse and joysticks

- Apps get the mouse and both joysticks next to the keyboard, decoded on the RP: pick an
Expand Down Expand Up @@ -45,7 +58,7 @@
- Host tests: `make -C tests/host test` checks the pure logic on the host, and CI runs it on
every pull request: the blits' clipping, the framebuffer layout from the chunky buffer to
the ST's screen, the agreement of the ST's and the RP's copies of every shared constant, the
sample converter, the audio slices and the IKBD decoder.
sample converter, the audio slices, the DMA sound output and the IKBD decoder.
- The cartridge window's layout lives in one m68k include, `inc/sidecart_layout.s`, which
`main.s` and `userfw.s` share; `userfw.s` no longer repeats its addresses.

Expand Down
41 changes: 25 additions & 16 deletions CLAUDE.md

Large diffs are not rendered by default.

35 changes: 29 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ at 50 Hz.** No m68k assembly, no bus timing, no double-buffering to manage.
framework does the chunked → Atari ST planar conversion for you.
- **~1 ms per VBL** for that chunky→planar conversion (split across both
cores), so it barely eats into your frame budget.
- **~6 kHz, 6-bit sampled sound** out the YM2149.
- **Sampled sound**: 8-bit at 12.5 kHz through the DMA sound chip on an
STE or a Mega STE, ~6 kHz 6-bit out the YM2149 elsewhere — the same
asset plays on both.
- **Atari ST keyboard, mouse and joysticks handled on the RP2040** —
decoded scancodes, mouse movement and stick states delivered straight to
your app.
Expand Down Expand Up @@ -301,10 +303,13 @@ your own graphics.

## 5. Audio (`audio.h`)

The ST plays one VBL of samples from a 1 KB cart buffer into the YM2149
every VBL, and the RP writes the next ones from a timer interrupt, so the
sound never tears and never repeats, whatever your frame rate. You supply
the bytes one of two ways.
On an STE or a Mega STE the sound goes through the DMA sound chip (8-bit,
12,517 Hz, no interrupt per sample); on a plain ST or Mega ST it goes
through the YM2149 (~6-bit, 5,585 Hz). The template picks the output at
every ST boot, and the RP writes the samples ahead of what the ST plays
from a timer interrupt, so the sound never tears and never repeats,
whatever your frame rate. Your audio is YM volume pairs or 8-bit PCM, and
either plays on both outputs: the RP converts at runtime.

### Loop a baked-in buffer

Expand All @@ -317,6 +322,24 @@ audio_play_loop(audio_sample_data, sizeof(audio_sample_data));
// ... then call audio_render_frame() once per main-loop iteration.
```

### 8-bit PCM

For a recording at its best on the DMA chip, convert it to signed 8-bit
PCM at 12,517 Hz (`tools/wav_to_ym4.py --mode pcm --target-rate 12517
--header-output my_sound.h my_sound.wav`), or generate PCM live at any
rate; on the YM the RP plays its upper 6 bits through the same table:

```c
#include "my_sound.h" // audio_sample_data[], AUDIO_SAMPLE_RATE_HZ
audio_play_pcm_loop(audio_sample_data, audio_sample_count, AUDIO_SAMPLE_RATE_HZ);

static void my_pcm(int8_t *buf, uint32_t samples) { /* ... */ }
audio_set_pcm_callback(my_pcm, AUDIO_DMA_RATE_HZ);
```

`audio_prefer_ym(true)` keeps an STE on the YM from its next boot;
`audio_uses_dma()` says which output plays.

### Generate audio live (callback)

For dynamic sound, install a fill callback. The library calls it for each
Expand All @@ -334,7 +357,7 @@ Either way, **`audio_render_frame()` must be called each loop iteration**:
it tops up a small FIFO (4 VBLs of samples) that the interrupt plays from.
Your loop may take up to 80 ms between two calls without the sound
noticing; a sample reaches the speaker at most 120 ms after your callback
made it. There's also `audio_play_yms_file(path)` to stream a `.YMS` file
made it on the YM, about 175 ms on the DMA chip. There's also `audio_play_yms_file(path)` to stream a `.YMS` file
from SD — see §7.

---
Expand Down
Loading
Loading