diff options
author | Chris Robinson <[email protected]> | 2020-08-26 17:16:36 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2020-08-26 17:23:50 -0700 |
commit | 97ecf5810fb4b978db96dd607fb723d5e19cd90e (patch) | |
tree | 497365e67e54462df29a73ab85a24ef6e109ada3 | |
parent | 577a8234f21cf2d62e0aec8ad202cc697ac4d40d (diff) |
Base the convolution example on the simpler stream example
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | examples/alconvolve.c | 515 | ||||
-rw-r--r-- | examples/alconvolve.cpp | 536 |
3 files changed, 516 insertions, 537 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 092c041d..55ccec6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1508,7 +1508,7 @@ if(ALSOFT_EXAMPLES) target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${UNICODE_FLAG}) - add_executable(alconvolve examples/alconvolve.cpp) + add_executable(alconvolve examples/alconvolve.c) target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} common SndFile::SndFile ex-common ${UNICODE_FLAG}) diff --git a/examples/alconvolve.c b/examples/alconvolve.c new file mode 100644 index 00000000..77ef83bf --- /dev/null +++ b/examples/alconvolve.c @@ -0,0 +1,515 @@ +/* + * OpenAL Convolution Reverb Example + * + * Copyright (c) 2020 by Chris Robinson <[email protected]> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for applying convolution reverb to a source. */ + +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alext.h" + +#include "common/alhelpers.h" + + +#ifndef AL_SOFT_convolution_reverb +#define AL_SOFT_convolution_reverb +#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 +#endif + + +/* Effect object functions */ +static LPALGENEFFECTS alGenEffects; +static LPALDELETEEFFECTS alDeleteEffects; +static LPALISEFFECT alIsEffect; +static LPALEFFECTI alEffecti; +static LPALEFFECTIV alEffectiv; +static LPALEFFECTF alEffectf; +static LPALEFFECTFV alEffectfv; +static LPALGETEFFECTI alGetEffecti; +static LPALGETEFFECTIV alGetEffectiv; +static LPALGETEFFECTF alGetEffectf; +static LPALGETEFFECTFV alGetEffectfv; + +/* Auxiliary Effect Slot object functions */ +static LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; +static LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; +static LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; +static LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; +static LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; +static LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; +static LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; +static LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; +static LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; +static LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; +static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + + +/* This stuff defines a simple streaming player object, the same as alstream.c. + * Comments are removed for brevity, see alstream.c for more details. + */ +#define NUM_BUFFERS 4 +#define BUFFER_SAMPLES 8192 + +typedef struct StreamPlayer { + ALuint buffers[NUM_BUFFERS]; + ALuint source; + + SNDFILE *sndfile; + SF_INFO sfinfo; + float *membuf; + + ALenum format; +} StreamPlayer; + +static StreamPlayer *NewPlayer(void) +{ + StreamPlayer *player; + + player = calloc(1, sizeof(*player)); + assert(player != NULL); + + alGenBuffers(NUM_BUFFERS, player->buffers); + assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); + + alGenSources(1, &player->source); + assert(alGetError() == AL_NO_ERROR && "Could not create source"); + + alSource3i(player->source, AL_POSITION, 0, 0, -1); + alSourcei(player->source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(player->source, AL_ROLLOFF_FACTOR, 0); + assert(alGetError() == AL_NO_ERROR && "Could not set source parameters"); + + return player; +} + +static void ClosePlayerFile(StreamPlayer *player) +{ + if(player->sndfile) + sf_close(player->sndfile); + player->sndfile = NULL; + + free(player->membuf); + player->membuf = NULL; +} + +static void DeletePlayer(StreamPlayer *player) +{ + ClosePlayerFile(player); + + alDeleteSources(1, &player->source); + alDeleteBuffers(NUM_BUFFERS, player->buffers); + if(alGetError() != AL_NO_ERROR) + fprintf(stderr, "Failed to delete object IDs\n"); + + memset(player, 0, sizeof(*player)); + free(player); +} + +static int OpenPlayerFile(StreamPlayer *player, const char *filename) +{ + size_t frame_size; + + ClosePlayerFile(player); + + player->sndfile = sf_open(filename, SFM_READ, &player->sfinfo); + if(!player->sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(NULL)); + return 0; + } + + if(player->sfinfo.channels == 1) + player->format = AL_FORMAT_MONO_FLOAT32; + else if(player->sfinfo.channels == 2) + player->format = AL_FORMAT_STEREO_FLOAT32; + else if(player->sfinfo.channels == 6) + player->format = AL_FORMAT_51CHN32; + else + { + fprintf(stderr, "Unsupported channel count: %d\n", player->sfinfo.channels); + sf_close(player->sndfile); + player->sndfile = NULL; + return 0; + } + + frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(float); + player->membuf = malloc(frame_size); + + return 1; +} + +static int StartPlayer(StreamPlayer *player) +{ + ALsizei i; + + alSourceRewind(player->source); + alSourcei(player->source, AL_BUFFER, 0); + + for(i = 0;i < NUM_BUFFERS;i++) + { + sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(slen < 1) break; + + slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); + alBufferData(player->buffers[i], player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering for playback\n"); + return 0; + } + + alSourceQueueBuffers(player->source, i, player->buffers); + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error starting playback\n"); + return 0; + } + + return 1; +} + +static int UpdatePlayer(StreamPlayer *player) +{ + ALint processed, state; + + alGetSourcei(player->source, AL_SOURCE_STATE, &state); + alGetSourcei(player->source, AL_BUFFERS_PROCESSED, &processed); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error checking source state\n"); + return 0; + } + + while(processed > 0) + { + ALuint bufid; + sf_count_t slen; + + alSourceUnqueueBuffers(player->source, 1, &bufid); + processed--; + + slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + if(slen > 0) + { + slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); + alBufferData(bufid, player->format, player->membuf, (ALsizei)slen, + player->sfinfo.samplerate); + alSourceQueueBuffers(player->source, 1, &bufid); + } + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error buffering data\n"); + return 0; + } + } + + if(state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued; + + alGetSourcei(player->source, AL_BUFFERS_QUEUED, &queued); + if(queued == 0) + return 0; + + alSourcePlay(player->source); + if(alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "Error restarting playback\n"); + return 0; + } + } + + return 1; +} + + +/* CreateEffect creates a new OpenAL effect object with a convolution reverb + * type, and returns the new effect ID. + */ +static ALuint CreateEffect(void) +{ + ALuint effect = 0; + ALenum err; + + printf("Using Convolution Reverb\n"); + + /* Create the effect object and set the convolution reverb effect type. */ + alGenEffects(1, &effect); + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT); + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); + if(alIsEffect(effect)) + alDeleteEffects(1, &effect); + return 0; + } + + return effect; +} + +/* LoadBuffer loads the named audio file into an OpenAL buffer object, and + * returns the new buffer ID. + */ +static ALuint LoadSound(const char *filename) +{ + ALenum err, format; + ALuint buffer; + SNDFILE *sndfile; + SF_INFO sfinfo; + float *membuf; + sf_count_t num_frames; + ALsizei num_bytes; + + /* Open the audio file and check that it's usable. */ + sndfile = sf_open(filename, SFM_READ, &sfinfo); + if(!sndfile) + { + fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); + return 0; + } + if(sfinfo.frames < 1 || sfinfo.frames > (sf_count_t)(INT_MAX/sizeof(float))/sfinfo.channels) + { + fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); + sf_close(sndfile); + return 0; + } + + /* Get the sound format, and figure out the OpenAL format. Use floats since + * impulse responses will usually have more than 16-bit precision. + */ + if(sfinfo.channels == 1) + format = AL_FORMAT_MONO_FLOAT32; + else if(sfinfo.channels == 2) + format = AL_FORMAT_STEREO_FLOAT32; + else + { + fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); + sf_close(sndfile); + return 0; + } + + /* Decode the whole audio file to a buffer. */ + membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(float)); + + num_frames = sf_readf_float(sndfile, membuf, sfinfo.frames); + if(num_frames < 1) + { + free(membuf); + sf_close(sndfile); + fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); + return 0; + } + num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(float); + + /* Buffer the audio data into a new buffer object, then free the data and + * close the file. + */ + buffer = 0; + alGenBuffers(1, &buffer); + alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate); + + free(membuf); + sf_close(sndfile); + + /* Check if an error occured, and clean up if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) + { + fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); + if(buffer && alIsBuffer(buffer)) + alDeleteBuffers(1, &buffer); + return 0; + } + + return buffer; +} + + +int main(int argc, char **argv) +{ + ALuint ir_buffer, effect, slot; + StreamPlayer *player; + int i; + + /* Print out usage if no arguments were specified */ + if(argc < 2) + { + fprintf(stderr, "Usage: %s [-device <name>] <impulse response file> <filenames...>\n", + argv[0]); + return 1; + } + + argv++; argc--; + if(InitAL(&argv, &argc) != 0) + return 1; + + if(!alcIsExtensionPresent(alcGetContextsDevice(alcGetCurrentContext()), "ALC_EXT_EFX")) + { + CloseAL(); + fprintf(stderr, "Error: EFX not supported\n"); + return 1; + } + + if(argc < 2) + { + CloseAL(); + fprintf(stderr, "Error: Missing impulse response or sound files\n"); + return 1; + } + + /* Define a macro to help load the function pointers. */ +#define LOAD_PROC(T, x) ((x) = (T)alGetProcAddress(#x)) + LOAD_PROC(LPALGENEFFECTS, alGenEffects); + LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); + LOAD_PROC(LPALISEFFECT, alIsEffect); + LOAD_PROC(LPALEFFECTI, alEffecti); + LOAD_PROC(LPALEFFECTIV, alEffectiv); + LOAD_PROC(LPALEFFECTF, alEffectf); + LOAD_PROC(LPALEFFECTFV, alEffectfv); + LOAD_PROC(LPALGETEFFECTI, alGetEffecti); + LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); + LOAD_PROC(LPALGETEFFECTF, alGetEffectf); + LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); + + LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); + LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); + LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); + LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); + LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); +#undef LOAD_PROC + + /* Load the impulse response sound into a buffer. */ + ir_buffer = LoadSound(argv[0]); + if(!ir_buffer) + { + CloseAL(); + return 1; + } + + /* Load the reverb into an effect. */ + effect = CreateEffect(); + if(!effect) + { + alDeleteBuffers(1, &ir_buffer); + CloseAL(); + return 1; + } + + /* Create the effect slot object. This is what "plays" an effect on sources + * that connect to it. + */ + slot = 0; + alGenAuxiliaryEffectSlots(1, &slot); + + /* Set the impulse response sound buffer on the effect slot. This allows + * effects to access it as needed. In this case, convolution reverb uses it + * as the filter source. NOTE: Unlike the effect object, the buffer *is* + * kept referenced and may not be changed or deleted as long as it's set, + * just like with a source. When another buffer is set, or the effect slot + * is deleted, the buffer reference is released. + * + * The effect slot's gain is reduced because the impulse responses I've + * tested with result in excessively loud reverb. Is that normal? Even with + * this, it seems a bit on the loud side. + * + * Also note: unlike standard or EAX reverb, there is no automatic + * attenuation of a source's reverb response with distance, so the reverb + * will remain full volume regardless of a given sound's distance from the + * listener. You can use a send filter to alter a given source's + * contribution to reverb. + */ + alAuxiliaryEffectSloti(slot, AL_BUFFER, (ALint)ir_buffer); + alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f); + alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, (ALint)effect); + assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); + + player = NewPlayer(); + /* Connect the player's source to the effect slot. */ + alSource3i(player->source, AL_AUXILIARY_SEND_FILTER, (ALint)slot, 0, AL_FILTER_NULL); + assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play each file listed on the command line */ + for(i = 1;i < argc;i++) + { + const char *namepart; + + if(!OpenPlayerFile(player, argv[i])) + continue; + + namepart = strrchr(argv[i], '/'); + if(namepart || (namepart=strrchr(argv[i], '\\'))) + namepart++; + else + namepart = argv[i]; + + printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), + player->sfinfo.samplerate); + fflush(stdout); + + if(!StartPlayer(player)) + { + ClosePlayerFile(player); + continue; + } + + while(UpdatePlayer(player)) + al_nssleep(10000000); + + ClosePlayerFile(player); + } + printf("Done.\n"); + + /* All files done. Delete the player and effect resources, and close down + * OpenAL. + */ + DeletePlayer(player); + player = NULL; + + alDeleteAuxiliaryEffectSlots(1, &slot); + alDeleteEffects(1, &effect); + alDeleteBuffers(1, &ir_buffer); + + CloseAL(); + + return 0; +} diff --git a/examples/alconvolve.cpp b/examples/alconvolve.cpp deleted file mode 100644 index 68ab5615..00000000 --- a/examples/alconvolve.cpp +++ /dev/null @@ -1,536 +0,0 @@ -/* - * OpenAL Convolution Reverb Example - * - * Copyright (c) 2020 by Chris Robinson <[email protected]> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/* This file contains a streaming audio player, using the convolution reverb - * effect. - */ - -#include <string.h> -#include <stdlib.h> -#include <stdio.h> - -#include <atomic> -#include <cassert> -#include <chrono> -#include <limits> -#include <memory> -#include <stdexcept> -#include <string> -#include <thread> -#include <vector> - -#include "sndfile.h" - -#include "AL/al.h" -#include "AL/alc.h" -#include "AL/alext.h" - -#include "common/alhelpers.h" - - -#ifndef AL_SOFT_callback_buffer -#define AL_SOFT_callback_buffer -typedef unsigned int ALbitfieldSOFT; -#define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 -#define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*LPALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numsamples); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, LPALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr, ALbitfieldSOFT flags); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); -#endif - -#ifndef AL_SOFT_convolution_reverb -#define AL_SOFT_convolution_reverb -#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 -#endif - - -namespace { - -/* Effect object functions */ -LPALGENEFFECTS alGenEffects; -LPALDELETEEFFECTS alDeleteEffects; -LPALISEFFECT alIsEffect; -LPALEFFECTI alEffecti; -LPALEFFECTIV alEffectiv; -LPALEFFECTF alEffectf; -LPALEFFECTFV alEffectfv; -LPALGETEFFECTI alGetEffecti; -LPALGETEFFECTIV alGetEffectiv; -LPALGETEFFECTF alGetEffectf; -LPALGETEFFECTFV alGetEffectfv; - -/* Auxiliary Effect Slot object functions */ -LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; -LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; -LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; -LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; -LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; -LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; -LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; -LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; -LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; -LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; -LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; - - -ALuint CreateEffect() -{ - /* Create the effect object and try to set convolution reverb. */ - ALuint effect{0}; - alGenEffects(1, &effect); - - printf("Using Convolution Reverb\n"); - - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT); - - /* Check if an error occured, and clean up if so. */ - if(ALenum err{alGetError()}) - { - fprintf(stderr, "OpenAL error: %s\n", alGetString(err)); - if(alIsEffect(effect)) - alDeleteEffects(1, &effect); - return 0; - } - - return effect; -} - - -ALuint LoadSound(const char *filename) -{ - /* Open the audio file and check that it's usable. */ - SF_INFO sfinfo{}; - SNDFILE *sndfile{sf_open(filename, SFM_READ, &sfinfo)}; - if(!sndfile) - { - fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(sndfile)); - return 0; - } - constexpr sf_count_t max_samples{std::numeric_limits<int>::max() / sizeof(float)}; - if(sfinfo.frames < 1 || sfinfo.frames > max_samples/sfinfo.channels) - { - fprintf(stderr, "Bad sample count in %s (%" PRId64 ")\n", filename, sfinfo.frames); - sf_close(sndfile); - return 0; - } - - /* Get the sound format, and figure out the OpenAL format. Use a float - * format since impulse responses are keen on having a low noise floor. - */ - ALenum format{}; - if(sfinfo.channels == 1) - format = AL_FORMAT_MONO_FLOAT32; - else if(sfinfo.channels == 2) - format = AL_FORMAT_STEREO_FLOAT32; - else - { - fprintf(stderr, "Unsupported channel count: %d\n", sfinfo.channels); - sf_close(sndfile); - return 0; - } - - auto membuf = std::make_unique<float[]>(static_cast<size_t>(sfinfo.frames * sfinfo.channels)); - - sf_count_t num_frames{sf_readf_float(sndfile, membuf.get(), sfinfo.frames)}; - if(num_frames < 1) - { - membuf = nullptr; - sf_close(sndfile); - fprintf(stderr, "Failed to read samples in %s (%" PRId64 ")\n", filename, num_frames); - return 0; - } - const auto num_bytes = static_cast<ALsizei>(num_frames * sfinfo.channels) * - ALsizei{sizeof(float)}; - - ALuint buffer{0}; - alGenBuffers(1, &buffer); - alBufferData(buffer, format, membuf.get(), num_bytes, sfinfo.samplerate); - - membuf = nullptr; - sf_close(sndfile); - - if(ALenum err{alGetError()}) - { - fprintf(stderr, "OpenAL Error: %s\n", alGetString(err)); - if(buffer && alIsBuffer(buffer)) - alDeleteBuffers(1, &buffer); - return 0; - } - - return buffer; -} - - -/* This is largely the same as in alstreamcb.cpp. Comments removed for brevity, - * see the aforementioned source for more details. - */ -using std::chrono::seconds; -using std::chrono::nanoseconds; - -LPALBUFFERCALLBACKSOFT alBufferCallbackSOFT; - -struct StreamPlayer { - std::unique_ptr<ALbyte[]> mBufferData; - size_t mBufferDataSize{0}; - std::atomic<size_t> mReadPos{0}; - std::atomic<size_t> mWritePos{0}; - - ALuint mBuffer{0}, mSource{0}; - size_t mStartOffset{0}; - - SNDFILE *mSndfile{nullptr}; - SF_INFO mSfInfo{}; - size_t mDecoderOffset{0}; - - ALenum mFormat; - - StreamPlayer() - { - alGenBuffers(1, &mBuffer); - if(ALenum err{alGetError()}) - throw std::runtime_error{"alGenBuffers failed"}; - alGenSources(1, &mSource); - if(ALenum err{alGetError()}) - { - alDeleteBuffers(1, &mBuffer); - throw std::runtime_error{"alGenSources failed"}; - } - } - ~StreamPlayer() - { - alDeleteSources(1, &mSource); - alDeleteBuffers(1, &mBuffer); - if(mSndfile) - sf_close(mSndfile); - } - - void close() - { - if(mSndfile) - { - alSourceRewind(mSource); - alSourcei(mSource, AL_BUFFER, 0); - sf_close(mSndfile); - mSndfile = nullptr; - } - } - - bool open(const char *filename) - { - close(); - - mSndfile = sf_open(filename, SFM_READ, &mSfInfo); - if(!mSndfile) - { - fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile)); - return false; - } - - mFormat = AL_NONE; - if(mSfInfo.channels == 1) - mFormat = AL_FORMAT_MONO_FLOAT32; - else if(mSfInfo.channels == 2) - mFormat = AL_FORMAT_STEREO_FLOAT32; - else if(mSfInfo.channels == 6) - mFormat = AL_FORMAT_51CHN32; - else - { - fprintf(stderr, "Unsupported channel count: %d\n", mSfInfo.channels); - sf_close(mSndfile); - mSndfile = nullptr; - - return false; - } - - mBufferDataSize = static_cast<ALuint>(mSfInfo.samplerate*mSfInfo.channels) * sizeof(float); - mBufferData.reset(new ALbyte[mBufferDataSize]); - mReadPos.store(0, std::memory_order_relaxed); - mWritePos.store(0, std::memory_order_relaxed); - mDecoderOffset = 0; - - return true; - } - - static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) - { return static_cast<StreamPlayer*>(userptr)->bufferCallback(data, size); } - ALsizei bufferCallback(void *data, ALsizei size) - { - ALsizei got{0}; - - size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) - { - const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; - if(woffset == roffset) break; - - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; - todo = std::min<size_t>(todo, static_cast<ALuint>(size-got)); - - memcpy(data, &mBufferData[roffset], todo); - data = static_cast<ALbyte*>(data) + todo; - got += static_cast<ALsizei>(todo); - - roffset += todo; - if(roffset == mBufferDataSize) - roffset = 0; - } - mReadPos.store(roffset, std::memory_order_release); - - return got; - } - - bool prepare() - { - alBufferCallbackSOFT(mBuffer, mFormat, mSfInfo.samplerate, bufferCallbackC, this, 0); - alSourcei(mSource, AL_BUFFER, static_cast<ALint>(mBuffer)); - if(ALenum err{alGetError()}) - { - fprintf(stderr, "Failed to set callback: %s (0x%04x)\n", alGetString(err), err); - return false; - } - return true; - } - - bool update() - { - ALenum state; - ALint pos; - alGetSourcei(mSource, AL_SAMPLE_OFFSET, &pos); - alGetSourcei(mSource, AL_SOURCE_STATE, &state); - - const size_t frame_size{static_cast<ALuint>(mSfInfo.channels) * sizeof(float)}; - size_t woffset{mWritePos.load(std::memory_order_acquire)}; - if(state != AL_INITIAL) - { - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - - roffset}; - const size_t curtime{((state==AL_STOPPED) ? (mDecoderOffset-readable) / frame_size - : (static_cast<ALuint>(pos) + mStartOffset/frame_size)) - / static_cast<ALuint>(mSfInfo.samplerate)}; - printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferDataSize); - } - else - fputs("Starting...", stdout); - fflush(stdout); - - while(!sf_error(mSndfile)) - { - size_t read_bytes; - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - if(roffset > woffset) - { - const size_t writable{roffset-woffset-1}; - if(writable < frame_size) break; - - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast<float*>(&mBufferData[woffset]), - static_cast<sf_count_t>(writable/frame_size))}; - if(num_frames < 1) break; - - read_bytes = static_cast<size_t>(num_frames) * frame_size; - woffset += read_bytes; - } - else - { - const size_t writable{!roffset ? mBufferDataSize-woffset-1 : - (mBufferDataSize-woffset)}; - if(writable < frame_size) break; - - sf_count_t num_frames{sf_readf_float(mSndfile, - reinterpret_cast<float*>(&mBufferData[woffset]), - static_cast<sf_count_t>(writable/frame_size))}; - if(num_frames < 1) break; - - read_bytes = static_cast<size_t>(num_frames) * frame_size; - woffset += read_bytes; - if(woffset == mBufferDataSize) - woffset = 0; - } - mWritePos.store(woffset, std::memory_order_release); - mDecoderOffset += read_bytes; - } - - if(state != AL_PLAYING && state != AL_PAUSED) - { - const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - - roffset}; - if(readable == 0) - return false; - - mStartOffset = mDecoderOffset - readable; - alSourcePlay(mSource); - if(alGetError() != AL_NO_ERROR) - return false; - } - return true; - } -}; - -} // namespace - -int main(int argc, char **argv) -{ - /* A simple RAII container for OpenAL startup and shutdown. */ - struct AudioManager { - AudioManager(char ***argv_, int *argc_) - { - if(InitAL(argv_, argc_) != 0) - throw std::runtime_error{"Failed to initialize OpenAL"}; - } - ~AudioManager() { CloseAL(); } - }; - - /* Print out usage if no arguments were specified */ - if(argc < 2) - { - fprintf(stderr, "Usage: %s [-device <name>] <impulse response sound> [sound files...]\n", - argv[0]); - return 1; - } - - argv++; argc--; - AudioManager almgr{&argv, &argc}; - - if(!alIsExtensionPresent("AL_SOFTX_callback_buffer")) - { - fprintf(stderr, "AL_SOFT_callback_buffer extension not available\n"); - return 1; - } - - /* Define a macro to help load the function pointers. */ -#define LOAD_PROC(T, x) ((x) = reinterpret_cast<T>(alGetProcAddress(#x))) - LOAD_PROC(LPALBUFFERCALLBACKSOFT, alBufferCallbackSOFT); - - LOAD_PROC(LPALGENEFFECTS, alGenEffects); - LOAD_PROC(LPALDELETEEFFECTS, alDeleteEffects); - LOAD_PROC(LPALISEFFECT, alIsEffect); - LOAD_PROC(LPALEFFECTI, alEffecti); - LOAD_PROC(LPALEFFECTIV, alEffectiv); - LOAD_PROC(LPALEFFECTF, alEffectf); - LOAD_PROC(LPALEFFECTFV, alEffectfv); - LOAD_PROC(LPALGETEFFECTI, alGetEffecti); - LOAD_PROC(LPALGETEFFECTIV, alGetEffectiv); - LOAD_PROC(LPALGETEFFECTF, alGetEffectf); - LOAD_PROC(LPALGETEFFECTFV, alGetEffectfv); - - LOAD_PROC(LPALGENAUXILIARYEFFECTSLOTS, alGenAuxiliaryEffectSlots); - LOAD_PROC(LPALDELETEAUXILIARYEFFECTSLOTS, alDeleteAuxiliaryEffectSlots); - LOAD_PROC(LPALISAUXILIARYEFFECTSLOT, alIsAuxiliaryEffectSlot); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTI, alAuxiliaryEffectSloti); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTIV, alAuxiliaryEffectSlotiv); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTF, alAuxiliaryEffectSlotf); - LOAD_PROC(LPALAUXILIARYEFFECTSLOTFV, alAuxiliaryEffectSlotfv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTI, alGetAuxiliaryEffectSloti); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTIV, alGetAuxiliaryEffectSlotiv); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTF, alGetAuxiliaryEffectSlotf); - LOAD_PROC(LPALGETAUXILIARYEFFECTSLOTFV, alGetAuxiliaryEffectSlotfv); -#undef LOAD_PROC - - /* Load the impulse response sound file into a buffer. */ - ALuint buffer{LoadSound(argv[0])}; - if(!buffer) return 1; - - /* Create the convolution reverb effect. */ - ALuint effect{CreateEffect()}; - if(!effect) - { - alDeleteBuffers(1, &buffer); - return 1; - } - - /* Create the effect slot object. This is what "plays" an effect on sources - * that connect to it. */ - ALuint slot{0}; - alGenAuxiliaryEffectSlots(1, &slot); - - /* Set the impulse response sound buffer on the effect slot. This allows - * effects to access it as needed. In this case, convolution reverb uses it - * as the filter source. NOTE: Unlike the effect object, the buffer *is* - * kept referenced and may not be changed or deleted as long as it's set, - * just like with a source. When another buffer is set, or the effect slot - * is deleted, the buffer reference is released. - * - * The effect slot's gain is reduced because the impulse responses I've - * tested with result in excessively loud reverb. Is that normal? Even with - * this, it seems a bit on the loud side. - * - * Also note: unlike standard or EAX reverb, there is no automatic - * attenuation of a source's reverb response with distance, so the reverb - * will remain full volume regardless of a given sound's distance from the - * listener. You can use a send filter to alter a given source's - * contribution to reverb. - */ - alAuxiliaryEffectSloti(slot, AL_BUFFER, static_cast<ALint>(buffer)); - alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, 1.0f / 16.0f); - alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, static_cast<ALint>(effect)); - assert(alGetError()==AL_NO_ERROR && "Failed to set effect slot"); - - ALCint refresh{25}; - alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); - - std::unique_ptr<StreamPlayer> player{new StreamPlayer{}}; - alSource3i(player->mSource, AL_AUXILIARY_SEND_FILTER, static_cast<ALint>(slot), 0, - AL_FILTER_NULL); - - for(int i{1};i < argc;++i) - { - if(!player->open(argv[i])) - continue; - - const char *namepart{strrchr(argv[i], '/')}; - if(namepart || (namepart=strrchr(argv[i], '\\'))) - ++namepart; - else - namepart = argv[i]; - - printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat), - player->mSfInfo.samplerate); - fflush(stdout); - - if(!player->prepare()) - { - player->close(); - continue; - } - - while(player->update()) - std::this_thread::sleep_for(nanoseconds{seconds{1}} / refresh); - putc('\n', stdout); - - player->close(); - } - /* All done. */ - printf("Done.\n"); - - player = nullptr; - alDeleteAuxiliaryEffectSlots(1, &slot); - alDeleteEffects(1, &effect); - alDeleteBuffers(1, &buffer); - - return 0; -} |