From 1a278d6105c8df9007f47cda61b6c02a2afc66b6 Mon Sep 17 00:00:00 2001 From: Roman Fomin Date: Tue, 4 Apr 2023 19:46:58 +0700 Subject: [PATCH] switch to OpenAl for sound mixing (#967) * switch to OpeanAl for sound mixing * Rewrote Load_SNDFile() to use float format. Made libsndfile mandatory. * update main.yml workflow * implement OpeanAl music streaming * remove i_sdlmusic.c and SDL_Mixer dependecy * enable mpeg support in libsndfile * require SndFile 1.1.0 * check SF_FORMAT_MPEG_LAYER_III symbol * use AL_GAIN for opl_gain --- .github/workflows/main.yml | 14 +- .github/workflows/win_msvc.yml | 4 +- CMakeLists.txt | 10 +- cmake/FindSDL2_mixer.cmake | 123 ----------- cmake/triplets/x64-windows.cmake | 5 - cmake/triplets/x86-windows.cmake | 5 - config.h.in | 1 + opl/CMakeLists.txt | 2 +- opl/opl_sdl.c | 152 ++----------- src/CMakeLists.txt | 22 +- src/i_flmusic.c | 10 +- src/i_oalmusic.c | 320 +++++++++++++++++++++++++++ src/i_oalmusic.h | 27 +++ src/i_sdlmusic.c | 272 ----------------------- src/i_sndfile.c | 273 ++++++++++++++++------- src/i_sndfile.h | 11 +- src/i_sound.c | 360 +++++++++++-------------------- src/sounds.c | 3 +- src/sounds.h | 2 - vcpkg.json | 18 +- 20 files changed, 740 insertions(+), 894 deletions(-) delete mode 100644 cmake/FindSDL2_mixer.cmake create mode 100644 src/i_oalmusic.c create mode 100644 src/i_oalmusic.h delete mode 100644 src/i_sdlmusic.c diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 56b322f9..3a93aaff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,15 @@ jobs: steps: - name: Install dependencies - run: sudo apt-get update && sudo apt-get install libfluidsynth-dev libsdl2-dev libsdl2-mixer-dev libsdl2-net-dev ninja-build + run: | + sudo apt-get update + sudo apt-get install \ + libsdl2-dev \ + libsdl2-net-dev \ + libopenal-dev \ + libsndfile1-dev \ + libfluidsynth-dev \ + ninja-build - uses: actions/checkout@v3 @@ -67,8 +75,10 @@ jobs: mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 - mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-SDL2_net + mingw-w64-x86_64-openal + mingw-w64-x86_64-libsndfile + mingw-w64-x86_64-fluidsynth - uses: actions/checkout@v3 diff --git a/.github/workflows/win_msvc.yml b/.github/workflows/win_msvc.yml index 8dd020c1..e5a68651 100644 --- a/.github/workflows/win_msvc.yml +++ b/.github/workflows/win_msvc.yml @@ -5,8 +5,8 @@ on: branches: [ master ] tags: ['*'] paths-ignore: ['**.md'] - pull_request: - branches: [ master ] + # pull_request: + # branches: [ master ] env: VCPKG_ROOT: C:\vcpkg diff --git a/CMakeLists.txt b/CMakeLists.txt index 2246d193..5189f4ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ include(CheckLibraryExists) include(CheckIncludeFile) include(CheckSymbolExists) +include(CheckCSourceCompiles) include(ExternalProject) # Adds the cmake directory to the CMake include path. @@ -72,15 +73,20 @@ option(CMAKE_FIND_PACKAGE_PREFER_CONFIG # Library requirements. find_package(SDL2 2.0.7 REQUIRED) -find_package(SDL2_mixer 2.0.2 REQUIRED) find_package(SDL2_net REQUIRED) +find_package(OpenAL REQUIRED) +find_package(SndFile 1.1.0 REQUIRED) find_package(FluidSynth 2.2.0) -find_package(SndFile 1.0.28) # Python 3 find_package(Python3 COMPONENTS Interpreter) +check_c_source_compiles( +"#include +int main(void) {return SF_FORMAT_MPEG_LAYER_III;}" +HAVE_SNDFILE_MPEG) + configure_file(config.h.in config.h) if(WIN32) diff --git a/cmake/FindSDL2_mixer.cmake b/cmake/FindSDL2_mixer.cmake deleted file mode 100644 index ebeefdb8..00000000 --- a/cmake/FindSDL2_mixer.cmake +++ /dev/null @@ -1,123 +0,0 @@ -# FindSDL2_mixer.cmake -# -# Copyright (c) 2018, Alex Mayfield -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * 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. -# * Neither the name of the nor the -# names of its contributors may be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 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. -# -# Currently works with the following generators: -# - Unix Makefiles (Linux, MSYS2, Linux MinGW) -# - Ninja (Linux, MSYS2, Linux MinGW) -# - Visual Studio - -# Cache variable that allows you to point CMake at a directory containing -# an extracted development library. -set(SDL2_MIXER_DIR "${SDL2_MIXER_DIR}" CACHE PATH "Location of SDL2_mixer library directory") - -# Use pkg-config to find library locations in *NIX environments. -find_package(PkgConfig QUIET) -if(PKG_CONFIG_FOUND) - pkg_search_module(PC_SDL2_MIXER QUIET SDL2_mixer) -endif() - -# Find the include directory. -if(CMAKE_SIZEOF_VOID_P STREQUAL 8) - find_path(SDL2_MIXER_INCLUDE_DIR "SDL_mixer.h" - HINTS - "${SDL2_MIXER_DIR}/include" - "${SDL2_MIXER_DIR}/include/SDL2" - "${SDL2_MIXER_DIR}/x86_64-w64-mingw32/include/SDL2" - ${PC_SDL2_MIXER_INCLUDE_DIRS}) -else() - find_path(SDL2_MIXER_INCLUDE_DIR "SDL_mixer.h" - HINTS - "${SDL2_MIXER_DIR}/include" - "${SDL2_MIXER_DIR}/include/SDL2" - "${SDL2_MIXER_DIR}/i686-w64-mingw32/include/SDL2" - ${PC_SDL2_MIXER_INCLUDE_DIRS}) -endif() - -# Find the version. Taken and modified from CMake's FindSDL.cmake. -if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h") - file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$") - file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$") - file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$") - string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}") - string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}") - string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}") - set(SDL2_MIXER_VERSION "${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH}") - unset(SDL2_MIXER_VERSION_MAJOR_LINE) - unset(SDL2_MIXER_VERSION_MINOR_LINE) - unset(SDL2_MIXER_VERSION_PATCH_LINE) - unset(SDL2_MIXER_VERSION_MAJOR) - unset(SDL2_MIXER_VERSION_MINOR) - unset(SDL2_MIXER_VERSION_PATCH) -endif() - -# Find the library. -if(CMAKE_SIZEOF_VOID_P STREQUAL 8) - find_library(SDL2_MIXER_LIBRARY "SDL2_mixer" - HINTS - "${SDL2_MIXER_DIR}/lib" - "${SDL2_MIXER_DIR}/lib/x64" - "${SDL2_MIXER_DIR}/x86_64-w64-mingw32/lib" - ${PC_SDL2_MIXER_LIBRARY_DIRS}) -else() - find_library(SDL2_MIXER_LIBRARY "SDL2_mixer" - HINTS - "${SDL2_MIXER_DIR}/lib" - "${SDL2_MIXER_DIR}/lib/x86" - "${SDL2_MIXER_DIR}/i686-w64-mingw32/lib" - ${PC_SDL2_MIXER_LIBRARY_DIRS}) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SDL2_mixer - FOUND_VAR SDL2_MIXER_FOUND - REQUIRED_VARS SDL2_MIXER_INCLUDE_DIR SDL2_MIXER_LIBRARY - VERSION_VAR SDL2_MIXER_VERSION -) - -if(SDL2_MIXER_FOUND) - # Imported target. - add_library(SDL2_mixer::SDL2_mixer UNKNOWN IMPORTED) - set_target_properties(SDL2_mixer::SDL2_mixer PROPERTIES - INTERFACE_COMPILE_OPTIONS "${PC_SDL2_MIXER_CFLAGS_OTHER}" - INTERFACE_INCLUDE_DIRECTORIES "${SDL2_MIXER_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES SDL2::SDL2 - IMPORTED_LOCATION "${SDL2_MIXER_LIBRARY}") - - if(WIN32) - # On Windows, we need to figure out the location of our library files - # so we can copy and package them. - get_filename_component(SDL2_MIXER_DLL_DIR "${SDL2_MIXER_LIBRARY}" DIRECTORY) - if(EXISTS "${SDL2_MIXER_DLL_DIR}/SDL2_mixer.dll") - set(SDL2_MIXER_DLL_DIR "${SDL2_MIXER_DLL_DIR}" CACHE INTERNAL "") - elseif(EXISTS "${SDL2_MIXER_DLL_DIR}/../bin/SDL2_mixer.dll") - get_filename_component(SDL2_MIXER_DLL_DIR "${SDL2_MIXER_DLL_DIR}/../bin" REALPATH) - set(SDL2_MIXER_DLL_DIR "${SDL2_MIXER_DLL_DIR}" CACHE INTERNAL "") - else() - message(ERROR "Can't find SDL2_mixer dynamic library directory.") - endif() - endif() -endif() diff --git a/cmake/triplets/x64-windows.cmake b/cmake/triplets/x64-windows.cmake index bc4419ac..f6c40253 100644 --- a/cmake/triplets/x64-windows.cmake +++ b/cmake/triplets/x64-windows.cmake @@ -2,8 +2,3 @@ set(VCPKG_TARGET_ARCHITECTURE x64) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_BUILD_TYPE release) - -if(${PORT} MATCHES "libsamplerate") - set(VCPKG_CXX_FLAGS "/fp:fast") - set(VCPKG_C_FLAGS "/fp:fast") -endif() diff --git a/cmake/triplets/x86-windows.cmake b/cmake/triplets/x86-windows.cmake index 40eb51a0..0a277bdb 100644 --- a/cmake/triplets/x86-windows.cmake +++ b/cmake/triplets/x86-windows.cmake @@ -2,8 +2,3 @@ set(VCPKG_TARGET_ARCHITECTURE x86) set(VCPKG_CRT_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_BUILD_TYPE release) - -if(${PORT} MATCHES "libsamplerate") - set(VCPKG_CXX_FLAGS "/fp:fast") - set(VCPKG_C_FLAGS "/fp:fast") -endif() diff --git a/config.h.in b/config.h.in index 342c2c78..60817357 100644 --- a/config.h.in +++ b/config.h.in @@ -8,3 +8,4 @@ #cmakedefine01 HAVE_DECL_STRCASECMP #cmakedefine01 HAVE_DECL_STRNCASECMP #cmakedefine WOOFDATADIR "@WOOFDATADIR@" +#cmakedefine01 HAVE_SNDFILE_MPEG diff --git a/opl/CMakeLists.txt b/opl/CMakeLists.txt index 02fea0c3..672cb167 100644 --- a/opl/CMakeLists.txt +++ b/opl/CMakeLists.txt @@ -12,4 +12,4 @@ target_woof_settings(opl) target_include_directories(opl INTERFACE "." PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../" "../src/") -target_link_libraries(opl SDL2_mixer::SDL2_mixer) +target_link_libraries(opl SDL2::SDL2) diff --git a/opl/opl_sdl.c b/opl/opl_sdl.c index 2e017861..af9355b5 100644 --- a/opl/opl_sdl.c +++ b/opl/opl_sdl.c @@ -21,11 +21,10 @@ #include #include #include -#include #include #include "SDL.h" -#include "SDL_mixer.h" +#include "i_oalmusic.h" #include "opl3.h" @@ -75,10 +74,6 @@ static uint64_t pause_offset; static opl3_chip opl_chip; static int opl_opl3mode; -// Temporary mixing buffer used by the mixing callback. - -static uint8_t *mix_buffer = NULL; - // Register number that was written. static int register_num = 0; @@ -88,19 +83,7 @@ static int register_num = 0; static opl_timer_t timer1 = { 12500, 0, 0, 0 }; static opl_timer_t timer2 = { 3125, 0, 0, 0 }; -// SDL parameters. - -static int sdl_was_initialized = 0; static int mixing_freq, mixing_channels; -static Uint16 mixing_format; - -static int SDLIsInitialized(void) -{ - int freq, channels; - Uint16 format; - - return Mix_QuerySpec(&freq, &format, &channels); -} // Advance time by the specified number of samples, invoking any // callback functions as appropriate. @@ -155,59 +138,19 @@ static void AdvanceTime(unsigned int nsamples) SDL_UnlockMutex(callback_queue_mutex); } -#if SDL_BYTEORDER == SDL_LIL_ENDIAN - #define OPL_SHORT SDL_SwapLE16 -#else - #define OPL_SHORT SDL_SwapBE16 -#endif - -int opl_gain = 200; - -static void MixAudioFormat(Uint8 *dst, const Uint8 *src, Uint32 len) -{ - Sint16 src1, src2; - int dst_sample; - - while (len--) - { - src1 = OPL_SHORT(*(Sint16 *)src); - src2 = OPL_SHORT(*(Sint16 *)dst); - src += 2; - dst_sample = src1 * opl_gain / 100 + src2; - if (dst_sample > SHRT_MAX) - { - dst_sample = SHRT_MAX; - } - else if (dst_sample < SHRT_MIN) - { - dst_sample = SHRT_MIN; - } - *(Sint16 *)dst = OPL_SHORT(dst_sample); - dst += 2; - } -} - // Call the OPL emulator code to fill the specified buffer. static void FillBuffer(uint8_t *buffer, unsigned int nsamples) { - // This seems like a reasonable assumption. mix_buffer is - // 1 second long, which should always be much longer than the - // SDL mix buffer. - assert(nsamples < mixing_freq); - - // OPL output is generated into temporary buffer and then mixed - // (to avoid overflows etc.) - OPL3_GenerateStream(&opl_chip, (Bit16s *) mix_buffer, nsamples); - MixAudioFormat(buffer, mix_buffer, nsamples * 2); + OPL3_GenerateStream(&opl_chip, (Bit16s *) buffer, nsamples); } // Callback function to fill a new sound buffer: -static void OPL_Mix_Callback(void *udata, Uint8 *stream, int len) +static int OPL_Callback(byte *stream, int len) { unsigned int filled, buffer_samples; - Uint8 *buffer = (Uint8*)stream; + uint8_t *buffer = stream; // Repeatedly call the OPL emulator update function until the buffer is // full. @@ -253,20 +196,14 @@ static void OPL_Mix_Callback(void *udata, Uint8 *stream, int len) AdvanceTime(nsamples); } + return len; } static void OPL_SDL_Shutdown(void) { - Mix_HookMusic(NULL, NULL); + I_OAL_HookMusic(NULL); - if (sdl_was_initialized) - { - Mix_CloseAudio(); - SDL_QuitSubSystem(SDL_INIT_AUDIO); - OPL_Queue_Destroy(callback_queue); - free(mix_buffer); - sdl_was_initialized = 0; - } + OPL_Queue_Destroy(callback_queue); /* if (opl_chip != NULL) @@ -289,63 +226,10 @@ static void OPL_SDL_Shutdown(void) } } -static unsigned int GetSliceSize(void) -{ - int limit; - int n; - - limit = (opl_sample_rate * MAX_SOUND_SLICE_TIME) / 1000; - - // Try all powers of two, not exceeding the limit. - - for (n=0;; ++n) - { - // 2^n <= limit < 2^n+1 ? - - if ((1 << (n + 1)) > limit) - { - return (1 << n); - } - } - - // Should never happen? - - return 1024; -} +int opl_gain = 200; static int OPL_SDL_Init(unsigned int port_base) { - // Check if SDL_mixer has been opened already - // If not, we must initialize it now - - if (!SDLIsInitialized()) - { - if (SDL_Init(SDL_INIT_AUDIO) < 0) - { - fprintf(stderr, "Unable to set up sound.\n"); - return 0; - } - - if (Mix_OpenAudioDevice(opl_sample_rate, AUDIO_S16SYS, 2, GetSliceSize(), NULL, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) < 0) - { - fprintf(stderr, "Error initialising SDL_mixer: %s\n", Mix_GetError()); - - SDL_QuitSubSystem(SDL_INIT_AUDIO); - return 0; - } - - SDL_PauseAudio(0); - - // When this module shuts down, it has the responsibility to - // shut down SDL. - - sdl_was_initialized = 1; - } - else - { - sdl_was_initialized = 0; - } - opl_sdl_paused = 0; pause_offset = 0; @@ -356,22 +240,9 @@ static int OPL_SDL_Init(unsigned int port_base) // Get the mixer frequency, format and number of channels. - Mix_QuerySpec(&mixing_freq, &mixing_format, &mixing_channels); - // Only supports AUDIO_S16SYS - - if (mixing_format != AUDIO_S16SYS || mixing_channels != 2) - { - fprintf(stderr, - "OPL_SDL only supports native signed 16-bit LSB, " - "stereo format!\n"); - - OPL_SDL_Shutdown(); - return 0; - } - - // Mix buffer: four bytes per sample (16 bits * 2 channels): - mix_buffer = malloc(mixing_freq * 4); + mixing_channels = 2; + mixing_freq = opl_sample_rate; // Create the emulator structure: @@ -381,7 +252,8 @@ static int OPL_SDL_Init(unsigned int port_base) callback_mutex = SDL_CreateMutex(); callback_queue_mutex = SDL_CreateMutex(); - Mix_HookMusic(OPL_Mix_Callback, NULL); + I_OAL_HookMusic(OPL_Callback); + I_OAL_SetGain((float)opl_gain / 100.0f); return 1; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c51b1537..f1ee007b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,8 +35,8 @@ set(WOOF_SOURCES i_flmusic.c i_glob.c i_glob.h i_main.c + i_oalmusic.c i_oalmusic.h i_oplmusic.c - i_sdlmusic.c i_sndfile.c i_sndfile.h i_sound.c i_sound.h i_system.c i_system.h @@ -190,13 +190,13 @@ if (FluidSynth_FOUND) target_compile_definitions(woof PRIVATE HAVE_FLUIDSYNTH) endif() -if (SndFile_FOUND) - list(APPEND WOOF_LIBRARIES SndFile::sndfile) - target_compile_definitions(woof PRIVATE HAVE_SNDFILE) -endif() - target_link_libraries(woof PRIVATE ${WOOF_LIBRARIES} - SDL2::SDL2 SDL2_mixer::SDL2_mixer SDL2_net::SDL2_net opl textscreen) + SDL2::SDL2 + SDL2_net::SDL2_net + OpenAL::OpenAL + SndFile::sndfile + opl + textscreen) # Optional features. # @@ -237,8 +237,12 @@ add_executable(woof-setup WIN32 ${SETUP_SOURCES}) target_woof_settings(woof-setup) target_include_directories(woof-setup PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/../") -target_link_libraries(woof-setup PRIVATE "$" - SDL2::SDL2 SDL2_net::SDL2_net textscreen setup) +target_link_libraries(woof-setup PRIVATE + "$" + SDL2::SDL2 + SDL2_net::SDL2_net + textscreen + setup) if(MSVC) target_link_options(woof-setup PRIVATE "/MANIFEST:NO") diff --git a/src/i_flmusic.c b/src/i_flmusic.c index 9d076f85..4414cb48 100644 --- a/src/i_flmusic.c +++ b/src/i_flmusic.c @@ -26,7 +26,7 @@ #endif #include "SDL.h" -#include "SDL_mixer.h" +#include "i_oalmusic.h" #include "doomtype.h" #include "i_system.h" @@ -52,7 +52,7 @@ static fluid_player_t *player = NULL; static char **soundfonts; static int soundfonts_num; -static void FL_Mix_Callback(void *udata, Uint8 *stream, int len) +static int FL_Callback(byte *stream, int len) { int result; @@ -62,6 +62,8 @@ static void FL_Mix_Callback(void *udata, Uint8 *stream, int len) { fprintf(stderr, "Error generating FluidSynth audio\n"); } + + return len; } // Load SNDFONT lump @@ -356,7 +358,7 @@ static void *I_FL_RegisterSong(void *data, int len) return NULL; } - Mix_HookMusic(FL_Mix_Callback, NULL); + I_OAL_HookMusic(FL_Callback); return (void *)1; } @@ -368,7 +370,7 @@ static void I_FL_UnRegisterSong(void *handle) fluid_synth_program_reset(synth); fluid_synth_system_reset(synth); - Mix_HookMusic(NULL, NULL); + I_OAL_HookMusic(NULL); delete_fluid_player(player); player = NULL; diff --git a/src/i_oalmusic.c b/src/i_oalmusic.c new file mode 100644 index 00000000..07ce0e0d --- /dev/null +++ b/src/i_oalmusic.c @@ -0,0 +1,320 @@ +// +// Copyright(C) 2023 Roman Fomin +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + +#include "i_oalmusic.h" + +#include +#include +#include +#include + +#include "SDL.h" + +#include "doomtype.h" +#include "i_sndfile.h" +#include "i_sound.h" + +// Define the number of buffers and buffer size (in milliseconds) to use. 4 +// buffers with 8192 samples each gives a nice per-chunk size, and lets the +// queue last for almost one second at 44.1khz. +#define NUM_BUFFERS 4 +#define BUFFER_SAMPLES 8192 + +typedef struct +{ + // These are the buffers and source to play out through OpenAL with + ALuint buffers[NUM_BUFFERS]; + ALuint source; + + byte *data; + + // The format of the output stream + ALenum format; + ALsizei freq; +} stream_player_t; + +static stream_player_t player; + +static SDL_Thread *player_thread_handle; +static int player_thread_running; + +static callback_func_t callback; + +static boolean UpdatePlayer(void) +{ + ALint processed, state; + + // Get relevant source info + alGetSourcei(player.source, AL_SOURCE_STATE, &state); + alGetSourcei(player.source, AL_BUFFERS_PROCESSED, &processed); + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "UpdatePlayer: Error checking source state\n"); + return false; + } + + // Unqueue and handle each processed buffer + while (processed > 0) + { + ALuint bufid; + ALsizei size; + + alSourceUnqueueBuffers(player.source, 1, &bufid); + processed--; + + // Read the next chunk of data, refill the buffer, and queue it back on + // the source. + if (callback) + { + size = callback(player.data, BUFFER_SAMPLES); + } + else + { + size = I_SND_FillStream(player.data, BUFFER_SAMPLES); + } + if (size > 0) + { + alBufferData(bufid, player.format, player.data, size, player.freq); + alSourceQueueBuffers(player.source, 1, &bufid); + } + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "UpdatePlayer: Error buffering data\n"); + return false; + } + } + + // Make sure the source hasn't underrun + if (state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued; + + // If no buffers are queued, playback is finished + alGetSourcei(player.source, AL_BUFFERS_QUEUED, &queued); + if (queued == 0) + { + return false; + } + + alSourcePlay(player.source); + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "UpdatePlayer: Error restarting playback\n"); + return false; + } + } + + return true; +} + +static boolean StartPlayer(void) +{ + int i; + + player.data = malloc(BUFFER_SAMPLES); + + // Rewind the source position and clear the buffer queue. + alSourceRewind(player.source); + alSourcei(player.source, AL_BUFFER, 0); + + // Fill the buffer queue + for (i = 0; i < NUM_BUFFERS; i++) + { + ALsizei size; + + // Get some data to give it to the buffer + if (callback) + { + size = callback(player.data, BUFFER_SAMPLES); + } + else + { + size = I_SND_FillStream(player.data, BUFFER_SAMPLES); + } + + if (size < 1) + break; + + alBufferData(player.buffers[i], player.format, player.data, + size, player.freq); + } + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "StartPlayer: Error buffering for playback.\n"); + return false; + } + + alSourceQueueBuffers(player.source, i, player.buffers); + + return true; +} + +static int PlayerThread(void *unused) +{ + SDL_SetThreadPriority(SDL_THREAD_PRIORITY_TIME_CRITICAL); + + while (player_thread_running && UpdatePlayer()) + { + SDL_Delay(1); + } + + return 0; +} + +static boolean I_OAL_InitMusic(int device) +{ + alGenBuffers(NUM_BUFFERS, player.buffers); + alGenSources(1, &player.source); + + // Set parameters so mono sources play out the front-center speaker and + // won't distance attenuate. + alSource3i(player.source, AL_POSITION, 0, 0, -1); + alSourcei(player.source, AL_SOURCE_RELATIVE, AL_TRUE); + alSourcei(player.source, AL_ROLLOFF_FACTOR, 0); + + return true; +} + +static void I_OAL_SetMusicVolume(int volume) +{ + alSourcef(player.source, AL_GAIN, (ALfloat)volume / 15.0f); +} + +static void I_OAL_PauseSong(void *handle) +{ + alSourcePause(player.source); +} + +static void I_OAL_ResumeSong(void *handle) +{ + alSourcePlay(player.source); +} + +static void I_OAL_PlaySong(void *handle, boolean looping) +{ + if (!callback) + { + I_SND_SetLooping(looping); + } + + alSourcePlay(player.source); + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "I_OAL_PlaySong: Error starting playback.\n"); + return; + } + + player_thread_running = true; + player_thread_handle = SDL_CreateThread(PlayerThread, NULL, NULL); +} + +static void I_OAL_StopSong(void *handle) +{ + alSourceStop(player.source); +} + +static void I_OAL_UnRegisterSong(void *handle) +{ + if (player_thread_running) + { + player_thread_running = false; + SDL_WaitThread(player_thread_handle, NULL); + } + + if (!callback) + { + I_SND_CloseStream(); + } + + if (player.data) + { + free(player.data); + player.data = NULL; + } +} + +static void I_OAL_ShutdownMusic(void) +{ + I_OAL_StopSong(NULL); + I_OAL_UnRegisterSong(NULL); + + alDeleteSources(1, &player.source); + alDeleteBuffers(NUM_BUFFERS, player.buffers); + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "I_OAL_ShutdownMusic: Failed to delete object IDs.\n"); + } + + memset(&player, 0, sizeof(stream_player_t)); +} + +// Prebuffers some audio from the file, and starts playing the source. + +static void *I_OAL_RegisterSong(void *data, int len) +{ + if (I_SND_OpenStream(data, len, &player.format, &player.freq) == false) + return NULL; + + StartPlayer(); + + return (void *)1; +} + +static int I_OAL_DeviceList(const char *devices[], int size, int *current_device) +{ + *current_device = 0; + return 0; +} + +void I_OAL_SetGain(float gain) +{ + alSourcef(player.source, AL_MAX_GAIN, 10.0f); + alSourcef(player.source, AL_GAIN, (ALfloat)gain); +} + +void I_OAL_HookMusic(callback_func_t callback_func) +{ + if (callback_func) + { + callback = callback_func; + + player.format = AL_FORMAT_STEREO16; + player.freq = snd_samplerate; + + StartPlayer(); + I_OAL_PlaySong(NULL, false); + } + else + { + callback = NULL; + + I_OAL_UnRegisterSong(NULL); + } +} + +music_module_t music_oal_module = +{ + I_OAL_InitMusic, + I_OAL_ShutdownMusic, + I_OAL_SetMusicVolume, + I_OAL_PauseSong, + I_OAL_ResumeSong, + I_OAL_RegisterSong, + I_OAL_PlaySong, + I_OAL_StopSong, + I_OAL_UnRegisterSong, + I_OAL_DeviceList, +}; diff --git a/src/i_oalmusic.h b/src/i_oalmusic.h new file mode 100644 index 00000000..8e1f56f9 --- /dev/null +++ b/src/i_oalmusic.h @@ -0,0 +1,27 @@ +// +// Copyright(C) 2023 Roman Fomin +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// + +#ifndef __I_OALMUSIC__ +#define __I_OALMUSIC__ + +#include "doomtype.h" + +typedef int (*callback_func_t)(byte *data, int size); + +void I_OAL_HookMusic(callback_func_t callback_func); +void I_OAL_SetGain(float gain); + +#endif \ No newline at end of file diff --git a/src/i_sdlmusic.c b/src/i_sdlmusic.c deleted file mode 100644 index d18ece43..00000000 --- a/src/i_sdlmusic.c +++ /dev/null @@ -1,272 +0,0 @@ -// -// Copyright (C) 1999 by -// id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// DESCRIPTION: -// System interface for SDL music. -// -//----------------------------------------------------------------------------- - -// haleyjd -#include "SDL.h" -#include "SDL_mixer.h" - -#include "doomstat.h" -#include "doomtype.h" -#include "i_sound.h" - -/// -// MUSIC API. -// - -// julian (10/25/2005): rewrote (nearly) entirely - -#include "mus2mid.h" -#include "memio.h" -#include "m_misc2.h" - -// Only one track at a time -static Mix_Music *music = NULL; - -// Some tracks are directly streamed from the RWops; -// we need to free them in the end -static SDL_RWops *rw = NULL; - -// Same goes for buffers that were allocated to convert music; -// since this concerns mus, we could do otherwise but this -// approach is better for consistency -static void *music_block = NULL; - -// Macro to make code more readable -#define CHECK_MUSIC(h) ((h) && music != NULL) - -// -// I_ShutdownMusic -// -// atexit handler. -// -static void I_SDL_UnRegisterSong(void *handle); -static void I_SDL_ShutdownMusic(void) -{ - I_SDL_UnRegisterSong((void *)1); -} - -// -// I_InitMusic -// -static boolean I_SDL_InitMusic(int device) -{ - // Initialize SDL_Mixer for MIDI music playback - // [crispy] initialize some more audio formats - Mix_Init(MIX_INIT_MID | MIX_INIT_FLAC | MIX_INIT_OGG | MIX_INIT_MP3); - - return true; -} - -// jff 1/18/98 changed interface to make mididata destroyable - -static void I_SDL_SetMusicVolume(int volume); -static void I_SDL_PlaySong(void *handle, boolean looping) -{ - if(CHECK_MUSIC(handle) && Mix_PlayMusic(music, looping ? -1 : 0) == -1) - { - doomprintf("I_PlaySong: Mix_PlayMusic failed\n"); - return; - } - - // haleyjd 10/28/05: make sure volume settings remain consistent - I_SDL_SetMusicVolume(snd_MusicVolume); -} - -// -// I_SetMusicVolume -// - -static int current_midi_volume; - -static void I_SDL_SetMusicVolume(int volume) -{ - current_midi_volume = volume; - - // haleyjd 09/04/06: adjust to use scale from 0 to 15 - Mix_VolumeMusic((current_midi_volume * 128) / 15); -} - -// -// I_PauseSong -// -static void I_SDL_PauseSong(void *handle) -{ - if(CHECK_MUSIC(handle)) - { - // Not for mids - if(Mix_GetMusicType(music) != MUS_MID) - Mix_PauseMusic(); - else - { - // haleyjd 03/21/06: set MIDI volume to zero on pause - Mix_VolumeMusic(0); - } - } -} - -// -// I_ResumeSong -// -static void I_SDL_ResumeSong(void *handle) -{ - if(CHECK_MUSIC(handle)) - { - // Not for mids - if(Mix_GetMusicType(music) != MUS_MID) - Mix_ResumeMusic(); - else - Mix_VolumeMusic((current_midi_volume * 128) / 15); - } -} - -// -// I_StopSong -// -static void I_SDL_StopSong(void *handle) -{ - if(CHECK_MUSIC(handle)) - Mix_HaltMusic(); -} - -// -// I_UnRegisterSong -// -static void I_SDL_UnRegisterSong(void *handle) -{ - if(CHECK_MUSIC(handle)) - { - // Stop and free song - I_SDL_StopSong(handle); - Mix_FreeMusic(music); - - // Free RWops - if(rw != NULL) - SDL_FreeRW(rw); - - // Free music block - if(music_block != NULL) - free(music_block); - - // Reinitialize all this - music = NULL; - rw = NULL; - music_block = NULL; - } -} - -// -// I_RegisterSong -// -static void *I_SDL_RegisterSong(void *data, int size) -{ - if(music != NULL) - I_SDL_UnRegisterSong((void *)1); - - if (!IsMus(data, size)) - { - rw = SDL_RWFromMem(data, size); - music = Mix_LoadMUS_RW(rw, false); - - // Sometimes SDL_Mixer fail to detect MP3, so we try again - if (!music) - music = Mix_LoadMUSType_RW(rw, MUS_MP3, false); - - if (!music) - printf("I_SDL_RegisterSong: %s\n", SDL_GetError()); - } - else // Assume a MUS file and try to convert - { - MEMFILE *instream; - MEMFILE *outstream; - byte *mid; - size_t midlen; - int result; - - instream = mem_fopen_read(data, size); - outstream = mem_fopen_write(); - - result = mus2mid(instream, outstream); - - if (result == 0) - { - void *outbuf; - - mem_get_buf(outstream, &outbuf, &midlen); - - mid = malloc(midlen); - memcpy(mid, outbuf, midlen); - } - - mem_fclose(instream); - mem_fclose(outstream); - - if (result) - { - doomprintf("Error loading music: %d", result); - return NULL; - } - - { - rw = SDL_RWFromMem(mid, midlen); - music = Mix_LoadMUS_RW(rw, false); - - if(music == NULL) - { - // Conversion failed, free everything - SDL_FreeRW(rw); - rw = NULL; - free(mid); - } - else - { - // Conversion succeeded - // -> save memory block to free when unregistering - music_block = mid; - } - } - } - - // the handle is a simple boolean - return music; -} - -static int I_SDL_DeviceList(const char *devices[], int size, int *current_device) -{ - *current_device = 0; - if (size > 0) - { - devices[0] = "SDL2 Mixer"; - return 1; - } - return 0; -} - -music_module_t music_sdl_module = -{ - I_SDL_InitMusic, - I_SDL_ShutdownMusic, - I_SDL_SetMusicVolume, - I_SDL_PauseSong, - I_SDL_ResumeSong, - I_SDL_RegisterSong, - I_SDL_PlaySong, - I_SDL_StopSong, - I_SDL_UnRegisterSong, - I_SDL_DeviceList, -}; diff --git a/src/i_sndfile.c b/src/i_sndfile.c index 3c647064..f3268aa3 100644 --- a/src/i_sndfile.c +++ b/src/i_sndfile.c @@ -18,9 +18,10 @@ #include "i_sndfile.h" -#if defined(HAVE_SNDFILE) - +#include #include +#include +#include typedef struct sfvio_data_s { @@ -98,90 +99,208 @@ static sf_count_t sfvio_tell(void *user_data) return data->offset; } -void *Load_SNDFile(void *data, SDL_AudioSpec *sample, Uint8 **wavdata, Uint32 *samplelen) +static SF_VIRTUAL_IO sfvio = +{ + sfvio_get_filelen, + sfvio_seek, + sfvio_read, + NULL, + sfvio_tell +}; + +typedef enum +{ + Int16, + Float, + //IMA4, + //MSADPCM +} sample_format_t; + +typedef struct { SNDFILE *sndfile; SF_INFO sfinfo; - SF_VIRTUAL_IO sfvio = - { - sfvio_get_filelen, - sfvio_seek, - sfvio_read, - NULL, - sfvio_tell - }; sfvio_data_t sfdata; - Uint32 wavlen; - short *local_wavdata; - sfdata.data = data; - sfdata.length = *samplelen; - sfdata.offset = 0; + sample_format_t sample_format; + ALint byteblockalign; + ALenum format; +} sndfile_t; - memset(&sfinfo, 0, sizeof(sfinfo)); - - sndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfdata); - - if (!sndfile) - { - fprintf(stderr, "sf_open_virtual: %s\n", sf_strerror(sndfile)); - return NULL; - } - - if (sfinfo.frames <= 0 || sfinfo.channels <= 0) - { - sf_close(sndfile); - return NULL; - } - - wavlen = sfinfo.frames * sfinfo.channels * sizeof(short); - local_wavdata = SDL_malloc(wavlen); - - if (!local_wavdata) - { - sf_close(sndfile); - return NULL; - } - - if (sf_readf_short(sndfile, local_wavdata, sfinfo.frames) < sfinfo.frames) - { - fprintf(stderr, "sf_readf_short: %s\n", sf_strerror(sndfile)); - sf_close(sndfile); - SDL_free(local_wavdata); - return NULL; - } - - sf_close(sndfile); - - sample->channels = sfinfo.channels; - sample->freq = sfinfo.samplerate; - sample->format = AUDIO_S16; - - *wavdata = (Uint8 *)local_wavdata; - *samplelen = wavlen; - - return sample; -} - -#else - -void *Load_SNDFile(void *data, SDL_AudioSpec *sample, Uint8 **wavdata, Uint32 *samplelen) +static void CloseFile(sndfile_t *file) { - SDL_RWops *RWops; - - if ((RWops = SDL_RWFromMem(data, *samplelen)) == NULL) + if (file->sndfile) { - fprintf(stderr, "SDL_RWFromMem: %s\n", SDL_GetError()); - return NULL; + sf_close(file->sndfile); + file->sndfile = NULL; } - - if (SDL_LoadWAV_RW(RWops, 1, sample, wavdata, samplelen) == NULL) // 1 = will call SDL_RWclose(RWops) for us - { - fprintf(stderr, "SDL_LoadWAV_RW: %s\n", SDL_GetError()); - return NULL; - } - - return sample; } +static boolean OpenFile(sndfile_t *file, void *data, sf_count_t size) +{ + file->sfdata.data = data; + file->sfdata.length = size; + file->sfdata.offset = 0; + memset(&file->sfinfo, 0, sizeof(file->sfinfo)); + + file->sndfile = sf_open_virtual(&sfvio, SFM_READ, &file->sfinfo, &file->sfdata); + + if (!file->sndfile) + { + fprintf(stderr, "sf_open_virtual: %s\n", sf_strerror(file->sndfile)); + return false; + } + + if (file->sfinfo.frames <= 0 || file->sfinfo.channels <= 0) + { + CloseFile(file); + return false; + } + + // Detect a suitable format to load. Formats like Vorbis and Opus use float + // natively, so load as float to avoid clipping when possible. Formats + // larger than 16-bit can also use float to preserve a bit more precision. + + file->sample_format = Int16; + + switch ((file->sfinfo.format & SF_FORMAT_SUBMASK)) + { + case SF_FORMAT_PCM_24: + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + case SF_FORMAT_VORBIS: + case SF_FORMAT_OPUS: + case SF_FORMAT_ALAC_20: + case SF_FORMAT_ALAC_24: + case SF_FORMAT_ALAC_32: +#if HAVE_SNDFILE_MPEG + case SF_FORMAT_MPEG_LAYER_I: + case SF_FORMAT_MPEG_LAYER_II: + case SF_FORMAT_MPEG_LAYER_III: #endif + if (alIsExtensionPresent("AL_EXT_FLOAT32")) + file->sample_format = Float; + break; + } + + file->byteblockalign = 1; + + if (file->sample_format == Int16) + { + file->byteblockalign = file->sfinfo.channels * 2; + } + else if (file->sample_format == Float) + { + file->byteblockalign = file->sfinfo.channels * 4; + } + + // Figure out the OpenAL format from the file and desired sample type. + + file->format = AL_NONE; + + if (file->sfinfo.channels == 1) + { + if (file->sample_format == Int16) + file->format = AL_FORMAT_MONO16; + else if (file->sample_format == Float) + file->format = AL_FORMAT_MONO_FLOAT32; + } + else if (file->sfinfo.channels == 2) + { + if (file->sample_format == Int16) + file->format = AL_FORMAT_STEREO16; + else if (file->sample_format == Float) + file->format = AL_FORMAT_STEREO_FLOAT32; + } + + if (file->format == AL_NONE) + { + fprintf(stderr, "Unsupported channel count: %d\n", file->sfinfo.channels); + CloseFile(file); + return false; + } + + return true; +} + +boolean I_SND_LoadFile(void *data, ALenum *format, byte **wavdata, + ALsizei *size, ALsizei *freq) +{ + sndfile_t file; + sf_count_t num_frames = 0; + void *local_wavdata = NULL; + + if (OpenFile(&file, data, *size) == false) + { + return false; + } + + local_wavdata = malloc((size_t)(file.sfinfo.frames * file.byteblockalign)); + + if (file.sample_format == Int16) + num_frames = sf_readf_short(file.sndfile, local_wavdata, file.sfinfo.frames); + else if (file.sample_format == Float) + num_frames = sf_readf_float(file.sndfile, local_wavdata, file.sfinfo.frames); + + if (num_frames < file.sfinfo.frames) + { + fprintf(stderr, "sf_readf: %s\n", sf_strerror(file.sndfile)); + CloseFile(&file); + free(local_wavdata); + return false; + } + + CloseFile(&file); + + *wavdata = local_wavdata; + *format = file.format; + *size = (ALsizei)(num_frames * file.byteblockalign); + *freq = file.sfinfo.samplerate; + + return true; +} + +static sndfile_t stream; +static boolean looping; + +boolean I_SND_OpenStream(void *data, ALsizei size, ALenum *format, ALsizei *freq) +{ + if (OpenFile(&stream, data, size) == false) + { + return false; + } + + *format = stream.format; + *freq = stream.sfinfo.samplerate; + + return true; +} + +void I_SND_SetLooping(boolean on) +{ + looping = on; +} + +int I_SND_FillStream(byte *data, ALsizei size) +{ + sf_count_t num_frames = 0; + sf_count_t frames = size / stream.byteblockalign; + + if (stream.sample_format == Int16) + num_frames = sf_readf_short(stream.sndfile, (short *)data, frames); + else if (stream.sample_format == Float) + num_frames = sf_readf_float(stream.sndfile, (float *)data, frames); + + if (num_frames < frames && looping) + { + sf_seek(stream.sndfile, 0, SEEK_SET); + } + + return (num_frames * stream.byteblockalign); +} + +void I_SND_CloseStream(void) +{ + CloseFile(&stream); +} diff --git a/src/i_sndfile.h b/src/i_sndfile.h index ee916e4b..bdb7383b 100644 --- a/src/i_sndfile.h +++ b/src/i_sndfile.h @@ -19,8 +19,15 @@ #ifndef __I_SNDFILE__ #define __I_SNDFILE__ -#include +#include +#include "doomtype.h" -void *Load_SNDFile(void *data, SDL_AudioSpec *sample, Uint8 **wavdata, Uint32 *samplelen); +boolean I_SND_LoadFile(void *data, ALenum *format, byte **wavdata, + ALsizei *size, ALsizei *freq); + +boolean I_SND_OpenStream(void *data, ALsizei size, ALenum *format, ALsizei *freq); +void I_SND_SetLooping(boolean on); +int I_SND_FillStream(byte *data, ALsizei size); +void I_SND_CloseStream(void); #endif diff --git a/src/i_sound.c b/src/i_sound.c index 65eaf889..d4022dc0 100644 --- a/src/i_sound.c +++ b/src/i_sound.c @@ -20,7 +20,8 @@ // haleyjd #include "SDL.h" -#include "SDL_mixer.h" +#include +#include #include #include "doomstat.h" @@ -31,7 +32,7 @@ // Music modules extern music_module_t music_win_module; extern music_module_t music_fl_module; -extern music_module_t music_sdl_module; +extern music_module_t music_oal_module; extern music_module_t music_opl_module; typedef struct @@ -44,8 +45,6 @@ static music_modules_t music_modules[] = { #if defined(_WIN32) { &music_win_module, 1 }, -#else - { &music_sdl_module, 1 }, #endif #if defined(HAVE_FLUIDSYNTH) { &music_fl_module, 1 }, @@ -63,24 +62,22 @@ static boolean snd_init = false; // haleyjd 10/28/05: updated for Julian's music code, need full quality now int snd_samplerate; char *snd_resampling_mode; -static Uint16 mix_format; -static int mix_channels; + +static ALuint *openal_sources; typedef struct { // SFX id of the playing sound effect. // Used to catch duplicates (like chainsaw). sfxinfo_t *sfx; // The channel data pointer. - unsigned char* data; - // [FG] let SDL_Mixer do the actual sound mixing - Mix_Chunk chunk; + ALuint *data; // haleyjd 06/16/08: unique id number int idnum; } channel_info_t; channel_info_t channelinfo[MAX_CHANNELS]; -// Pitch to stepping lookup, unused. +// Pitch to stepping lookup. float steptable[256]; // @@ -101,7 +98,7 @@ static void StopChannel(int channel) if (channelinfo[channel].data) { - Mix_HaltChannel(channel); + alSourceStop(openal_sources[channel]); // [FG] immediately free samples not connected to a sound SFX if (channelinfo[channel].sfx == NULL) @@ -132,85 +129,16 @@ static void StopChannel(int channel) #define SOUNDHDRSIZE 8 -// [FG] support multi-channel samples by converting them to mono first -static Uint8 *ConvertToMono(Uint8 **data, SDL_AudioSpec *sample, Uint32 *len) -{ - SDL_AudioCVT cvt; - - if (sample->channels < 1) - { - return NULL; - } - - if (SDL_BuildAudioCVT(&cvt, - sample->format, sample->channels, sample->freq, - sample->format, 1, sample->freq) < 0) - { - fprintf(stderr, "SDL_BuildAudioCVT: %s\n", SDL_GetError()); - return NULL; - } - - cvt.len = *len; - cvt.buf = (Uint8 *)SDL_malloc(cvt.len * cvt.len_mult); // [FG] will call SDL_FreeWAV() on this later - memset(cvt.buf, 0, cvt.len * cvt.len_mult); - memcpy(cvt.buf, *data, cvt.len); - - if (SDL_ConvertAudio(&cvt) < 0) - { - SDL_free(cvt.buf); - fprintf(stderr, "SDL_ConvertAudio: %s\n", SDL_GetError()); - return NULL; - } - - SDL_FreeWAV(*data); - - sample->channels = 1; - *data = cvt.buf; - *len = cvt.len_cvt; - - return *data; -} - -// Allocate a new sound chunk and pitch-shift an existing sound up-or-down -// into it, based on chocolate-doom/src/i_sdlsound.c:PitchShift(). -static void PitchShift(sfxinfo_t *sfx, int pitch, Mix_Chunk *chunk) -{ - Sint16 *inp, *outp; - Sint16 *srcbuf, *dstbuf; - Uint32 srclen, dstlen; - - srcbuf = (Sint16 *)sfx->data; - srclen = sfx->alen; - - dstlen = (int)(srclen * steptable[pitch]); - - // ensure that the new buffer length is a multiple of sample size - dstlen = (dstlen + 3) & (Uint32)~3; - - dstbuf = (Sint16 *)malloc(dstlen); - - // loop over output buffer. find corresponding input cell, copy over - for (outp = dstbuf; outp < dstbuf + dstlen/2; outp++) - { - inp = srcbuf + (int)((float)(outp - dstbuf) * srclen / dstlen); - *outp = *inp; - } - - chunk->abuf = (Uint8 *)dstbuf; - chunk->alen = dstlen; -} - // // CacheSound // // haleyjd: needs to take a sfxinfo_t ptr, not a sound id num // haleyjd 06/03/06: changed to return boolean for failure or success // -static boolean CacheSound(sfxinfo_t *sfx, int channel, int pitch) +static boolean CacheSound(sfxinfo_t *sfx, int channel) { int lumpnum, lumplen; - Uint8 *lumpdata = NULL, *wavdata = NULL; - Mix_Chunk *const chunk = &channelinfo[channel].chunk; + byte *lumpdata = NULL, *wavdata = NULL; #ifdef RANGECHECK if (channel < 0 || channel >= MAX_CHANNELS) @@ -245,24 +173,24 @@ static boolean CacheSound(sfxinfo_t *sfx, int channel, int pitch) // haleyjd 06/03/06: rewrote again to make sound data properly freeable while (sfx->data == NULL) { - SDL_AudioSpec sample; - SDL_AudioCVT cvt; - Uint8 *sampledata; - Uint32 samplelen; + byte *sampledata; + ALsizei size, freq; + ALenum format; + ALuint *buffer; // haleyjd: this should always be called (if lump is already loaded, // W_CacheLumpNum handles that for us). - lumpdata = (Uint8 *)W_CacheLumpNum(lumpnum, PU_STATIC); + lumpdata = (byte *)W_CacheLumpNum(lumpnum, PU_STATIC); // Check the header, and ensure this is a valid sound if (lumpdata[0] == 0x03 && lumpdata[1] == 0x00) { - sample.freq = (lumpdata[3] << 8) | lumpdata[2]; - samplelen = (lumpdata[7] << 24) | (lumpdata[6] << 16) | - (lumpdata[5] << 8) | lumpdata[4]; + freq = (lumpdata[3] << 8) | lumpdata[2]; + size = (lumpdata[7] << 24) | (lumpdata[6] << 16) | + (lumpdata[5] << 8) | lumpdata[4]; // don't play sounds that think they're longer than they really are - if (samplelen > lumplen - SOUNDHDRSIZE) + if (size > lumplen - SOUNDHDRSIZE) { break; } @@ -270,53 +198,29 @@ static boolean CacheSound(sfxinfo_t *sfx, int channel, int pitch) sampledata = lumpdata + SOUNDHDRSIZE; // All Doom sounds are 8-bit - sample.format = AUDIO_U8; - sample.channels = 1; + format = AL_FORMAT_MONO8; } else { - samplelen = lumplen; + size = lumplen; - if (Load_SNDFile(lumpdata, &sample, &wavdata, &samplelen) == NULL) + if (I_SND_LoadFile(lumpdata, &format, &wavdata, &size, &freq) == false) { break; } - if (sample.channels != 1) - { - if (ConvertToMono(&wavdata, &sample, &samplelen) == NULL) - { - break; - } - } - sampledata = wavdata; } - // Convert sound to target samplerate - if (SDL_BuildAudioCVT(&cvt, - sample.format, sample.channels, sample.freq, - mix_format, mix_channels, snd_samplerate) < 0) + buffer = malloc(sizeof(*buffer)); + alGenBuffers(1, buffer); + alBufferData(*buffer, format, sampledata, size, freq); + if (alGetError() != AL_NO_ERROR) { - fprintf(stderr, "SDL_BuildAudioCVT: %s\n", SDL_GetError()); - break; + fprintf(stderr, "CacheSound: Error buffering data.\n"); + break; } - - cvt.len = samplelen; - cvt.buf = (Uint8 *)malloc(cvt.len * cvt.len_mult); - // [FG] clear buffer (cvt.len * cvt.len_mult >= cvt.len_cvt) - memset(cvt.buf, 0, cvt.len * cvt.len_mult); - memcpy(cvt.buf, sampledata, cvt.len); - - if (SDL_ConvertAudio(&cvt) < 0) - { - free(cvt.buf); - fprintf(stderr, "SDL_ConvertAudio: %s\n", SDL_GetError()); - break; - } - - sfx->data = cvt.buf; - sfx->alen = cvt.len_cvt; + sfx->data = buffer; } // don't need original lump data any more @@ -326,7 +230,7 @@ static boolean CacheSound(sfxinfo_t *sfx, int channel, int pitch) } if (wavdata) { - SDL_FreeWAV(wavdata); + free(wavdata); } if (sfx->data == NULL) @@ -335,27 +239,10 @@ static boolean CacheSound(sfxinfo_t *sfx, int channel, int pitch) return false; } - // [FG] let SDL_Mixer do the actual sound mixing - chunk->allocated = 1; - chunk->volume = MIX_MAX_VOLUME; + // Preserve sound SFX id + channelinfo[channel].sfx = sfx; - if (pitch != NORM_PITCH) - { - PitchShift(sfx, pitch, chunk); - - // [FG] do not connect pitch-shifted samples to a sound SFX - channelinfo[channel].sfx = NULL; - } - else - { - chunk->abuf = sfx->data; - chunk->alen = sfx->alen; - - // Preserve sound SFX id - channelinfo[channel].sfx = sfx; - } - - channelinfo[channel].data = chunk->abuf; + channelinfo[channel].data = sfx->data; return true; } @@ -370,8 +257,8 @@ int forceFlipPan; // void I_UpdateSoundParams(int channel, int volume, int separation) { - int rightvol; - int leftvol; + ALfloat pan; + ALuint source; if (!snd_init) return; @@ -385,20 +272,16 @@ void I_UpdateSoundParams(int channel, int volume, int separation) if (forceFlipPan) separation = 254 - separation; - // [FG] linear stereo volume separation - leftvol = ((254 - separation) * volume) / 127; - rightvol = ((separation) * volume) / 127; + source = openal_sources[channel]; - if (leftvol < 0) - leftvol = 0; - else if (leftvol > 255) - leftvol = 255; - if (rightvol < 0) - rightvol = 0; - else if (rightvol > 255) - rightvol = 255; + alSourcef(source, AL_GAIN, (ALfloat)volume / 127.0f); - Mix_SetPanning(channel, leftvol, rightvol); + // Create a panning effect by moving the source in an arc around the listener. + // https://github.com/kcat/openal-soft/issues/194 + pan = (ALfloat)separation / 255.0f - 0.5f; + alSourcef(source, AL_ROLLOFF_FACTOR, 0.0f); + alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); + alSource3f(source, AL_POSITION, pan, 0.0f, -sqrtf(1.0f - pan * pan)); } // [FG] variable pitch bend range @@ -499,11 +382,26 @@ int I_StartSound(sfxinfo_t *sound, int vol, int sep, int pitch, boolean loop) if (channel == MAX_CHANNELS) return -1; - if (CacheSound(sound, channel, pitch)) + if (CacheSound(sound, channel)) { + ALuint source = openal_sources[channel]; + ALuint buffer = *channelinfo[channel].data; + channelinfo[channel].idnum = id++; // give the sound a unique id - Mix_PlayChannelTimed(channel, &channelinfo[channel].chunk, loop ? -1 : 0, -1); I_UpdateSoundParams(channel, vol, sep); + + alSourcei(source, AL_BUFFER, buffer); + alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE); + if (pitch != NORM_PITCH) + { + alSourcef(source, AL_PITCH, steptable[pitch]); + } + + alSourcePlay(source); + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "I_StartSound: Error playing sfx.\n"); + } } else channel = -1; @@ -537,6 +435,8 @@ void I_StopSound(int channel) // int I_SoundIsPlaying(int channel) { + ALint value; + if (!snd_init) return false; @@ -545,7 +445,8 @@ int I_SoundIsPlaying(int channel) I_Error("I_SoundIsPlaying: channel out of range"); #endif - return Mix_Playing(channel); + alGetSourcei(openal_sources[channel], AL_SOURCE_STATE, &value); + return (value == AL_PLAYING); } // @@ -606,37 +507,43 @@ void I_UpdateSound(void) // void I_ShutdownSound(void) { - if (snd_init) - { - Mix_CloseAudio(); - snd_init = 0; - } -} + int i; + ALCcontext *context; + ALCdevice *device; -// Calculate slice size, the result must be a power of two. - -static int GetSliceSize(void) -{ - int limit; - int n; - - limit = snd_samplerate / TICRATE; - - // Try all powers of two, not exceeding the limit. - - for (n=0;; ++n) + if (!snd_init) { - // 2^n <= limit < 2^n+1 ? - - if ((1 << (n + 1)) > limit) - { - return (1 << n); - } + return; } - // Should never happen? + context = alcGetCurrentContext(); - return 1024; + if (!context) + { + return; + } + + device = alcGetContextsDevice(context); + + alDeleteSources(MAX_CHANNELS, openal_sources); + for (i = 0; i < num_sfx; ++i) + { + if (S_sfx[i].data) + { + alDeleteBuffers(1, S_sfx[i].data); + free(S_sfx[i].data); + } + } + if (alGetError() != AL_NO_ERROR) + { + fprintf(stderr, "I_ShutdownSound: Failed to delete object IDs.\n"); + } + + alcMakeContextCurrent(NULL); + alcDestroyContext(context); + alcCloseDevice(device); + + snd_init = false; } // [FG] add links for likely missing sounds @@ -663,43 +570,45 @@ struct { void I_InitSound(void) { - if (!nosfxparm || !nomusicparm) - { + const ALCchar *name; + ALCdevice *device; + ALCcontext *context; + + if (nosfxparm) + { + return; + } + printf("I_InitSound: "); - SDL_SetHint(SDL_HINT_AUDIO_RESAMPLING_MODE, snd_resampling_mode); + name = alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); - if (SDL_Init(SDL_INIT_AUDIO) < 0) + device = alcOpenDevice(name); + if (device) { - printf("Couldn't initialize SDL audio: %s\n", SDL_GetError()); - return; + printf("Using '%s'.\n", name); + } + else + { + printf("Could not open a device.\n"); + return; } - if (Mix_OpenAudioDevice(snd_samplerate, AUDIO_S16SYS, 2, GetSliceSize(), NULL, - SDL_AUDIO_ALLOW_FREQUENCY_CHANGE) < 0) + context = alcCreateContext(device, NULL); + if (!context || alcMakeContextCurrent(context) == ALC_FALSE) { - printf("Couldn't open audio with desired format.\n"); - return; + fprintf(stderr, "I_InitSound: Error making context.\n"); + return; } - // [FG] feed actual sample frequency back into config variable - Mix_QuerySpec(&snd_samplerate, &mix_format, &mix_channels); - printf("Configured audio device with %.1f kHz (%s%d%s), %d channels.\n", - (float)snd_samplerate / 1000, - SDL_AUDIO_ISFLOAT(mix_format) ? "F" : SDL_AUDIO_ISSIGNED(mix_format) ? "S" : "U", - (int)SDL_AUDIO_BITSIZE(mix_format), - SDL_AUDIO_BITSIZE(mix_format) > 8 ? (SDL_AUDIO_ISBIGENDIAN(mix_format) ? "MSB" : "LSB") : "", - mix_channels); - - // [FG] let SDL_Mixer do the actual sound mixing - Mix_AllocateChannels(MAX_CHANNELS); + openal_sources = malloc(MAX_CHANNELS * sizeof(*openal_sources)); + alGenSources(MAX_CHANNELS, openal_sources); I_AtExit(I_ShutdownSound, true); snd_init = true; // [FG] precache all sound effects - if (!nosfxparm) { int i; @@ -710,7 +619,7 @@ void I_InitSound(void) if (!S_sfx[i].name) continue; - CacheSound(&S_sfx[i], 0, NORM_PITCH); + CacheSound(&S_sfx[i], 0); } StopChannel(0); printf("done.\n"); @@ -729,7 +638,6 @@ void I_InitSound(void) } } } - } } int midi_player; // current music module @@ -759,28 +667,18 @@ void I_SetMidiPlayer(int device) accum += num_devices; } - if (!midi_player_module->I_InitMusic(device)) - { - midi_player_module = music_modules[0].module; - if (midi_player_module != &music_sdl_module) - { - midi_player_module->I_InitMusic(0); - } - } + midi_player_module->I_InitMusic(device); active_module = midi_player_module; } boolean I_InitMusic(void) { - // haleyjd 04/11/03: don't use music if sfx aren't init'd - // (may be dependent, docs are unclear) if (nomusicparm) { return false; } - // always initilize SDL music - music_sdl_module.I_InitMusic(0); + music_oal_module.I_InitMusic(0); I_AtExit(I_ShutdownMusic, true); @@ -797,10 +695,8 @@ boolean I_InitMusic(void) // Fall back to module 0 device 0. midi_player = 0; midi_player_module = music_modules[0].module; - if (midi_player_module != &music_sdl_module) - { - midi_player_module->I_InitMusic(0); - } + midi_player_module->I_InitMusic(0); + active_module = midi_player_module; return true; @@ -808,9 +704,9 @@ boolean I_InitMusic(void) void I_ShutdownMusic(void) { - music_sdl_module.I_ShutdownMusic(); + music_oal_module.I_ShutdownMusic(); - if (midi_player_module && midi_player_module != &music_sdl_module) + if (midi_player_module) { midi_player_module->I_ShutdownMusic(); } @@ -860,7 +756,7 @@ void *I_RegisterSong(void *data, int size) midi_player_module->I_ShutdownMusic(); } - active_module = &music_sdl_module; + active_module = &music_oal_module; return active_module->I_RegisterSong(data, size); } diff --git a/src/sounds.c b/src/sounds.c index dc42a804..1e1195fb 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -125,8 +125,7 @@ musicinfo_t S_music[] = { .volume = -1, \ .data = NULL, \ .usefulness = -1, \ - .lumpnum = -1, \ - .alen = 0} + .lumpnum = -1} #define SOUND(n, s, p) \ SOUND_LINK(n, s, p, 0, -1) diff --git a/src/sounds.h b/src/sounds.h index e938cce9..45d3677d 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -67,8 +67,6 @@ typedef struct sfxinfo_struct { // lump number of sfx int lumpnum; - // haleyjd 04/23/08: additional caching data - unsigned int alen; // length of converted sound pointed to by data } sfxinfo_t; extern int parallel_sfx_limit; diff --git a/vcpkg.json b/vcpkg.json index ca790592..6779f210 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,19 +1,9 @@ { "dependencies": [ - { - "name": "sdl2", - "features": [ "samplerate" ] - }, - { - "name": "sdl2-mixer", - "features": [ "libmodplug", "opusfile" ] - }, + "sdl2", "sdl2-net", - { - "name": "libsndfile", - "default-features": false, - "features": [ "external-libs" ] - }, - "fluidsynth" + "libsndfile", + "fluidsynth", + "openal-soft" ] }