From 9b2ce3039a44b0cabbbecc9461d7e1e9bb457784 Mon Sep 17 00:00:00 2001 From: e Date: Fri, 29 Apr 2022 19:10:07 -0300 Subject: [PATCH] Add Panning Extension for Stereo formats --- AL/al.h | 11 ++ CMakeLists.txt | 1 + mojoal.c | 14 +- tests/testpanning.c | 350 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 tests/testpanning.c diff --git a/AL/al.h b/AL/al.h index 34e35d5..d061e17 100644 --- a/AL/al.h +++ b/AL/al.h @@ -296,6 +296,17 @@ typedef void ALvoid; */ #define AL_CONE_OUTER_GAIN 0x1022 +/** + * Balance extension. + * Type: ALfloat + * Range: [-1.0 - 1.0] + * Default: 0.0 + * + * Extension for balance applied to the source to enable direct panning even + * with stereo sounds. + */ +#define AL_EXT_BALANCE 0xF001 + /** * Source maximum distance. * Type: ALfloat diff --git a/CMakeLists.txt b/CMakeLists.txt index 39e2834..653a0c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,5 +22,6 @@ add_test_executable(testopenalinfo) add_test_executable(testqueueing) add_test_executable(testcapture) add_test_executable(testposition) +add_test_executable(testpanning) diff --git a/mojoal.c b/mojoal.c index b2a5dfb..cfc1719 100644 --- a/mojoal.c +++ b/mojoal.c @@ -471,6 +471,7 @@ SIMDALIGNEDSTRUCT ALsource ALfloat cone_inner_angle; ALfloat cone_outer_angle; ALfloat cone_outer_gain; + ALfloat EXT_balance; /* stereo linear balance extension */ ALbuffer *buffer; SDL_AudioStream *stream; /* for resampling. */ SDL_atomic_t total_queued_buffers; /* everything queued, playing and processed. AL_BUFFERS_QUEUED value. */ @@ -1710,7 +1711,12 @@ static void calculate_channel_gains(const ALCcontext *ctx, const ALsource *src, /* this goes through the steps the AL spec dictates for gain and distance attenuation... */ - if (!spatialize) { + if (src->queue_channels == 2) { + gain = SDL_min(SDL_max(src->gain, src->min_gain), src->max_gain) * ctx->listener.gain; + gains[0] = gain * SDL_min(1.f, 1.f - src->EXT_balance); // left channel + gains[1] = gain * SDL_min(1.f, 1.f + src->EXT_balance); // right channel + return; + } else if (!spatialize) { /* simpler path through the same AL spec details if not spatializing. */ gain = SDL_min(SDL_max(src->gain, src->min_gain), src->max_gain) * ctx->listener.gain; gains[0] = gains[1] = gain; /* no spatialization, but AL_GAIN (etc) is still applied. */ @@ -3727,6 +3733,8 @@ static void _alSourcefv(const ALuint name, const ALenum param, const ALfloat *va source_set_offset(src, param, *values); break; + case AL_EXT_BALANCE: src->EXT_balance = *values; break; + default: set_al_error(ctx, AL_INVALID_ENUM); return; } @@ -3751,6 +3759,7 @@ static void _alSourcef(const ALuint name, const ALenum param, const ALfloat valu case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: + case AL_EXT_BALANCE: _alSourcefv(name, param, &value); break; @@ -3932,6 +3941,8 @@ static void _alGetSourcefv(const ALuint name, const ALenum param, ALfloat *value *values = source_get_offset(src, param); break; + case AL_EXT_BALANCE: *values = src->EXT_balance; break; + default: set_al_error(ctx, AL_INVALID_ENUM); break; } } @@ -3953,6 +3964,7 @@ static void _alGetSourcef(const ALuint name, const ALenum param, ALfloat *value) case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: + case AL_EXT_BALANCE: _alGetSourcefv(name, param, value); break; default: set_al_error(get_current_context(), AL_INVALID_ENUM); break; diff --git a/tests/testpanning.c b/tests/testpanning.c new file mode 100644 index 0000000..061dd2e --- /dev/null +++ b/tests/testpanning.c @@ -0,0 +1,350 @@ +/** + * MojoAL; a simple drop-in OpenAL implementation. + * + * Please see the file LICENSE.txt in the source's root directory. + * + * This file written by Ryan C. Gordon. + */ + +/* This is just test code, you don't need to compile this with MojoAL. */ + +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "SDL.h" + +#ifdef __EMSCRIPTEN__ +#include +#endif + + +static LPALCTRACEDEVICELABEL palcTraceDeviceLabel; +static LPALCTRACECONTEXTLABEL palcTraceContextLabel; +static LPALTRACEPUSHSCOPE palTracePushScope; +static LPALTRACEPOPSCOPE palTracePopScope; +static LPALTRACEMESSAGE palTraceMessage; +static LPALTRACEBUFFERLABEL palTraceBufferLabel; +static LPALTRACESOURCELABEL palTraceSourceLabel; + +static int check_openal_error(const char *where) +{ + const ALenum err = alGetError(); + if (err != AL_NONE) { + printf("OpenAL Error at %s! %s (%u)\n", where, alGetString(err), (unsigned int) err); + return 1; + } + return 0; +} + +static ALenum get_openal_format(const SDL_AudioSpec *spec) +{ + if ((spec->channels == 1) && (spec->format == AUDIO_U8)) { + return AL_FORMAT_MONO8; + } else if ((spec->channels == 1) && (spec->format == AUDIO_S16SYS)) { + return AL_FORMAT_MONO16; + } else if ((spec->channels == 2) && (spec->format == AUDIO_U8)) { + return AL_FORMAT_STEREO8; + } else if ((spec->channels == 2) && (spec->format == AUDIO_S16SYS)) { + return AL_FORMAT_STEREO16; + } else if ((spec->channels == 1) && (spec->format == AUDIO_F32SYS)) { + return alIsExtensionPresent("AL_EXT_FLOAT32") ? alGetEnumValue("AL_FORMAT_MONO_FLOAT32") : AL_NONE; + } else if ((spec->channels == 2) && (spec->format == AUDIO_F32SYS)) { + return alIsExtensionPresent("AL_EXT_FLOAT32") ? alGetEnumValue("AL_FORMAT_STEREO_FLOAT32") : AL_NONE; + } + return AL_NONE; +} + +typedef struct +{ + ALuint sid; + int channels; + int x; + int y; +} obj; + +/* !!! FIXME: eventually, add more sources and sounds. */ +static obj objects[1]; /* a panning control. */ +static int draggingobj = -1; + +static int obj_under_mouse(const int x, const int y) +{ + const SDL_Point p = { x, y }; + const obj *o = objects; + int i; + for (i = 0; i < SDL_arraysize(objects); i++, o++) { + const SDL_Rect r = { o->x - 25, o->y - 25, 50, 50 }; + if (SDL_PointInRect(&p, &r)) { + return i; + } + } + return -1; +} + +static int mainloop(SDL_Renderer *renderer) +{ + int i; + SDL_Event e; + + alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); + + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_QUIT: + return 0; + + case SDL_KEYDOWN: + if (e.key.keysym.sym == SDLK_ESCAPE) { + return 0; + } + break; + + case SDL_MOUSEBUTTONUP: + case SDL_MOUSEBUTTONDOWN: + if (e.button.button == 1) { + if (e.button.state == SDL_RELEASED) { + if (palTraceMessage) palTraceMessage("Mouse button released"); + draggingobj = -1; + } else { + if (palTraceMessage) palTraceMessage("Mouse button pressed"); + draggingobj = obj_under_mouse(e.button.x, e.button.y); + } + } + break; + + case SDL_MOUSEMOTION: + if (draggingobj != -1) { + obj *o = &objects[draggingobj]; + o->x = SDL_min(800, SDL_max(0, e.motion.x)); + o->y = SDL_min(600, SDL_max(0, e.motion.y)); + + if (o->sid != 0) { + float panning = ((o->x / 400.0f) - 1.0f); /* it's the pan amount. */ + + if (panning != 0.0f) + { + if (o->channels == 1) + { + // https://github.com/kcat/openal-soft/issues/194 + alSourcei(o->sid, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(o->sid, AL_POSITION, panning, 0.0f, -sqrtf(1.0f - panning*panning)); + } + else + { + alSourcef(o->sid, AL_EXT_BALANCE, panning); + } + } + else + { + alSourcei(o->sid, AL_SOURCE_RELATIVE, AL_FALSE); + alSource3f(o->sid, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(o->sid, AL_EXT_BALANCE, 0.f); + } + + } + } + break; + + } + } + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(renderer); + + for (i = 0; i < SDL_arraysize(objects); i++) { + const obj *o = &objects[i]; + const SDL_Rect r = { o->x - 25, o->y - 25, 50, 50 }; + if (o->sid != 0) { + SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF); + } + SDL_RenderFillRect(renderer, &r); + } + + SDL_RenderPresent(renderer); + + return 1; +} + + +#ifdef __EMSCRIPTEN__ +static void emscriptenMainloop(void *arg) +{ + (void) mainloop((SDL_Renderer *) arg); +} +#endif + + +static void spatialize(SDL_Renderer *renderer, const char *fname) +{ + obj *o = objects; + SDL_AudioSpec spec; + ALenum alfmt = AL_NONE; + Uint8 *buf = NULL; + Uint32 buflen = 0; + ALuint sid = 0; + ALuint bid = 0; + + if (!SDL_LoadWAV(fname, &spec, &buf, &buflen)) { + printf("Loading '%s' failed! %s\n", fname, SDL_GetError()); + return; + } else if ((alfmt = get_openal_format(&spec)) == AL_NONE) { + printf("Can't queue '%s', format not supported by the AL.\n", fname); + SDL_FreeWAV(buf); + return; + } + + check_openal_error("startup"); + + printf("Now queueing '%s'...\n", fname); + + if (palTracePushScope) palTracePushScope("Initial setup"); + + alGenSources(1, &sid); + if (check_openal_error("alGenSources")) { + SDL_FreeWAV(buf); + return; + } + + if (palTraceSourceLabel) palTraceSourceLabel(sid, "Moving source"); + + alGenBuffers(1, &bid); + if (check_openal_error("alGenBuffers")) { + alDeleteSources(1, &sid); + check_openal_error("alDeleteSources"); + SDL_FreeWAV(buf); + return; + } + + if (palTraceBufferLabel) palTraceBufferLabel(bid, "Sound effect"); + + alBufferData(bid, alfmt, buf, buflen, spec.freq); + SDL_FreeWAV(buf); + check_openal_error("alBufferData"); + alSourcei(sid, AL_BUFFER, bid); + check_openal_error("alSourcei"); + alSourcei(sid, AL_LOOPING, AL_TRUE); + check_openal_error("alSourcei"); + alSourcePlay(sid); + check_openal_error("alSourcePlay"); + + /* the listener. */ + alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); + + o->sid = sid; + o->channels = spec.channels; + o->x = 400; + o->y = 300; + alSource3f(o->sid, AL_POSITION, ((o->x / 400.0f) - 1.0f) * 10.0f, 0.0f, ((o->y / 300.0f) - 1.0f) * 10.0f); + o++; + + if (palTracePopScope) palTracePopScope(); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(emscriptenMainloop, renderer, 0, 1); +#else + while (mainloop(renderer)) { /* go again */ } +#endif + + + //alSourcei(sid, AL_BUFFER, 0); /* force unqueueing */ + alDeleteSources(1, &sid); + check_openal_error("alDeleteSources"); + alDeleteBuffers(1, &bid); + check_openal_error("alDeleteBuffers"); +} + + +int main(int argc, char **argv) +{ + ALCdevice *device; + ALCcontext *context; + SDL_Window *window; + SDL_Renderer *renderer; + + if (argc != 2) { + fprintf(stderr, "USAGE: %s [wavfile]\n", argv[0]); + return 1; + } + + if (SDL_Init(SDL_INIT_VIDEO) == -1) { + fprintf(stderr, "SDL_Init(SDL_INIT_VIDEO) failed: %s\n", SDL_GetError()); + return 2; + } + + window = SDL_CreateWindow(argv[0], SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, 800, 600, + SDL_WINDOW_RESIZABLE); + + if (!window) { + fprintf(stderr, "SDL_CreateWindow() failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 3; + } + + renderer = SDL_CreateRenderer(window, -1, 0); + if (!renderer) { + fprintf(stderr, "SDL_CreateRenderer() failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 4; + } + SDL_RenderSetLogicalSize(renderer, 800, 600); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + + device = alcOpenDevice(NULL); + if (!device) + { + printf("Couldn't open OpenAL default device.\n"); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 5; + } + + if (alcIsExtensionPresent(device, "ALC_EXT_trace_info")) { + palcTraceDeviceLabel = (LPALCTRACEDEVICELABEL) alcGetProcAddress(device, "alcTraceDeviceLabel"); + palcTraceContextLabel = (LPALCTRACECONTEXTLABEL) alcGetProcAddress(device, "alcTraceContextLabel"); + } + + context = alcCreateContext(device, NULL); + if (!context) { + printf("Couldn't create OpenAL context.\n"); + alcCloseDevice(device); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 6; + } + + if (palcTraceDeviceLabel) palcTraceDeviceLabel(device, "The playback device"); + if (palcTraceContextLabel) palcTraceContextLabel(context, "Main context"); + + alcMakeContextCurrent(context); + + if (alIsExtensionPresent("AL_EXT_trace_info")) { + palTracePushScope = (LPALTRACEPUSHSCOPE) alGetProcAddress("alTracePushScope"); + palTracePopScope = (LPALTRACEPOPSCOPE) alGetProcAddress("alTracePopScope"); + palTraceMessage = (LPALTRACEMESSAGE) alGetProcAddress("alTraceMessage"); + palTraceBufferLabel = (LPALTRACEBUFFERLABEL) alGetProcAddress("alTraceBufferLabel"); + palTraceSourceLabel = (LPALTRACESOURCELABEL) alGetProcAddress("alTraceSourceLabel"); + } + + spatialize(renderer, argv[1]); + + alcMakeContextCurrent(NULL); + alcDestroyContext(context); + alcCloseDevice(device); + + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + printf("Done!\n"); + return 0; +} + +/* end of testposition.c ... */ +