138 lines
4.1 KiB
C
138 lines
4.1 KiB
C
|
|
/**
|
||
|
|
* @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);
|
||
|
|
}
|
||
|
|
|
||
|
|
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->stop = snd_stop;
|
||
|
|
obj->waveform = snd_waveform;
|
||
|
|
obj->envelope = snd_envelope;
|
||
|
|
obj->volume = snd_volume;
|
||
|
|
obj->voice_active = snd_voice_active;
|
||
|
|
SUCCEED_RETURN(errctx);
|
||
|
|
}
|