Don't over-allocate the active effect slot array
[openal-soft.git] / examples / alconvolve.c
blob597d6ea2c1706b6af6539f3697a3fb8ade1f9f5f
1 /*
2 * OpenAL Convolution Reverb Example
4 * Copyright (c) 2020 by Chris Robinson <chris.kcat@gmail.com>
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
25 /* This file contains an example for applying convolution to a source. */
27 #include <assert.h>
28 #include <inttypes.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
34 #include "sndfile.h"
36 #include "AL/al.h"
37 #include "AL/alext.h"
39 #include "common/alhelpers.h"
41 #include "win_main_utf8.h"
44 #ifndef AL_SOFT_convolution_effect
45 #define AL_SOFT_convolution_effect
46 #define AL_EFFECT_CONVOLUTION_SOFT 0xA000
47 #endif
50 /* Filter object functions */
51 static LPALGENFILTERS alGenFilters;
52 static LPALDELETEFILTERS alDeleteFilters;
53 static LPALISFILTER alIsFilter;
54 static LPALFILTERI alFilteri;
55 static LPALFILTERIV alFilteriv;
56 static LPALFILTERF alFilterf;
57 static LPALFILTERFV alFilterfv;
58 static LPALGETFILTERI alGetFilteri;
59 static LPALGETFILTERIV alGetFilteriv;
60 static LPALGETFILTERF alGetFilterf;
61 static LPALGETFILTERFV alGetFilterfv;
63 /* Effect object functions */
64 static LPALGENEFFECTS alGenEffects;
65 static LPALDELETEEFFECTS alDeleteEffects;
66 static LPALISEFFECT alIsEffect;
67 static LPALEFFECTI alEffecti;
68 static LPALEFFECTIV alEffectiv;
69 static LPALEFFECTF alEffectf;
70 static LPALEFFECTFV alEffectfv;
71 static LPALGETEFFECTI alGetEffecti;
72 static LPALGETEFFECTIV alGetEffectiv;
73 static LPALGETEFFECTF alGetEffectf;
74 static LPALGETEFFECTFV alGetEffectfv;
76 /* Auxiliary Effect Slot object functions */
77 static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
78 static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
79 static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
80 static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
81 static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
82 static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
83 static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
84 static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
85 static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
86 static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
87 static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
90 /* This stuff defines a simple streaming player object, the same as alstream.c.
91 * Comments are removed for brevity, see alstream.c for more details.
93 enum { NumBuffers = 4 };
94 enum { BufferSamples = 8192 };
96 typedef struct StreamPlayer {
97 ALuint buffers[NumBuffers];
98 ALuint source;
100 SNDFILE *sndfile;
101 SF_INFO sfinfo;
102 float *membuf;
104 ALenum format;
105 } StreamPlayer;
107 static StreamPlayer *NewPlayer(void)
109 StreamPlayer *player;
111 player = calloc(1, sizeof(*player));
112 assert(player != NULL);
114 alGenBuffers(NumBuffers, player->buffers);
115 assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
117 alGenSources(1, &player->source);
118 assert(alGetError() == AL_NO_ERROR && "Could not create source");
120 alSource3i(player->source, AL_POSITION, 0, 0, -1);
121 alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
122 alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
123 assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
125 return player;
128 static void ClosePlayerFile(StreamPlayer *player)
130 if(player->sndfile)
131 sf_close(player->sndfile);
132 player->sndfile = NULL;
134 free(player->membuf);
135 player->membuf = NULL;
138 static void DeletePlayer(StreamPlayer *player)
140 ClosePlayerFile(player);
142 alDeleteSources(1, &player->source);
143 alDeleteBuffers(NumBuffers, player->buffers);
144 if(alGetError() != AL_NO_ERROR)
145 fprintf(stderr, "Failed to delete object IDs\n");
147 memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */
148 free(player);
151 static int OpenPlayerFile(StreamPlayer *player, const char *filename)
153 size_t frame_size;
155 ClosePlayerFile(player);
157 player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo);
158 if(!player->sndfile)
160 fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL));
161 return 0;
164 player->format = AL_NONE;
165 if(player->sfinfo.channels == 1)
166 player->format = AL_FORMAT_MONO_FLOAT32;
167 else if(player->sfinfo.channels == 2)
168 player->format = AL_FORMAT_STEREO_FLOAT32;
169 else if(player->sfinfo.channels == 6)
170 player->format = AL_FORMAT_51CHN32;
171 else if(player->sfinfo.channels == 3)
173 if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
174 player->format = AL_FORMAT_BFORMAT2D_FLOAT32;
176 else if(player->sfinfo.channels == 4)
178 if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
179 player->format = AL_FORMAT_BFORMAT3D_FLOAT32;
181 if(!player->format)
183 fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels);
184 sf_close(player->sndfile);
185 player->sndfile = NULL;
186 return 0;
189 frame_size = (size_t)(BufferSamples * player->sfinfo.channels) * sizeof(float);
190 player->membuf = malloc(frame_size);
192 return 1;
195 static int StartPlayer(StreamPlayer *player)
197 ALsizei i;
199 alSourceRewind(player->source);
200 alSourcei(player->source, AL_BUFFER, 0);
202 for(i = 0;i < NumBuffers;i++)
204 sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples);
205 if(slen < 1) break;
207 slen *= player->sfinfo.channels * (sf_count_t)sizeof(float);
208 alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen,
209 player->sfinfo.samplerate);
211 if(alGetError() != AL_NO_ERROR)
213 fprintf(stderr, "Error buffering for playback\n");
214 return 0;
217 alSourceQueueBuffers(player->source, i, player->buffers);
218 alSourcePlay(player->source);
219 if(alGetError() != AL_NO_ERROR)
221 fprintf(stderr, "Error starting playback\n");
222 return 0;
225 return 1;
228 static int UpdatePlayer(StreamPlayer *player)
230 ALint processed, state;
232 alGetSourcei(player->source, AL_SOURCE_STATE, &state);
233 alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
234 if(alGetError() != AL_NO_ERROR)
236 fprintf(stderr, "Error checking source state\n");
237 return 0;
240 while(processed > 0)
242 ALuint bufid;
243 sf_count_t slen;
245 alSourceUnqueueBuffers(player->source, 1, &bufid);
246 processed--;
248 slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples);
249 if(slen > 0)
251 slen *= player->sfinfo.channels * (sf_count_t)sizeof(float);
252 alBufferData(bufid, player->format, player->membuf, (ALsizei)slen,
253 player->sfinfo.samplerate);
254 alSourceQueueBuffers(player->source, 1, &bufid);
256 if(alGetError() != AL_NO_ERROR)
258 fprintf(stderr, "Error buffering data\n");
259 return 0;
263 if(state != AL_PLAYING && state != AL_PAUSED)
265 ALint queued;
267 alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued);
268 if(queued == 0)
269 return 0;
271 alSourcePlay(player->source);
272 if(alGetError() != AL_NO_ERROR)
274 fprintf(stderr, "Error restarting playback\n");
275 return 0;
279 return 1;
283 /* CreateEffect creates a new OpenAL effect object with a convolution type, and
284 * returns the new effect ID.
286 static ALuint CreateEffect(void)
288 ALuint effect = 0;
289 ALenum err;
291 printf("Using Convolution\n");
293 /* Create the effect object and set the convolution effect type. */
294 alGenEffects(1, &effect);
295 alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_SOFT);
297 /* Check if an error occurred, and clean up if so. */
298 err = alGetError();
299 if(err != AL_NO_ERROR)
301 fprintf(stderr, "OpenAL error: %s\n", alGetString(err));
302 if(alIsEffect(effect))
303 alDeleteEffects(1, &effect);
304 return 0;
307 return effect;
310 /* LoadBuffer loads the named audio file into an OpenAL buffer object, and
311 * returns the new buffer ID.
313 static ALuint LoadSound(const char *filename)
315 const char *namepart;
316 ALenum err, format;
317 ALuint buffer;
318 SNDFILE *sndfile;
319 SF_INFO sfinfo;
320 float *membuf;
321 sf_count_t num_frames;
322 ALsizei num_bytes;
324 /* Open the audio file and check that it's usable. */
325 sndfile = sf_open(filename, SFM_READ, &sfinfo);
326 if(!sndfile)
328 fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile));
329 return 0;
331 if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(float))/sfinfo.channels)
333 fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames);
334 sf_close(sndfile);
335 return 0;
338 /* Get the sound format, and figure out the OpenAL format. Use floats since
339 * impulse responses will usually have more than 16-bit precision.
341 format = AL_NONE;
342 if(sfinfo.channels == 1)
343 format = AL_FORMAT_MONO_FLOAT32;
344 else if(sfinfo.channels == 2)
345 format = AL_FORMAT_STEREO_FLOAT32;
346 else if(sfinfo.channels == 3)
348 if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
349 format = AL_FORMAT_BFORMAT2D_FLOAT32;
351 else if(sfinfo.channels == 4)
353 if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
354 format = AL_FORMAT_BFORMAT3D_FLOAT32;
356 if(!format)
358 fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels);
359 sf_close(sndfile);
360 return 0;
363 namepart = strrchr(filename, '/');
364 if(!namepart) namepart = strrchr(filename, '\\');
365 if(!namepart) namepart = filename;
366 else namepart++;
368 printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart,
369 FormatName(format), sfinfo.samplerate, sfinfo.frames,
370 (double)sfinfo.frames / sfinfo.samplerate);
371 fflush(stdout);
373 /* Decode the whole audio file to a buffer. */
374 membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(float));
376 num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames);
377 if(num_frames < 1)
379 free(membuf);
380 sf_close(sndfile);
381 fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames);
382 return 0;
384 num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(float);
386 /* Buffer the audio data into a new buffer object, then free the data and
387 * close the file.
389 buffer = 0;
390 alGenBuffers(1, &buffer);
391 alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate);
393 free(membuf);
394 sf_close(sndfile);
396 /* Check if an error occurred, and clean up if so. */
397 err = alGetError();
398 if(err != AL_NO_ERROR)
400 fprintf(stderr, "OpenAL Error: %s\n", alGetString(err));
401 if(buffer && alIsBuffer(buffer))
402 alDeleteBuffers(1, &buffer);
403 return 0;
406 return buffer;
410 int main(int argc, char **argv)
412 ALuint ir_buffer, filter, effect, slot;
413 StreamPlayer *player;
414 int i;
416 /* Print out usage if no arguments were specified */
417 if(argc < 2)
419 fprintf(stderr, "Usage: %s [-device <name>] <impulse response file> "
420 "<[-dry | -nodry] filename>...\n", argv[0]);
421 return 1;
424 argv++; argc--;
425 if(InitAL(&argv, &argc) != 0)
426 return 1;
428 if(!alIsExtensionPresent("AL_SOFTX_convolution_effect"))
430 CloseAL();
431 fprintf(stderr, "Error: Convolution effect not supported\n");
432 return 1;
435 if(argc < 2)
437 CloseAL();
438 fprintf(stderr, "Error: Missing impulse response or sound files\n");
439 return 1;
442 /* Define a macro to help load the function pointers. */
443 #define LOAD_PROC(T, x) ((x) = FUNCTION_CAST(T, alGetProcAddress(#x)))
444 LOAD_PROC(LPALGENFILTERS, alGenFilters);
445 LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters);
446 LOAD_PROC(LPALISFILTER, alIsFilter);
447 LOAD_PROC(LPALFILTERI, alFilteri);
448 LOAD_PROC(LPALFILTERIV, alFilteriv);
449 LOAD_PROC(LPALFILTERF, alFilterf);
450 LOAD_PROC(LPALFILTERFV, alFilterfv);
451 LOAD_PROC(LPALGETFILTERI, alGetFilteri);
452 LOAD_PROC(LPALGETFILTERIV, alGetFilteriv);
453 LOAD_PROC(LPALGETFILTERF, alGetFilterf);
454 LOAD_PROC(LPALGETFILTERFV, alGetFilterfv);
456 LOAD_PROC(LPALGENEFFECTS, alGenEffects);
457 LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
458 LOAD_PROC(LPALISEFFECT, alIsEffect);
459 LOAD_PROC(LPALEFFECTI, alEffecti);
460 LOAD_PROC(LPALEFFECTIV, alEffectiv);
461 LOAD_PROC(LPALEFFECTF, alEffectf);
462 LOAD_PROC(LPALEFFECTFV, alEffectfv);
463 LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
464 LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
465 LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
466 LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
468 LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
469 LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
470 LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
471 LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
472 LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
473 LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
474 LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
475 LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
476 LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
477 LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
478 LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
479 #undef LOAD_PROC
481 /* Load the reverb into an effect. */
482 effect = CreateEffect();
483 if(!effect)
485 CloseAL();
486 return 1;
489 /* Load the impulse response sound into a buffer. */
490 ir_buffer = LoadSound(argv[0]);
491 if(!ir_buffer)
493 alDeleteEffects(1, &effect);
494 CloseAL();
495 return 1;
498 /* Create the effect slot object. This is what "plays" an effect on sources
499 * that connect to it.
501 slot = 0;
502 alGenAuxiliaryEffectSlots(1, &slot);
504 /* Set the impulse response sound buffer on the effect slot. This allows
505 * effects to access it as needed. In this case, convolution uses it as the
506 * filter source. NOTE: Unlike the effect object, the buffer *is* kept
507 * referenced and may not be changed or deleted as long as it's set, just
508 * like with a source. When another buffer is set, or the effect slot is
509 * deleted, the buffer reference is released.
511 * The effect slot's gain is reduced because the impulse responses I've
512 * tested with result in excessively loud reverb. Is that normal? Even with
513 * this, it seems a bit on the loud side.
515 * Also note: unlike standard or EAX reverb, there is no automatic
516 * attenuation of a source's reverb response with distance, so the reverb
517 * will remain full volume regardless of a given sound's distance from the
518 * listener. You can use a send filter to alter a given source's
519 * contribution to reverb.
521 alAuxiliaryEffectSloti(slot, AL_BUFFER, (ALint)ir_buffer);
522 alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f);
523 alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect);
524 assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot");
526 /* Create a filter that can silence the dry path. */
527 filter = 0;
528 alGenFilters(1, &filter);
529 alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
530 alFilterf(filter, AL_LOWPASS_GAIN, 0.0f);
532 player = NewPlayer();
533 /* Connect the player's source to the effect slot. */
534 alSource3i(player->source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL);
535 assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
537 /* Play each file listed on the command line */
538 for(i = 1;i < argc;i++)
540 const char *namepart;
542 if(argc-i > 1)
544 if(strcasecmp(argv[i], "-nodry") == 0)
546 alSourcei(player->source, AL_DIRECT_FILTER, (ALint)filter);
547 ++i;
549 else if(strcasecmp(argv[i], "-dry") == 0)
551 alSourcei(player->source, AL_DIRECT_FILTER, AL_FILTER_NULL);
552 ++i;
556 if(!OpenPlayerFile(player, argv[i]))
557 continue;
559 namepart = strrchr(argv[i], '/');
560 if(!namepart) namepart = strrchr(argv[i], '\\');
561 if(!namepart) namepart = argv[i];
562 else namepart++;
564 printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format),
565 player->sfinfo.samplerate);
566 fflush(stdout);
568 if(!StartPlayer(player))
570 ClosePlayerFile(player);
571 continue;
574 while(UpdatePlayer(player))
575 al_nssleep(10000000);
577 ClosePlayerFile(player);
579 printf("Done.\n");
581 /* All files done. Delete the player and effect resources, and close down
582 * OpenAL.
584 DeletePlayer(player);
585 player = NULL;
587 alDeleteAuxiliaryEffectSlots(1, &slot);
588 alDeleteEffects(1, &effect);
589 alDeleteFilters(1, &filter);
590 alDeleteBuffers(1, &ir_buffer);
592 CloseAL();
594 return 0;