git.y1.nz

SameBoy

Accurate GB/GBC emulator
download: https://git.y1.nz/archives/sameboy.tar.gz
README | Files | Log | Refs | LICENSE

SDL/audio/openal.c

      1 #include "audio.h"
      2 #if defined(__APPLE__)
      3 #include <OpenAL/al.h>
      4 #include <OpenAL/alc.h>
      5 
      6 #else
      7 #include <AL/al.h>
      8 #include <AL/alc.h>
      9 #endif
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 
     14 #define BUFFER_LEN_MS 5
     15 
     16 static ALCdevice *al_device = NULL;
     17 static ALCcontext *al_context = NULL;
     18 static GB_sample_t *audio_buffer = NULL;
     19 static ALuint al_source = 0;
     20 static ALCint sample_rate = 0;
     21 static unsigned buffer_size = 0;
     22 static unsigned buffer_pos = 0;
     23 static bool is_paused = false;
     24 
     25 #define AL_ERR_STRINGIFY(x) #x
     26 #define AL_ERR_TOSTRING(x) AL_ERR_STRINGIFY(x)
     27 #define AL_ERROR(msg) check_al_error(msg, AL_ERR_TOSTRING(__LINE__))
     28 
     29 // Check if the previous OpenAL call returned an error.
     30 // If an error occurred a message will be logged to stderr.
     31 static bool check_al_error(const char *user_msg, const char *line)
     32 {
     33     ALCenum error = alGetError();
     34     const char *description = "";
     35 
     36     switch (error) {
     37         case AL_NO_ERROR:
     38             return false;
     39         case AL_INVALID_NAME:
     40             description = "A bad name (ID) was passed to an OpenAL function";
     41             break;
     42         case AL_INVALID_ENUM:
     43             description = "An invalid enum value was passed to an OpenAL function";
     44             break;
     45         case AL_INVALID_VALUE:
     46             description = "An invalid value was passed to an OpenAL function";
     47             break;
     48         case AL_INVALID_OPERATION:
     49             description = "The requested operation is not valid";
     50             break;
     51         case AL_OUT_OF_MEMORY:
     52             description = "The requested operation resulted in OpenAL running out of memory";
     53             break;
     54     }
     55 
     56     if (user_msg != NULL) {
     57         fprintf(stderr, "[OpenAL:%s] %s: %s\n", line, user_msg, description);
     58     }
     59     else {
     60         fprintf(stderr, "[OpenAL:%s] %s\n", line, description);
     61     }
     62 
     63     return true;
     64 }
     65 
     66 static void _audio_deinit(void)
     67 {
     68     // Stop the source (this should mark all queued buffers as processed)
     69     alSourceStop(al_source);
     70 
     71     // Check if there are buffers that can be freed
     72     ALint processed;
     73     alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed);
     74     if (!AL_ERROR("Failed to query number of processed buffers")) {
     75         // Try to free the buffers, we do not care about potential errors here
     76         while (processed--) {
     77             ALuint buffer;
     78             alSourceUnqueueBuffers(al_source, 1, &buffer);
     79             alDeleteBuffers(1, &buffer);
     80         }
     81     }
     82 
     83     alDeleteSources(1, &al_source);
     84     if (al_context) {
     85         alcDestroyContext(al_context);
     86         al_context = NULL;
     87     }
     88     
     89     if (al_device) {
     90         alcCloseDevice(al_device);
     91         al_device = NULL;
     92     }
     93 
     94     if (audio_buffer) {
     95         free(audio_buffer);
     96         audio_buffer = NULL;
     97     }
     98 }
     99 
    100 static void free_processed_buffers(void)
    101 {
    102     ALint processed;
    103     alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed);
    104     if (AL_ERROR("Failed to query number of processed buffers")) {
    105         return;
    106     }
    107 
    108     while (processed--) {
    109         ALuint buffer;
    110 
    111         alSourceUnqueueBuffers(al_source, 1, &buffer);
    112         if (AL_ERROR("Failed to unqueue buffer")) {
    113             return;
    114         }
    115 
    116         alDeleteBuffers(1, &buffer);
    117         /* Due to a limitation in Apple's OpenAL implementation, this function
    118            can fail once in a few times. If it does, ignore the warning, and let
    119            this buffer be freed in a later call to free_processed_buffers. */
    120 #if defined(__APPLE__)
    121         if (alGetError()) return;
    122 #else
    123         if (AL_ERROR("Failed to delete buffer")) {
    124             return;
    125         }
    126 #endif
    127     }
    128 }
    129 
    130 static bool _audio_is_playing(void)
    131 {
    132     ALenum state;
    133     alGetSourcei(al_source, AL_SOURCE_STATE, &state);
    134     if (AL_ERROR("Failed to query source state")) {
    135         return false;
    136     }
    137 
    138     return state == AL_PLAYING;
    139 }
    140 
    141 static void _audio_set_paused(bool paused)
    142 {
    143     is_paused = paused;
    144     if (paused) {
    145         alSourcePause(al_source);
    146     }
    147     else {
    148         alSourcePlay(al_source);
    149     }
    150 }
    151 
    152 static void _audio_clear_queue(void)
    153 {
    154     bool is_playing = _audio_is_playing();
    155 
    156     // Stopping a source clears its queue
    157     alSourceStop(al_source);
    158     if (AL_ERROR(NULL)) {
    159         return;
    160     }
    161 
    162     free_processed_buffers();
    163     buffer_pos = 0;
    164 
    165     if (is_playing) {
    166         _audio_set_paused(false);
    167     }
    168 }
    169 
    170 static unsigned _audio_get_frequency(void)
    171 {
    172     return sample_rate;
    173 }
    174 
    175 static size_t _audio_get_queue_length(void)
    176 {
    177     // Get the number of all attached buffers
    178     ALint buffers;
    179     alGetSourcei(al_source, AL_BUFFERS_QUEUED, &buffers);
    180     if (AL_ERROR("Failed to query number of queued buffers")) {
    181         buffers = 0;
    182     }
    183 
    184     // Get the number of all processed buffers (ready to be detached)
    185     ALint processed;
    186     alGetSourcei(al_source, AL_BUFFERS_PROCESSED, &processed);
    187     if (AL_ERROR("Failed to query number of processed buffers")) {
    188         processed = 0;
    189     }
    190 
    191     return (buffers - processed) * buffer_size + buffer_pos;
    192 }
    193 
    194 static void _audio_queue_sample(GB_sample_t *sample)
    195 {
    196     if (is_paused) return;
    197     audio_buffer[buffer_pos++] = *sample;
    198 
    199     if (buffer_pos == buffer_size) {
    200         buffer_pos = 0;
    201 
    202         ALuint al_buffer;
    203         alGenBuffers(1, &al_buffer);
    204         if (AL_ERROR("Failed to create audio buffer")) {
    205             return;
    206         }
    207 
    208         alBufferData(al_buffer, AL_FORMAT_STEREO16, audio_buffer, buffer_size * sizeof(GB_sample_t), sample_rate);
    209         if (AL_ERROR("Failed to buffer data")) {
    210             return;
    211         }
    212 
    213         alSourceQueueBuffers(al_source, 1, &al_buffer);
    214         if (AL_ERROR("Failed to queue buffer")) {
    215             return;
    216         }
    217 
    218         // In case of an audio underrun, the source might
    219         // have finished playing all attached buffers
    220         // which means its status will be "AL_STOPPED".
    221         if (!_audio_is_playing()) {
    222             alSourcePlay(al_source);
    223         }
    224 
    225         free_processed_buffers();
    226     }
    227 }
    228 
    229 static bool _audio_init(void)
    230 {
    231     // Open the default device
    232     al_device = alcOpenDevice(NULL);
    233     if (!al_device) {
    234         AL_ERROR("Failed to open device");
    235         return false;
    236     }
    237 
    238     // Create a new audio context without special attributes
    239     al_context = alcCreateContext(al_device, NULL);
    240     if (al_context == NULL) {
    241         AL_ERROR("Failed to create context");
    242         _audio_deinit();
    243         return false;
    244     }
    245 
    246     // Enable our audio context
    247     if (!alcMakeContextCurrent(al_context)) {
    248         AL_ERROR("Failed to set context");
    249         _audio_deinit();
    250         return false;
    251     }
    252 
    253     // Query the sample rate of the playback device
    254     alcGetIntegerv(al_device, ALC_FREQUENCY, 1, &sample_rate);
    255     if (AL_ERROR("Failed to query sample rate")) {
    256         _audio_deinit();
    257         return false;
    258     }
    259     if (sample_rate == 0) {
    260         sample_rate = 48000;
    261     }
    262 
    263     // Allocate our working buffer
    264     buffer_size = (sample_rate * BUFFER_LEN_MS) / 1000;
    265     audio_buffer = malloc(buffer_size * sizeof(GB_sample_t));
    266     if (audio_buffer == NULL) {
    267         fprintf(stderr, "Failed to allocate audio buffer\n");
    268         _audio_deinit();
    269         return false;
    270     }
    271 
    272     // Create our playback source
    273     alGenSources(1, &al_source);
    274     if (AL_ERROR(NULL)) {
    275         _audio_deinit();
    276         return false;
    277     }
    278 
    279     // Keep the pitch as is
    280     alSourcef(al_source, AL_PITCH, 1);
    281     if (AL_ERROR(NULL)) {
    282         _audio_deinit();
    283         return false;
    284     }
    285 
    286     // Keep the volume as is
    287     alSourcef(al_source, AL_GAIN, 1);
    288     if (AL_ERROR(NULL)) {
    289         _audio_deinit();
    290         return false;
    291     }
    292 
    293     // Position our source at the center of the 3D space
    294     alSource3f(al_source, AL_POSITION, 0, 0, 0);
    295     if (AL_ERROR(NULL)) {
    296         _audio_deinit();
    297         return false;
    298     }
    299 
    300     // Our source is fixed in space
    301     alSource3f(al_source, AL_VELOCITY, 0, 0, 0);
    302     if (AL_ERROR(NULL)) {
    303         _audio_deinit();
    304         return false;
    305     }
    306 
    307     // Our source does not loop
    308     alSourcei(al_source, AL_LOOPING, AL_FALSE);
    309     if (AL_ERROR(NULL)) {
    310         _audio_deinit();
    311         return false;
    312     }
    313 
    314     return true;
    315 }
    316 
    317 GB_AUDIO_DRIVER(OpenAL);

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.