2026-07-31 21:50:50 -04:00
|
|
|
# 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
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl
|
2026-07-31 21:50:50 -04:00
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl
|
2026-07-31 21:50:50 -04:00
|
|
|
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
|
|
|
|
|
`PLAY`s and then carries on works the way you expect; a program that `PLAY`s and then
|
|
|
|
|
immediately `QUIT`s may not hear all of it.
|
|
|
|
|
|
|
|
|
|
`M` — measure — is accepted and does nothing. There is no bar-line accounting to do.
|
|
|
|
|
|
|
|
|
|
## TEMPO
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl
|
2026-07-31 21:50:50 -04:00
|
|
|
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
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic requires=akgl
|
2026-07-31 21:50:50 -04:00
|
|
|
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**:
|
|
|
|
|
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
```basic
|
|
|
|
|
10 FILTER 1000, 1, 0, 0, 8
|
2026-07-31 21:50:50 -04:00
|
|
|
```
|
Execute every documented example as a test
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand
exactly once, when it was written, which is not a standard that survives a
changing interpreter -- and four were already wrong: two transcripts showing a
leading space PRINT does not emit, akbasic_TextSink in README.md missing the
two members it had grown hours earlier, and FILTER's refusal quoted with
wording the code does not use.
tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds.
BASIC programs and transcripts run and are byte-compared against an `output`
block; C snippets compile with -fsyntax-only against the real include path,
which CMake writes out because it is transitive through akerror, akstdlib and
akgl; shell blocks run in a sandbox. Anything that would reconfigure the build
tree, hit the network or re-enter the suite is tagged norun with the reason in
MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision.
An untagged block is a failure rather than a default, and the pass line
reports what it executed by kind. Both exist because the way a harness like
this dies is by quietly matching nothing and passing -- which it duly did on
the first CTest run, where a generator expression evaluating to nothing still
contributed an empty argument that the script read as a filename. The count is
what caught it.
The excerpt check earns its own mention: a block tagged
`c excerpt=include/akbasic/sink.h` must still appear in that header, comments
and whitespace ignored. Compiling it would only redefine the type, so a
compile check could not have found the stale struct, and did not.
Registered as the CTest case docs_examples in both configurations. Fixing the
four wrong examples turned up two interpreter defects, fixed in the previous
commit and recorded in TODO.md section 8.
MAINTENANCE.md is new: the fence-tag reference, what to do when the case
fails, and the conventions that until now only existed inside source comments
-- the three test lists and how two of them invert "passed", the sorted verb
table, that a golden file is never edited to suit this interpreter, and that a
fix gets mutation-checked with a file copy rather than git checkout.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 22:41:36 -04:00
|
|
|
|
|
|
|
|
```output
|
|
|
|
|
? 10 : RUNTIME ERROR FILTER needs a filter stage this device does not have
|
|
|
|
|
|
2026-07-31 21:50:50 -04:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
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.
|