Files
akbasic/src/audio_akgl.c

159 lines
5.0 KiB
C
Raw Normal View History

Wire the sink and the three devices to libakgl src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in the akbasic_akgl target, which is the only thing here that links SDL. -DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and it now builds and passes. The sink is what section 3 has been waiting on. Its character grid comes from akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is done on the character grid rather than by handing SDL_ttf a wraplength, because the cursor has to land somewhere definite: a program that PRINTs a long string and then PRINTs again expects the second to start on the row after the first ended, and only the code that placed the characters knows which row that is. tests/akgl_backends.c draws into a 128x128 software renderer under the dummy video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c established, which needs no display and no offscreen harness. It asserts the seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the right colour out. Four things in libakgl had to be worked around to get here. All four are commented at their site with "filed upstream" and recorded in TODO.md section 3: - An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to be *installed*. It builds its own vendored copies only when it is top-level, and they are sitting right there in deps/libakgl/deps. Every lookup is guarded with if(NOT TARGET ...), so this adds those five subdirectories before add_subdirectory(deps/libakgl) -- the same trick and the same ordering requirement akerror::akerror and akstdlib::akstdlib already need. - akgl/controller.h does not compile on its own: it declares handlers taking an akgl_Actor * and includes nothing that declares the type. - There is no way to attach a 2D backend to a renderer you already have. akgl_render_init2d() installs the vtable but also creates its own window and writes the camera global, so it belongs to the akgl_game_init() path -- which is exactly the path an embedding host is not on. The test assigns the six pointers by hand. - akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it reaches through renderer->draw_texture without checking it. Same class of defect 42b60f7's own commit added a draw test for. The sink's readline reports end of input rather than reading: a drawn text layer is not a source of lines, and INPUT through one wants a line editor built on the keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT already handles it. That editor is the next piece of work there. 70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
/**
* @file audio_akgl.c
* @brief Wires the audio backend record to libakgl's tone generator.
*
* Another thin adaptor. The one thing in here worth reading is the waveform
* mapping: akbasic_Waveform and akgl_AudioWaveform currently agree on every
* value, and the mapping is still written out rather than cast across, because
* two enumerations agreeing by accident is not a contract and a future SOUND
* waveform number would break the coincidence silently.
*/
#include <string.h>
#include <akerror.h>
#include <akgl/audio.h>
#include <akgl/error.h>
#include <akbasic/akgl.h>
#include <akbasic/error.h>
/**
* @brief Map a BASIC waveform onto libakgl's.
*
* @param waveform The BASIC-side selection.
* @param dest Output destination populated by the function.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKBASIC_ERR_BOUNDS When the waveform is not one of the four SOUND defines.
*/
static akerr_ErrorContext *to_akgl_waveform(akbasic_Waveform waveform, akgl_AudioWaveform *dest)
{
PREPARE_ERROR(errctx);
switch ( waveform ) {
case AKBASIC_WAVE_TRIANGLE:
*dest = AKGL_AUDIO_WAVE_TRIANGLE;
break;
case AKBASIC_WAVE_SAWTOOTH:
*dest = AKGL_AUDIO_WAVE_SAWTOOTH;
break;
case AKBASIC_WAVE_SQUARE:
*dest = AKGL_AUDIO_WAVE_SQUARE;
break;
case AKBASIC_WAVE_NOISE:
*dest = AKGL_AUDIO_WAVE_NOISE;
break;
default:
FAIL_RETURN(errctx, AKBASIC_ERR_BOUNDS, "No such waveform %d", (int)waveform);
}
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *snd_tone(akbasic_AudioBackend *self, int voice, double hz, int ms)
{
PREPARE_ERROR(errctx);
(void)self;
FAIL_ZERO_RETURN(errctx, (ms >= 0), AKERR_VALUE, "Negative note duration %d", ms);
PASS(errctx, akgl_audio_tone(voice, (float32_t)hz, (uint32_t)ms));
SUCCEED_RETURN(errctx);
}
/**
* @brief A note whose pitch moves, for SOUND's dir/min/step arguments.
*
* New in libakgl 0.3.0, which is why the record's entry may be NULL and why
* SOUND checks before using it. The sweep is advanced on the mixer's own frame
* counter -- doing it from akbasic_runtime_step() instead would tie audible
* pitch to how often a host happens to call us, which is the reason this was
* filed upstream rather than faked here.
*/
static akerr_ErrorContext *snd_sweep(akbasic_AudioBackend *self, int voice, double from_hz, double to_hz, double step_hz, int ms)
{
PREPARE_ERROR(errctx);
(void)self;
FAIL_ZERO_RETURN(errctx, (ms >= 0), AKERR_VALUE, "Negative note duration %d", ms);
PASS(errctx, akgl_audio_sweep(voice, (float32_t)from_hz, (float32_t)to_hz,
(float32_t)step_hz, (uint32_t)ms));
SUCCEED_RETURN(errctx);
}
Wire the sink and the three devices to libakgl src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in the akbasic_akgl target, which is the only thing here that links SDL. -DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and it now builds and passes. The sink is what section 3 has been waiting on. Its character grid comes from akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is done on the character grid rather than by handing SDL_ttf a wraplength, because the cursor has to land somewhere definite: a program that PRINTs a long string and then PRINTs again expects the second to start on the row after the first ended, and only the code that placed the characters knows which row that is. tests/akgl_backends.c draws into a 128x128 software renderer under the dummy video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c established, which needs no display and no offscreen harness. It asserts the seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the right colour out. Four things in libakgl had to be worked around to get here. All four are commented at their site with "filed upstream" and recorded in TODO.md section 3: - An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to be *installed*. It builds its own vendored copies only when it is top-level, and they are sitting right there in deps/libakgl/deps. Every lookup is guarded with if(NOT TARGET ...), so this adds those five subdirectories before add_subdirectory(deps/libakgl) -- the same trick and the same ordering requirement akerror::akerror and akstdlib::akstdlib already need. - akgl/controller.h does not compile on its own: it declares handlers taking an akgl_Actor * and includes nothing that declares the type. - There is no way to attach a 2D backend to a renderer you already have. akgl_render_init2d() installs the vtable but also creates its own window and writes the camera global, so it belongs to the akgl_game_init() path -- which is exactly the path an embedding host is not on. The test assigns the six pointers by hand. - akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it reaches through renderer->draw_texture without checking it. Same class of defect 42b60f7's own commit added a draw test for. The sink's readline reports end of input rather than reading: a drawn text layer is not a source of lines, and INPUT through one wants a line editor built on the keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT already handles it. That editor is the next piece of work there. 70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
static akerr_ErrorContext *snd_stop(akbasic_AudioBackend *self, int voice)
{
PREPARE_ERROR(errctx);
(void)self;
PASS(errctx, akgl_audio_stop(voice));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *snd_waveform(akbasic_AudioBackend *self, int voice, akbasic_Waveform waveform)
{
PREPARE_ERROR(errctx);
akgl_AudioWaveform mapped = AKGL_AUDIO_WAVE_SQUARE;
(void)self;
PASS(errctx, to_akgl_waveform(waveform, &mapped));
PASS(errctx, akgl_audio_waveform(voice, mapped));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *snd_envelope(akbasic_AudioBackend *self, int voice, int attack, int decay, double sustain, int release)
{
PREPARE_ERROR(errctx);
(void)self;
FAIL_ZERO_RETURN(errctx, (attack >= 0 && decay >= 0 && release >= 0), AKERR_VALUE,
"Negative envelope stage: %d/%d/%d", attack, decay, release);
PASS(errctx, akgl_audio_envelope(voice, (uint32_t)attack, (uint32_t)decay,
(float32_t)sustain, (uint32_t)release));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *snd_volume(akbasic_AudioBackend *self, double level)
{
PREPARE_ERROR(errctx);
(void)self;
PASS(errctx, akgl_audio_volume((float32_t)level));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *snd_voice_active(akbasic_AudioBackend *self, int voice, bool *active)
{
PREPARE_ERROR(errctx);
(void)self;
PASS(errctx, akgl_audio_voice_active(voice, active));
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_audio_init_akgl(akbasic_AudioBackend *obj)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
"NULL backend in audio_init_akgl");
PASS(errctx, akgl_error_init());
/*
* Opens an SDL audio device. A host that owns its own audio pipeline can
* skip this entirely and pull samples with akgl_audio_mix() instead -- the
* voice table works either way, which is the design upstream chose and the
* reason its own suite can be deterministic.
*/
PASS(errctx, akgl_audio_init());
obj->self = NULL; /* the voice table is libakgl's, not ours */
obj->tone = snd_tone;
obj->sweep = snd_sweep;
Wire the sink and the three devices to libakgl src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in the akbasic_akgl target, which is the only thing here that links SDL. -DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and it now builds and passes. The sink is what section 3 has been waiting on. Its character grid comes from akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is done on the character grid rather than by handing SDL_ttf a wraplength, because the cursor has to land somewhere definite: a program that PRINTs a long string and then PRINTs again expects the second to start on the row after the first ended, and only the code that placed the characters knows which row that is. tests/akgl_backends.c draws into a 128x128 software renderer under the dummy video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c established, which needs no display and no offscreen harness. It asserts the seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the right colour out. Four things in libakgl had to be worked around to get here. All four are commented at their site with "filed upstream" and recorded in TODO.md section 3: - An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to be *installed*. It builds its own vendored copies only when it is top-level, and they are sitting right there in deps/libakgl/deps. Every lookup is guarded with if(NOT TARGET ...), so this adds those five subdirectories before add_subdirectory(deps/libakgl) -- the same trick and the same ordering requirement akerror::akerror and akstdlib::akstdlib already need. - akgl/controller.h does not compile on its own: it declares handlers taking an akgl_Actor * and includes nothing that declares the type. - There is no way to attach a 2D backend to a renderer you already have. akgl_render_init2d() installs the vtable but also creates its own window and writes the camera global, so it belongs to the akgl_game_init() path -- which is exactly the path an embedding host is not on. The test assigns the six pointers by hand. - akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it reaches through renderer->draw_texture without checking it. Same class of defect 42b60f7's own commit added a draw test for. The sink's readline reports end of input rather than reading: a drawn text layer is not a source of lines, and INPUT through one wants a line editor built on the keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT already handles it. That editor is the next piece of work there. 70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 08:46:53 -04:00
obj->stop = snd_stop;
obj->waveform = snd_waveform;
obj->envelope = snd_envelope;
obj->volume = snd_volume;
obj->voice_active = snd_voice_active;
SUCCEED_RETURN(errctx);
}