/** * @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 #include #include #include #include #include /** * @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); } 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; 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); }