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.