Sound: implement the BASIC 7.0 sound verbs and the PLAY parser
SOUND, ENVELOPE, VOL, PLAY and TEMPO against the akbasic_AudioBackend record, plus FILTER, which is in the table only so that it can be refused with a reason. The PLAY note-string parser and TEMPO are here rather than in libakgl because that is where its audio commit says they belong: a tone generator synthesises pitches, but deciding that O4CDEFG is five quarter notes starting at middle C is a language question. PLAY does not block. On a C128 it holds the program until the last note ends, which section 1.6 forbids outright, so it parses the string into a fixed queue and returns; akbasic_runtime_step() releases one note at a time against whatever time the host last passed to akbasic_runtime_settime(). The driver now steps one at a time with the clock refreshed in between rather than making a single unbounded run() call -- a tune whose notes all measured themselves against a frozen zero would rush out at once. That loop is its own function because CATCH expands to a break and PASS expands to a return of the context, and main() returns an int; wrapping the loop is what the protocol prescribes for that shape. src/audio_tables.c holds the three conversions, laid out as tables because each is somewhere a wrong constant produces a plausible wrong pitch rather than an error anybody would notice. Two are transcriptions -- the SID frequency formula and its non-linear ADSR rate tables, where decay is exactly three times attack. The third is not: BASIC 7.0 never published what a whole note lasts at a given TEMPO, so 16000 ms at TEMPO 1 is a calibration choice putting a default quarter note at 120 bpm, and it is labelled as a choice where it is made. SOUND's frequency sweep is refused rather than faked, and filed upstream as akgl_audio_sweep. The only way to fake it here is to re-issue tones from step(), which ties audible pitch to how often the host calls us -- a tune that changes key with the frame rate. FILTER is refused for the reason upstream already gave: there is no filter stage and SDL3 has no primitive to build one from. The PLAY parser is tested through akbasic_play_parse() directly rather than through a program, because running one also runs the queue service -- and with the clock at zero every duration has already expired, so the queue empties before an assertion can look at it. Draining is correct behaviour and is tested on its own; the parse tests ask a different question. 68/68 ctest, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
tests/language/audio/no_device.txt
Normal file
3
tests/language/audio/no_device.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
STATE VERBS OK
|
||||
? 80 : RUNTIME ERROR SOUND needs an audio device and this runtime has none
|
||||
|
||||
Reference in New Issue
Block a user