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> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
This commit is contained in:
@@ -73,6 +73,61 @@ static akerr_ErrorContext *collect_numbers(akbasic_Runtime *obj, akbasic_ASTLeaf
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Issue SOUND's swept note, translating its arguments out of SID space.
|
||||
*
|
||||
* BASIC 7.0 spells the sweep as `dir`, `min` and `step`, all in the SID's
|
||||
* 16-bit register units rather than in hertz, and `dir` picks the shape:
|
||||
*
|
||||
* 0 no sweep -- handled by the caller, which issues a held note instead
|
||||
* 1 up, from the SOUND frequency toward min
|
||||
* 2 down, from the SOUND frequency toward min
|
||||
* 3 oscillate between the two
|
||||
*
|
||||
* **Direction 3 sweeps once rather than oscillating**, and that is a deviation
|
||||
* worth knowing about: akgl_audio_sweep runs one pass from a start to an end,
|
||||
* and a genuine oscillation needs the mixer to turn around at the endpoints.
|
||||
* Sweeping once in the direction the endpoints imply is the closest honest
|
||||
* approximation; TODO.md section 5 records it.
|
||||
*
|
||||
* Directions 1 and 2 both name `min` as the far end, so the *value* of min is
|
||||
* what decides which way the pitch actually travels -- a `min` above the
|
||||
* starting frequency rises whatever `dir` says. That is the SID's behaviour and
|
||||
* akgl_audio_sweep works the same way, comparing its two endpoints, so the two
|
||||
* agree without this having to second-guess either.
|
||||
*/
|
||||
static akerr_ErrorContext AKERR_NOIGNORE *sweep_note(akbasic_Runtime *obj, int voice, double hz, double *args, int ms)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
double tohz = 0.0;
|
||||
double stephz = 0.0;
|
||||
double stepbase = 0.0;
|
||||
|
||||
PASS(errctx, akbasic_audio_register_to_hz((int)args[4], &tohz));
|
||||
|
||||
/*
|
||||
* `step` is a register *delta*, not a register value, so it cannot go
|
||||
* through register_to_hz on its own -- that maps a position, and a delta has
|
||||
* no position. Convert it as the distance between register 0 and register
|
||||
* `step`, which is the same linear scale the table applies to everything
|
||||
* else.
|
||||
*/
|
||||
PASS(errctx, akbasic_audio_register_to_hz(0, &stepbase));
|
||||
PASS(errctx, akbasic_audio_register_to_hz((int)args[5], &stephz));
|
||||
stephz -= stepbase;
|
||||
|
||||
/*
|
||||
* A zero step would never arrive and libakgl refuses it outright. One hertz
|
||||
* is the smallest move that still gets there, and a program that asked for
|
||||
* no movement asked for a held note -- which is what it gets.
|
||||
*/
|
||||
if ( stephz <= 0.0 ) {
|
||||
stephz = 1.0;
|
||||
}
|
||||
PASS(errctx, obj->audio->sweep(obj->audio, voice, hz, tohz, stephz, ms));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------- SOUND --- */
|
||||
|
||||
akerr_ErrorContext *akbasic_cmd_sound(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
|
||||
@@ -110,13 +165,27 @@ akerr_ErrorContext *akbasic_cmd_sound(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
|
||||
/*
|
||||
* Arguments 4 through 6 are a frequency sweep: a direction, a floor and a
|
||||
* step. There is no akgl_audio_* equivalent, and faking one by re-issuing
|
||||
* tones from akbasic_runtime_step() would tie audible pitch to how often the
|
||||
* host happens to call us -- a tune that changes key with the frame rate.
|
||||
* Refused, and filed upstream as akgl_audio_sweep. TODO.md section 7.
|
||||
* step. `dir` is 0 to hold, 1 to sweep up, 2 to sweep down and 3 to
|
||||
* oscillate; `min` is the far end of the sweep as a register value, and
|
||||
* `step` is how far the pitch moves per tick.
|
||||
*
|
||||
* This was refused outright until libakgl 0.3.0 grew akgl_audio_sweep --
|
||||
* faking it by re-issuing tones from akbasic_runtime_step() would have tied
|
||||
* audible pitch to how often the host happens to call us, a tune that
|
||||
* changes key with the frame rate. It is still refused when the host's
|
||||
* backend has no sweep, which is what an older one looks like.
|
||||
*/
|
||||
FAIL_NONZERO_RETURN(errctx, (count >= 4 && args[3] != 0.0), AKBASIC_ERR_DEVICE,
|
||||
"SOUND's frequency sweep needs a device capability that does not exist yet");
|
||||
if ( count >= 4 && args[3] != 0.0 ) {
|
||||
FAIL_ZERO_RETURN(errctx, (args[3] >= 0.0 && args[3] <= 3.0), AKBASIC_ERR_BOUNDS,
|
||||
"SOUND direction %d out of range (0 to 3)", (int)args[3]);
|
||||
FAIL_ZERO_RETURN(errctx, (obj->audio->sweep != NULL), AKBASIC_ERR_DEVICE,
|
||||
"SOUND's frequency sweep needs an audio device that can sweep");
|
||||
FAIL_ZERO_RETURN(errctx, (count >= 6), AKBASIC_ERR_SYNTAX,
|
||||
"SOUND's frequency sweep expected a direction, a minimum and a step");
|
||||
PASS(errctx, sweep_note(obj, voice, hz, args, ms));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
if ( count >= 7 ) {
|
||||
/* The seventh argument selects a waveform, 0 through 3. */
|
||||
|
||||
Reference in New Issue
Block a user