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
This commit is contained in:
Roman Fomin 2023-04-04 19:46:58 +07:00 committed by GitHub
parent 28a88c41dc
commit 1a278d6105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 740 additions and 894 deletions

View File

@ -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

View File

@ -5,8 +5,8 @@ on:
branches: [ master ]
tags: ['*']
paths-ignore: ['**.md']
pull_request:
branches: [ master ]
# pull_request:
# branches: [ master ]
env:
VCPKG_ROOT: C:\vcpkg

View File

@ -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 <sndfile.h>
int main(void) {return SF_FORMAT_MPEG_LAYER_III;}"
HAVE_SNDFILE_MPEG)
configure_file(config.h.in config.h)
if(WIN32)

View File

@ -1,123 +0,0 @@
# FindSDL2_mixer.cmake
#
# Copyright (c) 2018, Alex Mayfield <alexmax2742@gmail.com>
# 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 <organization> 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 <COPYRIGHT HOLDER> 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()

View File

@ -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()

View File

@ -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()

View File

@ -8,3 +8,4 @@
#cmakedefine01 HAVE_DECL_STRCASECMP
#cmakedefine01 HAVE_DECL_STRNCASECMP
#cmakedefine WOOFDATADIR "@WOOFDATADIR@"
#cmakedefine01 HAVE_SNDFILE_MPEG

View File

@ -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)

View File

@ -21,11 +21,10 @@
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#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;
}

View File

@ -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 "$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>"
SDL2::SDL2 SDL2_net::SDL2_net textscreen setup)
target_link_libraries(woof-setup PRIVATE
"$<TARGET_NAME_IF_EXISTS:SDL2::SDL2main>"
SDL2::SDL2
SDL2_net::SDL2_net
textscreen
setup)
if(MSVC)
target_link_options(woof-setup PRIVATE "/MANIFEST:NO")

View File

@ -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;

320
src/i_oalmusic.c Normal file
View File

@ -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 <AL/al.h>
#include <AL/alext.h>
#include <stdlib.h>
#include <string.h>
#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,
};

27
src/i_oalmusic.h Normal file
View File

@ -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

View File

@ -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,
};

View File

@ -18,9 +18,10 @@
#include "i_sndfile.h"
#if defined(HAVE_SNDFILE)
#include <AL/alext.h>
#include <sndfile.h>
#include <stdlib.h>
#include <string.h>
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);
}

View File

@ -19,8 +19,15 @@
#ifndef __I_SNDFILE__
#define __I_SNDFILE__
#include <SDL_audio.h>
#include <AL/al.h>
#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

View File

@ -20,7 +20,8 @@
// haleyjd
#include "SDL.h"
#include "SDL_mixer.h"
#include <AL/al.h>
#include <AL/alc.h>
#include <math.h>
#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);
}

View File

@ -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)

View File

@ -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;

View File

@ -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"
]
}