diff --git a/CMakeLists.txt b/CMakeLists.txt index b541a9c20..dfedd7f19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,9 @@ if( CMAKE_COMPILER_IS_GNUCXX ) set( PROFILE 0 CACHE BOOL "Enable profiling with gprof for Debug and RelWithDebInfo build types." ) endif( CMAKE_COMPILER_IS_GNUCXX ) +option( NO_FMOD "Disable FMODEx sound support" ) +option( NO_OPENAL "Disable OpenAL sound support" ) + find_package( BZip2 ) find_package( JPEG ) find_package( ZLIB ) diff --git a/output_sdl/CMakeLists.txt b/output_sdl/CMakeLists.txt index b959a6873..1e7a9eb00 100644 --- a/output_sdl/CMakeLists.txt +++ b/output_sdl/CMakeLists.txt @@ -1,8 +1,10 @@ cmake_minimum_required( VERSION 2.4 ) -add_library( output_sdl MODULE output_sdl.c ) -include_directories( ${FMOD_INCLUDE_DIR} ${SDL_INCLUDE_DIR} ) +if( NOT NO_FMOD AND FMOD_INCLUDE_DIR ) + add_library( output_sdl MODULE output_sdl.c ) + include_directories( ${FMOD_INCLUDE_DIR} ${SDL_INCLUDE_DIR} ) -FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so ]; then ln -sf output_sdl/liboutput_sdl.so ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so; fi" ) -add_custom_command( TARGET output_sdl POST_BUILD - COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make - COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make ) + FILE( WRITE ${CMAKE_CURRENT_BINARY_DIR}/link-make "if [ ! -e ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so ]; then ln -sf output_sdl/liboutput_sdl.so ${ZDOOM_OUTPUT_DIR}/liboutput_sdl.so; fi" ) + add_custom_command( TARGET output_sdl POST_BUILD + COMMAND chmod +x ${CMAKE_CURRENT_BINARY_DIR}/link-make + COMMAND /bin/sh -c ${CMAKE_CURRENT_BINARY_DIR}/link-make ) +endif( NOT NO_FMOD AND FMOD_INCLUDE_DIR ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b763dee3..d7b4dfcbf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,6 +115,60 @@ if( WIN32 ) message( FATAL_ERROR "Could not find DirectX 9 libraries" ) endif( NOT DX_LIBS_FOUND ) + + if( NOT NO_OPENAL ) + SET( GST_SEARCH_PATHS + ENV GSTSDK_DIR + ENV MINGDIR + "C:/Program Files/gstreamer" + "C:/gstreamer" ) + + message( STATUS "Looking for glib.h" ) + find_path( GLIB_INCLUDE_DIR glib.h + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES include include/glib-2.0 ) + if( GLIB_INCLUDE_DIR ) + message(STATUS "Looking for glib.h - found: ${GLIB_INCLUDE_DIR}") + else( GLIB_INCLUDE_DIR ) + message(STATUS "Looking for glib.h - not found") + endif( GLIB_INCLUDE_DIR ) + + message( STATUS "Looking for gst/gst.h" ) + find_path( GST_INCLUDE_DIR gst/gst.h + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES include include/gstreamer-0.10 ) + if( GST_INCLUDE_DIR ) + message( STATUS "Looking for gst/gst.h - found: ${GST_INCLUDE_DIR}" ) + else( GST_INCLUDE_DIR ) + message( STATUS "Looking for gst/gst.h - not found" ) + endif( GST_INCLUDE_DIR ) + + message( STATUS "Looking for al.h" ) + set( OPENAL_SEARCH_PATHS + ENV ALSDK_DIR + ENV MINGDIR + "C:/Program Files/OpenAL 1.1 SDK" + "C:/Program Files (x86)/OpenAL 1.1 SDK" + "E:/Programs/Dev/OpenAL" + "E:/Program Files (x86)/OpenAL 1.1 SDK" ) + + find_path( OPENAL_INCLUDE_DIR al.h + PATHS ${OPENAL_SEARCH_PATHS} + PATH_SUFFIXES include include/AL ) + if( OPENAL_INCLUDE_DIR ) + message( STATUS "Looking for al.h - found: ${OPENAL_INCLUDE_DIR}" ) + else( OPENAL_INCLUDE_DIR ) + message( STATUS "Looking for al.h - not found" ) + endif( OPENAL_INCLUDE_DIR ) + + if( NOT OPENAL_INCLUDE_DIR OR NOT GLIB_INCLUDE_DIR OR NOT GST_INCLUDE_DIR ) + set( NO_OPENAL ON ) + else( NOT OPENAL_INCLUDE_DIR OR NOT GLIB_INCLUDE_DIR OR NOT GST_INCLUDE_DIR ) + include_directories( ${OPENAL_INCLUDE_DIR} ${GLIB_INCLUDE_DIR} ${GST_INCLUDE_DIR} ) + endif( NOT OPENAL_INCLUDE_DIR OR NOT GLIB_INCLUDE_DIR OR NOT GST_INCLUDE_DIR ) + endif( NOT NO_OPENAL ) + + set( ZDOOM_LIBS wsock32 winmm @@ -129,12 +183,57 @@ if( WIN32 ) ws2_32 setupapi oleaut32 ) + + if( NOT NO_OPENAL ) + message( STATUS "Looking for GST libraries" ) + find_library( OPENAL_LIBRARY NAMES OpenAL32 + PATHS ${OPENAL_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY1 NAMES gstapp-0.10 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY2 NAMES gstaudio-0.10 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY3 NAMES gstreamer-0.10 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY4 NAMES gthread-2.0 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY5 NAMES gmodule-2.0 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY6 NAMES gobject-2.0 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + find_library( GST_LIBRARY7 NAMES glib-2.0 + PATHS ${GST_SEARCH_PATHS} + PATH_SUFFIXES lib ) + if( OPENAL_LIBRARY AND GST_LIBRARY1 AND GST_LIBRARY2 AND GST_LIBRARY3 AND GST_LIBRARY4 AND GST_LIBRARY5 AND GST_LIBRARY6 AND GST_LIBRARY7 ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${OPENAL_LIBRARY}" "${GST_LIBRARY1}" "${GST_LIBRARY2}" "${GST_LIBRARY3}" "${GST_LIBRARY4}" "${GST_LIBRARY5}" "${GST_LIBRARY6}" "${GST_LIBRARY7}" ) + message( STATUS "Looking for GST libraries - found" ) + else( OPENAL_LIBRARY AND GST_LIBRARY1 AND GST_LIBRARY2 AND GST_LIBRARY3 AND GST_LIBRARY4 AND GST_LIBRARY5 AND GST_LIBRARY6 AND GST_LIBRARY7 ) + set( NO_OPENAL ON ) + message( STATUS "Looking for GST libraries - not found" ) + endif( OPENAL_LIBRARY AND GST_LIBRARY1 AND GST_LIBRARY2 AND GST_LIBRARY3 AND GST_LIBRARY4 AND GST_LIBRARY5 AND GST_LIBRARY6 AND GST_LIBRARY7 ) + endif( NOT NO_OPENAL ) else( WIN32 ) if( APPLE ) set( FMOD_SEARCH_PATHS "/Developer/FMOD Programmers API Mac/api" ) set( FMOD_INC_PATH_SUFFIXES PATH_SUFFIXES inc ) set( FMOD_LIB_PATH_SUFFIXES PATH_SUFFIXES lib ) set( NO_GTK ON ) + + if( NOT NO_OPENAL ) + check_library_exists( gstapp-0.10 gst_app_src_set_callbacks "" HAS_GSTAPP ) + check_library_exists( gstaudio-0.10 gst_audio_set_channel_positions "" HAS_GSTAUDIO ) + if( HAS_GSTAPP AND HAS_GSTAUDIO ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} gstapp-0.10 gst-audio-0.10 "-framework OpenAL") + else( HAS_GSTAPP AND HAS_GSTAUDIO ) + set( NO_OPENAL ON ) + endif( HAS_GSTAPP AND HAS_GSTAUDIO ) + endif( NOT NO_OPENAL ) else( APPLE ) option( NO_GTK "Disable GTK+ dialogs (Not applicable to Windows)" ) option( VALGRIND "Add special Valgrind sequences to self-modifying code" ) @@ -160,6 +259,14 @@ else( WIN32 ) set( NO_GTK ON ) endif( GTK2_FOUND ) endif( NOT NO_GTK ) + + pkg_check_modules( OPENAL openal gstreamer-app-0.10>=0.10.23 gstreamer-audio-0.10 ) + if( OPENAL_FOUND ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} ${OPENAL_LIBRARIES} ) + include_directories( ${OPENAL_INCLUDE_DIRS} ) + else( OPENAL_FOUND ) + set( NO_OPENAL ON ) + endif( OPENAL_FOUND ) endif( APPLE ) set( NASM_NAMES nasm ) @@ -182,59 +289,69 @@ else( WIN32 ) endif( FPU_CONTROL_DIR ) endif( WIN32 ) -# Decide on the name of the FMOD library we want to use. -if( NOT FMOD_LIB_NAME AND MSVC ) - set( FMOD_LIB_NAME fmodex${X64}_vc ) -endif( NOT FMOD_LIB_NAME AND MSVC ) +if( NOT NO_FMOD ) + # Search for FMOD include files + if( NOT WIN32 ) + find_path( FMOD_INCLUDE_DIR fmod.hpp + PATHS ${FMOD_LOCAL_INC_DIRS} ) + endif( NOT WIN32 ) -if( NOT FMOD_LIB_NAME AND BORLAND ) - set( FMOD_LIB_NAME fmodex${X64}_bc ) -endif( NOT FMOD_LIB_NAME AND BORLAND ) + if( NOT FMOD_INCLUDE_DIR ) + find_path( FMOD_INCLUDE_DIR fmod.hpp + PATHS ${FMOD_SEARCH_PATHS} + ${FMOD_INC_PATH_SUFFIXES} ) + endif( NOT FMOD_INCLUDE_DIR ) -if( NOT FMOD_LIB_NAME ) - set( FMOD_LIB_NAME fmodex${X64} ) -endif( NOT FMOD_LIB_NAME ) + if( FMOD_INCLUDE_DIR ) + message( STATUS "FMOD include files found at ${FMOD_INCLUDE_DIR}" ) + include_directories( "${FMOD_INCLUDE_DIR}" ) + else( FMOD_INCLUDE_DIR ) + message( STATUS "Could not find FMOD include files" ) + set( NO_FMOD ON ) + endif( FMOD_INCLUDE_DIR ) +endif( NOT NO_FMOD ) +if( NOT NO_FMOD ) + # Decide on the name of the FMOD library we want to use. + if( NOT FMOD_LIB_NAME AND MSVC ) + set( FMOD_LIB_NAME fmodex${X64}_vc ) + endif( NOT FMOD_LIB_NAME AND MSVC ) -# Search for FMOD include files + if( NOT FMOD_LIB_NAME AND BORLAND ) + set( FMOD_LIB_NAME fmodex${X64}_bc ) + endif( NOT FMOD_LIB_NAME AND BORLAND ) -if( NOT WIN32 ) - find_path( FMOD_INCLUDE_DIR fmod.hpp - PATHS ${FMOD_LOCAL_INC_DIRS} ) -endif( NOT WIN32 ) + if( NOT FMOD_LIB_NAME ) + set( FMOD_LIB_NAME fmodex${X64} ) + endif( NOT FMOD_LIB_NAME ) -if( NOT FMOD_INCLUDE_DIR ) - find_path( FMOD_INCLUDE_DIR fmod.hpp - PATHS ${FMOD_SEARCH_PATHS} - ${FMOD_INC_PATH_SUFFIXES} ) -endif( NOT FMOD_INCLUDE_DIR ) + # Search for FMOD library + if( WIN32 OR APPLE ) + find_library( FMOD_LIBRARY ${FMOD_LIB_NAME} + PATHS ${FMOD_SEARCH_PATHS} + ${FMOD_LIB_PATH_SUFFIXES} ) + else( WIN32 OR APPLE ) + find_library( FMOD_LIBRARY + NAMES ${FMOD_VERSIONS} + PATHS ${FMOD_LOCAL_LIB_DIRS} ) + endif( WIN32 OR APPLE ) -if( FMOD_INCLUDE_DIR ) - message( STATUS "FMOD include files found at ${FMOD_INCLUDE_DIR}" ) -else( FMOD_INCLUDE_DIR ) - message( SEND_ERROR "Could not find FMOD include files" ) -endif( FMOD_INCLUDE_DIR ) - - -# Search for FMOD library - -if( WIN32 OR APPLE ) - find_library( FMOD_LIBRARY ${FMOD_LIB_NAME} - PATHS ${FMOD_SEARCH_PATHS} - ${FMOD_LIB_PATH_SUFFIXES} ) -else( WIN32 OR APPLE ) - find_library( FMOD_LIBRARY - NAMES ${FMOD_VERSIONS} - PATHS ${FMOD_LOCAL_LIB_DIRS} ) -endif( WIN32 OR APPLE ) - -if( FMOD_LIBRARY ) - message( STATUS "FMOD library found at ${FMOD_LIBRARY}" ) -else( FMOD_LIBRARY ) - message( SEND_ERROR "Could not find FMOD library" ) -endif( FMOD_LIBRARY ) + if( FMOD_LIBRARY ) + message( STATUS "FMOD library found at ${FMOD_LIBRARY}" ) + set( ZDOOM_LIBS ${ZDOOM_LIBS} "${FMOD_LIBRARY}" ) + else( FMOD_LIBRARY ) + message( STATUS "Could not find FMOD library" ) + set( NO_FMOD ON ) + endif( FMOD_LIBRARY ) +endif( NOT NO_FMOD ) +if( NO_FMOD ) + add_definitions( -DNO_FMOD=1 ) +endif( NO_FMOD ) +if( NO_OPENAL ) + add_definitions( -DNO_OPENAL=1 ) +endif( NO_OPENAL ) # Search for NASM @@ -476,8 +593,8 @@ add_custom_target( revision_check ALL # Libraries ZDoom needs -set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" "${FMOD_LIBRARY}" ) -include_directories( "${ZLIB_INCLUDE_DIR}" "${FMOD_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" ) +set( ZDOOM_LIBS ${ZDOOM_LIBS} "${ZLIB_LIBRARIES}" "${JPEG_LIBRARIES}" "${BZIP2_LIBRARIES}" ) +include_directories( "${ZLIB_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" "${JPEG_INCLUDE_DIR}" ) # Start defining source files for ZDoom @@ -795,6 +912,7 @@ add_executable( zdoom WIN32 sound/music_stream.cpp sound/music_timidity_mididevice.cpp sound/music_win_mididevice.cpp + sound/oalsound.cpp textures/automaptexture.cpp textures/bitmap.cpp textures/buildtexture.cpp diff --git a/src/m_options.cpp b/src/m_options.cpp index 7e17ef798..eed3e9d85 100644 --- a/src/m_options.cpp +++ b/src/m_options.cpp @@ -122,6 +122,8 @@ void M_SizeDisplay (int diff); int M_StringHeight (char *string); +int BuildALDeviceList(valueenum_t **menulist); + EColorRange LabelColor; EColorRange ValueColor; EColorRange MoreColor; @@ -1167,9 +1169,15 @@ EXTERN_CVAR (Int, snd_samplerate) EXTERN_CVAR (Bool, snd_hrtf) EXTERN_CVAR (Bool, snd_waterreverb) EXTERN_CVAR (Float, snd_waterlp) +EXTERN_CVAR (Float, snd_waterabsorption) EXTERN_CVAR (Int, snd_mididevice) +EXTERN_CVAR (String, snd_backend) +EXTERN_CVAR (String, snd_aldevice) +EXTERN_CVAR (Bool, snd_efx) static void MakeSoundChanges (); +static void FMODSoundOptions (); +static void OpenALSoundOptions (); static void AdvSoundOptions (); static void ModReplayerOptions (); @@ -1213,7 +1221,7 @@ static value_t BufferCounts[] = { 12.f, "12" } }; -static valueenum_t Outputs[] = +static valueenum_t FMODOutputs[] = { { "Default", "Default" }, #if defined(_WIN32) @@ -1234,6 +1242,17 @@ static valueenum_t Outputs[] = { "No sound", "No sound" } }; +static valueenum_t Backends[] = +{ +#ifndef NO_FMOD + { "fmod", "FMOD" }, +#endif +#ifndef NO_OPENAL + { "openal", "OpenAL" }, +#endif + { "null", "No sound" } +}; + static valueenum_t OutputFormats[] = { { "PCM-8", "8-bit" }, @@ -1263,6 +1282,49 @@ static valueenum_t Resamplers[] = { "Spline", "Spline" } }; +static menuitem_t FMODSoundItems[] = +{ + { slider, "Underwater cutoff", {&snd_waterlp}, {0.0}, {2000.0},{50.0}, {NULL} }, + { ediscrete,"Output system", {&snd_output}, {countof(FMODOutputs)}, {0.0}, {0.0}, {(value_t *)FMODOutputs} }, + { ediscrete,"Output format", {&snd_output_format}, {5.0}, {0.0}, {0.0}, {(value_t *)OutputFormats} }, + { ediscrete,"Speaker mode", {&snd_speakermode}, {8.0}, {0.0}, {0.0}, {(value_t *)SpeakerModes} }, + { ediscrete,"Resampler", {&snd_resampler}, {4.0}, {0.0}, {0.0}, {(value_t *)Resamplers} }, + { discrete, "HRTF filter", {&snd_hrtf}, {2.0}, {0.0}, {0.0}, {(value_t *)OnOff} }, + { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, + { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, + { discrete, "Buffer count", {&snd_buffercount}, {12.0},{0.0}, {0.0}, {BufferCounts} }, +}; + +static menu_t FMODSoundMenu = +{ + "FMOD SOUND OPTIONS", + 0, + countof(FMODSoundItems), + 0, + FMODSoundItems, +}; + + +static valueenum_t *ALOutputs = NULL; + +static menuitem_t OpenALSoundItems[] = +{ + { ediscrete, "Playback device", {&snd_aldevice}, {0.0}, {0.0},{0.0}, {(value_t *)ALOutputs} }, + { discrete, "Enable EFX", {&snd_efx}, {2.0}, {0.0},{0.0}, {(value_t *)YesNo} }, + { redtext, " ", {NULL}, {0.0}, {0.0},{0.0}, {NULL} }, + { whitetext, "Requires EFX", {NULL}, {0.0}, {0.0},{0.0}, {NULL} }, + { slider, "Underwater absorption",{&snd_waterabsorption}, {0.0}, {10.0},{0.5},{NULL} }, +}; + +static menu_t OpenALSoundMenu = +{ + "OPENAL SOUND OPTIONS", + 0, + countof(OpenALSoundItems), + 0, + OpenALSoundItems, +}; + static menuitem_t SoundItems[] = { { slider, "Sounds volume", {&snd_sfxvolume}, {0.0}, {1.0}, {0.05f}, {NULL} }, @@ -1270,17 +1332,18 @@ static menuitem_t SoundItems[] = { discrete, "MIDI device", {&snd_mididevice}, {0.0}, {0.0}, {0.0}, {NULL} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Underwater reverb", {&snd_waterreverb}, {2.0}, {0.0}, {0.0}, {OnOff} }, - { slider, "Underwater cutoff", {&snd_waterlp}, {0.0}, {2000.0},{50.0}, {NULL} }, { discrete, "Randomize pitches", {&snd_pitched}, {2.0}, {0.0}, {0.0}, {OnOff} }, { slider, "Sound channels", {&snd_channels}, {8.0}, {256.0}, {8.0}, {NULL} }, + { ediscrete,"Sound backend", {&snd_backend}, {countof(Backends)}, {0.0}, {0.0}, {(value_t *)Backends} }, + { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, +#ifndef NO_FMOD + { more, "FMOD sound options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)FMODSoundOptions} }, +#endif +#ifndef NO_OPENAL + { more, "OpenAL sound options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)OpenALSoundOptions} }, +#endif { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { more, "Restart sound", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)MakeSoundChanges} }, - { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, - { ediscrete,"Output system", {&snd_output}, {countof(Outputs)}, {0.0}, {0.0}, {(value_t *)Outputs} }, - { ediscrete,"Output format", {&snd_output_format}, {5.0}, {0.0}, {0.0}, {(value_t *)OutputFormats} }, - { ediscrete,"Speaker mode", {&snd_speakermode}, {8.0}, {0.0}, {0.0}, {(value_t *)SpeakerModes} }, - { ediscrete,"Resampler", {&snd_resampler}, {4.0}, {0.0}, {0.0}, {(value_t *)Resamplers} }, - { discrete, "HRTF filter", {&snd_hrtf}, {2.0}, {0.0}, {0.0}, {(value_t *)OnOff} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { more, "Advanced options", {NULL}, {0.0}, {0.0}, {0.0}, {(value_t *)AdvSoundOptions} }, @@ -1309,8 +1372,6 @@ EXTERN_CVAR (Bool, opl_onechip) static menuitem_t AdvSoundItems[] = { { discrete, "Sample rate", {&snd_samplerate}, {8.0}, {0.0}, {0.0}, {SampleRates} }, - { discrete, "Buffer size", {&snd_buffersize}, {8.0}, {0.0}, {0.0}, {BufferSizes} }, - { discrete, "Buffer count", {&snd_buffercount}, {12.0}, {0.0}, {0.0}, {BufferCounts} }, { redtext, " ", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { whitetext,"OPL Synthesis", {NULL}, {0.0}, {0.0}, {0.0}, {NULL} }, { discrete, "Only emulate one OPL chip", {&opl_onechip}, {2.0}, {0.0}, {0.0}, {OnOff} }, @@ -1342,7 +1403,7 @@ EXTERN_CVAR(Int, mod_autochip_scan_threshold) static value_t ModReplayers[] = { - { 0.0, "FMOD" }, + { 0.0, "Sound System" }, { 1.0, "foo_dumb" } }; @@ -3380,6 +3441,36 @@ CCMD (menu_sound) SoundOptions (); } +static void FMODSoundOptions () +{ + M_SwitchMenu (&FMODSoundMenu); +} + +CCMD (menu_fmodsound) +{ +#ifndef NO_FMOD + M_StartControlPanel (true); + OptionsActive = true; + FMODSoundOptions (); +#endif +} + +static void OpenALSoundOptions () +{ + OpenALSoundItems[0].b.numvalues = BuildALDeviceList (&ALOutputs); + OpenALSoundItems[0].e.enumvalues = ALOutputs; + M_SwitchMenu (&OpenALSoundMenu); +} + +CCMD (menu_openalsound) +{ +#ifndef NO_OPENAL + M_StartControlPanel (true); + OptionsActive = true; + OpenALSoundOptions (); +#endif +} + static void AdvSoundOptions () { M_SwitchMenu (&AdvSoundMenu); @@ -3825,6 +3916,14 @@ CCMD (addmenukey) void M_Deinit () { + if (ALOutputs) + { + for (size_t i = 0;ALOutputs[i].value;i++) + delete[] const_cast(ALOutputs[i].value); + delete[] ALOutputs; + ALOutputs = NULL; + } + // Free bitdepth names for the modes menu. for (size_t i = 0; i < countof(Depths); ++i) { diff --git a/src/sdl/crashcatcher.c b/src/sdl/crashcatcher.c index 7dc928407..53f3c6a67 100644 --- a/src/sdl/crashcatcher.c +++ b/src/sdl/crashcatcher.c @@ -97,8 +97,7 @@ static void gdb_info(pid_t pid) strcpy(respfile, "gdb-respfile-XXXXXX"); if((fd = mkstemp(respfile)) >= 0 && (f = fdopen(fd, "w"))) { - fprintf(f, "signal SIGCHLD\n" - "shell echo \"\"\n" + fprintf(f, "shell echo \"\"\n" "shell echo \"* Loaded Libraries\"\n" "info sharedlibrary\n" "shell echo \"\"\n" @@ -115,36 +114,8 @@ static void gdb_info(pid_t pid) "x/x $eip-3\nx/x $eip\n" "shell echo \"\"\n" "shell echo \"* Backtrace\"\n" - "backtrace full\n" -#if 0 /* This sorta works to print out the core, but is too slow and skips 0's.. */ - "shell echo \"\"\n" - "shell echo \"* Stack\"\n" - "set var $_sp = $esp\n" - "while $_sp <= $ebp - 12\n" - " printf \"%%08x: \", $_sp\n" - " set var $_i = $_sp\n" - " while $_i < $_sp + 16\n" - " printf \"%%08x \", {int} $_i\n" - " set $_i += 4\n" - " end\n" - " set var $_i = $_sp\n" - " while $_i < $_sp + 16\n" - " printf \"%%c\", {int} $_i\n" - " set ++$_i\n" - " end\n" - " set var $_sp += 16\n" - " printf \"\\n\"\n" - "end\n" - "if $_sp <= $ebp\n" - " printf \"%%08x: \", $esp\n" - " while $_sp <= $ebp\n" - " printf \"%%08x \", {int} $_i\n" - " set $_sp += 4\n" - " end\n" - " printf \"\\n\"\n" - "end\n" -#endif - "kill\n" + "thread apply all backtrace full\n" + "detach\n" "quit\n"); fclose(f); @@ -152,8 +123,8 @@ static void gdb_info(pid_t pid) snprintf(buf, sizeof(buf), "gdb --quiet --batch --command=%s --pid=%i", respfile, pid); printf("Executing: %s\n", buf); fflush(stdout); - system(buf); + system(buf); /* Clean up */ remove(respfile); } @@ -168,6 +139,7 @@ static void gdb_info(pid_t pid) printf("Could not create gdb command file\n"); } fflush(stdout); + kill(pid, SIGKILL); } @@ -267,8 +239,8 @@ static void crash_catcher(int signum, siginfo_t *siginfo, void *context) /* Make sure the effective uid is the real uid */ if (getuid() != geteuid()) { - fprintf(stderr, "%s (signal %i)\ngetuid() does not match geteuid().\n", sigdesc, signum); - _exit(-1); + raise(signum); + return; } #endif @@ -325,48 +297,18 @@ static void crash_catcher(int signum, siginfo_t *siginfo, void *context) } gdb_info(pid); -#if 0 /* Why won't this work? */ - if(ucontext) - { - unsigned char *ptr = ucontext->uc_stack.ss_sp; - size_t len; - - fprintf(f, "\n* Stack\n"); - for(len = ucontext->uc_stack.ss_size/4;len > 0; len -= 4) - { - fprintf(f, "0x%08x:", (int)ptr); - for(i = 0;i < ((len < 4) ? len : 4);++i) - { - fprintf(f, " %02x%02x%02x%02x", ptr[i*4 + 0], ptr[i*4 + 1], - ptr[i*4 + 2], ptr[i*4 + 3]); - } - fputc(' ', f); - fflush(f); - for(i = 0;i < ((len < 4) ? len : 4);++i) - { - fprintf(f, "%c", *(ptr++)); - fprintf(f, "%c", *(ptr++)); - fprintf(f, "%c", *(ptr++)); - fprintf(f, "%c", *(ptr++)); - } - fputc('\n', f); - fflush(f); - } - } -#endif - if(f != stderr) { fclose(f); #if (defined __unix__) if(cc_logfile) { - char buf[256]; + char buf[512]; snprintf(buf, sizeof(buf), - "if (which gxmessage > /dev/null 2>&1);" - "then gxmessage -buttons \"Damn it:0\" -center -title \"Very Fatal Error\" -file %s;" - "elif (which xmessage > /dev/null 2>&1);" - "then xmessage -buttons \"Damn it:0\" -center -file %s -geometry 600x400;" + "if (which gxmessage > /dev/null 2>&1) ; then\n" + " gxmessage -buttons \"Damn it:0\" -center -title \"Very Fatal Error\" -file %s\n" + "elif (which xmessage > /dev/null 2>&1) ; then\n" + " xmessage -buttons \"Damn it:0\" -center -file %s -geometry 600x400\n" "fi", cc_logfile, cc_logfile); system(buf); } @@ -376,8 +318,8 @@ static void crash_catcher(int signum, siginfo_t *siginfo, void *context) _exit(0); default: - /* Wait and let the child attach gdb */ - waitpid(dbg_pid, NULL, 0); + /* Wait indefinitely; we'll be killed when gdb is done */ + while(1) usleep(1000000); } } @@ -388,12 +330,13 @@ int cc_install_handlers(int num_signals, int *signals, const char *logfile, int memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = crash_catcher; - -#if !defined(__FreeBSD__) && !defined(__APPLE__) + +#ifdef SA_ONESHOT sa.sa_flags = SA_ONESHOT | SA_NODEFER | SA_SIGINFO; #else sa.sa_flags = SA_NODEFER | SA_SIGINFO; #endif + sigemptyset(&sa.sa_mask); cc_logfile = logfile; cc_user_info = user_info; diff --git a/src/sound/efx.h b/src/sound/efx.h new file mode 100644 index 000000000..1f89ae9b9 --- /dev/null +++ b/src/sound/efx.h @@ -0,0 +1,758 @@ +#ifndef AL_EFX_H +#define AL_EFX_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#define ALC_EXT_EFX_NAME "ALC_EXT_EFX" + +#define ALC_EFX_MAJOR_VERSION 0x20001 +#define ALC_EFX_MINOR_VERSION 0x20002 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 + + +/* Listener properties. */ +#define AL_METERS_PER_UNIT 0x20004 + +/* Source properties. */ +#define AL_DIRECT_FILTER 0x20005 +#define AL_AUXILIARY_SEND_FILTER 0x20006 +#define AL_AIR_ABSORPTION_FACTOR 0x20007 +#define AL_ROOM_ROLLOFF_FACTOR 0x20008 +#define AL_CONE_OUTER_GAINHF 0x20009 +#define AL_DIRECT_FILTER_GAINHF_AUTO 0x2000A +#define AL_AUXILIARY_SEND_FILTER_GAIN_AUTO 0x2000B +#define AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO 0x2000C + + +/* Effect properties. */ + +/* Reverb effect parameters */ +#define AL_REVERB_DENSITY 0x0001 +#define AL_REVERB_DIFFUSION 0x0002 +#define AL_REVERB_GAIN 0x0003 +#define AL_REVERB_GAINHF 0x0004 +#define AL_REVERB_DECAY_TIME 0x0005 +#define AL_REVERB_DECAY_HFRATIO 0x0006 +#define AL_REVERB_REFLECTIONS_GAIN 0x0007 +#define AL_REVERB_REFLECTIONS_DELAY 0x0008 +#define AL_REVERB_LATE_REVERB_GAIN 0x0009 +#define AL_REVERB_LATE_REVERB_DELAY 0x000A +#define AL_REVERB_AIR_ABSORPTION_GAINHF 0x000B +#define AL_REVERB_ROOM_ROLLOFF_FACTOR 0x000C +#define AL_REVERB_DECAY_HFLIMIT 0x000D + +/* EAX Reverb effect parameters */ +#define AL_EAXREVERB_DENSITY 0x0001 +#define AL_EAXREVERB_DIFFUSION 0x0002 +#define AL_EAXREVERB_GAIN 0x0003 +#define AL_EAXREVERB_GAINHF 0x0004 +#define AL_EAXREVERB_GAINLF 0x0005 +#define AL_EAXREVERB_DECAY_TIME 0x0006 +#define AL_EAXREVERB_DECAY_HFRATIO 0x0007 +#define AL_EAXREVERB_DECAY_LFRATIO 0x0008 +#define AL_EAXREVERB_REFLECTIONS_GAIN 0x0009 +#define AL_EAXREVERB_REFLECTIONS_DELAY 0x000A +#define AL_EAXREVERB_REFLECTIONS_PAN 0x000B +#define AL_EAXREVERB_LATE_REVERB_GAIN 0x000C +#define AL_EAXREVERB_LATE_REVERB_DELAY 0x000D +#define AL_EAXREVERB_LATE_REVERB_PAN 0x000E +#define AL_EAXREVERB_ECHO_TIME 0x000F +#define AL_EAXREVERB_ECHO_DEPTH 0x0010 +#define AL_EAXREVERB_MODULATION_TIME 0x0011 +#define AL_EAXREVERB_MODULATION_DEPTH 0x0012 +#define AL_EAXREVERB_AIR_ABSORPTION_GAINHF 0x0013 +#define AL_EAXREVERB_HFREFERENCE 0x0014 +#define AL_EAXREVERB_LFREFERENCE 0x0015 +#define AL_EAXREVERB_ROOM_ROLLOFF_FACTOR 0x0016 +#define AL_EAXREVERB_DECAY_HFLIMIT 0x0017 + +/* Chorus effect parameters */ +#define AL_CHORUS_WAVEFORM 0x0001 +#define AL_CHORUS_PHASE 0x0002 +#define AL_CHORUS_RATE 0x0003 +#define AL_CHORUS_DEPTH 0x0004 +#define AL_CHORUS_FEEDBACK 0x0005 +#define AL_CHORUS_DELAY 0x0006 + +/* Distortion effect parameters */ +#define AL_DISTORTION_EDGE 0x0001 +#define AL_DISTORTION_GAIN 0x0002 +#define AL_DISTORTION_LOWPASS_CUTOFF 0x0003 +#define AL_DISTORTION_EQCENTER 0x0004 +#define AL_DISTORTION_EQBANDWIDTH 0x0005 + +/* Echo effect parameters */ +#define AL_ECHO_DELAY 0x0001 +#define AL_ECHO_LRDELAY 0x0002 +#define AL_ECHO_DAMPING 0x0003 +#define AL_ECHO_FEEDBACK 0x0004 +#define AL_ECHO_SPREAD 0x0005 + +/* Flanger effect parameters */ +#define AL_FLANGER_WAVEFORM 0x0001 +#define AL_FLANGER_PHASE 0x0002 +#define AL_FLANGER_RATE 0x0003 +#define AL_FLANGER_DEPTH 0x0004 +#define AL_FLANGER_FEEDBACK 0x0005 +#define AL_FLANGER_DELAY 0x0006 + +/* Frequency shifter effect parameters */ +#define AL_FREQUENCY_SHIFTER_FREQUENCY 0x0001 +#define AL_FREQUENCY_SHIFTER_LEFT_DIRECTION 0x0002 +#define AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION 0x0003 + +/* Vocal morpher effect parameters */ +#define AL_VOCAL_MORPHER_PHONEMEA 0x0001 +#define AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING 0x0002 +#define AL_VOCAL_MORPHER_PHONEMEB 0x0003 +#define AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING 0x0004 +#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 +#define AL_VOCAL_MORPHER_RATE 0x0006 + +/* Pitchshifter effect parameters */ +#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 +#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 + +/* Ringmodulator effect parameters */ +#define AL_RING_MODULATOR_FREQUENCY 0x0001 +#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 +#define AL_RING_MODULATOR_WAVEFORM 0x0003 + +/* Autowah effect parameters */ +#define AL_AUTOWAH_ATTACK_TIME 0x0001 +#define AL_AUTOWAH_RELEASE_TIME 0x0002 +#define AL_AUTOWAH_RESONANCE 0x0003 +#define AL_AUTOWAH_PEAK_GAIN 0x0004 + +/* Compressor effect parameters */ +#define AL_COMPRESSOR_ONOFF 0x0001 + +/* Equalizer effect parameters */ +#define AL_EQUALIZER_LOW_GAIN 0x0001 +#define AL_EQUALIZER_LOW_CUTOFF 0x0002 +#define AL_EQUALIZER_MID1_GAIN 0x0003 +#define AL_EQUALIZER_MID1_CENTER 0x0004 +#define AL_EQUALIZER_MID1_WIDTH 0x0005 +#define AL_EQUALIZER_MID2_GAIN 0x0006 +#define AL_EQUALIZER_MID2_CENTER 0x0007 +#define AL_EQUALIZER_MID2_WIDTH 0x0008 +#define AL_EQUALIZER_HIGH_GAIN 0x0009 +#define AL_EQUALIZER_HIGH_CUTOFF 0x000A + +/* Effect type */ +#define AL_EFFECT_FIRST_PARAMETER 0x0000 +#define AL_EFFECT_LAST_PARAMETER 0x8000 +#define AL_EFFECT_TYPE 0x8001 + +/* Effect types, used with the AL_EFFECT_TYPE property */ +#define AL_EFFECT_NULL 0x0000 +#define AL_EFFECT_REVERB 0x0001 +#define AL_EFFECT_CHORUS 0x0002 +#define AL_EFFECT_DISTORTION 0x0003 +#define AL_EFFECT_ECHO 0x0004 +#define AL_EFFECT_FLANGER 0x0005 +#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +#define AL_EFFECT_VOCAL_MORPHER 0x0007 +#define AL_EFFECT_PITCH_SHIFTER 0x0008 +#define AL_EFFECT_RING_MODULATOR 0x0009 +#define AL_EFFECT_AUTOWAH 0x000A +#define AL_EFFECT_COMPRESSOR 0x000B +#define AL_EFFECT_EQUALIZER 0x000C +#define AL_EFFECT_EAXREVERB 0x8000 + +/* Auxiliary Effect Slot properties. */ +#define AL_EFFECTSLOT_EFFECT 0x0001 +#define AL_EFFECTSLOT_GAIN 0x0002 +#define AL_EFFECTSLOT_AUXILIARY_SEND_AUTO 0x0003 + +/* NULL Auxiliary Slot ID to disable a source send. */ +#define AL_EFFECTSLOT_NULL 0x0000 + + +/* Filter properties. */ + +/* Lowpass filter parameters */ +#define AL_LOWPASS_GAIN 0x0001 +#define AL_LOWPASS_GAINHF 0x0002 + +/* Highpass filter parameters */ +#define AL_HIGHPASS_GAIN 0x0001 +#define AL_HIGHPASS_GAINLF 0x0002 + +/* Bandpass filter parameters */ +#define AL_BANDPASS_GAIN 0x0001 +#define AL_BANDPASS_GAINLF 0x0002 +#define AL_BANDPASS_GAINHF 0x0003 + +/* Filter type */ +#define AL_FILTER_FIRST_PARAMETER 0x0000 +#define AL_FILTER_LAST_PARAMETER 0x8000 +#define AL_FILTER_TYPE 0x8001 + +/* Filter types, used with the AL_FILTER_TYPE property */ +#define AL_FILTER_NULL 0x0000 +#define AL_FILTER_LOWPASS 0x0001 +#define AL_FILTER_HIGHPASS 0x0002 +#define AL_FILTER_BANDPASS 0x0003 + + +/* Effect object function types. */ +typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); +typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); + +/* Filter object function types. */ +typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); +typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); + +/* Auxiliary Effect Slot object function types. */ +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); + +#ifdef AL_ALEXT_PROTOTYPES +AL_API ALvoid AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); +AL_API ALvoid AL_APIENTRY alDeleteEffects(ALsizei n, ALuint *effects); +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); +AL_API ALvoid AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); +AL_API ALvoid AL_APIENTRY alDeleteFilters(ALsizei n, ALuint *filters); +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); +AL_API ALvoid AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); + +AL_API ALvoid AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API ALvoid AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); +AL_API ALvoid AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); +AL_API ALvoid AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +#endif + +/* Filter ranges and defaults. */ + +/* Lowpass filter */ +#define LOWPASS_MIN_GAIN (0.0f) +#define LOWPASS_MAX_GAIN (1.0f) +#define LOWPASS_DEFAULT_GAIN (1.0f) + +#define LOWPASS_MIN_GAINHF (0.0f) +#define LOWPASS_MAX_GAINHF (1.0f) +#define LOWPASS_DEFAULT_GAINHF (1.0f) + +/* Highpass filter */ +#define HIGHPASS_MIN_GAIN (0.0f) +#define HIGHPASS_MAX_GAIN (1.0f) +#define HIGHPASS_DEFAULT_GAIN (1.0f) + +#define HIGHPASS_MIN_GAINLF (0.0f) +#define HIGHPASS_MAX_GAINLF (1.0f) +#define HIGHPASS_DEFAULT_GAINLF (1.0f) + +/* Bandpass filter */ +#define BANDPASS_MIN_GAIN (0.0f) +#define BANDPASS_MAX_GAIN (1.0f) +#define BANDPASS_DEFAULT_GAIN (1.0f) + +#define BANDPASS_MIN_GAINHF (0.0f) +#define BANDPASS_MAX_GAINHF (1.0f) +#define BANDPASS_DEFAULT_GAINHF (1.0f) + +#define BANDPASS_MIN_GAINLF (0.0f) +#define BANDPASS_MAX_GAINLF (1.0f) +#define BANDPASS_DEFAULT_GAINLF (1.0f) + + +/* Effect parameter ranges and defaults. */ + +/* Standard reverb effect */ +#define AL_REVERB_MIN_DENSITY (0.0f) +#define AL_REVERB_MAX_DENSITY (1.0f) +#define AL_REVERB_DEFAULT_DENSITY (1.0f) + +#define AL_REVERB_MIN_DIFFUSION (0.0f) +#define AL_REVERB_MAX_DIFFUSION (1.0f) +#define AL_REVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_REVERB_MIN_GAIN (0.0f) +#define AL_REVERB_MAX_GAIN (1.0f) +#define AL_REVERB_DEFAULT_GAIN (0.32f) + +#define AL_REVERB_MIN_GAINHF (0.0f) +#define AL_REVERB_MAX_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_GAINHF (0.89f) + +#define AL_REVERB_MIN_DECAY_TIME (0.1f) +#define AL_REVERB_MAX_DECAY_TIME (20.0f) +#define AL_REVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_REVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_REVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_REVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_REVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_REVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_REVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_REVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_REVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_REVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_REVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_REVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_REVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_REVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_REVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_REVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_REVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_REVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_REVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* EAX reverb effect */ +#define AL_EAXREVERB_MIN_DENSITY (0.0f) +#define AL_EAXREVERB_MAX_DENSITY (1.0f) +#define AL_EAXREVERB_DEFAULT_DENSITY (1.0f) + +#define AL_EAXREVERB_MIN_DIFFUSION (0.0f) +#define AL_EAXREVERB_MAX_DIFFUSION (1.0f) +#define AL_EAXREVERB_DEFAULT_DIFFUSION (1.0f) + +#define AL_EAXREVERB_MIN_GAIN (0.0f) +#define AL_EAXREVERB_MAX_GAIN (1.0f) +#define AL_EAXREVERB_DEFAULT_GAIN (0.32f) + +#define AL_EAXREVERB_MIN_GAINHF (0.0f) +#define AL_EAXREVERB_MAX_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINHF (0.89f) + +#define AL_EAXREVERB_MIN_GAINLF (0.0f) +#define AL_EAXREVERB_MAX_GAINLF (1.0f) +#define AL_EAXREVERB_DEFAULT_GAINLF (1.0f) + +#define AL_EAXREVERB_MIN_DECAY_TIME (0.1f) +#define AL_EAXREVERB_MAX_DECAY_TIME (20.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_TIME (1.49f) + +#define AL_EAXREVERB_MIN_DECAY_HFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_HFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_HFRATIO (0.83f) + +#define AL_EAXREVERB_MIN_DECAY_LFRATIO (0.1f) +#define AL_EAXREVERB_MAX_DECAY_LFRATIO (2.0f) +#define AL_EAXREVERB_DEFAULT_DECAY_LFRATIO (1.0f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_GAIN (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_GAIN (3.16f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN (0.05f) + +#define AL_EAXREVERB_MIN_REFLECTIONS_DELAY (0.0f) +#define AL_EAXREVERB_MAX_REFLECTIONS_DELAY (0.3f) +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY (0.007f) + +#define AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_GAIN (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_GAIN (10.0f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN (1.26f) + +#define AL_EAXREVERB_MIN_LATE_REVERB_DELAY (0.0f) +#define AL_EAXREVERB_MAX_LATE_REVERB_DELAY (0.1f) +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY (0.011f) + +#define AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ (0.0f) + +#define AL_EAXREVERB_MIN_ECHO_TIME (0.075f) +#define AL_EAXREVERB_MAX_ECHO_TIME (0.25f) +#define AL_EAXREVERB_DEFAULT_ECHO_TIME (0.25f) + +#define AL_EAXREVERB_MIN_ECHO_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_ECHO_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_ECHO_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_MODULATION_TIME (0.04f) +#define AL_EAXREVERB_MAX_MODULATION_TIME (4.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_TIME (0.25f) + +#define AL_EAXREVERB_MIN_MODULATION_DEPTH (0.0f) +#define AL_EAXREVERB_MAX_MODULATION_DEPTH (1.0f) +#define AL_EAXREVERB_DEFAULT_MODULATION_DEPTH (0.0f) + +#define AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF (0.892f) +#define AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF (1.0f) +#define AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF (0.994f) + +#define AL_EAXREVERB_MIN_HFREFERENCE (1000.0f) +#define AL_EAXREVERB_MAX_HFREFERENCE (20000.0f) +#define AL_EAXREVERB_DEFAULT_HFREFERENCE (5000.0f) + +#define AL_EAXREVERB_MIN_LFREFERENCE (20.0f) +#define AL_EAXREVERB_MAX_LFREFERENCE (1000.0f) +#define AL_EAXREVERB_DEFAULT_LFREFERENCE (250.0f) + +#define AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_EAXREVERB_MIN_DECAY_HFLIMIT AL_FALSE +#define AL_EAXREVERB_MAX_DECAY_HFLIMIT AL_TRUE +#define AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT AL_TRUE + +/* Chorus effect */ +#define AL_CHORUS_WAVEFORM_SINUSOID (0) +#define AL_CHORUS_WAVEFORM_TRIANGLE (1) + +#define AL_CHORUS_MIN_WAVEFORM (0) +#define AL_CHORUS_MAX_WAVEFORM (1) +#define AL_CHORUS_DEFAULT_WAVEFORM (1) + +#define AL_CHORUS_MIN_PHASE (-180) +#define AL_CHORUS_MAX_PHASE (180) +#define AL_CHORUS_DEFAULT_PHASE (90) + +#define AL_CHORUS_MIN_RATE (0.0f) +#define AL_CHORUS_MAX_RATE (10.0f) +#define AL_CHORUS_DEFAULT_RATE (1.1f) + +#define AL_CHORUS_MIN_DEPTH (0.0f) +#define AL_CHORUS_MAX_DEPTH (1.0f) +#define AL_CHORUS_DEFAULT_DEPTH (0.1f) + +#define AL_CHORUS_MIN_FEEDBACK (-1.0f) +#define AL_CHORUS_MAX_FEEDBACK (1.0f) +#define AL_CHORUS_DEFAULT_FEEDBACK (0.25f) + +#define AL_CHORUS_MIN_DELAY (0.0f) +#define AL_CHORUS_MAX_DELAY (0.016f) +#define AL_CHORUS_DEFAULT_DELAY (0.016f) + +/* Distortion effect */ +#define AL_DISTORTION_MIN_EDGE (0.0f) +#define AL_DISTORTION_MAX_EDGE (1.0f) +#define AL_DISTORTION_DEFAULT_EDGE (0.2f) + +#define AL_DISTORTION_MIN_GAIN (0.01f) +#define AL_DISTORTION_MAX_GAIN (1.0f) +#define AL_DISTORTION_DEFAULT_GAIN (0.05f) + +#define AL_DISTORTION_MIN_LOWPASS_CUTOFF (80.0f) +#define AL_DISTORTION_MAX_LOWPASS_CUTOFF (24000.0f) +#define AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF (8000.0f) + +#define AL_DISTORTION_MIN_EQCENTER (80.0f) +#define AL_DISTORTION_MAX_EQCENTER (24000.0f) +#define AL_DISTORTION_DEFAULT_EQCENTER (3600.0f) + +#define AL_DISTORTION_MIN_EQBANDWIDTH (80.0f) +#define AL_DISTORTION_MAX_EQBANDWIDTH (24000.0f) +#define AL_DISTORTION_DEFAULT_EQBANDWIDTH (3600.0f) + +/* Echo effect */ +#define AL_ECHO_MIN_DELAY (0.0f) +#define AL_ECHO_MAX_DELAY (0.207f) +#define AL_ECHO_DEFAULT_DELAY (0.1f) + +#define AL_ECHO_MIN_LRDELAY (0.0f) +#define AL_ECHO_MAX_LRDELAY (0.404f) +#define AL_ECHO_DEFAULT_LRDELAY (0.1f) + +#define AL_ECHO_MIN_DAMPING (0.0f) +#define AL_ECHO_MAX_DAMPING (0.99f) +#define AL_ECHO_DEFAULT_DAMPING (0.5f) + +#define AL_ECHO_MIN_FEEDBACK (0.0f) +#define AL_ECHO_MAX_FEEDBACK (1.0f) +#define AL_ECHO_DEFAULT_FEEDBACK (0.5f) + +#define AL_ECHO_MIN_SPREAD (-1.0f) +#define AL_ECHO_MAX_SPREAD (1.0f) +#define AL_ECHO_DEFAULT_SPREAD (-1.0f) + +/* Flanger effect */ +#define AL_FLANGER_WAVEFORM_SINUSOID (0) +#define AL_FLANGER_WAVEFORM_TRIANGLE (1) + +#define AL_FLANGER_MIN_WAVEFORM (0) +#define AL_FLANGER_MAX_WAVEFORM (1) +#define AL_FLANGER_DEFAULT_WAVEFORM (1) + +#define AL_FLANGER_MIN_PHASE (-180) +#define AL_FLANGER_MAX_PHASE (180) +#define AL_FLANGER_DEFAULT_PHASE (0) + +#define AL_FLANGER_MIN_RATE (0.0f) +#define AL_FLANGER_MAX_RATE (10.0f) +#define AL_FLANGER_DEFAULT_RATE (0.27f) + +#define AL_FLANGER_MIN_DEPTH (0.0f) +#define AL_FLANGER_MAX_DEPTH (1.0f) +#define AL_FLANGER_DEFAULT_DEPTH (1.0f) + +#define AL_FLANGER_MIN_FEEDBACK (-1.0f) +#define AL_FLANGER_MAX_FEEDBACK (1.0f) +#define AL_FLANGER_DEFAULT_FEEDBACK (-0.5f) + +#define AL_FLANGER_MIN_DELAY (0.0f) +#define AL_FLANGER_MAX_DELAY (0.004f) +#define AL_FLANGER_DEFAULT_DELAY (0.002f) + +/* Frequency shifter effect */ +#define AL_FREQUENCY_SHIFTER_MIN_FREQUENCY (0.0f) +#define AL_FREQUENCY_SHIFTER_MAX_FREQUENCY (24000.0f) +#define AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY (0.0f) + +#define AL_FREQUENCY_SHIFTER_MIN_LEFT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_LEFT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION (0) + +#define AL_FREQUENCY_SHIFTER_DIRECTION_DOWN (0) +#define AL_FREQUENCY_SHIFTER_DIRECTION_UP (1) +#define AL_FREQUENCY_SHIFTER_DIRECTION_OFF (2) + +#define AL_FREQUENCY_SHIFTER_MIN_RIGHT_DIRECTION (0) +#define AL_FREQUENCY_SHIFTER_MAX_RIGHT_DIRECTION (2) +#define AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION (0) + +/* Vocal morpher effect */ +#define AL_VOCAL_MORPHER_MIN_PHONEMEA (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB (0) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB (29) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB (10) + +#define AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING (-24) +#define AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING (24) +#define AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING (0) + +#define AL_VOCAL_MORPHER_PHONEME_A (0) +#define AL_VOCAL_MORPHER_PHONEME_E (1) +#define AL_VOCAL_MORPHER_PHONEME_I (2) +#define AL_VOCAL_MORPHER_PHONEME_O (3) +#define AL_VOCAL_MORPHER_PHONEME_U (4) +#define AL_VOCAL_MORPHER_PHONEME_AA (5) +#define AL_VOCAL_MORPHER_PHONEME_AE (6) +#define AL_VOCAL_MORPHER_PHONEME_AH (7) +#define AL_VOCAL_MORPHER_PHONEME_AO (8) +#define AL_VOCAL_MORPHER_PHONEME_EH (9) +#define AL_VOCAL_MORPHER_PHONEME_ER (10) +#define AL_VOCAL_MORPHER_PHONEME_IH (11) +#define AL_VOCAL_MORPHER_PHONEME_IY (12) +#define AL_VOCAL_MORPHER_PHONEME_UH (13) +#define AL_VOCAL_MORPHER_PHONEME_UW (14) +#define AL_VOCAL_MORPHER_PHONEME_B (15) +#define AL_VOCAL_MORPHER_PHONEME_D (16) +#define AL_VOCAL_MORPHER_PHONEME_F (17) +#define AL_VOCAL_MORPHER_PHONEME_G (18) +#define AL_VOCAL_MORPHER_PHONEME_J (19) +#define AL_VOCAL_MORPHER_PHONEME_K (20) +#define AL_VOCAL_MORPHER_PHONEME_L (21) +#define AL_VOCAL_MORPHER_PHONEME_M (22) +#define AL_VOCAL_MORPHER_PHONEME_N (23) +#define AL_VOCAL_MORPHER_PHONEME_P (24) +#define AL_VOCAL_MORPHER_PHONEME_R (25) +#define AL_VOCAL_MORPHER_PHONEME_S (26) +#define AL_VOCAL_MORPHER_PHONEME_T (27) +#define AL_VOCAL_MORPHER_PHONEME_V (28) +#define AL_VOCAL_MORPHER_PHONEME_Z (29) + +#define AL_VOCAL_MORPHER_WAVEFORM_SINUSOID (0) +#define AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE (1) +#define AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH (2) + +#define AL_VOCAL_MORPHER_MIN_WAVEFORM (0) +#define AL_VOCAL_MORPHER_MAX_WAVEFORM (2) +#define AL_VOCAL_MORPHER_DEFAULT_WAVEFORM (0) + +#define AL_VOCAL_MORPHER_MIN_RATE (0.0f) +#define AL_VOCAL_MORPHER_MAX_RATE (10.0f) +#define AL_VOCAL_MORPHER_DEFAULT_RATE (1.41f) + +/* Pitch shifter effect */ +#define AL_PITCH_SHIFTER_MIN_COARSE_TUNE (-12) +#define AL_PITCH_SHIFTER_MAX_COARSE_TUNE (12) +#define AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE (12) + +#define AL_PITCH_SHIFTER_MIN_FINE_TUNE (-50) +#define AL_PITCH_SHIFTER_MAX_FINE_TUNE (50) +#define AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE (0) + +/* Ring modulator effect */ +#define AL_RING_MODULATOR_MIN_FREQUENCY (0.0f) +#define AL_RING_MODULATOR_MAX_FREQUENCY (8000.0f) +#define AL_RING_MODULATOR_DEFAULT_FREQUENCY (440.0f) + +#define AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF (0.0f) +#define AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF (24000.0f) +#define AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF (800.0f) + +#define AL_RING_MODULATOR_SINUSOID (0) +#define AL_RING_MODULATOR_SAWTOOTH (1) +#define AL_RING_MODULATOR_SQUARE (2) + +#define AL_RING_MODULATOR_MIN_WAVEFORM (0) +#define AL_RING_MODULATOR_MAX_WAVEFORM (2) +#define AL_RING_MODULATOR_DEFAULT_WAVEFORM (0) + +/* Autowah effect */ +#define AL_AUTOWAH_MIN_ATTACK_TIME (0.0001f) +#define AL_AUTOWAH_MAX_ATTACK_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_ATTACK_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RELEASE_TIME (0.0001f) +#define AL_AUTOWAH_MAX_RELEASE_TIME (1.0f) +#define AL_AUTOWAH_DEFAULT_RELEASE_TIME (0.06f) + +#define AL_AUTOWAH_MIN_RESONANCE (2.0f) +#define AL_AUTOWAH_MAX_RESONANCE (1000.0f) +#define AL_AUTOWAH_DEFAULT_RESONANCE (1000.0f) + +#define AL_AUTOWAH_MIN_PEAK_GAIN (0.00003f) +#define AL_AUTOWAH_MAX_PEAK_GAIN (31621.0f) +#define AL_AUTOWAH_DEFAULT_PEAK_GAIN (11.22f) + +/* Compressor effect */ +#define AL_COMPRESSOR_MIN_ONOFF (0) +#define AL_COMPRESSOR_MAX_ONOFF (1) +#define AL_COMPRESSOR_DEFAULT_ONOFF (1) + +/* Equalizer effect */ +#define AL_EQUALIZER_MIN_LOW_GAIN (0.126f) +#define AL_EQUALIZER_MAX_LOW_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_LOW_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_LOW_CUTOFF (50.0f) +#define AL_EQUALIZER_MAX_LOW_CUTOFF (800.0f) +#define AL_EQUALIZER_DEFAULT_LOW_CUTOFF (200.0f) + +#define AL_EQUALIZER_MIN_MID1_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID1_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID1_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID1_CENTER (200.0f) +#define AL_EQUALIZER_MAX_MID1_CENTER (3000.0f) +#define AL_EQUALIZER_DEFAULT_MID1_CENTER (500.0f) + +#define AL_EQUALIZER_MIN_MID1_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID1_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID1_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_MID2_GAIN (0.126f) +#define AL_EQUALIZER_MAX_MID2_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_MID2_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_MID2_CENTER (1000.0f) +#define AL_EQUALIZER_MAX_MID2_CENTER (8000.0f) +#define AL_EQUALIZER_DEFAULT_MID2_CENTER (3000.0f) + +#define AL_EQUALIZER_MIN_MID2_WIDTH (0.01f) +#define AL_EQUALIZER_MAX_MID2_WIDTH (1.0f) +#define AL_EQUALIZER_DEFAULT_MID2_WIDTH (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_GAIN (0.126f) +#define AL_EQUALIZER_MAX_HIGH_GAIN (7.943f) +#define AL_EQUALIZER_DEFAULT_HIGH_GAIN (1.0f) + +#define AL_EQUALIZER_MIN_HIGH_CUTOFF (4000.0f) +#define AL_EQUALIZER_MAX_HIGH_CUTOFF (16000.0f) +#define AL_EQUALIZER_DEFAULT_HIGH_CUTOFF (6000.0f) + + +/* Source parameter value ranges and defaults. */ +#define AL_MIN_AIR_ABSORPTION_FACTOR (0.0f) +#define AL_MAX_AIR_ABSORPTION_FACTOR (10.0f) +#define AL_DEFAULT_AIR_ABSORPTION_FACTOR (0.0f) + +#define AL_MIN_ROOM_ROLLOFF_FACTOR (0.0f) +#define AL_MAX_ROOM_ROLLOFF_FACTOR (10.0f) +#define AL_DEFAULT_ROOM_ROLLOFF_FACTOR (0.0f) + +#define AL_MIN_CONE_OUTER_GAINHF (0.0f) +#define AL_MAX_CONE_OUTER_GAINHF (1.0f) +#define AL_DEFAULT_CONE_OUTER_GAINHF (1.0f) + +#define AL_MIN_DIRECT_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_DIRECT_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_DIRECT_FILTER_GAINHF_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAIN_AUTO AL_TRUE + +#define AL_MIN_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_FALSE +#define AL_MAX_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE +#define AL_DEFAULT_AUXILIARY_SEND_FILTER_GAINHF_AUTO AL_TRUE + + +/* Listener parameter value ranges and defaults. */ +#define AL_MIN_METERS_PER_UNIT FLT_MIN +#define AL_MAX_METERS_PER_UNIT FLT_MAX +#define AL_DEFAULT_METERS_PER_UNIT (1.0f) + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* AL_EFX_H */ diff --git a/src/sound/fmod_wrap.h b/src/sound/fmod_wrap.h index f77eed36c..be676d9dd 100644 --- a/src/sound/fmod_wrap.h +++ b/src/sound/fmod_wrap.h @@ -2,6 +2,8 @@ #ifndef FMOD_WRAP_H #define FMOD_WRAP_H +#ifndef NO_FMOD + #if !defined(_WIN32) || defined(_MSC_VER) // Use the real C++ interface if it's supported on this platform. #include "fmod.hpp" @@ -610,3 +612,4 @@ namespace FMOD #endif #endif +#endif diff --git a/src/sound/fmodsound.cpp b/src/sound/fmodsound.cpp index 30efe22d9..cdbb70645 100644 --- a/src/sound/fmodsound.cpp +++ b/src/sound/fmodsound.cpp @@ -75,6 +75,36 @@ extern HWND Window; #define SPECTRUM_SIZE 256 +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +ReverbContainer *ForcedEnvironment; + +CVAR (Int, snd_driver, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Int, snd_buffercount, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, snd_hrtf, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, snd_profile, false, 0) + +// Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter. +CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + // Clamp to the DSP unit's limits. + if (*self < 10 && *self != 0) + { + self = 10; + } + else if (*self > 22000) + { + self = 22000; + } +} + +#ifndef NO_FMOD + // TYPES ------------------------------------------------------------------- struct FEnumList @@ -104,34 +134,6 @@ EXTERN_CVAR (Int, snd_channels) extern int sfx_empty; -// PUBLIC DATA DEFINITIONS ------------------------------------------------- - -ReverbContainer *ForcedEnvironment; - -CVAR (Int, snd_driver, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Int, snd_buffercount, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Bool, snd_hrtf, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Bool, snd_waterreverb, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_resampler, "Linear", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_speakermode, "Auto", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_output_format, "PCM-16", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (String, snd_midipatchset, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Bool, snd_profile, false, 0) - -// Underwater low-pass filter cutoff frequency. Set to 0 to disable the filter. -CUSTOM_CVAR (Float, snd_waterlp, 250, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -{ - // Clamp to the DSP unit's limits. - if (*self < 10 && *self != 0) - { - self = 10; - } - else if (*self > 22000) - { - self = 22000; - } -} - // PRIVATE DATA DEFINITIONS ------------------------------------------------ static const ReverbContainer *PrevEnvironment; @@ -2900,3 +2902,5 @@ void FMODSoundRenderer::InitCreateSoundExInfo(FMOD_CREATESOUNDEXINFO *exinfo) co } memset((BYTE *)exinfo + sizeof(exinfo->cbsize), 0, exinfo->cbsize - sizeof(exinfo->cbsize)); } + +#endif // NO_FMOD diff --git a/src/sound/fmodsound.h b/src/sound/fmodsound.h index 1460b697b..fcaffda8d 100644 --- a/src/sound/fmodsound.h +++ b/src/sound/fmodsound.h @@ -2,6 +2,8 @@ #define FMODSOUND_H #include "i_sound.h" + +#ifndef NO_FMOD #include "fmod_wrap.h" class FMODSoundRenderer : public SoundRenderer @@ -117,3 +119,4 @@ private: }; #endif +#endif diff --git a/src/sound/i_sound.cpp b/src/sound/i_sound.cpp index b25bbd6fb..8261d4611 100644 --- a/src/sound/i_sound.cpp +++ b/src/sound/i_sound.cpp @@ -53,6 +53,7 @@ extern HINSTANCE g_hInst; #include #include "fmodsound.h" +#include "oalsound.h" #include "m_swap.h" #include "stats.h" @@ -77,6 +78,14 @@ CVAR (Int, snd_samplerate, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Int, snd_buffersize, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (String, snd_output, "default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +#ifndef NO_FMOD +CVAR (String, snd_backend, "fmod", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +#elif !defined(NO_OPENAL) +CVAR (String, snd_backend, "openal", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +#else +CVAR (String, snd_backend, "null", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +#endif + // killough 2/21/98: optionally use varying pitched sounds CVAR (Bool, snd_pitched, false, CVAR_ARCHIVE) @@ -243,9 +252,17 @@ void I_InitSound () return; } - GSnd = new FMODSoundRenderer; - - if (!GSnd->IsValid ()) + if(stricmp(snd_backend, "null") == 0) + GSnd = new NullSoundRenderer; +#ifndef NO_FMOD + else if(stricmp(snd_backend, "fmod") == 0) + GSnd = new FMODSoundRenderer; +#endif +#ifndef NO_OPENAL + else if(stricmp(snd_backend, "openal") == 0) + GSnd = new OpenALSoundRenderer; +#endif + if (!GSnd || !GSnd->IsValid ()) { I_CloseSound(); GSnd = new NullSoundRenderer; diff --git a/src/sound/i_soundinternal.h b/src/sound/i_soundinternal.h index 157c9c87b..b0f42913a 100644 --- a/src/sound/i_soundinternal.h +++ b/src/sound/i_soundinternal.h @@ -96,6 +96,8 @@ struct FISoundChannel // callback that can't be passed a sound channel pointer FRolloffInfo Rolloff; float DistanceScale; + float DistanceSqr; + bool ManualGain; }; diff --git a/src/sound/music_midi_base.cpp b/src/sound/music_midi_base.cpp index d7983a70f..77c8dde9c 100644 --- a/src/sound/music_midi_base.cpp +++ b/src/sound/music_midi_base.cpp @@ -85,7 +85,7 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) values[0].value = -3.0; values[1].name = "TiMidity++"; values[1].value = -2.0; - values[2].name = "FMOD"; + values[2].name = "Sound System"; values[2].value = -1.0; for (id = 0, p = 3; id < nummididevices; ++id) { @@ -153,7 +153,7 @@ CCMD (snd_listmididevices) PrintMidiDevice (-3, "Emulated OPL FM Synth", MOD_FMSYNTH, 0); PrintMidiDevice (-2, "TiMidity++", 0, MOD_SWSYNTH); - PrintMidiDevice (-1, "FMOD", 0, MOD_SWSYNTH); + PrintMidiDevice (-1, "Sound System", 0, MOD_SWSYNTH); if (nummididevices != 0) { for (id = 0; id < nummididevices; ++id) @@ -195,7 +195,7 @@ void I_BuildMIDIMenuList (struct value_t **outValues, float *numValues) values[0].value = -3.0; values[1].name = "TiMidity++"; values[1].value = -2.0; - values[2].name = "FMOD"; + values[2].name = "Sound System"; values[2].value = -1.0; *numValues = 3.f; } @@ -205,6 +205,6 @@ CCMD (snd_listmididevices) { Printf("%s-3. Emulated OPL FM Synth\n", -3 == snd_mididevice ? TEXTCOLOR_BOLD : ""); Printf("%s-2. TiMidity++\n", -2 == snd_mididevice ? TEXTCOLOR_BOLD : ""); - Printf("%s-1. FMOD\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); + Printf("%s-1. Sound System\n", -1 == snd_mididevice ? TEXTCOLOR_BOLD : ""); } #endif diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp new file mode 100644 index 000000000..06107751e --- /dev/null +++ b/src/sound/oalsound.cpp @@ -0,0 +1,2823 @@ +/* +** oalsound.cpp +** System interface for sound; uses OpenAL +** +**--------------------------------------------------------------------------- +** Copyright 2008-2010 Chris Robinson +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define USE_WINDOWS_DWORD +#endif + +#include "doomstat.h" +#include "templates.h" +#include "oalsound.h" +#include "c_cvars.h" +#include "c_dispatch.h" +#include "i_system.h" +#include "v_text.h" +#include "gi.h" +#include "actor.h" +#include "r_state.h" +#include "w_wad.h" +#include "i_music.h" +#include "i_musicinterns.h" +#include "tempfiles.h" + + +CVAR (String, snd_aldevice, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR (Float, snd_waterabsorption, 10.0f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if(*self < 0.0f) + self = 0.0f; + else if(*self > 10.0f) + self = 10.0f; +} + +int BuildALDeviceList(valueenum_t **menulist) +{ + std::vector list; + static const char def[] = ""; + + valueenum_t val; + size_t len = strlen(def)+1; + + char *n = new char[len]; + memcpy(n, def, len); + + val.value = n; + val.name = "Default"; + list.push_back(val); + +#ifndef NO_OPENAL + const ALCchar *names = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ? + alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) : + alcGetString(NULL, ALC_DEVICE_SPECIFIER)); + if(!names) + Printf("Failed to get device list: %s\n", alcGetString(NULL, alcGetError(NULL))); + else while(*names) + { + len = strlen(names)+1; + + n = new char[len]; + memcpy(n, names, len); + + val.value = n; + val.name = val.value; + list.push_back(val); + + names += len; + } +#endif + + static const valueenum_t listend = {NULL,NULL}; + list.push_back(listend); + + valueenum_t *newlist = new valueenum_t[list.size()]; + memcpy(&newlist[0], &list[0], list.size()*sizeof(newlist[0])); + + if(*menulist) + { + for(size_t i = 0;(*menulist)[i].value;i++) + delete[] const_cast((*menulist)[i].value); + delete[] *menulist; + } + *menulist = newlist; + return list.size()-1; +} + +#ifndef NO_OPENAL + +#include +#include +#include +#include + + +EXTERN_CVAR (Int, snd_channels) +EXTERN_CVAR (Int, snd_samplerate) +EXTERN_CVAR (Bool, snd_waterreverb) +EXTERN_CVAR (Bool, snd_pitched) + + +#define foreach(type, name, vec) \ + for(std::vector::iterator (name) = (vec).begin(), \ + (_end_##name) = (vec).end(); \ + (name) != (_end_##name);(name)++) + + +static ALenum checkALError(const char *fn, unsigned int ln) +{ + ALenum err = alGetError(); + if(err != AL_NO_ERROR) + { + if(strchr(fn, '/')) + fn = strrchr(fn, '/')+1; + else if(strchr(fn, '\\')) + fn = strrchr(fn, '\\')+1; + Printf(">>>>>>>>>>>> Received AL error %s (%#x), %s:%u\n", alGetString(err), err, fn, ln); + } + return err; +} +#define getALError() checkALError(__FILE__, __LINE__) + +static ALCenum checkALCError(ALCdevice *device, const char *fn, unsigned int ln) +{ + ALCenum err = alcGetError(device); + if(err != ALC_NO_ERROR) + { + if(strchr(fn, '/')) + fn = strrchr(fn, '/')+1; + else if(strchr(fn, '\\')) + fn = strrchr(fn, '\\')+1; + Printf(">>>>>>>>>>>> Received ALC error %s (%#x), %s:%u\n", alcGetString(device, err), err, fn, ln); + } + return err; +} +#define getALCError(d) checkALCError((d), __FILE__, __LINE__) + +static ALsizei GetBufferLength(ALuint buffer, const char *fn, unsigned int ln) +{ + ALint bits, channels, size; + alGetBufferi(buffer, AL_BITS, &bits); + alGetBufferi(buffer, AL_CHANNELS, &channels); + alGetBufferi(buffer, AL_SIZE, &size); + if(checkALError(fn, ln) == AL_NO_ERROR) + return (ALsizei)(size / (channels * bits / 8)); + return 0; +} +#define getBufferLength(b) GetBufferLength((b), __FILE__, __LINE__) + +extern ReverbContainer *ForcedEnvironment; + +#define PITCH_MULT (0.7937005f) /* Approx. 4 semitones lower; what Nash suggested */ + +#define PITCH(pitch) (snd_pitched ? (pitch)/128.f : 1.f) + + +static float GetRolloff(const FRolloffInfo *rolloff, float distance) +{ + if(distance <= rolloff->MinDistance) + return 1.f; + // Logarithmic rolloff has no max distance where it goes silent. + if(rolloff->RolloffType == ROLLOFF_Log) + return rolloff->MinDistance / + (rolloff->MinDistance + rolloff->RolloffFactor*(distance-rolloff->MinDistance)); + if(distance >= rolloff->MaxDistance) + return 0.f; + + float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance); + if(rolloff->RolloffType == ROLLOFF_Linear) + return volume; + + if(rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve != NULL) + return S_SoundCurve[int(S_SoundCurveSize * (1.f - volume))] / 127.f; + return (powf(10.f, volume) - 1.f) / 9.f; +} + + +/*** GStreamer start ***/ +#include +#include +#include +#include +#include + +/* Bad GStreamer, using enums for bit fields... (GStreamerMM probably fixes this) */ +static GstMessageType operator|(const GstMessageType &a, const GstMessageType &b) +{ return GstMessageType((unsigned)a|(unsigned)b); } +static GstSeekFlags operator|(const GstSeekFlags &a, const GstSeekFlags &b) +{ return GstSeekFlags((unsigned)a|(unsigned)b); } + +static void PrintErrMsg(const char *str, GstMessage *msg) +{ + GError *error; + gchar *debug; + + gst_message_parse_error(msg, &error, &debug); + Printf("%s: %s\n", str, error->message); + DPrintf("%s\n", debug); + + g_error_free(error); + g_free(debug); +} + +static GstCaps *SupportedBufferFormatCaps(int forcebits=0) +{ + GstStructure *structure; + GstCaps *caps; + + caps = gst_caps_new_empty(); + if(alIsExtensionPresent("AL_EXT_MCFORMATS")) + { + static const struct { + gint count; + GstAudioChannelPosition pos[8]; + } chans[] = { + { 1, + { GST_AUDIO_CHANNEL_POSITION_FRONT_MONO } }, + { 2, + { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT } }, + { 4, + { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT } }, + { 6, + { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_LFE, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT } }, + { 7, + { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_LFE, + GST_AUDIO_CHANNEL_POSITION_REAR_CENTER, + GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT, + GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT } }, + { 8, + { GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_LFE, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT, + GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT } }, + }; + static const char *fmt32[] = { + "AL_FORMAT_MONO_FLOAT32", "AL_FORMAT_STEREO_FLOAT32", "AL_FORMAT_QUAD32", + "AL_FORMAT_51CHN32", "AL_FORMAT_61CHN32", "AL_FORMAT_71CHN32", NULL + }; + static const char *fmt16[] = { + "AL_FORMAT_MONO16", "AL_FORMAT_STEREO16", "AL_FORMAT_QUAD16", + "AL_FORMAT_51CHN16", "AL_FORMAT_61CHN16", "AL_FORMAT_71CHN16", NULL + }; + static const char *fmt8[] = { + "AL_FORMAT_MONO8", "AL_FORMAT_STEREO8", "AL_FORMAT_QUAD8", + "AL_FORMAT_51CHN8", "AL_FORMAT_61CHN8", "AL_FORMAT_71CHN8", NULL + }; + + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + { + for(size_t i = 0;fmt32[i];i++) + { + if(forcebits && forcebits != 32) + break; + + ALenum val = alGetEnumValue(fmt32[i]); + if(getALError() != AL_NO_ERROR || val == 0 || val == -1) + continue; + + structure = gst_structure_new("audio/x-raw-float", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 32, NULL); + gst_structure_set(structure, "channels", G_TYPE_INT, + chans[i].count, NULL); + if(chans[i].count > 2) + gst_audio_set_channel_positions(structure, chans[i].pos); + gst_caps_append_structure(caps, structure); + } + } + for(size_t i = 0;fmt16[i];i++) + { + if(forcebits && forcebits != 16) + break; + + ALenum val = alGetEnumValue(fmt16[i]); + if(getALError() != AL_NO_ERROR || val == 0 || val == -1) + continue; + + structure = gst_structure_new("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 16, + "depth", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN, TRUE, NULL); + gst_structure_set(structure, "channels", G_TYPE_INT, + chans[i].count, NULL); + if(chans[i].count > 2) + gst_audio_set_channel_positions(structure, chans[i].pos); + gst_caps_append_structure(caps, structure); + } + for(size_t i = 0;fmt8[i];i++) + { + if(forcebits && forcebits != 8) + break; + + ALenum val = alGetEnumValue(fmt8[i]); + if(getALError() != AL_NO_ERROR || val == 0 || val == -1) + continue; + + structure = gst_structure_new("audio/x-raw-int", + "width", G_TYPE_INT, 8, + "depth", G_TYPE_INT, 8, + "signed", G_TYPE_BOOLEAN, FALSE, NULL); + gst_structure_set(structure, "channels", G_TYPE_INT, + chans[i].count, NULL); + if(chans[i].count > 2) + gst_audio_set_channel_positions(structure, chans[i].pos); + gst_caps_append_structure(caps, structure); + } + } + else + { + if(alIsExtensionPresent("AL_EXT_FLOAT32") && + (!forcebits || forcebits == 32)) + { + structure = gst_structure_new("audio/x-raw-float", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 32, + "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); + gst_caps_append_structure(caps, structure); + } + if(!forcebits || forcebits == 16) + { + structure = gst_structure_new("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 16, + "depth", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN, TRUE, + "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); + gst_caps_append_structure(caps, structure); + } + if(!forcebits || forcebits == 8) + { + structure = gst_structure_new("audio/x-raw-int", + "width", G_TYPE_INT, 8, + "depth", G_TYPE_INT, 8, + "signed", G_TYPE_BOOLEAN, FALSE, + "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); + gst_caps_append_structure(caps, structure); + } + } + return caps; +} + +static ALenum FormatFromDesc(int bits, int channels) +{ + if(bits == 8) + { + if(channels == 1) return AL_FORMAT_MONO8; + if(channels == 2) return AL_FORMAT_STEREO8; + if(channels == 4) return AL_FORMAT_QUAD8; + if(channels == 6) return AL_FORMAT_51CHN8; + if(channels == 7) return AL_FORMAT_61CHN8; + if(channels == 8) return AL_FORMAT_71CHN8; + } + if(bits == 16) + { + if(channels == 1) return AL_FORMAT_MONO16; + if(channels == 2) return AL_FORMAT_STEREO16; + if(channels == 4) return AL_FORMAT_QUAD16; + if(channels == 6) return AL_FORMAT_51CHN16; + if(channels == 7) return AL_FORMAT_61CHN16; + if(channels == 8) return AL_FORMAT_71CHN16; + } + if(bits == 32) + { + if(channels == 1) return AL_FORMAT_MONO_FLOAT32; + if(channels == 2) return AL_FORMAT_STEREO_FLOAT32; + if(channels == 4) return AL_FORMAT_QUAD32; + if(channels == 6) return AL_FORMAT_51CHN32; + if(channels == 7) return AL_FORMAT_61CHN32; + if(channels == 8) return AL_FORMAT_71CHN32; + } + return AL_NONE; +} + +class OpenALSoundStream : public SoundStream +{ + OpenALSoundRenderer *Renderer; + GstElement *gstPipeline; + GstTagList *TagList; + gint64 LoopPts[2]; + ALuint Source; + + bool Playing; + bool Looping; + + // Custom OpenAL sink; this is pretty crappy compared to the real + // openalsink element, but it gets the job done + static const ALsizei MaxSamplesQueued = 32768; + std::vector Buffers; + ALsizei SamplesQueued; + ALsizei SampleRate; + ALenum Format; + + static void sink_eos(GstAppSink *sink, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + if(!self->Playing) + return; + + ALint state; + do { + g_usleep(10000); + alGetSourcei(self->Source, AL_SOURCE_STATE, &state); + } while(getALError() == AL_NO_ERROR && state == AL_PLAYING); + + alSourceRewind(self->Source); + getALError(); + } + + static GstFlowReturn sink_preroll(GstAppSink *sink, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + // get the buffer from appsink + GstBuffer *buffer = gst_app_sink_pull_preroll(sink); + if(!buffer) return GST_FLOW_ERROR; + + GstCaps *caps = GST_BUFFER_CAPS(buffer); + gint bits = 0, channels = 0, rate = 0, i; + for(i = gst_caps_get_size(caps)-1;i >= 0;i--) + { + GstStructure *struc = gst_caps_get_structure(caps, i); + if(gst_structure_has_field(struc, "width")) + gst_structure_get_int(struc, "width", &bits); + if(gst_structure_has_field(struc, "channels")) + gst_structure_get_int(struc, "channels", &channels); + if(gst_structure_has_field(struc, "rate")) + gst_structure_get_int(struc, "rate", &rate); + } + + self->SampleRate = rate; + self->Format = FormatFromDesc(bits, channels); + + gst_buffer_unref(buffer); + if(self->Format == AL_NONE || self->SampleRate <= 0) + return GST_FLOW_ERROR; + return GST_FLOW_OK; + } + + static GstFlowReturn sink_buffer(GstAppSink *sink, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + GstBuffer *buffer = gst_app_sink_pull_buffer(sink); + if(!buffer) return GST_FLOW_ERROR; + + if(GST_BUFFER_SIZE(buffer) == 0) + { + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + ALint processed, state; + next_buffer: + do { + alGetSourcei(self->Source, AL_SOURCE_STATE, &state); + alGetSourcei(self->Source, AL_BUFFERS_PROCESSED, &processed); + if(getALError() != AL_NO_ERROR) + { + gst_buffer_unref(buffer); + return GST_FLOW_ERROR; + } + if(processed > 0 || self->SamplesQueued < MaxSamplesQueued || + state != AL_PLAYING) + break; + + g_usleep(10000); + } while(1); + + if(!self->Playing) + { + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + ALuint bufID; + if(processed == 0) + { + alGenBuffers(1, &bufID); + if(getALError() != AL_NO_ERROR) + { + gst_buffer_unref(buffer); + return GST_FLOW_ERROR; + } + self->Buffers.push_back(bufID); + } + else while(1) + { + alSourceUnqueueBuffers(self->Source, 1, &bufID); + if(getALError() != AL_NO_ERROR) + { + gst_buffer_unref(buffer); + return GST_FLOW_ERROR; + } + + self->SamplesQueued -= getBufferLength(bufID); + processed--; + if(self->SamplesQueued < MaxSamplesQueued) + break; + if(processed == 0) + goto next_buffer; + self->Buffers.erase(find(self->Buffers.begin(), self->Buffers.end(), bufID)); + alDeleteBuffers(1, &bufID); + } + + alBufferData(bufID, self->Format, GST_BUFFER_DATA(buffer), + GST_BUFFER_SIZE(buffer), self->SampleRate); + alSourceQueueBuffers(self->Source, 1, &bufID); + gst_buffer_unref(buffer); + + if(getALError() != AL_NO_ERROR) + return GST_FLOW_ERROR; + + self->SamplesQueued += getBufferLength(bufID); + if(state != AL_PLAYING && processed == 0) + { + alSourcePlay(self->Source); + if(getALError() != AL_NO_ERROR) + return GST_FLOW_ERROR; + } + return GST_FLOW_OK; + } + + // Memory-based data source + std::vector MemData; + size_t MemDataPos; + + static void need_memdata(GstAppSrc *appsrc, guint size, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + if(self->MemDataPos >= self->MemData.size()) + { + gst_app_src_end_of_stream(appsrc); + return; + } + + // "read" the data it wants, up to the remaining amount + guint8 *data = &self->MemData[self->MemDataPos]; + size = (std::min)(size, (guint)(self->MemData.size() - self->MemDataPos)); + self->MemDataPos += size; + + GstBuffer *buffer = gst_buffer_new(); + GST_BUFFER_DATA(buffer) = data; + GST_BUFFER_SIZE(buffer) = size; + + // this takes ownership of the buffer; don't unref + gst_app_src_push_buffer(appsrc, buffer); + } + + static gboolean seek_memdata(GstAppSrc *appsrc, guint64 position, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + if(position > self->MemData.size()) + return FALSE; + self->MemDataPos = position; + return TRUE; + } + + static void memdata_source(GObject *object, GObject *orig, GParamSpec *pspec, OpenALSoundStream *self) + { + GstElement *elem; + g_object_get(self->gstPipeline, "source", &elem, NULL); + + GstAppSrc *appsrc = GST_APP_SRC(elem); + GstAppSrcCallbacks callbacks = { + need_memdata, NULL, seek_memdata + }; + gst_app_src_set_callbacks(appsrc, &callbacks, self, NULL); + gst_app_src_set_size(appsrc, self->MemData.size()); + gst_app_src_set_stream_type(appsrc, GST_APP_STREAM_TYPE_RANDOM_ACCESS); + + gst_object_unref(appsrc); + } + + // Callback-based data source + SoundStreamCallback Callback; + void *UserData; + int BufferBytes; + GstCaps *SrcCaps; + + static void need_callback(GstAppSrc *appsrc, guint size, gpointer user_data) + { + OpenALSoundStream *self = static_cast(user_data); + + GstBuffer *buffer; + if(!self->Playing) + buffer = gst_buffer_new_and_alloc(0); + else + { + buffer = gst_buffer_new_and_alloc(size); + if(!self->Callback(self, GST_BUFFER_DATA(buffer), GST_BUFFER_SIZE(buffer), self->UserData)) + { + gst_buffer_unref(buffer); + + gst_app_src_end_of_stream(appsrc); + return; + } + } + + gst_app_src_push_buffer(appsrc, buffer); + } + + static void callback_source(GObject *object, GObject *orig, GParamSpec *pspec, OpenALSoundStream *self) + { + GstElement *elem; + g_object_get(self->gstPipeline, "source", &elem, NULL); + + GstAppSrc *appsrc = GST_APP_SRC(elem); + GstAppSrcCallbacks callbacks = { + need_callback, NULL, NULL + }; + gst_app_src_set_callbacks(appsrc, &callbacks, self, NULL); + gst_app_src_set_size(appsrc, -1); + gst_app_src_set_max_bytes(appsrc, self->BufferBytes); + gst_app_src_set_stream_type(appsrc, GST_APP_STREAM_TYPE_STREAM); + gst_app_src_set_caps(appsrc, self->SrcCaps); + + gst_object_unref(appsrc); + } + + // General methods + virtual bool SetupSource() + { + // We don't actually use this source if we have an openalsink. However, + // holding on to it helps ensure we don't overrun our allotted voice + // count. + if(Renderer->FreeSfx.size() == 0) + { + FSoundChan *lowest = Renderer->FindLowestChannel(); + if(lowest) Renderer->StopChannel(lowest); + + if(Renderer->FreeSfx.size() == 0) + return false; + } + Source = Renderer->FreeSfx.back(); + Renderer->FreeSfx.pop_back(); + + alSource3f(Source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSource3f(Source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(Source, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSourcef(Source, AL_MAX_GAIN, 1.0f); + alSourcef(Source, AL_GAIN, 1.0f); + alSourcef(Source, AL_PITCH, 1.0f); + alSourcef(Source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcef(Source, AL_SEC_OFFSET, 0.0f); + alSourcei(Source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(Source, AL_LOOPING, AL_FALSE); + if(Renderer->EnvSlot) + { + alSourcef(Source, AL_ROOM_ROLLOFF_FACTOR, 0.0f); + alSourcef(Source, AL_AIR_ABSORPTION_FACTOR, 0.0f); + alSourcei(Source, AL_DIRECT_FILTER, AL_FILTER_NULL); + alSource3i(Source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL); + } + + return (getALError() == AL_NO_ERROR); + } + + bool PipelineSetup() + { + TagList = gst_tag_list_new(); + g_return_val_if_fail(TagList != NULL, false); + + // Flags (can be combined): + // 0x01: video - Render the video stream + // 0x02: audio - Render the audio stream + // 0x04: text - Render subtitles + // 0x08: vis - Render visualisation when no video is present + // 0x10: soft-volume - Use software volume + // 0x20: native-audio - Only use native audio formats + // 0x40: native-video - Only use native video formats + // 0x80: download - Attempt progressive download buffering + int flags = 0x02 | 0x10; + + gstPipeline = gst_element_factory_make("playbin2", NULL); + g_return_val_if_fail(gstPipeline != NULL, false); + + GstElement *sink = gst_element_factory_make("openalsink", NULL); + if(sink != NULL) + { + // Give the sink our device, so it can create its own context and + // source to play with (and not risk cross-contaminating errors) + g_object_set(sink, "device-handle", Renderer->Device, NULL); + } + else + { + static bool warned = false; + if(!warned) + g_warning("Could not create an openalsink\n"); + warned = true; + + sink = gst_element_factory_make("appsink", NULL); + g_return_val_if_fail(sink != NULL, false); + + GstAppSink *appsink = GST_APP_SINK(sink); + GstAppSinkCallbacks callbacks = { + sink_eos, sink_preroll, sink_buffer, NULL + }; + GstCaps *caps = SupportedBufferFormatCaps(); + + gst_app_sink_set_callbacks(appsink, &callbacks, this, NULL); + gst_app_sink_set_drop(appsink, FALSE); + gst_app_sink_set_caps(appsink, caps); + gst_caps_unref(caps); + } + + // This takes ownership of the element; don't unref it + g_object_set(gstPipeline, "audio-sink", sink, NULL); + g_object_set(gstPipeline, "flags", flags, NULL); + return true; + } + + void HandleLoopTags() + { + // FIXME: Sample offsets assume a 44.1khz rate. Need to find some way + // to get the actual rate of the file from GStreamer + bool looppt_is_samples; + unsigned int looppt; + gchar *valstr; + + LoopPts[0] = 0; + if(gst_tag_list_get_string(TagList, "LOOP_START", &valstr)) + { + g_print("Got LOOP_START string: %s\n", valstr); + if(!S_ParseTimeTag(valstr, &looppt_is_samples, &looppt)) + Printf("Invalid LOOP_START tag: '%s'\n", valstr); + else + LoopPts[0] = (looppt_is_samples ? ((gint64)looppt*1000000000/44100) : + ((gint64)looppt*1000000)); + g_free(valstr); + } + LoopPts[1] = -1; + if(gst_tag_list_get_string(TagList, "LOOP_END", &valstr)) + { + g_print("Got LOOP_END string: %s\n", valstr); + if(!S_ParseTimeTag(valstr, &looppt_is_samples, &looppt)) + Printf("Invalid LOOP_END tag: '%s'\n", valstr); + else + { + LoopPts[1] = (looppt_is_samples ? ((gint64)looppt*1000000000/44100) : + ((gint64)looppt*1000000)); + if(LoopPts[1] <= LoopPts[0]) + LoopPts[1] = -1; + } + g_free(valstr); + } + } + + bool PreparePipeline() + { + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return false; + + GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PAUSED); + if(ret == GST_STATE_CHANGE_ASYNC) + { + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_TAG | GST_MESSAGE_ASYNC_DONE; + GstMessage *msg; + while((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + { + PrintErrMsg("Play Error", msg); + ret = GST_STATE_CHANGE_FAILURE; + break; + } + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) + { + GstTagList *tags = NULL; + gst_message_parse_tag(msg, &tags); + + gst_tag_list_insert(TagList, tags, GST_TAG_MERGE_KEEP); + + gst_tag_list_free(tags); + } + else break; + gst_message_unref(msg); + } + } + else if(ret == GST_STATE_CHANGE_SUCCESS) + { + GstMessage *msg; + while((msg=gst_bus_pop(bus)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) + { + GstTagList *tags = NULL; + gst_message_parse_tag(msg, &tags); + + gst_tag_list_insert(TagList, tags, GST_TAG_MERGE_KEEP); + + gst_tag_list_free(tags); + } + gst_message_unref(msg); + } + } + HandleLoopTags(); + + gst_object_unref(bus); + bus = NULL; + + return (ret != GST_STATE_CHANGE_FAILURE); + } + +public: + FTempFileName tmpfile; + ALfloat Volume; + + OpenALSoundStream(OpenALSoundRenderer *renderer) + : Renderer(renderer), gstPipeline(NULL), TagList(NULL), Source(0), + Playing(false), Looping(false), SamplesQueued(0), Callback(NULL), + UserData(NULL), BufferBytes(0), SrcCaps(NULL), Volume(1.0f) + { + LoopPts[0] = LoopPts[1] = 0; + Renderer->Streams.push_back(this); + } + + virtual ~OpenALSoundStream() + { + Playing = false; + + if(SrcCaps) + gst_caps_unref(SrcCaps); + SrcCaps = NULL; + + if(gstPipeline) + { + gst_element_set_state(gstPipeline, GST_STATE_NULL); + gst_object_unref(gstPipeline); + gstPipeline = NULL; + } + + if(TagList) + gst_tag_list_free(TagList); + TagList = NULL; + + if(Source) + { + alSourceRewind(Source); + alSourcei(Source, AL_BUFFER, 0); + + Renderer->FreeSfx.push_back(Source); + Source = 0; + } + + if(Buffers.size() > 0) + { + alDeleteBuffers(Buffers.size(), &Buffers[0]); + Buffers.clear(); + } + getALError(); + + Renderer->Streams.erase(find(Renderer->Streams.begin(), + Renderer->Streams.end(), this)); + Renderer = NULL; + } + + virtual bool Play(bool looping, float vol) + { + if(Playing) + return true; + + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return false; + + Looping = looping; + SetVolume(vol); + + if(Looping) + { + const GstSeekFlags flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_SEGMENT; + gst_element_seek(gstPipeline, 1.0, GST_FORMAT_TIME, flags, + GST_SEEK_TYPE_NONE, 0, GST_SEEK_TYPE_SET, LoopPts[1]); + } + + // Start playing the stream + Playing = true; + GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PLAYING); + if(ret == GST_STATE_CHANGE_FAILURE) + Playing = false; + if(ret == GST_STATE_CHANGE_ASYNC) + { + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_ASYNC_DONE; + GstMessage *msg; + if((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + { + PrintErrMsg("Play Error", msg); + Playing = false; + } + gst_message_unref(msg); + } + } + + gst_object_unref(bus); + bus = NULL; + + return Playing; + } + + virtual void Stop() + { + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return; + + // Stop the stream + GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PAUSED); + if(ret == GST_STATE_CHANGE_ASYNC) + { + Playing = false; + // Wait for the state change before requesting a seek + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_ASYNC_DONE; + GstMessage *msg; + if((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + PrintErrMsg("Stop Error", msg); + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ASYNC_DONE) + ret = GST_STATE_CHANGE_SUCCESS; + gst_message_unref(msg); + } + } + if(ret == GST_STATE_CHANGE_SUCCESS) + { + Playing = false; + + alSourceRewind(Source); + getALError(); + + const GstSeekFlags flags = GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT; + gst_element_seek_simple(gstPipeline, GST_FORMAT_TIME, flags, 0); + } + + gst_object_unref(bus); + bus = NULL; + } + + virtual bool SetPaused(bool paused) + { + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return false; + + GstStateChangeReturn ret; + ret = gst_element_set_state(gstPipeline, (paused ? GST_STATE_PAUSED : GST_STATE_PLAYING)); + if(ret == GST_STATE_CHANGE_ASYNC) + { + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_ASYNC_DONE; + GstMessage *msg; + if((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + { + PrintErrMsg("Pause Error", msg); + ret = GST_STATE_CHANGE_FAILURE; + } + gst_message_unref(msg); + } + } + + if(ret != GST_STATE_CHANGE_FAILURE && paused) + { + alSourcePause(Source); + getALError(); + } + + gst_object_unref(bus); + bus = NULL; + + return (ret != GST_STATE_CHANGE_FAILURE); + } + + virtual void SetVolume(float vol) + { + Volume = vol; + g_object_set(gstPipeline, "volume", (double)(Volume*Renderer->MusicVolume), NULL); + } + + virtual unsigned int GetPosition() + { + GstFormat format = GST_FORMAT_TIME; + gint64 pos; + + // Position will be handled in milliseconds; GStreamer's time format is in nanoseconds + if(gst_element_query_position(gstPipeline, &format, &pos) && format == GST_FORMAT_TIME) + return (unsigned int)(pos / 1000000); + return 0; + } + + virtual bool SetPosition(unsigned int val) + { + gint64 pos = (gint64)val * 1000000; + return gst_element_seek_simple(gstPipeline, GST_FORMAT_TIME, GST_SEEK_FLAG_ACCURATE, pos); + } + + virtual bool IsEnded() + { + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return true; + + GstMessage *msg; + while((msg=gst_bus_pop(bus)) != NULL) + { + switch(GST_MESSAGE_TYPE(msg)) + { + case GST_MESSAGE_SEGMENT_DONE: + case GST_MESSAGE_EOS: + Playing = false; + if(Looping) + Playing = gst_element_seek(gstPipeline, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_SEGMENT, + GST_SEEK_TYPE_SET, LoopPts[0], + GST_SEEK_TYPE_SET, LoopPts[1]); + break; + + case GST_MESSAGE_ERROR: + PrintErrMsg("Pipeline Error", msg); + Playing = false; + break; + + case GST_MESSAGE_WARNING: + PrintErrMsg("Pipeline Warning", msg); + break; + + default: + break; + } + + gst_message_unref(msg); + } + + gst_object_unref(bus); + bus = NULL; + + return !Playing; + } + + bool Init(const char *filename) + { + if(!SetupSource() || !PipelineSetup()) + return false; + + GError *err = NULL; + gchar *uri; + if(g_path_is_absolute(filename)) + uri = g_filename_to_uri(filename, NULL, &err); + else if(g_strrstr(filename, "://") != NULL) + uri = g_strdup(filename); + else + { + gchar *curdir = g_get_current_dir(); + gchar *absolute_path = g_strconcat(curdir, G_DIR_SEPARATOR_S, filename, NULL); + uri = g_filename_to_uri(absolute_path, NULL, &err); + g_free(absolute_path); + g_free(curdir); + } + + if(!uri) + { + if(err) + { + Printf("Failed to convert "TEXTCOLOR_BOLD"%s"TEXTCOLOR_NORMAL" to URI: %s\n", filename, err->message); + g_error_free(err); + } + return false; + } + + g_object_set(gstPipeline, "uri", uri, NULL); + g_free(uri); + + return PreparePipeline(); + } + + bool Init(const BYTE *data, unsigned int datalen) + { + // Can't keep the original pointer since the memory can apparently be + // overwritten at some point (happens with MIDI data, at least) + MemData.resize(datalen); + memcpy(&MemData[0], data, datalen); + MemDataPos = 0; + + if(!SetupSource() || !PipelineSetup()) + return false; + + g_object_set(gstPipeline, "uri", "appsrc://", NULL); + g_signal_connect(gstPipeline, "deep-notify::source", G_CALLBACK(memdata_source), this); + + return PreparePipeline(); + } + + bool Init(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) + { + Callback = callback; + UserData = userdata; + BufferBytes = buffbytes; + + GstStructure *structure; + if((flags&Float)) + structure = gst_structure_new("audio/x-raw-float", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 32, NULL); + else if((flags&Bits32)) + structure = gst_structure_new("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 32, + "depth", G_TYPE_INT, 32, + "signed", G_TYPE_BOOLEAN, TRUE, NULL); + else if((flags&Bits8)) + structure = gst_structure_new("audio/x-raw-int", + "width", G_TYPE_INT, 8, + "depth", G_TYPE_INT, 8, + "signed", G_TYPE_BOOLEAN, TRUE, NULL); + else + structure = gst_structure_new("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, 16, + "depth", G_TYPE_INT, 16, + "signed", G_TYPE_BOOLEAN, TRUE, NULL); + gst_structure_set(structure, "channels", G_TYPE_INT, (flags&Mono)?1:2, NULL); + gst_structure_set(structure, "rate", G_TYPE_INT, samplerate, NULL); + + SrcCaps = gst_caps_new_full(structure, NULL); + + if(!SrcCaps || !SetupSource() || !PipelineSetup()) + return false; + + g_object_set(gstPipeline, "uri", "appsrc://", NULL); + g_signal_connect(gstPipeline, "deep-notify::source", G_CALLBACK(callback_source), this); + + return PreparePipeline(); + } +}; + + +class Decoder +{ + GstElement *gstPipeline, *gstSink; + GstTagList *TagList; + + const guint8 *MemData; + size_t MemDataSize; + size_t MemDataPos; + GstCaps *SrcCaps; + + static void need_memdata(GstAppSrc *appsrc, guint size, gpointer user_data) + { + Decoder *self = static_cast(user_data); + GstFlowReturn ret; + + if(self->MemDataPos >= self->MemDataSize) + { + gst_app_src_end_of_stream(appsrc); + return; + } + + const guint8 *data = &self->MemData[self->MemDataPos]; + size = (std::min)(size, (guint)(self->MemDataSize - self->MemDataPos)); + self->MemDataPos += size; + + GstBuffer *buffer = gst_buffer_new(); + GST_BUFFER_DATA(buffer) = const_cast(data); + GST_BUFFER_SIZE(buffer) = size; + + gst_app_src_push_buffer(appsrc, buffer); + } + + static gboolean seek_memdata(GstAppSrc *appsrc, guint64 position, gpointer user_data) + { + Decoder *self = static_cast(user_data); + + if(position > self->MemDataSize) + return FALSE; + self->MemDataPos = position; + return TRUE; + } + + static void memdata_source(GObject *object, GObject *orig, GParamSpec *pspec, Decoder *self) + { + GstElement *elem; + g_object_get(self->gstPipeline, "source", &elem, NULL); + + GstAppSrc *appsrc = GST_APP_SRC(elem); + GstAppSrcCallbacks callbacks = { + need_memdata, NULL, seek_memdata + }; + gst_app_src_set_callbacks(appsrc, &callbacks, self, NULL); + gst_app_src_set_size(appsrc, self->MemDataSize); + gst_app_src_set_caps(appsrc, self->SrcCaps); + gst_app_src_set_stream_type(appsrc, GST_APP_STREAM_TYPE_RANDOM_ACCESS); + + gst_object_unref(appsrc); + } + + static GstFlowReturn sink_preroll(GstAppSink *sink, gpointer user_data) + { + Decoder *self = static_cast(user_data); + + GstBuffer *buffer = gst_app_sink_pull_preroll(sink); + if(!buffer) return GST_FLOW_ERROR; + + if(self->OutRate == 0) + { + GstCaps *caps = GST_BUFFER_CAPS(buffer); + + gint channels = 0, rate = 0, bits = 0, i; + for(i = gst_caps_get_size(caps)-1;i >= 0;i--) + { + GstStructure *struc = gst_caps_get_structure(caps, i); + if(gst_structure_has_field(struc, "channels")) + gst_structure_get_int(struc, "channels", &channels); + if(gst_structure_has_field(struc, "rate")) + gst_structure_get_int(struc, "rate", &rate); + if(gst_structure_has_field(struc, "width")) + gst_structure_get_int(struc, "width", &bits); + } + + self->OutChannels = channels; + self->OutBits = bits; + self->OutRate = rate; + } + + gst_buffer_unref(buffer); + if(self->OutRate <= 0) + return GST_FLOW_ERROR; + return GST_FLOW_OK; + } + + static GstFlowReturn sink_buffer(GstAppSink *sink, gpointer user_data) + { + Decoder *self = static_cast(user_data); + + GstBuffer *buffer = gst_app_sink_pull_buffer(sink); + if(!buffer) return GST_FLOW_ERROR; + + guint newsize = GST_BUFFER_SIZE(buffer); + size_t pos = self->OutData.size(); + self->OutData.resize(pos+newsize); + + memcpy(&self->OutData[pos], GST_BUFFER_DATA(buffer), newsize); + + gst_buffer_unref(buffer); + return GST_FLOW_OK; + } + + bool PipelineSetup(int forcebits) + { + if(forcebits && forcebits != 8 && forcebits != 16) + return false; + + TagList = gst_tag_list_new(); + g_return_val_if_fail(TagList != NULL, false); + + gstPipeline = gst_element_factory_make("playbin2", NULL); + g_return_val_if_fail(gstPipeline != NULL, false); + + gstSink = gst_element_factory_make("appsink", NULL); + g_return_val_if_fail(gstSink != NULL, false); + + GstAppSink *appsink = GST_APP_SINK(gstSink); + GstAppSinkCallbacks callbacks = { + NULL, sink_preroll, sink_buffer, NULL + }; + + GstCaps *caps = SupportedBufferFormatCaps(forcebits); + g_object_set(appsink, "sync", FALSE, NULL); + gst_app_sink_set_callbacks(appsink, &callbacks, this, NULL); + gst_app_sink_set_drop(appsink, FALSE); + gst_app_sink_set_caps(appsink, caps); + + g_object_set(gstPipeline, "audio-sink", gst_object_ref(gstSink), NULL); + g_object_set(gstPipeline, "flags", 0x02, NULL); + + gst_caps_unref(caps); + return true; + } + + void HandleLoopTags(unsigned int looppt[2], bool looppt_is_samples[2], bool has_looppt[2]) + { + gchar *valstr; + + if(gst_tag_list_get_string(TagList, "LOOP_START", &valstr)) + { + g_print("Got LOOP_START string: %s\n", valstr); + has_looppt[0] = S_ParseTimeTag(valstr, &looppt_is_samples[0], &looppt[0]); + if(!has_looppt[0]) + Printf("Invalid LOOP_START tag: '%s'\n", valstr); + g_free(valstr); + } + if(gst_tag_list_get_string(TagList, "LOOP_END", &valstr)) + { + g_print("Got LOOP_END string: %s\n", valstr); + has_looppt[1] = S_ParseTimeTag(valstr, &looppt_is_samples[1], &looppt[1]); + if(!has_looppt[1]) + Printf("Invalid LOOP_END tag: '%s'\n", valstr); + g_free(valstr); + } + } + +public: + std::vector OutData; + ALint LoopPts[2]; + ALsizei OutRate; + ALuint OutChannels; + ALuint OutBits; + + Decoder() + : gstPipeline(NULL), gstSink(NULL), TagList(NULL), SrcCaps(NULL), + OutRate(0), OutChannels(0), OutBits(0) + { LoopPts[0] = LoopPts[1] = 0; } + + virtual ~Decoder() + { + if(SrcCaps) + gst_caps_unref(SrcCaps); + SrcCaps = NULL; + + if(gstSink) + gst_object_unref(gstSink); + gstSink = NULL; + + if(gstPipeline) + { + gst_element_set_state(gstPipeline, GST_STATE_NULL); + gst_object_unref(gstPipeline); + gstPipeline = NULL; + } + + if(TagList) + gst_tag_list_free(TagList); + TagList = NULL; + } + + bool DecodeRaw(const void *data, unsigned int datalen, int rawbits, int rawchannels, int rawrate) + { + GstStructure *structure = gst_structure_new("audio/x-raw-int", + "endianness", G_TYPE_INT, G_BYTE_ORDER, + "width", G_TYPE_INT, rawbits, + "depth", G_TYPE_INT, rawbits, + "signed", G_TYPE_BOOLEAN, ((rawbits==8)?FALSE:TRUE), NULL); + gst_structure_set(structure, "channels", G_TYPE_INT, rawchannels, NULL); + gst_structure_set(structure, "rate", G_TYPE_INT, rawrate, NULL); + + SrcCaps = gst_caps_new_full(structure, NULL); + if(!SrcCaps) + return false; + return Decode(data, datalen); + } + + bool Decode(const void *data, unsigned int datalen, int forcebits=0) + { + MemData = static_cast(data); + MemDataSize = datalen; + MemDataPos = 0; + OutData.clear(); + + if(!PipelineSetup(forcebits)) + return false; + + g_object_set(gstPipeline, "uri", "appsrc://", NULL); + g_signal_connect(gstPipeline, "deep-notify::source", G_CALLBACK(memdata_source), this); + + + GstBus *bus = gst_element_get_bus(gstPipeline); + if(!bus) return false; + GstMessage *msg; + + GstStateChangeReturn ret = gst_element_set_state(gstPipeline, GST_STATE_PLAYING); + if(ret == GST_STATE_CHANGE_ASYNC) + { + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_TAG | GST_MESSAGE_ASYNC_DONE; + while((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ASYNC_DONE) + { + ret = GST_STATE_CHANGE_SUCCESS; + break; + } + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) + { + GstTagList *tags = NULL; + gst_message_parse_tag(msg, &tags); + + gst_tag_list_insert(TagList, tags, GST_TAG_MERGE_KEEP); + + gst_tag_list_free(tags); + } + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + { + ret = GST_STATE_CHANGE_FAILURE; + PrintErrMsg("Decoder Error", msg); + break; + } + gst_message_unref(msg); + } + } + + bool err = true; + if(ret == GST_STATE_CHANGE_SUCCESS) + { + const GstMessageType types = GST_MESSAGE_ERROR | GST_MESSAGE_TAG | GST_MESSAGE_EOS; + while((msg=gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, types)) != NULL) + { + if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_EOS) + { + err = false; + break; + } + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) + { + GstTagList *tags = NULL; + gst_message_parse_tag(msg, &tags); + + gst_tag_list_insert(TagList, tags, GST_TAG_MERGE_KEEP); + + gst_tag_list_free(tags); + } + else if(GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) + { + PrintErrMsg("Decoder Error", msg); + break; + } + gst_message_unref(msg); + } + } + + if(!err) + { + ALuint FrameSize = OutChannels*OutBits/8; + if(OutData.size() >= FrameSize) + { + // HACK: Evilness abound. Seems GStreamer doesn't like (certain?) + // wave files and produces an extra sample, which can cause an + // audible click at the end. Cut it. + OutData.resize(OutData.size() - FrameSize); + } + + unsigned int looppt[2] = { 0, 0 }; + bool looppt_is_samples[2] = { true, true }; + bool has_looppt[2] = { false, false }; + HandleLoopTags(looppt, looppt_is_samples, has_looppt); + + if(has_looppt[0] || has_looppt[1]) + { + if(!has_looppt[0]) + LoopPts[0] = 0; + else if(looppt_is_samples[0]) + LoopPts[0] = (std::min)((ALint)looppt[0], + (ALint)(OutData.size() / FrameSize)); + else + LoopPts[0] = (std::min)((ALint)((gint64)looppt[0] * OutRate / 1000), + (ALint)(OutData.size() / FrameSize)); + + if(!has_looppt[1]) + LoopPts[1] = OutData.size() / FrameSize; + else if(looppt_is_samples[1]) + LoopPts[1] = (std::min)((ALint)looppt[1], + (ALint)(OutData.size() / FrameSize)); + else + LoopPts[1] = (std::min)((ALint)((gint64)looppt[1] * OutRate / 1000), + (ALint)(OutData.size() / FrameSize)); + } + } + + gst_object_unref(bus); + bus = NULL; + + return !err; + } +}; +/*** GStreamer end ***/ + +template +static void LoadALFunc(const char *name, T *x) +{ *x = reinterpret_cast(alGetProcAddress(name)); } + +OpenALSoundRenderer::OpenALSoundRenderer() + : Device(NULL), Context(NULL), SrcDistanceModel(false), SFXPaused(0), + PrevEnvironment(NULL), EnvSlot(0) +{ + EnvFilters[0] = EnvFilters[1] = 0; + + Printf("I_InitSound: Initializing OpenAL\n"); + + static bool GSTInited = false; + if(!GSTInited) + { + GError *error; + if(!gst_init_check(NULL, NULL, &error)) + { + Printf("Failed to initialize GStreamer: %s\n", error->message); + g_error_free(error); + return; + } + GSTInited = true; + } + + if((*snd_aldevice)[0] != 0) + { + Device = alcOpenDevice(*snd_aldevice); + if(!Device) + Printf(TEXTCOLOR_BLUE" Failed to open device "TEXTCOLOR_BOLD"%s"TEXTCOLOR_BLUE". Trying default.\n", *snd_aldevice); + } + + if(!Device) + { + Device = alcOpenDevice(NULL); + if(!Device) + { + Printf(TEXTCOLOR_RED" Could not open audio device\n"); + return; + } + } + + Printf(" Opened device "TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_DEVICE_SPECIFIER)); + ALCint major=0, minor=0; + alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major); + alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor); + DPrintf(" ALC Version: "TEXTCOLOR_BLUE"%d.%d\n", major, minor); + DPrintf(" ALC Extensions: "TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS)); + + DisconnectNotify = alcIsExtensionPresent(Device, "ALC_EXT_disconnect"); + + std::vector attribs; + if(*snd_samplerate > 0) + { + attribs.push_back(ALC_FREQUENCY); + attribs.push_back(*snd_samplerate); + } + // Make sure one source is capable of stereo output with the rest doing + // mono, without running out of voices + attribs.push_back(ALC_MONO_SOURCES); + attribs.push_back((std::max)(*snd_channels, 2) - 1); + attribs.push_back(ALC_STEREO_SOURCES); + attribs.push_back(1); + // Other attribs..? + attribs.push_back(0); + + Context = alcCreateContext(Device, &attribs[0]); + if(!Context || alcMakeContextCurrent(Context) == ALC_FALSE) + { + Printf(TEXTCOLOR_RED" Failed to setup context: %s\n", alcGetString(Device, alcGetError(Device))); + if(Context) + alcDestroyContext(Context); + Context = NULL; + alcCloseDevice(Device); + Device = NULL; + return; + } + attribs.clear(); + + DPrintf(" Vendor: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR)); + DPrintf(" Renderer: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER)); + DPrintf(" Version: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION)); + DPrintf(" Extensions: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS)); + + SrcDistanceModel = alIsExtensionPresent("AL_EXT_source_distance_model"); + LoopPoints = alIsExtensionPresent("AL_EXT_loop_points"); + + alDopplerFactor(0.5f); + alSpeedOfSound(343.3f * 96.0f); + alDistanceModel(AL_INVERSE_DISTANCE); + if(SrcDistanceModel) + alEnable(AL_SOURCE_DISTANCE_MODEL); + + ALenum err = getALError(); + if(err != AL_NO_ERROR) + { + alcMakeContextCurrent(NULL); + alcDestroyContext(Context); + Context = NULL; + alcCloseDevice(Device); + Device = NULL; + return; + } + + ALCint numMono=0, numStereo=0; + alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &numMono); + alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &numStereo); + + Sources.resize((std::min)((std::max)(*snd_channels, 2), numMono+numStereo)); + for(size_t i = 0;i < Sources.size();i++) + { + alGenSources(1, &Sources[i]); + if(getALError() != AL_NO_ERROR) + { + Sources.resize(i); + break; + } + FreeSfx.push_back(Sources[i]); + } + if(Sources.size() == 0) + { + Printf(TEXTCOLOR_RED" Error: could not generate any sound sources!\n"); + alcMakeContextCurrent(NULL); + alcDestroyContext(Context); + Context = NULL; + alcCloseDevice(Device); + Device = NULL; + return; + } + DPrintf(" Allocated "TEXTCOLOR_BLUE"%u"TEXTCOLOR_NORMAL" sources\n", Sources.size()); + + LastWaterAbsorb = 0.0f; + if(*snd_efx && alcIsExtensionPresent(Device, "ALC_EXT_EFX")) + { + // EFX function pointers +#define LOAD_FUNC(x) (LoadALFunc(#x, &x)) + LOAD_FUNC(alGenEffects); + LOAD_FUNC(alDeleteEffects); + LOAD_FUNC(alIsEffect); + LOAD_FUNC(alEffecti); + LOAD_FUNC(alEffectiv); + LOAD_FUNC(alEffectf); + LOAD_FUNC(alEffectfv); + LOAD_FUNC(alGetEffecti); + LOAD_FUNC(alGetEffectiv); + LOAD_FUNC(alGetEffectf); + LOAD_FUNC(alGetEffectfv); + + LOAD_FUNC(alGenFilters); + LOAD_FUNC(alDeleteFilters); + LOAD_FUNC(alIsFilter); + LOAD_FUNC(alFilteri); + LOAD_FUNC(alFilteriv); + LOAD_FUNC(alFilterf); + LOAD_FUNC(alFilterfv); + LOAD_FUNC(alGetFilteri); + LOAD_FUNC(alGetFilteriv); + LOAD_FUNC(alGetFilterf); + LOAD_FUNC(alGetFilterfv); + + LOAD_FUNC(alGenAuxiliaryEffectSlots); + LOAD_FUNC(alDeleteAuxiliaryEffectSlots); + LOAD_FUNC(alIsAuxiliaryEffectSlot); + LOAD_FUNC(alAuxiliaryEffectSloti); + LOAD_FUNC(alAuxiliaryEffectSlotiv); + LOAD_FUNC(alAuxiliaryEffectSlotf); + LOAD_FUNC(alAuxiliaryEffectSlotfv); + LOAD_FUNC(alGetAuxiliaryEffectSloti); + LOAD_FUNC(alGetAuxiliaryEffectSlotiv); + LOAD_FUNC(alGetAuxiliaryEffectSlotf); + LOAD_FUNC(alGetAuxiliaryEffectSlotfv); +#undef LOAD_FUNC + if(getALError() == AL_NO_ERROR) + { + ALuint envReverb; + alGenEffects(1, &envReverb); + if(getALError() == AL_NO_ERROR) + { + alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + if(alGetError() == AL_NO_ERROR) + DPrintf(" EAX Reverb found\n"); + alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + if(alGetError() == AL_NO_ERROR) + DPrintf(" Standard Reverb found\n"); + + alDeleteEffects(1, &envReverb); + getALError(); + } + + alGenAuxiliaryEffectSlots(1, &EnvSlot); + alGenFilters(2, EnvFilters); + if(getALError() == AL_NO_ERROR) + { + alFilteri(EnvFilters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS); + alFilteri(EnvFilters[1], AL_FILTER_TYPE, AL_FILTER_LOWPASS); + if(getALError() == AL_NO_ERROR) + DPrintf(" Lowpass found\n"); + else + { + alDeleteFilters(2, EnvFilters); + EnvFilters[0] = EnvFilters[1] = 0; + alDeleteAuxiliaryEffectSlots(1, &EnvSlot); + EnvSlot = 0; + getALError(); + } + } + else + { + alDeleteFilters(2, EnvFilters); + alDeleteAuxiliaryEffectSlots(1, &EnvSlot); + EnvFilters[0] = EnvFilters[1] = 0; + EnvSlot = 0; + getALError(); + } + } + } + + if(EnvSlot) + Printf(" EFX enabled\n"); + + snd_sfxvolume.Callback(); +} + +OpenALSoundRenderer::~OpenALSoundRenderer() +{ + if(!Device) + return; + + while(Streams.size() > 0) + delete Streams[0]; + + alDeleteSources(Sources.size(), &Sources[0]); + Sources.clear(); + FreeSfx.clear(); + SfxGroup.clear(); + PausableSfx.clear(); + ReverbSfx.clear(); + + for(EffectMap::iterator i = EnvEffects.begin();i != EnvEffects.end();i++) + { + if(i->second) + alDeleteEffects(1, &(i->second)); + } + EnvEffects.clear(); + + if(EnvSlot) + { + alDeleteAuxiliaryEffectSlots(1, &EnvSlot); + alDeleteFilters(2, EnvFilters); + } + EnvSlot = 0; + EnvFilters[0] = EnvFilters[1] = 0; + + alcMakeContextCurrent(NULL); + alcDestroyContext(Context); + Context = NULL; + alcCloseDevice(Device); + Device = NULL; +} + +void OpenALSoundRenderer::SetSfxVolume(float volume) +{ + SfxVolume = volume; + + FSoundChan *schan = Channels; + while(schan) + { + if(schan->SysChannel != NULL) + { + ALuint source = *((ALuint*)schan->SysChannel); + volume = SfxVolume; + + alcSuspendContext(Context); + alSourcef(source, AL_MAX_GAIN, volume); + if(schan->ManualGain) + volume *= GetRolloff(&schan->Rolloff, sqrt(schan->DistanceSqr)); + alSourcef(source, AL_GAIN, volume); + } + schan = schan->NextChan; + } + + getALError(); +} + +void OpenALSoundRenderer::SetMusicVolume(float volume) +{ + MusicVolume = volume; + foreach(OpenALSoundStream*, i, Streams) + (*i)->SetVolume((*i)->Volume); +} + +unsigned int OpenALSoundRenderer::GetMSLength(SoundHandle sfx) +{ + if(sfx.data) + { + ALuint buffer = *((ALuint*)sfx.data); + if(alIsBuffer(buffer)) + { + ALint bits, channels, freq, size; + alGetBufferi(buffer, AL_BITS, &bits); + alGetBufferi(buffer, AL_CHANNELS, &channels); + alGetBufferi(buffer, AL_FREQUENCY, &freq); + alGetBufferi(buffer, AL_SIZE, &size); + if(getALError() == AL_NO_ERROR) + return (unsigned int)(size / (channels*bits/8) * 1000.0 / freq); + } + } + return 0; +} + +unsigned int OpenALSoundRenderer::GetSampleLength(SoundHandle sfx) +{ + if(sfx.data) + return getBufferLength(*((ALuint*)sfx.data)); + return 0; +} + +float OpenALSoundRenderer::GetOutputRate() +{ + ALCint rate = *snd_samplerate; // Default, just in case + alcGetIntegerv(Device, ALC_FREQUENCY, 1, &rate); + return (float)rate; +} + + +SoundHandle OpenALSoundRenderer::LoadSoundRaw(BYTE *sfxdata, int length, int frequency, int channels, int bits, int loopstart) +{ + SoundHandle retval = { NULL }; + + if(length == 0) return retval; + + if(bits == -8) + { + // Simple signed->unsigned conversion + for(int i = 0;i < length;i++) + sfxdata[i] ^= 0x80; + bits = -bits; + } + + ALenum format = AL_NONE; + if(bits == 16) + { + if(channels == 1) format = AL_FORMAT_MONO16; + if(channels == 2) format = AL_FORMAT_STEREO16; + } + else if(bits == 8) + { + if(channels == 1) format = AL_FORMAT_MONO8; + if(channels == 2) format = AL_FORMAT_STEREO8; + } + + Decoder decoder; + if(format == AL_NONE && decoder.DecodeRaw(sfxdata, length, bits, channels, frequency)) + { + sfxdata = &decoder.OutData[0]; + length = decoder.OutData.size(); + frequency = decoder.OutRate; + bits = decoder.OutBits; + channels = decoder.OutChannels; + format = FormatFromDesc(bits, channels); + } + if(format == AL_NONE || frequency <= 0) + { + Printf("Unhandled format: %d bit, %d channel, %d hz\n", bits, channels, frequency); + return retval; + } + length -= length%(channels*bits/8); + + ALenum err; + ALuint buffer = 0; + alGenBuffers(1, &buffer); + alBufferData(buffer, format, sfxdata, length, frequency); + if((err=getALError()) != AL_NO_ERROR) + { + Printf("Failed to buffer data: %s\n", alGetString(err)); + alDeleteBuffers(1, &buffer); + getALError(); + return retval; + } + + if(loopstart > 0 && LoopPoints) + { + ALint loops[2] = { + loopstart, + length / (channels*bits/8) + }; + Printf("Setting loop points %d -> %d\n", loops[0], loops[1]); + alBufferiv(buffer, AL_LOOP_POINTS, loops); + getALError(); + } + else if(loopstart > 0) + { + static bool warned = false; + if(!warned) + Printf("Loop points not supported!\n"); + warned = true; + } + + retval.data = new ALuint(buffer); + return retval; +} + +SoundHandle OpenALSoundRenderer::LoadSound(BYTE *sfxdata, int length) +{ + SoundHandle retval = { NULL }; + + Decoder decoder; + if(!decoder.Decode(sfxdata, length)) + { + DPrintf("Failed to decode sound\n"); + return retval; + } + + ALenum format = FormatFromDesc(decoder.OutBits, decoder.OutChannels); + if(format == AL_NONE) + { + Printf("Unhandled format: %d bit, %d channel\n", decoder.OutBits, decoder.OutChannels); + return retval; + } + + ALenum err; + ALuint buffer = 0; + alGenBuffers(1, &buffer); + alBufferData(buffer, format, &decoder.OutData[0], + decoder.OutData.size(), decoder.OutRate); + if((err=getALError()) != AL_NO_ERROR) + { + Printf("Failed to buffer data: %s\n", alGetString(err)); + alDeleteBuffers(1, &buffer); + getALError(); + return retval; + } + + if(LoopPoints && decoder.LoopPts[0] > decoder.LoopPts[1]) + { + alBufferiv(buffer, AL_LOOP_POINTS, decoder.LoopPts); + getALError(); + } + else if(decoder.LoopPts[0] > decoder.LoopPts[1]) + { + static bool warned = false; + if(!warned) + Printf("Loop points not supported!\n"); + warned = true; + } + + retval.data = new ALuint(buffer); + return retval; +} + +void OpenALSoundRenderer::UnloadSound(SoundHandle sfx) +{ + if(!sfx.data) + return; + + FSoundChan *schan = Channels; + while(schan) + { + if(schan->SysChannel) + { + ALint bufID = 0; + alGetSourcei(*((ALuint*)schan->SysChannel), AL_BUFFER, &bufID); + if(bufID == *((ALint*)sfx.data)) + { + FSoundChan *next = schan->NextChan; + StopChannel(schan); + schan = next; + continue; + } + } + schan = schan->NextChan; + } + + alDeleteBuffers(1, ((ALuint*)sfx.data)); + getALError(); + delete ((ALuint*)sfx.data); +} + +short *OpenALSoundRenderer::DecodeSample(int outlen, const void *coded, int sizebytes, ECodecType type) +{ + Decoder decoder; + // Force 16-bit + if(!decoder.Decode(coded, sizebytes, 16)) + { + DPrintf("Failed to decode sample\n"); + return NULL; + } + if(decoder.OutBits != 16 || decoder.OutChannels != 1) + { + DPrintf("Sample is not mono\n"); + return NULL; + } + + short *samples = (short*)malloc(outlen); + if((size_t)outlen > decoder.OutData.size()) + { + memcpy(samples, &decoder.OutData[0], decoder.OutData.size()); + memset(&samples[decoder.OutData.size()/sizeof(short)], 0, outlen-decoder.OutData.size()); + } + else + memcpy(samples, &decoder.OutData[0], outlen); + + return samples; +} + +SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) +{ + OpenALSoundStream *stream = new OpenALSoundStream(this); + if(!stream->Init(callback, buffbytes, flags, samplerate, userdata)) + { + delete stream; + stream = NULL; + } + return stream; +} + +SoundStream *OpenALSoundRenderer::OpenStream(const char *filename, int flags, int offset, int length) +{ + std::auto_ptr stream(new OpenALSoundStream(this)); + + if(offset > 0) + { + // If there's an offset to the start of the data, separate it into its + // own temp file + FILE *infile = fopen(filename, "rb"); + FILE *f = fopen(stream->tmpfile, "wb"); + if(!infile || !f || fseek(infile, offset, SEEK_SET) != 0) + { + if(infile) fclose(infile); + if(f) fclose(f); + return NULL; + } + BYTE buf[1024]; + size_t got; + do { + got = (std::min)(sizeof(buf), (size_t)length); + got = fread(buf, 1, got, infile); + if(got == 0) + break; + } while(fwrite(buf, 1, got, f) == got && (length-=got) > 0); + fclose(f); + fclose(infile); + + filename = stream->tmpfile; + } + + bool ok = ((offset == -1) ? stream->Init((const BYTE*)filename, length) : + stream->Init(filename)); + if(ok == false) + return NULL; + + return stream.release(); +} + +FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan) +{ + if(FreeSfx.size() == 0) + { + FSoundChan *lowest = FindLowestChannel(); + if(lowest) StopChannel(lowest); + + if(FreeSfx.size() == 0) + return NULL; + } + + ALuint buffer = *((ALuint*)sfx.data); + ALuint &source = *find(Sources.begin(), Sources.end(), FreeSfx.back()); + alSource3f(source, AL_POSITION, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP)?AL_TRUE:AL_FALSE); + alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); + alSourcef(source, AL_MAX_DISTANCE, 1000.0f); + alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcef(source, AL_MAX_GAIN, SfxVolume); + alSourcef(source, AL_GAIN, SfxVolume*vol); + if(EnvSlot) + { + if(!(chanflags&SNDF_NOREVERB)) + { + alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]); + alSourcef(source, AL_AIR_ABSORPTION_FACTOR, LastWaterAbsorb); + } + else + { + alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL); + alSourcef(source, AL_AIR_ABSORPTION_FACTOR, 0.0f); + } + alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, 0.0f); + alSourcef(source, AL_PITCH, PITCH(pitch)); + } + else if(LastWaterAbsorb > 0.0f && !(chanflags&SNDF_NOREVERB)) + alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT); + else + alSourcef(source, AL_PITCH, PITCH(pitch)); + if(!reuse_chan) + alSourcef(source, AL_SEC_OFFSET, 0.0f); + else + { + if((chanflags&SNDF_ABSTIME)) + alSourcef(source, AL_SEC_OFFSET, reuse_chan->StartTime.Lo/1000.0f); + else + { + // FIXME: set offset based on the current time and the StartTime + alSourcef(source, AL_SEC_OFFSET, 0.0f); + } + } + if(getALError() != AL_NO_ERROR) + return NULL; + + alSourcei(source, AL_BUFFER, buffer); + if((chanflags&SNDF_NOPAUSE) || !SFXPaused) + alSourcePlay(source); + if(getALError() != AL_NO_ERROR) + { + alSourcei(source, AL_BUFFER, 0); + getALError(); + return NULL; + } + + if(!(chanflags&SNDF_NOREVERB)) + ReverbSfx.push_back(source); + if(!(chanflags&SNDF_NOPAUSE)) + PausableSfx.push_back(source); + SfxGroup.push_back(source); + FreeSfx.pop_back(); + + FISoundChannel *chan = reuse_chan; + if(!chan) chan = S_GetChannel(&source); + else chan->SysChannel = &source; + + chan->Rolloff.RolloffType = ROLLOFF_Linear; + chan->Rolloff.MaxDistance = 2.0f; + chan->Rolloff.MinDistance = 1.0f; + chan->DistanceScale = 1.0f; + chan->DistanceSqr = (2.0f-vol)*(2.0f-vol); + chan->ManualGain = true; + + return chan; +} + +FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol, + FRolloffInfo *rolloff, float distscale, int pitch, int priority, const FVector3 &pos, const FVector3 &vel, + int channum, int chanflags, FISoundChannel *reuse_chan) +{ + float dist_sqr = (pos - listener->position).LengthSquared() * + distscale*distscale; + + if(FreeSfx.size() == 0) + { + FSoundChan *lowest = FindLowestChannel(); + if(lowest) + { + if(lowest->Priority < priority || (lowest->Priority == priority && + lowest->DistanceSqr > dist_sqr)) + StopChannel(lowest); + } + if(FreeSfx.size() == 0) + return NULL; + } + + float rolloffFactor, gain; + bool manualGain = true; + + ALuint buffer = *((ALuint*)sfx.data); + ALint channels = 1; + alGetBufferi(buffer, AL_CHANNELS, &channels); + + ALuint &source = *find(Sources.begin(), Sources.end(), FreeSfx.back()); + alSource3f(source, AL_POSITION, pos[0], pos[1], -pos[2]); + alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]); + alSource3f(source, AL_DIRECTION, 0.0f, 0.0f, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); + + alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP)?AL_TRUE:AL_FALSE); + + // Multi-channel sources won't attenuate in OpenAL, and "area sounds" have + // special rolloff properties (they have a panning radius of 32 units, but + // starts attenuating at MinDistance). + if(channels == 1 && !(chanflags&SNDF_AREA)) + { + if(rolloff->RolloffType == ROLLOFF_Log) + { + if(SrcDistanceModel) + alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE); + alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale); + alSourcef(source, AL_MAX_DISTANCE, (1000.0f+rolloff->MinDistance)/distscale); + rolloffFactor = rolloff->RolloffFactor; + manualGain = false; + gain = 1.0f; + } + else if(rolloff->RolloffType == ROLLOFF_Linear && SrcDistanceModel) + { + alSourcei(source, AL_DISTANCE_MODEL, AL_LINEAR_DISTANCE); + alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale); + alSourcef(source, AL_MAX_DISTANCE, rolloff->MaxDistance/distscale); + rolloffFactor = 1.0f; + manualGain = false; + gain = 1.0f; + } + } + if(manualGain) + { + if(SrcDistanceModel) + alSourcei(source, AL_DISTANCE_MODEL, AL_NONE); + if((chanflags&SNDF_AREA) && rolloff->MinDistance < 32.0f) + alSourcef(source, AL_REFERENCE_DISTANCE, 32.0f/distscale); + else + alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale); + alSourcef(source, AL_MAX_DISTANCE, (1000.0f+rolloff->MinDistance)/distscale); + rolloffFactor = 0.0f; + gain = GetRolloff(rolloff, sqrt(dist_sqr)); + } + alSourcef(source, AL_ROLLOFF_FACTOR, rolloffFactor); + alSourcef(source, AL_MAX_GAIN, SfxVolume); + alSourcef(source, AL_GAIN, SfxVolume * gain); + + if(EnvSlot) + { + if(!(chanflags&SNDF_NOREVERB)) + { + alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]); + alSourcef(source, AL_AIR_ABSORPTION_FACTOR, LastWaterAbsorb); + } + else + { + alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL); + alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL); + alSourcef(source, AL_AIR_ABSORPTION_FACTOR, 0.0f); + } + alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, rolloffFactor); + alSourcef(source, AL_PITCH, PITCH(pitch)); + } + else if(LastWaterAbsorb > 0.0f && !(chanflags&SNDF_NOREVERB)) + alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT); + else + alSourcef(source, AL_PITCH, PITCH(pitch)); + if(!reuse_chan) + alSourcef(source, AL_SEC_OFFSET, 0.0f); + else + { + if((chanflags&SNDF_ABSTIME)) + alSourcef(source, AL_SEC_OFFSET, reuse_chan->StartTime.Lo/1000.0f); + else + { + // FIXME: set offset based on the current time and the StartTime + alSourcef(source, AL_SAMPLE_OFFSET, 0.0f); + } + } + if(getALError() != AL_NO_ERROR) + return NULL; + + alSourcei(source, AL_BUFFER, buffer); + if((chanflags&SNDF_NOPAUSE) || !SFXPaused) + alSourcePlay(source); + if(getALError() != AL_NO_ERROR) + { + alSourcei(source, AL_BUFFER, 0); + getALError(); + return NULL; + } + + if(!(chanflags&SNDF_NOREVERB)) + ReverbSfx.push_back(source); + if(!(chanflags&SNDF_NOPAUSE)) + PausableSfx.push_back(source); + SfxGroup.push_back(source); + FreeSfx.pop_back(); + + FISoundChannel *chan = reuse_chan; + if(!chan) chan = S_GetChannel(&source); + else chan->SysChannel = &source; + + chan->Rolloff = *rolloff; + chan->DistanceScale = distscale; + chan->DistanceSqr = dist_sqr; + chan->ManualGain = manualGain; + + return chan; +} + +void OpenALSoundRenderer::StopChannel(FISoundChannel *chan) +{ + if(chan == NULL || chan->SysChannel == NULL) + return; + + ALuint source = *((ALuint*)chan->SysChannel); + // Release first, so it can be properly marked as evicted if it's being + // forcefully killed + S_ChannelEnded(chan); + + alSourceRewind(source); + alSourcei(source, AL_BUFFER, 0); + getALError(); + + std::vector::iterator i; + + i = find(PausableSfx.begin(), PausableSfx.end(), source); + if(i != PausableSfx.end()) PausableSfx.erase(i); + i = find(ReverbSfx.begin(), ReverbSfx.end(), source); + if(i != ReverbSfx.end()) ReverbSfx.erase(i); + + SfxGroup.erase(find(SfxGroup.begin(), SfxGroup.end(), source)); + FreeSfx.push_back(source); +} + +unsigned int OpenALSoundRenderer::GetPosition(FISoundChannel *chan) +{ + if(chan == NULL || chan->SysChannel == NULL) + return 0; + + ALint pos; + alGetSourcei(*((ALuint*)chan->SysChannel), AL_SAMPLE_OFFSET, &pos); + if(getALError() == AL_NO_ERROR) + return pos; + return 0; +} + + +void OpenALSoundRenderer::SetSfxPaused(bool paused, int slot) +{ + int oldslots = SFXPaused; + + if(paused) + { + SFXPaused |= 1 << slot; + if(oldslots == 0 && PausableSfx.size() > 0) + { + alSourcePausev(PausableSfx.size(), &PausableSfx[0]); + getALError(); + PurgeStoppedSources(); + } + } + else + { + SFXPaused &= ~(1 << slot); + if(SFXPaused == 0 && oldslots != 0 && PausableSfx.size() > 0) + { + alSourcePlayv(PausableSfx.size(), &PausableSfx[0]); + getALError(); + } + } +} + +void OpenALSoundRenderer::SetInactive(bool inactive) +{ +} + +void OpenALSoundRenderer::Sync(bool sync) +{ + if(sync) + { + if(SfxGroup.size() > 0) + { + alSourcePausev(SfxGroup.size(), &SfxGroup[0]); + getALError(); + PurgeStoppedSources(); + } + } + else + { + // Might already be something to handle this; basically, get a vector + // of all values in SfxGroup that are not also in PausableSfx (when + // SFXPaused is non-0). + std::vector toplay = SfxGroup; + if(SFXPaused) + { + std::vector::iterator i = toplay.begin(); + while(i != toplay.end()) + { + if(find(PausableSfx.begin(), PausableSfx.end(), *i) != PausableSfx.end()) + i = toplay.erase(i); + else + i++; + } + } + if(toplay.size() > 0) + { + alSourcePlayv(toplay.size(), &toplay[0]); + getALError(); + } + } +} + +void OpenALSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel) +{ + if(chan == NULL || chan->SysChannel == NULL) + return; + + alcSuspendContext(Context); + + ALuint source = *((ALuint*)chan->SysChannel); + alSource3f(source, AL_POSITION, pos[0], pos[1], -pos[2]); + alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]); + + chan->DistanceSqr = (pos - listener->position).LengthSquared() * + chan->DistanceScale*chan->DistanceScale; + // Not all sources can use the distance models provided by OpenAL. + // For the ones that can't, apply the calculated attenuation as the + // source gain. Positions still handle the panning, + if(chan->ManualGain) + { + float gain = GetRolloff(&chan->Rolloff, sqrt(chan->DistanceSqr)); + alSourcef(source, AL_GAIN, SfxVolume*gain); + } + + getALError(); +} + +void OpenALSoundRenderer::UpdateListener(SoundListener *listener) +{ + if(!listener->valid) + return; + + alcSuspendContext(Context); + + float angle = listener->angle; + ALfloat orient[6]; + // forward + orient[0] = cos(angle); + orient[1] = 0.0f; + orient[2] = -sin(angle); + // up + orient[3] = 0.0f; + orient[4] = 1.0f; + orient[5] = 0.0f; + + alListenerfv(AL_ORIENTATION, orient); + alListener3f(AL_POSITION, listener->position.X, + listener->position.Y, + -listener->position.Z); + alListener3f(AL_VELOCITY, listener->velocity.X, + listener->velocity.Y, + -listener->velocity.Z); + getALError(); + + const ReverbContainer *env = ForcedEnvironment; + if(!env) + { + env = listener->Environment; + if(!env) + env = DefaultEnvironments[0]; + } + if(env != PrevEnvironment || env->Modified) + { + PrevEnvironment = env; + DPrintf("Reverb Environment %s\n", env->Name); + + if(EnvSlot != 0) + LoadReverb(env); + + const_cast(env)->Modified = false; + } + + // NOTE: Moving into and out of water (and changing water absorption) will + // undo pitch variations on sounds if either snd_waterreverb or EFX are + // disabled. + if(listener->underwater || env->SoftwareWater) + { + if(LastWaterAbsorb != *snd_waterabsorption) + { + LastWaterAbsorb = *snd_waterabsorption; + + if(EnvSlot != 0 && *snd_waterreverb) + { + // Find the "Underwater" reverb environment + env = Environments; + while(env && env->ID != 0x1600) + env = env->Next; + LoadReverb(env ? env : DefaultEnvironments[0]); + + alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 0.25f); + alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 0.75f); + alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.0f); + alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.0f); + + // Apply the updated filters on the sources + foreach(ALuint, i, ReverbSfx) + { + alSourcef(*i, AL_AIR_ABSORPTION_FACTOR, LastWaterAbsorb); + alSourcei(*i, AL_DIRECT_FILTER, EnvFilters[0]); + alSource3i(*i, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]); + } + } + else + { + foreach(ALuint, i, ReverbSfx) + alSourcef(*i, AL_PITCH, PITCH_MULT); + } + getALError(); + } + } + else + { + if(LastWaterAbsorb > 0.0f) + { + LastWaterAbsorb = 0.0f; + + if(EnvSlot != 0) + { + LoadReverb(env); + + alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 1.0f); + alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 1.0f); + alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.0f); + alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.0f); + foreach(ALuint, i, ReverbSfx) + { + alSourcef(*i, AL_AIR_ABSORPTION_FACTOR, 0.0f); + alSourcei(*i, AL_DIRECT_FILTER, EnvFilters[0]); + alSource3i(*i, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]); + } + } + else + { + foreach(ALuint, i, ReverbSfx) + alSourcef(*i, AL_PITCH, 1.0f); + } + getALError(); + } + } +} + +void OpenALSoundRenderer::UpdateSounds() +{ + alcProcessContext(Context); + + if(DisconnectNotify) + { + ALCint connected = ALC_TRUE; + alcGetIntegerv(Device, ALC_CONNECTED, 1, &connected); + if(connected == ALC_FALSE) + { + Printf("Sound device disconnected; restarting...\n"); + static char snd_reset[] = "snd_reset"; + AddCommandString(snd_reset); + return; + } + } + + PurgeStoppedSources(); +} + +bool OpenALSoundRenderer::IsValid() +{ + return Device != NULL; +} + +void OpenALSoundRenderer::MarkStartTime(FISoundChannel *chan) +{ + // FIXME: Get current time (preferably from the audio clock, but the system + // time will have to do) + chan->StartTime.AsOne = 0; +} + +float OpenALSoundRenderer::GetAudibility(FISoundChannel *chan) +{ + if(chan == NULL || chan->SysChannel == NULL) + return 0.0f; + + ALuint source = *((ALuint*)chan->SysChannel); + ALfloat volume = 0.0f; + + if(!chan->ManualGain) + volume = SfxVolume * GetRolloff(&chan->Rolloff, sqrt(chan->DistanceSqr)); + else + { + alGetSourcef(source, AL_GAIN, &volume); + getALError(); + } + return volume; +} + + +void OpenALSoundRenderer::PrintStatus() +{ + Printf("Output device: "TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_DEVICE_SPECIFIER)); + getALCError(Device); + + ALCint frequency=0, major=0, minor=0, mono=0, stereo=0; + alcGetIntegerv(Device, ALC_FREQUENCY, 1, &frequency); + alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major); + alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor); + alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &mono); + alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &stereo); + if(getALCError(Device) == AL_NO_ERROR) + { + Printf("Device sample rate: "TEXTCOLOR_BLUE"%d"TEXTCOLOR_NORMAL"hz\n", frequency); + Printf("ALC Version: "TEXTCOLOR_BLUE"%d.%d\n", major, minor); + Printf("ALC Extensions: "TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS)); + Printf("Available sources: "TEXTCOLOR_BLUE"%d"TEXTCOLOR_NORMAL" ("TEXTCOLOR_BLUE"%d"TEXTCOLOR_NORMAL" mono, "TEXTCOLOR_BLUE"%d"TEXTCOLOR_NORMAL" stereo)\n", mono+stereo, mono, stereo); + } + if(!alcIsExtensionPresent(Device, "ALC_EXT_EFX")) + Printf("EFX not found\n"); + else + { + ALCint sends; + alcGetIntegerv(Device, ALC_EFX_MAJOR_VERSION, 1, &major); + alcGetIntegerv(Device, ALC_EFX_MINOR_VERSION, 1, &minor); + alcGetIntegerv(Device, ALC_MAX_AUXILIARY_SENDS, 1, &sends); + if(getALCError(Device) == AL_NO_ERROR) + { + Printf("EFX Version: "TEXTCOLOR_BLUE"%d.%d\n", major, minor); + Printf("Auxiliary sends: "TEXTCOLOR_BLUE"%d\n", sends); + } + } + Printf("Vendor: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR)); + Printf("Renderer: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER)); + Printf("Version: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION)); + Printf("Extensions: "TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS)); + getALError(); +} + +FString OpenALSoundRenderer::GatherStats() +{ + ALCint updates = 1; + alcGetIntegerv(Device, ALC_REFRESH, 1, &updates); + getALCError(Device); + + ALuint total = Sources.size(); + ALuint used = SfxGroup.size()+Streams.size(); + ALuint unused = FreeSfx.size(); + FString out; + out.Format("%u sources ("TEXTCOLOR_YELLOW"%u"TEXTCOLOR_NORMAL" active, "TEXTCOLOR_YELLOW"%u"TEXTCOLOR_NORMAL" free), Update interval: "TEXTCOLOR_YELLOW"%d"TEXTCOLOR_NORMAL"ms", + total, used, unused, 1000/updates); + return out; +} + +void OpenALSoundRenderer::PrintDriversList() +{ + const ALCchar *drivers = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ? + alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) : + alcGetString(NULL, ALC_DEVICE_SPECIFIER)); + const ALCchar *current = alcGetString(Device, ALC_DEVICE_SPECIFIER); + if(drivers == NULL) + { + Printf(TEXTCOLOR_YELLOW"Failed to retrieve device list: %s\n", alcGetString(NULL, alcGetError(NULL))); + return; + } + + Printf("%c%s%d. %s\n", ' ', (!(*snd_aldevice)[0] ? TEXTCOLOR_BOLD : ""), 0, + "Default"); + for(int i = 1;*drivers;i++) + { + Printf("%c%s%d. %s\n", ((strcmp(current, drivers)==0) ? '*' : ' '), + ((strcmp(*snd_aldevice, drivers)==0) ? TEXTCOLOR_BOLD : ""), i, + drivers); + drivers += strlen(drivers)+1; + } +} + +void OpenALSoundRenderer::PurgeStoppedSources() +{ + // Release channels that are stopped + foreach(ALuint, i, SfxGroup) + { + ALint state = AL_PLAYING; + alGetSourcei(*i, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING || state == AL_PAUSED) + continue; + + FSoundChan *schan = Channels; + while(schan) + { + if(schan->SysChannel != NULL && *i == *((ALuint*)schan->SysChannel)) + { + StopChannel(schan); + break; + } + schan = schan->NextChan; + } + } + getALError(); +} + +void OpenALSoundRenderer::LoadReverb(const ReverbContainer *env) +{ + ALuint &envReverb = EnvEffects[env->ID]; + bool doLoad = (env->Modified || !envReverb); + + if(!envReverb) + { + bool ok = false; + alGenEffects(1, &envReverb); + if(getALError() == AL_NO_ERROR) + { + alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + ok = (alGetError() == AL_NO_ERROR); + if(!ok) + { + alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB); + ok = (alGetError() == AL_NO_ERROR); + } + if(!ok) + { + alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_NULL); + ok = (alGetError() == AL_NO_ERROR); + } + if(!ok) + { + alDeleteEffects(1, &envReverb); + getALError(); + } + } + if(!ok) + { + envReverb = 0; + doLoad = false; + } + } + + if(doLoad) + { + const REVERB_PROPERTIES &props = env->Properties; + ALint type = AL_EFFECT_NULL; + + alGetEffecti(envReverb, AL_EFFECT_TYPE, &type); +#define mB2Gain(x) ((float)pow(10.0, (x)/2000.0)) + if(type == AL_EFFECT_EAXREVERB) + { + ALfloat reflectpan[3] = { props.ReflectionsPan0, + props.ReflectionsPan1, + props.ReflectionsPan2 }; + ALfloat latepan[3] = { props.ReverbPan0, props.ReverbPan1, + props.ReverbPan2 }; +#undef SETPARAM +#define SETPARAM(e,t,v) alEffectf((e), AL_EAXREVERB_##t, clamp((v), AL_EAXREVERB_MIN_##t, AL_EAXREVERB_MAX_##t)) + SETPARAM(envReverb, DENSITY, props.Density/100.0f); + SETPARAM(envReverb, DIFFUSION, props.Diffusion/100.0f); + SETPARAM(envReverb, GAIN, mB2Gain(props.Room)); + SETPARAM(envReverb, GAINHF, mB2Gain(props.RoomHF)); + SETPARAM(envReverb, GAINLF, mB2Gain(props.RoomLF)); + SETPARAM(envReverb, DECAY_TIME, props.DecayTime); + SETPARAM(envReverb, DECAY_HFRATIO, props.DecayHFRatio); + SETPARAM(envReverb, DECAY_LFRATIO, props.DecayLFRatio); + SETPARAM(envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections)); + SETPARAM(envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay); + alEffectfv(envReverb, AL_EAXREVERB_REFLECTIONS_PAN, reflectpan); + SETPARAM(envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb)); + SETPARAM(envReverb, LATE_REVERB_DELAY, props.ReverbDelay); + alEffectfv(envReverb, AL_EAXREVERB_LATE_REVERB_PAN, latepan); + SETPARAM(envReverb, ECHO_TIME, props.EchoTime); + SETPARAM(envReverb, ECHO_DEPTH, props.EchoDepth); + SETPARAM(envReverb, MODULATION_TIME, props.ModulationTime); + SETPARAM(envReverb, MODULATION_DEPTH, props.ModulationDepth); + SETPARAM(envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF)); + SETPARAM(envReverb, HFREFERENCE, props.HFReference); + SETPARAM(envReverb, LFREFERENCE, props.LFReference); + SETPARAM(envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor); + alEffecti(envReverb, AL_EAXREVERB_DECAY_HFLIMIT, + (props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE); +#undef SETPARAM + } + else if(type == AL_EFFECT_REVERB) + { +#define SETPARAM(e,t,v) alEffectf((e), AL_REVERB_##t, clamp((v), AL_REVERB_MIN_##t, AL_REVERB_MAX_##t)) + SETPARAM(envReverb, DENSITY, props.Density/100.0f); + SETPARAM(envReverb, DIFFUSION, props.Diffusion/100.0f); + SETPARAM(envReverb, GAIN, mB2Gain(props.Room)); + SETPARAM(envReverb, GAINHF, mB2Gain(props.RoomHF)); + SETPARAM(envReverb, DECAY_TIME, props.DecayTime); + SETPARAM(envReverb, DECAY_HFRATIO, props.DecayHFRatio); + SETPARAM(envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections)); + SETPARAM(envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay); + SETPARAM(envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb)); + SETPARAM(envReverb, LATE_REVERB_DELAY, props.ReverbDelay); + SETPARAM(envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF)); + SETPARAM(envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor); + alEffecti(envReverb, AL_REVERB_DECAY_HFLIMIT, + (props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE); +#undef SETPARAM + } +#undef mB2Gain + } + + alAuxiliaryEffectSloti(EnvSlot, AL_EFFECTSLOT_EFFECT, envReverb); + getALError(); +} + +FSoundChan *OpenALSoundRenderer::FindLowestChannel() +{ + FSoundChan *schan = Channels; + FSoundChan *lowest = NULL; + while(schan) + { + if(schan->SysChannel != NULL) + { + if(!lowest || schan->Priority < lowest->Priority || + (schan->Priority == lowest->Priority && + schan->DistanceSqr > lowest->DistanceSqr)) + lowest = schan; + } + schan = schan->NextChan; + } + return lowest; +} + +#endif // NO_OPENAL diff --git a/src/sound/oalsound.h b/src/sound/oalsound.h new file mode 100644 index 000000000..08c3c65d1 --- /dev/null +++ b/src/sound/oalsound.h @@ -0,0 +1,211 @@ +#ifndef OALSOUND_H +#define OALSOUND_H + + +#include +#include + +#include "i_sound.h" +#include "s_sound.h" +#include "m_menu.h" + + +#ifndef NO_OPENAL + +#ifdef _WIN32 +#include +#include +#elif defined(__APPLE__) +#include +#include +#else +#include +#include +#endif + +#ifndef ALC_ENUMERATE_ALL_EXT +#define ALC_ENUMERATE_ALL_EXT 1 +#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#ifndef ALC_EXT_disconnect +#define ALC_EXT_disconnect 1 +#define ALC_CONNECTED 0x313 +#endif + +#ifndef AL_EXT_source_distance_model +#define AL_EXT_source_distance_model 1 +#define AL_SOURCE_DISTANCE_MODEL 0x200 +#endif + +#ifndef AL_EXT_loop_points +#define AL_EXT_loop_points 1 +#define AL_LOOP_POINTS 0x2015 +#endif + +#ifndef AL_EXT_float32 +#define AL_EXT_float32 1 +#define AL_FORMAT_MONO_FLOAT32 0x10010 +#define AL_FORMAT_STEREO_FLOAT32 0x10011 +#endif + +#ifndef AL_EXT_MCFORMATS +#define AL_EXT_MCFORMATS 1 +#define AL_FORMAT_QUAD8 0x1204 +#define AL_FORMAT_QUAD16 0x1205 +#define AL_FORMAT_QUAD32 0x1206 +#define AL_FORMAT_REAR8 0x1207 +#define AL_FORMAT_REAR16 0x1208 +#define AL_FORMAT_REAR32 0x1209 +#define AL_FORMAT_51CHN8 0x120A +#define AL_FORMAT_51CHN16 0x120B +#define AL_FORMAT_51CHN32 0x120C +#define AL_FORMAT_61CHN8 0x120D +#define AL_FORMAT_61CHN16 0x120E +#define AL_FORMAT_61CHN32 0x120F +#define AL_FORMAT_71CHN8 0x1210 +#define AL_FORMAT_71CHN16 0x1211 +#define AL_FORMAT_71CHN32 0x1212 +#endif + +#include "efx.h" + + +class OpenALSoundStream; + +class OpenALSoundRenderer : public SoundRenderer +{ +public: + OpenALSoundRenderer(); + virtual ~OpenALSoundRenderer(); + + virtual void SetSfxVolume(float volume); + virtual void SetMusicVolume(float volume); + virtual SoundHandle LoadSound(BYTE *sfxdata, int length); + virtual SoundHandle LoadSoundRaw(BYTE *sfxdata, int length, int frequency, int channels, int bits, int loopstart); + virtual void UnloadSound(SoundHandle sfx); + virtual unsigned int GetMSLength(SoundHandle sfx); + virtual unsigned int GetSampleLength(SoundHandle sfx); + virtual float GetOutputRate(); + + // Streaming sounds. + virtual SoundStream *CreateStream(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata); + virtual SoundStream *OpenStream(const char *filename, int flags, int offset, int length); + + // Starts a sound. + virtual FISoundChannel *StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan); + virtual FISoundChannel *StartSound3D(SoundHandle sfx, SoundListener *listener, float vol, FRolloffInfo *rolloff, float distscale, int pitch, int priority, const FVector3 &pos, const FVector3 &vel, int channum, int chanflags, FISoundChannel *reuse_chan); + + // Stops a sound channel. + virtual void StopChannel(FISoundChannel *chan); + + // Returns position of sound on this channel, in samples. + virtual unsigned int GetPosition(FISoundChannel *chan); + + // Synchronizes following sound startups. + virtual void Sync(bool sync); + + // Pauses or resumes all sound effect channels. + virtual void SetSfxPaused(bool paused, int slot); + + // Pauses or resumes *every* channel, including environmental reverb. + virtual void SetInactive(bool inactive); + + // Updates the volume, separation, and pitch of a sound channel. + virtual void UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel); + + virtual void UpdateListener(SoundListener *); + virtual void UpdateSounds(); + + virtual short *DecodeSample(int outlen, const void *coded, int sizebytes, ECodecType type); + + virtual void MarkStartTime(FISoundChannel*); + virtual float GetAudibility(FISoundChannel*); + + + virtual bool IsValid(); + virtual void PrintStatus(); + virtual void PrintDriversList(); + virtual FString GatherStats(); + +private: + // EFX Extension function pointer variables. Loaded after context creation + // if EFX is supported. These pointers may be context- or device-dependant, + // thus can't be static + // Effect objects + LPALGENEFFECTS alGenEffects; + LPALDELETEEFFECTS alDeleteEffects; + LPALISEFFECT alIsEffect; + LPALEFFECTI alEffecti; + LPALEFFECTIV alEffectiv; + LPALEFFECTF alEffectf; + LPALEFFECTFV alEffectfv; + LPALGETEFFECTI alGetEffecti; + LPALGETEFFECTIV alGetEffectiv; + LPALGETEFFECTF alGetEffectf; + LPALGETEFFECTFV alGetEffectfv; + // Filter objects + LPALGENFILTERS alGenFilters; + LPALDELETEFILTERS alDeleteFilters; + LPALISFILTER alIsFilter; + LPALFILTERI alFilteri; + LPALFILTERIV alFilteriv; + LPALFILTERF alFilterf; + LPALFILTERFV alFilterfv; + LPALGETFILTERI alGetFilteri; + LPALGETFILTERIV alGetFilteriv; + LPALGETFILTERF alGetFilterf; + LPALGETFILTERFV alGetFilterfv; + // Auxiliary slot objects + LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots; + LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots; + LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot; + LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti; + LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv; + LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf; + LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv; + LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti; + LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv; + LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf; + LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; + + + void LoadReverb(const ReverbContainer *env); + void PurgeStoppedSources(); + static FSoundChan *FindLowestChannel(); + + ALCdevice *Device; + ALCcontext *Context; + + bool LoopPoints; + bool SrcDistanceModel; + bool DisconnectNotify; + + std::vector Sources; + + ALfloat SfxVolume; + ALfloat MusicVolume; + + int SFXPaused; + std::vector FreeSfx; + std::vector PausableSfx; + std::vector ReverbSfx; + std::vector SfxGroup; + + const ReverbContainer *PrevEnvironment; + + typedef std::map EffectMap; + ALuint EnvSlot; + EffectMap EnvEffects; + + ALuint EnvFilters[2]; + float LastWaterAbsorb; + + std::vector Streams; + friend class OpenALSoundStream; +}; + +#endif // NO_OPENAL + +#endif diff --git a/zdoom.vcproj b/zdoom.vcproj index 913cff4a9..4efe7e185 100644 --- a/zdoom.vcproj +++ b/zdoom.vcproj @@ -5413,6 +5413,18 @@ RelativePath=".\src\sound\fmodsound.h" > + + + + + +