Consume libakgl 0.3.0: every workaround deleted, two capabilities gained

0.3.0 closed all ten API gaps this port had filed. The four workarounds go
with them: the CMake block that declared libakgl's vendored dependencies by
hand, the akgl/actor.h include in three files, and the six vtable pointers
assigned by hand in two more, now akgl_render_bind2d().

Two gaps were capabilities rather than inconveniences, and both are now real:

The line editor takes the composed UTF-8 text the ring carries in preference
to the keycode, so shifted characters, keyboard layouts, compose keys and dead
keys all work. A double quote can be typed, which means a BASIC string literal
can be typed -- the sharp end of the old limitation. Letters are no longer
folded to upper case.

SOUND's dir/min/step reach akgl_audio_sweep instead of being refused. dir 3
sweeps once rather than oscillating and TODO.md section 5 says so. A backend
with no sweep still refuses the swept note and plays the held one.

The adaptors now carry an AKGL_VERSION_AT_LEAST(0, 3, 0) floor, verified by
temporarily demanding 0.4.0 and watching it fire.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:25:14 -04:00
parent 583c0abbd2
commit fca9ad4a89
16 changed files with 495 additions and 265 deletions

View File

@@ -131,20 +131,58 @@ static void test_sound(void)
harness_stop();
/*
* The frequency sweep is refused rather than faked. Faking it would mean
* re-issuing tones from step(), which would tie audible pitch to how often
* the host calls us -- a tune that changes key with the frame rate.
* The frequency sweep reaches the device, which it could not until libakgl
* 0.3.0 grew akgl_audio_sweep -- it was refused outright before, because
* faking it would have meant re-issuing tones from step() and tying audible
* pitch to how often the host calls us.
*
* Sweeping *down*: register 1000 is below register 16777, so the endpoints
* decide the direction and both the SID and akgl_audio_sweep read them that
* way.
*/
TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 1, 500, 10\n"));
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "frequency sweep") != NULL,
"a sweep should be refused, got \"%s\"", HARNESS_OUTPUT);
TEST_REQUIRE_OK(run_program("10 SOUND 1, 16777, 60, 2, 1000, 10\n"));
TEST_REQUIRE(strstr(MOCK.log, "sweep v0 ") != NULL,
"a sweep should reach the device, got \"%s\"", MOCK.log);
TEST_REQUIRE(strstr(MOCK.log, "1000ms") != NULL,
"60 jiffies should still be 1000 ms on a sweep, got \"%s\"", MOCK.log);
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "") != NULL && HARNESS_OUTPUT[0] == '\0',
"a sweep should not be refused, got \"%s\"", HARNESS_OUTPUT);
harness_stop();
/* A sweep direction of zero is "no sweep" and must still play. */
/* A direction outside 0..3 is a typo worth catching. */
TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 9, 500, 10\n"));
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SOUND direction 9") != NULL,
"direction 9 should be refused, got \"%s\"", HARNESS_OUTPUT);
harness_stop();
/* A sweep needs all three of its arguments. */
TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 1\n"));
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "direction, a minimum and a step") != NULL,
"a partial sweep should be refused, got \"%s\"", HARNESS_OUTPUT);
harness_stop();
/* A sweep direction of zero is "no sweep" and must still play a held note. */
TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 0\n"));
TEST_REQUIRE(strstr(MOCK.log, "tone v0 ") != NULL,
"direction 0 is not a sweep and should play, got \"%s\"", MOCK.log);
harness_stop();
/*
* A backend with no sweep is still a valid backend -- that is what a host
* written against libakgl 0.2.0 looks like -- and SOUND refuses the swept
* note rather than dereferencing a NULL entry point.
*/
TEST_REQUIRE_OK(harness_start(NULL));
mock_devices_init();
MOCK_AUDIO.sweep = NULL;
TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS,
&MOCK_AUDIO, &MOCK_INPUT));
TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 SOUND 1, 1000, 60, 1, 500, 10\n"));
TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
TEST_REQUIRE(strstr(HARNESS_OUTPUT, "audio device that can sweep") != NULL,
"a backend without sweep should refuse, got \"%s\"", HARNESS_OUTPUT);
harness_stop();
}
/** @brief ENVELOPE maps the SID rate numbers, and sustain is a level not a time. */