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
25 /* This file contains an example for applying convolution to a source. */
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
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
];
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");
128 static void ClosePlayerFile(StreamPlayer
*player
)
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.*) */
151 static int OpenPlayerFile(StreamPlayer
*player
, const char *filename
)
155 ClosePlayerFile(player
);
157 player
->sndfile
= sf_open(filename
, SFM_READ
, &player
->sfinfo
);
160 fprintf(stderr
, "Could not open audio in %s: %s\n", filename
, sf_strerror(NULL
));
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
;
183 fprintf(stderr
, "Unsupported channel count: %d\n", player
->sfinfo
.channels
);
184 sf_close(player
->sndfile
);
185 player
->sndfile
= NULL
;
189 frame_size
= (size_t)(BufferSamples
* player
->sfinfo
.channels
) * sizeof(float);
190 player
->membuf
= malloc(frame_size
);
195 static int StartPlayer(StreamPlayer
*player
)
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
);
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");
217 alSourceQueueBuffers(player
->source
, i
, player
->buffers
);
218 alSourcePlay(player
->source
);
219 if(alGetError() != AL_NO_ERROR
)
221 fprintf(stderr
, "Error starting playback\n");
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");
245 alSourceUnqueueBuffers(player
->source
, 1, &bufid
);
248 slen
= sf_readf_float(player
->sndfile
, player
->membuf
, BufferSamples
);
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");
263 if(state
!= AL_PLAYING
&& state
!= AL_PAUSED
)
267 alGetSourcei(player
->source
, AL_BUFFERS_QUEUED
, &queued
);
271 alSourcePlay(player
->source
);
272 if(alGetError() != AL_NO_ERROR
)
274 fprintf(stderr
, "Error restarting playback\n");
283 /* CreateEffect creates a new OpenAL effect object with a convolution type, and
284 * returns the new effect ID.
286 static ALuint
CreateEffect(void)
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. */
299 if(err
!= AL_NO_ERROR
)
301 fprintf(stderr
, "OpenAL error: %s\n", alGetString(err
));
302 if(alIsEffect(effect
))
303 alDeleteEffects(1, &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
;
321 sf_count_t num_frames
;
324 /* Open the audio file and check that it's usable. */
325 sndfile
= sf_open(filename
, SFM_READ
, &sfinfo
);
328 fprintf(stderr
, "Could not open audio in %s: %s\n", filename
, sf_strerror(sndfile
));
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
);
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.
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
;
358 fprintf(stderr
, "Unsupported channel count: %d\n", sfinfo
.channels
);
363 namepart
= strrchr(filename
, '/');
364 if(!namepart
) namepart
= strrchr(filename
, '\\');
365 if(!namepart
) namepart
= filename
;
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
);
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
);
381 fprintf(stderr
, "Failed to read samples in %s (%" PRId64
")\n", filename
, num_frames
);
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
390 alGenBuffers(1, &buffer
);
391 alBufferData(buffer
, format
, membuf
, num_bytes
, sfinfo
.samplerate
);
396 /* Check if an error occurred, and clean up if so. */
398 if(err
!= AL_NO_ERROR
)
400 fprintf(stderr
, "OpenAL Error: %s\n", alGetString(err
));
401 if(buffer
&& alIsBuffer(buffer
))
402 alDeleteBuffers(1, &buffer
);
410 int main(int argc
, char **argv
)
412 ALuint ir_buffer
, filter
, effect
, slot
;
413 StreamPlayer
*player
;
416 /* Print out usage if no arguments were specified */
419 fprintf(stderr
, "Usage: %s [-device <name>] <impulse response file> "
420 "<[-dry | -nodry] filename>...\n", argv
[0]);
425 if(InitAL(&argv
, &argc
) != 0)
428 if(!alIsExtensionPresent("AL_SOFTX_convolution_effect"))
431 fprintf(stderr
, "Error: Convolution effect not supported\n");
438 fprintf(stderr
, "Error: Missing impulse response or sound files\n");
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
);
481 /* Load the reverb into an effect. */
482 effect
= CreateEffect();
489 /* Load the impulse response sound into a buffer. */
490 ir_buffer
= LoadSound(argv
[0]);
493 alDeleteEffects(1, &effect
);
498 /* Create the effect slot object. This is what "plays" an effect on sources
499 * that connect to it.
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. */
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
;
544 if(strcasecmp(argv
[i
], "-nodry") == 0)
546 alSourcei(player
->source
, AL_DIRECT_FILTER
, (ALint
)filter
);
549 else if(strcasecmp(argv
[i
], "-dry") == 0)
551 alSourcei(player
->source
, AL_DIRECT_FILTER
, AL_FILTER_NULL
);
556 if(!OpenPlayerFile(player
, argv
[i
]))
559 namepart
= strrchr(argv
[i
], '/');
560 if(!namepart
) namepart
= strrchr(argv
[i
], '\\');
561 if(!namepart
) namepart
= argv
[i
];
564 printf("Playing: %s (%s, %dhz)\n", namepart
, FormatName(player
->format
),
565 player
->sfinfo
.samplerate
);
568 if(!StartPlayer(player
))
570 ClosePlayerFile(player
);
574 while(UpdatePlayer(player
))
575 al_nssleep(10000000);
577 ClosePlayerFile(player
);
581 /* All files done. Delete the player and effect resources, and close down
584 DeletePlayer(player
);
587 alDeleteAuxiliaryEffectSlots(1, &slot
);
588 alDeleteEffects(1, &effect
);
589 alDeleteFilters(1, &filter
);
590 alDeleteBuffers(1, &ir_buffer
);