]> git.tdb.fi Git - ext/openal.git/blob - examples/alconvolve.c
Import OpenAL Soft 1.23.1 sources
[ext/openal.git] / examples / alconvolve.c
1 /*
2  * OpenAL Convolution Reverb Example
3  *
4  * Copyright (c) 2020 by Chris Robinson <chris.kcat@gmail.com>
5  *
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:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
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.
23  */
24
25 /* This file contains an example for applying convolution reverb to a source. */
26
27 #include <assert.h>
28 #include <inttypes.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #include "sndfile.h"
35
36 #include "AL/al.h"
37 #include "AL/alext.h"
38
39 #include "common/alhelpers.h"
40
41
42 #ifndef AL_SOFT_convolution_reverb
43 #define AL_SOFT_convolution_reverb
44 #define AL_EFFECT_CONVOLUTION_REVERB_SOFT        0xA000
45 #endif
46
47
48 /* Filter object functions */
49 static LPALGENFILTERS alGenFilters;
50 static LPALDELETEFILTERS alDeleteFilters;
51 static LPALISFILTER alIsFilter;
52 static LPALFILTERI alFilteri;
53 static LPALFILTERIV alFilteriv;
54 static LPALFILTERF alFilterf;
55 static LPALFILTERFV alFilterfv;
56 static LPALGETFILTERI alGetFilteri;
57 static LPALGETFILTERIV alGetFilteriv;
58 static LPALGETFILTERF alGetFilterf;
59 static LPALGETFILTERFV alGetFilterfv;
60
61 /* Effect object functions */
62 static LPALGENEFFECTS alGenEffects;
63 static LPALDELETEEFFECTS alDeleteEffects;
64 static LPALISEFFECT alIsEffect;
65 static LPALEFFECTI alEffecti;
66 static LPALEFFECTIV alEffectiv;
67 static LPALEFFECTF alEffectf;
68 static LPALEFFECTFV alEffectfv;
69 static LPALGETEFFECTI alGetEffecti;
70 static LPALGETEFFECTIV alGetEffectiv;
71 static LPALGETEFFECTF alGetEffectf;
72 static LPALGETEFFECTFV alGetEffectfv;
73
74 /* Auxiliary Effect Slot object functions */
75 static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
76 static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
77 static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
78 static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
79 static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
80 static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
81 static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
82 static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
83 static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
84 static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
85 static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
86
87
88 /* This stuff defines a simple streaming player object, the same as alstream.c.
89  * Comments are removed for brevity, see alstream.c for more details.
90  */
91 #define NUM_BUFFERS 4
92 #define BUFFER_SAMPLES 8192
93
94 typedef struct StreamPlayer {
95     ALuint buffers[NUM_BUFFERS];
96     ALuint source;
97
98     SNDFILE *sndfile;
99     SF_INFO sfinfo;
100     float *membuf;
101
102     ALenum format;
103 } StreamPlayer;
104
105 static StreamPlayer *NewPlayer(void)
106 {
107     StreamPlayer *player;
108
109     player = calloc(1, sizeof(*player));
110     assert(player != NULL);
111
112     alGenBuffers(NUM_BUFFERS, player->buffers);
113     assert(alGetError() == AL_NO_ERROR && "Could not create buffers");
114
115     alGenSources(1, &player->source);
116     assert(alGetError() == AL_NO_ERROR && "Could not create source");
117
118     alSource3i(player->source, AL_POSITION, 0, 0, -1);
119     alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE);
120     alSourcei(player->source, AL_ROLLOFF_FACTOR, 0);
121     assert(alGetError() == AL_NO_ERROR && "Could not set source parameters");
122
123     return player;
124 }
125
126 static void ClosePlayerFile(StreamPlayer *player)
127 {
128     if(player->sndfile)
129         sf_close(player->sndfile);
130     player->sndfile = NULL;
131
132     free(player->membuf);
133     player->membuf = NULL;
134 }
135
136 static void DeletePlayer(StreamPlayer *player)
137 {
138     ClosePlayerFile(player);
139
140     alDeleteSources(1, &player->source);
141     alDeleteBuffers(NUM_BUFFERS, player->buffers);
142     if(alGetError() != AL_NO_ERROR)
143         fprintf(stderr, "Failed to delete object IDs\n");
144
145     memset(player, 0, sizeof(*player));
146     free(player);
147 }
148
149 static int OpenPlayerFile(StreamPlayer *player, const char *filename)
150 {
151     size_t frame_size;
152
153     ClosePlayerFile(player);
154
155     player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo);
156     if(!player->sndfile)
157     {
158         fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL));
159         return 0;
160     }
161
162     player->format = AL_NONE;
163     if(player->sfinfo.channels == 1)
164         player->format = AL_FORMAT_MONO_FLOAT32;
165     else if(player->sfinfo.channels == 2)
166         player->format = AL_FORMAT_STEREO_FLOAT32;
167     else if(player->sfinfo.channels == 6)
168         player->format = AL_FORMAT_51CHN32;
169     else if(player->sfinfo.channels == 3)
170     {
171         if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
172             player->format = AL_FORMAT_BFORMAT2D_FLOAT32;
173     }
174     else if(player->sfinfo.channels == 4)
175     {
176         if(sf_command(player->sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
177             player->format = AL_FORMAT_BFORMAT3D_FLOAT32;
178     }
179     if(!player->format)
180     {
181         fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels);
182         sf_close(player->sndfile);
183         player->sndfile = NULL;
184         return 0;
185     }
186
187     frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(float);
188     player->membuf = malloc(frame_size);
189
190     return 1;
191 }
192
193 static int StartPlayer(StreamPlayer *player)
194 {
195     ALsizei i;
196
197     alSourceRewind(player->source);
198     alSourcei(player->source, AL_BUFFER, 0);
199
200     for(i = 0;i < NUM_BUFFERS;i++)
201     {
202         sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES);
203         if(slen < 1) break;
204
205         slen *= player->sfinfo.channels * (sf_count_t)sizeof(float);
206         alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen,
207             player->sfinfo.samplerate);
208     }
209     if(alGetError() != AL_NO_ERROR)
210     {
211         fprintf(stderr, "Error buffering for playback\n");
212         return 0;
213     }
214
215     alSourceQueueBuffers(player->source, i, player->buffers);
216     alSourcePlay(player->source);
217     if(alGetError() != AL_NO_ERROR)
218     {
219         fprintf(stderr, "Error starting playback\n");
220         return 0;
221     }
222
223     return 1;
224 }
225
226 static int UpdatePlayer(StreamPlayer *player)
227 {
228     ALint processed, state;
229
230     alGetSourcei(player->source, AL_SOURCE_STATE, &state);
231     alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed);
232     if(alGetError() != AL_NO_ERROR)
233     {
234         fprintf(stderr, "Error checking source state\n");
235         return 0;
236     }
237
238     while(processed > 0)
239     {
240         ALuint bufid;
241         sf_count_t slen;
242
243         alSourceUnqueueBuffers(player->source, 1, &bufid);
244         processed--;
245
246         slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES);
247         if(slen > 0)
248         {
249             slen *= player->sfinfo.channels * (sf_count_t)sizeof(float);
250             alBufferData(bufid, player->format, player->membuf, (ALsizei)slen,
251                 player->sfinfo.samplerate);
252             alSourceQueueBuffers(player->source, 1, &bufid);
253         }
254         if(alGetError() != AL_NO_ERROR)
255         {
256             fprintf(stderr, "Error buffering data\n");
257             return 0;
258         }
259     }
260
261     if(state != AL_PLAYING && state != AL_PAUSED)
262     {
263         ALint queued;
264
265         alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued);
266         if(queued == 0)
267             return 0;
268
269         alSourcePlay(player->source);
270         if(alGetError() != AL_NO_ERROR)
271         {
272             fprintf(stderr, "Error restarting playback\n");
273             return 0;
274         }
275     }
276
277     return 1;
278 }
279
280
281 /* CreateEffect creates a new OpenAL effect object with a convolution reverb
282  * type, and returns the new effect ID.
283  */
284 static ALuint CreateEffect(void)
285 {
286     ALuint effect = 0;
287     ALenum err;
288
289     printf("Using Convolution Reverb\n");
290
291     /* Create the effect object and set the convolution reverb effect type. */
292     alGenEffects(1, &effect);
293     alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT);
294
295     /* Check if an error occured, and clean up if so. */
296     err = alGetError();
297     if(err != AL_NO_ERROR)
298     {
299         fprintf(stderr, "OpenAL error: %s\n", alGetString(err));
300         if(alIsEffect(effect))
301             alDeleteEffects(1, &effect);
302         return 0;
303     }
304
305     return effect;
306 }
307
308 /* LoadBuffer loads the named audio file into an OpenAL buffer object, and
309  * returns the new buffer ID.
310  */
311 static ALuint LoadSound(const char *filename)
312 {
313     const char *namepart;
314     ALenum err, format;
315     ALuint buffer;
316     SNDFILE *sndfile;
317     SF_INFO sfinfo;
318     float *membuf;
319     sf_count_t num_frames;
320     ALsizei num_bytes;
321
322     /* Open the audio file and check that it's usable. */
323     sndfile = sf_open(filename, SFM_READ, &sfinfo);
324     if(!sndfile)
325     {
326         fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile));
327         return 0;
328     }
329     if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(float))/sfinfo.channels)
330     {
331         fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames);
332         sf_close(sndfile);
333         return 0;
334     }
335
336     /* Get the sound format, and figure out the OpenAL format. Use floats since
337      * impulse responses will usually have more than 16-bit precision.
338      */
339     format = AL_NONE;
340     if(sfinfo.channels == 1)
341         format = AL_FORMAT_MONO_FLOAT32;
342     else if(sfinfo.channels == 2)
343         format = AL_FORMAT_STEREO_FLOAT32;
344     else if(sfinfo.channels == 3)
345     {
346         if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
347             format = AL_FORMAT_BFORMAT2D_FLOAT32;
348     }
349     else if(sfinfo.channels == 4)
350     {
351         if(sf_command(sndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT)
352             format = AL_FORMAT_BFORMAT3D_FLOAT32;
353     }
354     if(!format)
355     {
356         fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels);
357         sf_close(sndfile);
358         return 0;
359     }
360
361     namepart = strrchr(filename, '/');
362     if(namepart || (namepart=strrchr(filename, '\\')))
363         namepart++;
364     else
365         namepart = filename;
366     printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart,
367         FormatName(format), sfinfo.samplerate, sfinfo.frames,
368         (double)sfinfo.frames / sfinfo.samplerate);
369     fflush(stdout);
370
371     /* Decode the whole audio file to a buffer. */
372     membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(float));
373
374     num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames);
375     if(num_frames < 1)
376     {
377         free(membuf);
378         sf_close(sndfile);
379         fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames);
380         return 0;
381     }
382     num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(float);
383
384     /* Buffer the audio data into a new buffer object, then free the data and
385      * close the file.
386      */
387     buffer = 0;
388     alGenBuffers(1, &buffer);
389     alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate);
390
391     free(membuf);
392     sf_close(sndfile);
393
394     /* Check if an error occured, and clean up if so. */
395     err = alGetError();
396     if(err != AL_NO_ERROR)
397     {
398         fprintf(stderr, "OpenAL Error: %s\n", alGetString(err));
399         if(buffer && alIsBuffer(buffer))
400             alDeleteBuffers(1, &buffer);
401         return 0;
402     }
403
404     return buffer;
405 }
406
407
408 int main(int argc, char **argv)
409 {
410     ALuint ir_buffer, filter, effect, slot;
411     StreamPlayer *player;
412     int i;
413
414     /* Print out usage if no arguments were specified */
415     if(argc < 2)
416     {
417         fprintf(stderr, "Usage: %s [-device <name>] <impulse response file> "
418             "<[-dry | -nodry] filename>...\n", argv[0]);
419         return 1;
420     }
421
422     argv++; argc--;
423     if(InitAL(&argv, &argc) != 0)
424         return 1;
425
426     if(!alIsExtensionPresent("AL_SOFTX_convolution_reverb"))
427     {
428         CloseAL();
429         fprintf(stderr, "Error: Convolution revern not supported\n");
430         return 1;
431     }
432
433     if(argc < 2)
434     {
435         CloseAL();
436         fprintf(stderr, "Error: Missing impulse response or sound files\n");
437         return 1;
438     }
439
440     /* Define a macro to help load the function pointers. */
441 #define LOAD_PROC(T, x)  ((x) = FUNCTION_CAST(T, alGetProcAddress(#x)))
442     LOAD_PROC(LPALGENFILTERS, alGenFilters);
443     LOAD_PROC(LPALDELETEFILTERS, alDeleteFilters);
444     LOAD_PROC(LPALISFILTER, alIsFilter);
445     LOAD_PROC(LPALFILTERI, alFilteri);
446     LOAD_PROC(LPALFILTERIV, alFilteriv);
447     LOAD_PROC(LPALFILTERF, alFilterf);
448     LOAD_PROC(LPALFILTERFV, alFilterfv);
449     LOAD_PROC(LPALGETFILTERI, alGetFilteri);
450     LOAD_PROC(LPALGETFILTERIV, alGetFilteriv);
451     LOAD_PROC(LPALGETFILTERF, alGetFilterf);
452     LOAD_PROC(LPALGETFILTERFV, alGetFilterfv);
453
454     LOAD_PROC(LPALGENEFFECTS, alGenEffects);
455     LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects);
456     LOAD_PROC(LPALISEFFECT, alIsEffect);
457     LOAD_PROC(LPALEFFECTI, alEffecti);
458     LOAD_PROC(LPALEFFECTIV, alEffectiv);
459     LOAD_PROC(LPALEFFECTF, alEffectf);
460     LOAD_PROC(LPALEFFECTFV, alEffectfv);
461     LOAD_PROC(LPALGETEFFECTI, alGetEffecti);
462     LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv);
463     LOAD_PROC(LPALGETEFFECTF, alGetEffectf);
464     LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv);
465
466     LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots);
467     LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots);
468     LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot);
469     LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti);
470     LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv);
471     LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf);
472     LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv);
473     LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti);
474     LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv);
475     LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf);
476     LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv);
477 #undef LOAD_PROC
478
479     /* Load the reverb into an effect. */
480     effect = CreateEffect();
481     if(!effect)
482     {
483         CloseAL();
484         return 1;
485     }
486
487     /* Load the impulse response sound into a buffer. */
488     ir_buffer = LoadSound(argv[0]);
489     if(!ir_buffer)
490     {
491         alDeleteEffects(1, &effect);
492         CloseAL();
493         return 1;
494     }
495
496     /* Create the effect slot object. This is what "plays" an effect on sources
497      * that connect to it.
498      */
499     slot = 0;
500     alGenAuxiliaryEffectSlots(1, &slot);
501
502     /* Set the impulse response sound buffer on the effect slot. This allows
503      * effects to access it as needed. In this case, convolution reverb uses it
504      * as the filter source. NOTE: Unlike the effect object, the buffer *is*
505      * kept referenced and may not be changed or deleted as long as it's set,
506      * just like with a source. When another buffer is set, or the effect slot
507      * is deleted, the buffer reference is released.
508      *
509      * The effect slot's gain is reduced because the impulse responses I've
510      * tested with result in excessively loud reverb. Is that normal? Even with
511      * this, it seems a bit on the loud side.
512      *
513      * Also note: unlike standard or EAX reverb, there is no automatic
514      * attenuation of a source's reverb response with distance, so the reverb
515      * will remain full volume regardless of a given sound's distance from the
516      * listener. You can use a send filter to alter a given source's
517      * contribution to reverb.
518      */
519     alAuxiliaryEffectSloti(slot, AL_BUFFER, (ALint)ir_buffer);
520     alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f);
521     alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect);
522     assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot");
523
524     /* Create a filter that can silence the dry path. */
525     filter = 0;
526     alGenFilters(1, &filter);
527     alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
528     alFilterf(filter, AL_LOWPASS_GAIN, 0.0f);
529
530     player = NewPlayer();
531     /* Connect the player's source to the effect slot. */
532     alSource3i(player->source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL);
533     assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source");
534
535     /* Play each file listed on the command line */
536     for(i = 1;i < argc;i++)
537     {
538         const char *namepart;
539
540         if(argc-i > 1)
541         {
542             if(strcasecmp(argv[i], "-nodry") == 0)
543             {
544                 alSourcei(player->source, AL_DIRECT_FILTER, (ALint)filter);
545                 ++i;
546             }
547             else if(strcasecmp(argv[i], "-dry") == 0)
548             {
549                 alSourcei(player->source, AL_DIRECT_FILTER, AL_FILTER_NULL);
550                 ++i;
551             }
552         }
553
554         if(!OpenPlayerFile(player, argv[i]))
555             continue;
556
557         namepart = strrchr(argv[i], '/');
558         if(namepart || (namepart=strrchr(argv[i], '\\')))
559             namepart++;
560         else
561             namepart = argv[i];
562
563         printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format),
564             player->sfinfo.samplerate);
565         fflush(stdout);
566
567         if(!StartPlayer(player))
568         {
569             ClosePlayerFile(player);
570             continue;
571         }
572
573         while(UpdatePlayer(player))
574             al_nssleep(10000000);
575
576         ClosePlayerFile(player);
577     }
578     printf("Done.\n");
579
580     /* All files done. Delete the player and effect resources, and close down
581      * OpenAL.
582      */
583     DeletePlayer(player);
584     player = NULL;
585
586     alDeleteAuxiliaryEffectSlots(1, &slot);
587     alDeleteEffects(1, &effect);
588     alDeleteFilters(1, &filter);
589     alDeleteBuffers(1, &ir_buffer);
590
591     CloseAL();
592
593     return 0;
594 }