Files
akbasic/docs/07-sound.md
Andrew Kesterson cb0e2d0800
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m10s
akbasic CI Build / sanitizers (push) Failing after 4m5s
akbasic CI Build / coverage (push) Failing after 3m29s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Failing after 3m19s
Add two Breakout examples and the tutorials that build them
Two complete games in `examples/breakout/`, both 100% BASIC: `characters/`
draws its wall in the text grid with two `DATA` sprites for the ball and
paddle, and `sprites/` loads CC0 artwork and captures its whole screen with
`SSHAPE`/`SPRSAV`. They take opposite shapes for reasons that are entirely
this interpreter's, which is what the chapters are for.

`docs/17-tutorial-breakout.md` and `docs/18-tutorial-breakout-artwork.md`
build each one a step at a time, and end in a checklist of the rules a real
program runs into: create every name before the loop starts, write a text row
whole, loop with `GOTO` rather than `DO`, put the float on the left. Every
trap is a runnable block with its own output rather than a claim -- the value
pool dying at four thousand names, the skipped `BEGIN` block that breaks its
caller's `RETURN`, `SSHAPE` ignoring a subscript, `READ`'s single cursor.

Five figures, generated from the listings beside them by `docs_screenshots`,
and a `breakout_art` setup so the ones that load artwork load the example's
own. The character game's wall cannot be photographed -- the screenshot host
omits the text layer on purpose -- so it is shown as compared output instead.

`docs/07-sound.md` never said `SOUND`'s frequency is a SID register value
rather than hertz, which both games depend on. It says so now, with the
conversion from `src/audio_tables.c:84`.

`TODO.md` gains the thirteen defects the two games turned up -- §6 items 30
to 33 and all of §9 -- each with a reduction that fits on a screen, the file
and line of the cause, and what a fix would touch.

Verified: `docs_examples` passes in both build configurations,
`docs_screenshots --check` re-renders all thirteen figures and byte-compares
them, the full 109-test suite passes in both builds, every quoted fragment
was checked to appear verbatim in the listing it came from, and every
relative link and anchor resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 22:54:42 -04:00

3.3 KiB

7. Sound

The sound verbs need the SDL build and an audio device. Without one they refuse by name, and a machine with no sound card still runs the interpreter — only SOUND and PLAY fail, and they say so.

SOUND

10 SOUND 1, 4000, 60

Voice, frequency, duration. Voices are numbered from 1. The duration is in jiffies — sixtieths of a second — as on a C128, so 60 is one second.

The frequency is a SID register value, not hertz. As on a C128 the pitch is frequency * 1022730 / 16777216 — the NTSC system clock over the oscillator's 24-bit accumulator — so the 4000 above is about 244 Hz, and the argument ranges from 0 to 65535. Work the notes you need out once and write the arithmetic down beside them: 4298 is C4, 8579 is C5, 17175 is C6. PLAY takes note names and does the conversion for you.

Further arguments give a frequency sweep: a step, a direction and a range, which is what makes a siren or a laser.

SOUND does not block. On a C128 the note starts and the program carries on; here the same is true, and it has to be — an embedded interpreter that stopped the host's frame loop for a second would freeze the game.

PLAY

PLAY takes a string of notes:

10 PLAY "C D E F G"

Within the string:

A to G a note
# $ sharp, flat
O n octave
V n voice
T n envelope preset
U n volume
W, H, Q, I, S whole, half, quarter, eighth, sixteenth notes
R rest
. dotted

Notes are queued and released in time, paced by the host's clock. A program that PLAYs and then carries on works the way you expect; a program that PLAYs and then immediately QUITs may not hear all of it.

M — measure — is accepted and does nothing. There is no bar-line accounting to do.

TEMPO

10 TEMPO 8

How fast PLAY releases its queue. The number is the same range a C128 uses; the constant that turns it into milliseconds is a calibration choice rather than a transcription, so it may not match a real machine exactly.

ENVELOPE and VOL

10 ENVELOPE 0, 5, 9, 4, 6
20 VOL 8

ENVELOPE defines one of the ten presets PLAY's T selects: attack, decay, sustain and release. VOL sets the overall volume, 0 to 15.

FILTER

FILTER parses and then refuses at execution:

10 FILTER 1000, 1, 0, 0, 8
? 10 : RUNTIME ERROR FILTER needs a filter stage this device does not have

It sets the SID's filter cutoff, band switches and resonance. The audio backend synthesises raw waveforms and mixes them; there is no filter stage to configure and SDL supplies no primitive to build one from. Refusing is deliberate — a program that asks for a low-pass and gets an unfiltered square wave has been lied to.

This is the one verb still waiting on a capability from the graphics library. It is filed there rather than worked around here.

The clock

Everything with a duration — SOUND, PLAY, TEMPO, and SLEEP from Chapter 2's neighbourhood — is paced by a clock the host provides. The standalone interpreter sets it from a monotonic timer every step, so it just works.

An embedded host that never sets the clock gets durations that expire immediately: audible, but never a hang. That is the intended failure direction.