From 37fc04b9eb716f1e4b50bdc7e9f5867782a692db Mon Sep 17 00:00:00 2001 From: xtremeqg Date: Thu, 30 Jan 2025 16:53:24 +0200 Subject: [PATCH] Use OpenAL instead of Miles Sound System (#3842) - People are now free to delete MSS32.DLL and WSND7R.DLL - Removes dependency on the last remaining closed source DLLs --- Makefile | 18 +- deps/openal | 1 + mingw32.cmake | 9 + src/bflib_sndlib.c | 484 ------------------------------- src/bflib_sndlib.cpp | 641 ++++++++++++++++++++++++++++++++++++++++++ src/bflib_sndlib.h | 35 +-- src/bflib_sound.c | 101 +------ src/bflib_sound.h | 10 +- src/cdrom.cpp | 154 ++++++++++ src/config_objects.c | 6 +- src/config_trapdoor.c | 6 +- src/front_torture.c | 2 +- src/game_heap.c | 22 -- src/main.cpp | 5 - src/map_events.c | 2 +- src/sounds.c | 192 +------------ src/sounds.h | 26 -- 17 files changed, 838 insertions(+), 876 deletions(-) create mode 160000 deps/openal create mode 100644 mingw32.cmake delete mode 100644 src/bflib_sndlib.c create mode 100644 src/bflib_sndlib.cpp create mode 100644 src/cdrom.cpp diff --git a/Makefile b/Makefile index 1a5fb5de71..dd89b4bd58 100644 --- a/Makefile +++ b/Makefile @@ -137,6 +137,7 @@ obj/bflib_vidraw_spr_norm.o \ obj/bflib_vidraw_spr_onec.o \ obj/bflib_vidraw_spr_remp.o \ obj/bflib_vidsurface.o \ +obj/cdrom.o \ obj/config.o \ obj/config_campaigns.o \ obj/config_creature.o \ @@ -346,12 +347,13 @@ LINKLIB = -mwindows \ -L"deps/ffmpeg/libavcodec" -lavcodec \ -L"deps/ffmpeg/libswresample" -lswresample \ -L"deps/ffmpeg/libavutil" -lavutil \ + -L"deps/openal" -lOpenAL32 \ -L"deps/astronomy" -lastronomy \ -L"deps/enet" -lenet \ -L"deps/spng" -lspng \ -L"deps/centijson" -ljson \ -L"deps/zlib" -lminizip -lz \ - -lwinmm -lmingw32 -limagehlp -lws2_32 -ldbghelp -lbcrypt + -lwinmm -lmingw32 -limagehlp -lws2_32 -ldbghelp -lbcrypt -lole32 -luuid INCS = \ -I"deps/zlib/include" \ -I"deps/spng/include" \ @@ -362,6 +364,7 @@ INCS = \ -I"deps/centitoml" \ -I"deps/astronomy/include" \ -I"deps/ffmpeg" \ + -I"deps/openal/include" \ -I"obj" # To find ver_defs.h CXXINCS = $(INCS) @@ -375,7 +378,7 @@ HVLOG_MAIN_OBJ = $(subst obj/,obj/hvlog/,$(MAIN_OBJ)) ENABLE_EXTRACT ?= 1 # flags to generate dependency files -DEPFLAGS = -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -DSPNG_STATIC=1 +DEPFLAGS = -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -DSPNG_STATIC=1 -DAL_LIBTYPE_STATIC # other flags to include while compiling INCFLAGS = # code optimization and debugging flags @@ -523,7 +526,7 @@ ifdef CV2PDB $(CV2PDB) -C "$@" endif -obj/std/centitoml/toml_api.o obj/hvlog/centitoml/toml_api.o: deps/centitoml/toml_api.c build-before +obj/std/centitoml/toml_api.o obj/hvlog/centitoml/toml_api.o: deps/centitoml/toml_api.c -$(ECHO) 'Building file: $<' $(CC) $(CFLAGS) -o"$@" "$<" -$(ECHO) ' ' @@ -585,7 +588,7 @@ clean-libexterns: libexterns.mk -$(RM) -rf deps/enet deps/zlib deps/spng deps/astronomy deps/centijson -$(RM) libexterns -deps/enet deps/zlib deps/spng deps/astronomy deps/centijson deps/ffmpeg: +deps/enet deps/zlib deps/spng deps/astronomy deps/centijson deps/ffmpeg deps/openal: $(MKDIR) $@ src/api.c: deps/centijson/include/json.h @@ -596,6 +599,7 @@ deps/centitoml/toml_api.c: deps/centijson/include/json.h deps/centitoml/toml_conv.c: deps/centijson/include/json.h src/bflib_fmvids.cpp: deps/ffmpeg/libavformat/avformat.h obj/std/bflib_fmvids.o obj/hvlog/bflib_fmvids.o: CXXFLAGS += -Wno-error=deprecated-declarations +src/bflib_sndlib.cpp: deps/openal/AL/al.h deps/enet-mingw32.tar.gz: curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/initial/enet-mingw32.tar.gz" @@ -633,6 +637,12 @@ deps/ffmpeg-mingw32.tar.gz: deps/ffmpeg/libavformat/avformat.h: deps/ffmpeg-mingw32.tar.gz | deps/ffmpeg tar xzmf $< -C deps/ffmpeg +deps/openal-mingw32.tar.gz: + curl -Lso $@ "https://github.com/dkfans/kfx-deps/releases/download/2024-11-14/openal-mingw32.tar.gz" + +deps/openal/AL/al.h: deps/openal-mingw32.tar.gz | deps/openal + tar xzmf $< -C deps/openal + include tool_png2ico.mk include tool_pngpal2raw.mk include tool_png2bestpal.mk diff --git a/deps/openal b/deps/openal new file mode 160000 index 0000000000..d3875f333f --- /dev/null +++ b/deps/openal @@ -0,0 +1 @@ +Subproject commit d3875f333fb6abe2f39d82caca329414871ae53b diff --git a/mingw32.cmake b/mingw32.cmake new file mode 100644 index 0000000000..c3380596d8 --- /dev/null +++ b/mingw32.cmake @@ -0,0 +1,9 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(TOOLCHAIN_PREFIX i686-w64-mingw32) +set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc-posix) +set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++-posix) +set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres) +set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/src/bflib_sndlib.c b/src/bflib_sndlib.c deleted file mode 100644 index 3313805d97..0000000000 --- a/src/bflib_sndlib.c +++ /dev/null @@ -1,484 +0,0 @@ -/******************************************************************************/ -// Bullfrog Engine Emulation Library - for use to remake classic games like -// Syndicate Wars, Magic Carpet or Dungeon Keeper. -/******************************************************************************/ -/** @file bflib_sndlib.c - * Low-level sound and music related routines. - * @par Purpose: - * Hardware wrapper to play music and sound in games. - * @par Comment: - * Windows version os Bullfrog Engine uses Miles Sound System, wrapped - * with WSND7R.DLL. This library contains definitions of the exported functions. - * @author KeeperFX Team - * @date 16 Nov 2008 - 30 Dec 2008 - * @par Copying and copyrights: - * 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. - */ -/******************************************************************************/ -#include "pre_inc.h" -#include "bflib_sndlib.h" - -#include -#include -#include -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include - -#include "bflib_basics.h" -#include "post_inc.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - /******************************************************************************/ - // Global variables - - typedef int(WINAPI *FARPROCI)(int); - typedef int(WINAPI *FARPROCII)(int, int); - typedef int(WINAPI *FARPROCIII)(int, int, int); - typedef int(WINAPI *FARPROCS)(const char *); - typedef int(WINAPI *FARPROCP)(const void *); - typedef int(WINAPI *FARPROCSIII)(const char *, int, int, int); - typedef int(WINAPI *FARPROCIIII)(int, int, int, int); - typedef struct SampleInfo *(WINAPI *FARPROC_PLAY1)(int, int, int, int, int, unsigned char, unsigned char, void *, int); - - HMODULE WSND7R; - - FARPROC _FreeAudio; - FARPROC _SetRedbookVolume; - FARPROC _SetSoundMasterVolume; - FARPROC _SetMusicMasterVolume; - FARPROC _GetSoundInstalled; - FARPROC _PlayRedbookTrack; - FARPROC _PauseRedbookTrack; - FARPROC _ResumeRedbookTrack; - FARPROC _MonitorStreamedSoundTrack; - FARPROC _StopRedbookTrack; - FARPROC _GetSoundDriver; - FARPROC _StopAllSamples; - FARPROC _GetFirstSampleInfoStructure; - FARPROC _InitAudio; - FARPROC _SetupAudioOptionDefaults; - FARPROC _IsSamplePlaying; - FARPROC _GetLastSampleInfoStructure; - FARPROC _GetCurrentSoundMasterVolume; - FARPROC _StopSample; - FARPROC _SetSampleVolume; - FARPROC _SetSamplePan; - FARPROC _SetSamplePitch; - FARPROC _PlaySampleFromAddress; - - /******************************************************************************/ - // Functions - - // GetModuleHandleExA(0,"WSND7R",&hModule); seems not to work - - /** - * Initialize the Miles Sound System library. - * - * When sounds have been disabled by the user, the library will not be loaded. - * The seperate functions can still be called and will silently return 0 in that case. - * - * If the library is successfuly loaded it's possible that the functions have not. - * In that case it will simply log an error and continue execution. - * - * @returns -1 when sound is disabled, 0 if the library failed to load, 1 if the library is loaded - */ - int init_miles_sound_system() - { - if (SoundDisabled == true) - { - return -1; - } - - WSND7R = LoadLibrary("WSND7R"); - - if (WSND7R == NULL) - { - ERRORLOG("Failed to load Miles Sound System"); - return 0; - } - - _FreeAudio = GetProcAddress(WSND7R, "_FreeAudio@0"); - if (_FreeAudio == NULL) - { - ERRORLOG("Can't get address of FreeAudio function; skipped."); - } - - _SetRedbookVolume = GetProcAddress(WSND7R, "_SetRedbookVolume@4"); - if (_SetRedbookVolume == NULL) - { - ERRORLOG("Can't get address of SetRedbookVolume function; skipped."); - } - - _SetSoundMasterVolume = GetProcAddress(WSND7R, "_SetSoundMasterVolume@4"); - if (_SetSoundMasterVolume == NULL) - { - ERRORLOG("Can't get address of SetSoundMasterVolume function; skipped."); - } - - _SetMusicMasterVolume = GetProcAddress(WSND7R, "_SetMusicMasterVolume@4"); - if (_SetMusicMasterVolume == NULL) - { - ERRORLOG("Can't get address of SetMusicMasterVolume function; skipped."); - } - - _GetSoundInstalled = GetProcAddress(WSND7R, "_GetSoundInstalled@0"); - if (_GetSoundInstalled == NULL) - { - ERRORLOG("Can't get address of GetSoundInstalled function; skipped."); - } - - _PlayRedbookTrack = GetProcAddress(WSND7R, "_PlayRedbookTrack@4"); - if (_PlayRedbookTrack == NULL) - { - ERRORLOG("Can't get address of PlayRedbookTrack function; skipped."); - } - - _PauseRedbookTrack = GetProcAddress(WSND7R, "_PauseRedbookTrack@0"); - if (_PauseRedbookTrack == NULL) - { - ERRORLOG("Can't get address of PauseRedbookTrack function; skipped."); - } - - _ResumeRedbookTrack = GetProcAddress(WSND7R, "_ResumeRedbookTrack@0"); - if (_ResumeRedbookTrack == NULL) - { - ERRORLOG("Can't get address of ResumeRedbookTrack function; skipped."); - } - - _MonitorStreamedSoundTrack = GetProcAddress(WSND7R, "_MonitorStreamedSoundTrack@0"); - if (_MonitorStreamedSoundTrack == NULL) - { - ERRORLOG("Can't get address of MonitorStreamedSoundTrack function; skipped."); - } - - _StopRedbookTrack = GetProcAddress(WSND7R, "_StopRedbookTrack@0"); - if (_StopRedbookTrack == NULL) - { - ERRORLOG("Can't get address of StopRedbookTrack function; skipped."); - } - - _GetSoundDriver = GetProcAddress(WSND7R, "_GetSoundDriver@0"); - if (_GetSoundDriver == NULL) - { - ERRORLOG("Can't get address of GetSoundDriver function; skipped."); - } - - _StopAllSamples = GetProcAddress(WSND7R, "_StopAllSamples@0"); - if (_StopAllSamples == NULL) - { - ERRORLOG("Can't get address of StopAllSamples function; skipped."); - } - - _GetFirstSampleInfoStructure = GetProcAddress(WSND7R, "_GetFirstSampleInfoStructure@0"); - if (_GetFirstSampleInfoStructure == NULL) - { - ERRORLOG("Can't get address of GetFirstSampleInfoStructure function; skipped."); - } - - _InitAudio = GetProcAddress(WSND7R, "_InitAudio@4"); - if (_InitAudio == NULL) - { - ERRORLOG("Can't get address of InitAudio function; skipped."); - } - - _SetupAudioOptionDefaults = GetProcAddress(WSND7R, "_SetupAudioOptionDefaults@4"); - if (_SetupAudioOptionDefaults == NULL) - { - ERRORLOG("Can't get address of SetupAudioOptionDefaults function; skipped."); - } - - _IsSamplePlaying = GetProcAddress(WSND7R, "_IsSamplePlaying@12"); - if (_IsSamplePlaying == NULL) - { - ERRORLOG("Can't get address of IsSamplePlaying function; skipped."); - } - - _GetLastSampleInfoStructure = GetProcAddress(WSND7R, "_GetLastSampleInfoStructure@0"); - if (_GetLastSampleInfoStructure == NULL) - { - ERRORLOG("Can't get address of GetLastSampleInfoStructure function; skipped."); - } - - _GetCurrentSoundMasterVolume = GetProcAddress(WSND7R, "_GetCurrentSoundMasterVolume@0"); - if (_GetCurrentSoundMasterVolume == NULL) - { - ERRORLOG("Can't get address of GetCurrentSoundMasterVolume function; skipped."); - } - - _StopSample = GetProcAddress(WSND7R, "_StopSample@8"); - if (_StopSample == NULL) - { - ERRORLOG("Can't get address of StopSample function; skipped."); - } - - _SetSampleVolume = GetProcAddress(WSND7R, "_SetSampleVolume@16"); - if (_SetSampleVolume == NULL) - { - ERRORLOG("Can't get address of SetSampleVolume function; skipped."); - } - - _SetSamplePan = GetProcAddress(WSND7R, "_SetSamplePan@16"); - if (_SetSamplePan == NULL) - { - ERRORLOG("Can't get address of SetSamplePan function; skipped."); - } - - _SetSamplePitch = GetProcAddress(WSND7R, "_SetSamplePitch@16"); - if (_SetSamplePitch == NULL) - { - ERRORLOG("Can't get address of SetSamplePitch function; skipped."); - } - - _PlaySampleFromAddress = GetProcAddress(WSND7R, "_PlaySampleFromAddress@36"); - if (_PlaySampleFromAddress == NULL) - { - ERRORLOG("Can't get address of PlaySampleFromAddress function; skipped."); - } - - return 1; - } - - void FreeAudio(void) - { - if (_FreeAudio != NULL) - { - _FreeAudio(); - } - } - - void SetRedbookVolume(SoundVolume volume) - { - if (_SetRedbookVolume != NULL) - { - ((FARPROCI)_SetRedbookVolume)(volume); - } - } - - void SetSoundMasterVolume(SoundVolume volume) - { - if (_SetSoundMasterVolume != NULL) - { - ((FARPROCI)_SetSoundMasterVolume)(volume); - } - } - - void SetMusicMasterVolume(SoundVolume volume) - { - if (_SetMusicMasterVolume != NULL) - { - ((FARPROCI)_SetMusicMasterVolume)(volume); - } - } - - TbBool GetSoundInstalled(void) - { - if (_GetSoundInstalled != NULL) - { - return _GetSoundInstalled(); - } - - return 0; - } - - void PlayRedbookTrack(int track) - { - if (_PlayRedbookTrack != NULL) - { - ((FARPROCI)_PlayRedbookTrack)(track); - } - } - - void PauseRedbookTrack(void) - { - if (_PauseRedbookTrack != NULL) - { - _PauseRedbookTrack(); - } - } - - void ResumeRedbookTrack(void) - { - if (_ResumeRedbookTrack != NULL) - { - _ResumeRedbookTrack(); - } - } - - void MonitorStreamedSoundTrack(void) - { - if (_MonitorStreamedSoundTrack != NULL) - { - _MonitorStreamedSoundTrack(); - } - } - - void StopRedbookTrack(void) - { - if (_StopRedbookTrack != NULL) - { - _StopRedbookTrack(); - } - } - - void *GetSoundDriver(void) - { - if (_GetSoundDriver != NULL) - { - return (void *)_GetSoundDriver(); - } - - return 0; - } - - void StopAllSamples(void) - { - if (_StopAllSamples != NULL) - { - _StopAllSamples(); - } - } - - struct SampleInfo *GetFirstSampleInfoStructure(void) - { - if (_GetFirstSampleInfoStructure != NULL) - { - return (struct SampleInfo *)_GetFirstSampleInfoStructure(); - } - - return 0; - } - - TbBool InitAudio(struct SoundSettings * settings) - { - if (_InitAudio != NULL) - { - return ((FARPROCP)_InitAudio)(settings); - } - - return false; - } - - void SetupAudioOptionDefaults(struct SoundSettings * settings) - { - if (_SetupAudioOptionDefaults != NULL) - { - ((FARPROCP)_SetupAudioOptionDefaults)(settings); - } - } - - TbBool IsSamplePlaying(SoundMilesID a3) - { - if (_IsSamplePlaying != NULL) - { - return (unsigned char)((FARPROCIII)_IsSamplePlaying)(0, 0, a3); - } - - return false; - } - - struct SampleInfo *GetLastSampleInfoStructure(void) - { - if (_GetLastSampleInfoStructure != NULL) - { - return (struct SampleInfo *)_GetLastSampleInfoStructure(); - } - - return NULL; - } - - SoundVolume GetCurrentSoundMasterVolume(void) - { - if (_GetCurrentSoundMasterVolume != NULL) - { - return _GetCurrentSoundMasterVolume(); - } - - return 0; - } - - void StopSample(SoundEmitterID emit_id, SoundSmplTblID smptbl_id) - { - if (_StopSample != NULL) - { - ((FARPROCII)_StopSample)(emit_id, smptbl_id); - } - } - - void SetSampleVolume(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundVolume volume) - { - if (_SetSampleVolume != NULL) - { - ((FARPROCIIII)_SetSampleVolume)(emit_id, smptbl_id, volume, 0); - } - } - - void SetSamplePan(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundPan pan) - { - if (_SetSamplePan != NULL) - { - ((FARPROCIIII)_SetSamplePan)(emit_id, smptbl_id, pan, 0); - } - } - - void SetSamplePitch(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundPitch pitch) - { - if (_SetSamplePitch != NULL) - { - ((FARPROCIIII)_SetSamplePitch)(emit_id, smptbl_id, pitch, 0); - } - } - - struct SampleInfo *PlaySampleFromAddress(SoundEmitterID emit_id, SoundSmplTblID smpl_idx, SoundVolume volume, SoundPan pan, SoundPitch pitch, unsigned char a6, unsigned char a7, void *buf, SoundSFXID sfxid) - { - if (_PlaySampleFromAddress != NULL) - { - return ((FARPROC_PLAY1)(void *)_PlaySampleFromAddress)(emit_id, smpl_idx, volume, pan, pitch, a6, a7, buf, sfxid); - } - - return NULL; - } - - void unload_miles_sound_system() - { - if (WSND7R != NULL) - { - FreeLibrary(WSND7R); - WSND7R = NULL; - } - - _FreeAudio = NULL; - _SetRedbookVolume = NULL; - _SetSoundMasterVolume = NULL; - _SetMusicMasterVolume = NULL; - _GetSoundInstalled = NULL; - _PlayRedbookTrack = NULL; - _PauseRedbookTrack = NULL; - _ResumeRedbookTrack = NULL; - _MonitorStreamedSoundTrack = NULL; - _StopRedbookTrack = NULL; - _GetSoundDriver = NULL; - _StopAllSamples = NULL; - _GetFirstSampleInfoStructure = NULL; - _InitAudio = NULL; - _SetupAudioOptionDefaults = NULL; - _IsSamplePlaying = NULL; - _GetLastSampleInfoStructure = NULL; - _GetCurrentSoundMasterVolume = NULL; - _StopSample = NULL; - _SetSampleVolume = NULL; - _SetSamplePan = NULL; - _SetSamplePitch = NULL; - _PlaySampleFromAddress = NULL; - } - -/******************************************************************************/ -#ifdef __cplusplus -} -#endif diff --git a/src/bflib_sndlib.cpp b/src/bflib_sndlib.cpp new file mode 100644 index 0000000000..0421aee62e --- /dev/null +++ b/src/bflib_sndlib.cpp @@ -0,0 +1,641 @@ +#include "pre_inc.h" +#include "config.h" +#include "bflib_sndlib.h" +#include "bflib_sound.h" +#include "bflib_fileio.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "post_inc.h" + +namespace { + +struct device_deleter { + void operator()(ALCdevice * device) { + alcCloseDevice(device); + } +}; + +struct context_deleter { + void operator()(ALCcontext * context) { + alcMakeContextCurrent(nullptr); + alcDestroyContext(context); + } +}; + +using ALCdevice_ptr = std::unique_ptr; +using ALCcontext_ptr = std::unique_ptr; + +SoundVolume g_master_volume = 0; +SoundVolume g_music_volume = 0; +ALCdevice_ptr g_openal_device; +ALCcontext_ptr g_openal_context; + +const char * alErrorStr(ALenum code) { + switch (code) { + case AL_NO_ERROR: return "No error"; + case AL_INVALID_NAME: return "Invalid name"; + case AL_INVALID_ENUM: return "Invalid enum value"; + case AL_INVALID_VALUE: return "Invalid value"; + case AL_INVALID_OPERATION: return "Invalid operation"; + case AL_OUT_OF_MEMORY: return "Out of memory"; + } + return "Unknown"; +} + +class openal_error : public std::runtime_error { +public: + inline openal_error(const char * description, ALenum errcode = alGetError()) + : runtime_error(std::string("OpenAL error: ") + description + ": " + alErrorStr(errcode)) + {} +}; + +class openal_buffer { +public: + ALuint id = 0; + + openal_buffer() { + ALuint buffers[1]; + alGenBuffers(1, buffers); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot create buffer", errcode); + } + id = buffers[0]; + } + + inline ~openal_buffer() noexcept { + alDeleteBuffers(1, &id); + } + + openal_buffer(const openal_buffer &) = delete; + openal_buffer & operator=(const openal_buffer &) = delete; + + inline openal_buffer(openal_buffer && other) + : id(std::exchange(other.id, 0)) {} + + inline openal_buffer & operator=(openal_buffer && other) { + id = std::exchange(other.id, 0); + return *this; + } +}; + +class openal_source { +public: + ALuint id = 0; + SoundMilesID mss_id = 0; + SoundEmitterID emit_id = 0; + SoundSmplTblID smptbl_id = 0; + SoundBankID bank_id = 0; + + openal_source() { + ALuint sources[1]; + alGenSources(1, sources); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot create source", errcode); + } + id = sources[0]; + } + + inline ~openal_source() noexcept { + alDeleteSources(1, &id); + } + + void play(const openal_buffer & buffer) { + alSourcei(id, AL_BUFFER, buffer.id); + auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot attach buffer", errcode); + } + alSourcePlay(id); + errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot play source", errcode); + } + } + + void stop() { + alSourceStop(id); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot stop source", errcode); + } + } + + void gain(SoundVolume volume) { + alSourcef(id, AL_GAIN, float(volume) / FULL_LOUDNESS); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot set volume", errcode); + } + } + + void pitch(SoundPitch pitch) { + alSourcef(id, AL_PITCH, float(pitch) / NORMAL_PITCH); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot set pitch", errcode); + } + } + + void pan(SoundPan pan) { + alSource3f(id, AL_POSITION, -(float(64 - pan) / 64), 0, 0); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot set position", errcode); + } + } + + bool is_playing() const { + ALint state = 0; + alGetSourcei(id, AL_SOURCE_STATE, &state); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot get source state", errcode); + } + return state == AL_PLAYING; + } + + openal_source(const openal_source &) = delete; + openal_source & operator=(const openal_source &) = delete; + + inline openal_source(openal_source && other) + : id(std::exchange(other.id, 0)) + , mss_id(std::exchange(other.mss_id, 0)) + , emit_id(std::exchange(other.emit_id, 0)) + , smptbl_id(std::exchange(other.smptbl_id, 0)) + , bank_id(std::exchange(other.bank_id, 0)){} + + inline openal_source & operator=(openal_source && other) { + id = std::exchange(other.id, 0); + mss_id = std::exchange(other.mss_id, 0); + emit_id = std::exchange(other.emit_id, 0); + smptbl_id = std::exchange(other.smptbl_id, 0); + bank_id = std::exchange(other.bank_id, 0); + return *this; + } +}; + +inline uint32_t make_fourcc(const char (& code)[5]) { + return + (uint32_t(code[0]) << 0) | + (uint32_t(code[1]) << 8) | + (uint32_t(code[2]) << 16) | + (uint32_t(code[3]) << 24); +} + +#define WAVE_FORMAT_PCM 1 +#define WAVE_FORMAT_ADPCM 2 + +#pragma pack(1) +struct riff_chunk_t { + uint32_t tag; + uint32_t size; + // zero or more bytes of data + // padding byte if data size not a multiple of two +}; +#pragma pack() + +#pragma pack(1) +struct WAVEFORMATEX { + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + // uint16_t cbSize; +}; +#pragma pack() + +class wave_file { +public: + wave_file(std::ifstream & stream) { + riff_chunk_t riff_header; + stream.read(reinterpret_cast(&riff_header), sizeof(riff_header)); + if (riff_header.tag != make_fourcc("RIFF")) { + throw std::runtime_error("Expected RIFF chunk"); + } + uint32_t filetype; + stream.read(reinterpret_cast(&filetype), sizeof(filetype)); + if (filetype != make_fourcc("WAVE")) { + throw std::runtime_error("Expected WAVE chunk"); + } + riff_chunk_t chunk; + for (bool have_format = false, have_data = false; !(have_format && have_data);) { + stream.read(reinterpret_cast(&chunk), sizeof(chunk)); + if (chunk.tag == make_fourcc("fmt ")) { + if (chunk.size < sizeof(WAVEFORMATEX)) { + throw std::runtime_error("Expected WAVEFORMATEX struct"); + } + WAVEFORMATEX formatex; + stream.read(reinterpret_cast(&formatex), sizeof(formatex)); + if (!(formatex.wFormatTag == WAVE_FORMAT_PCM || formatex.wFormatTag == WAVE_FORMAT_ADPCM)) { + throw std::runtime_error("Unsupported format"); + } else if (formatex.nChannels == 1 && formatex.wBitsPerSample == 4) { + m_format = AL_FORMAT_MONO_MSADPCM_SOFT; + } else if (formatex.nChannels == 1 && formatex.wBitsPerSample == 8) { + m_format = AL_FORMAT_MONO8; + } else if (formatex.nChannels == 1 && formatex.wBitsPerSample == 16) { + m_format = AL_FORMAT_MONO16; + } else if (formatex.nChannels == 2 && formatex.wBitsPerSample == 4) { + m_format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } else if (formatex.nChannels == 2 && formatex.wBitsPerSample == 8) { + m_format = AL_FORMAT_STEREO8; + } else if (formatex.nChannels == 2 && formatex.wBitsPerSample == 16) { + m_format = AL_FORMAT_STEREO16; + } else { + throw std::runtime_error("Unsupported format"); + } + m_samplerate = formatex.nSamplesPerSec; + if (chunk.size > sizeof(formatex)) { + stream.seekg(chunk.size - sizeof(formatex), std::ios::cur); + } + have_format = true; + } else if (chunk.tag == make_fourcc("data")) { + m_pcm.resize(chunk.size); + stream.read(reinterpret_cast(m_pcm.data()), m_pcm.size()); + have_data = true; + } else { + stream.seekg(chunk.size, std::ios::cur); + } + } + } + + inline const std::vector & pcm() const { + return m_pcm; + } + + inline int samplerate() const { + return m_samplerate; + } + + inline ALenum format() const { + return m_format; + } + +protected: + int m_samplerate = 0; + ALenum m_format = 0; + std::vector m_pcm; +}; + +struct sound_sample { + + std::string name; + SoundSFXID sfx_id; + openal_buffer buffer; + + sound_sample(const char * _name, SoundSFXID _sfx_id, const wave_file & wav) { + name = _name; + sfx_id = _sfx_id; + const auto & pcm = wav.pcm(); + const auto format = wav.format(); + if (format == AL_FORMAT_MONO_MSADPCM_SOFT) { + // Needed for heart6a.wav + std::vector converted(pcm.size() * 2); + for (size_t i = 0; i < pcm.size(); ++i) { + converted[(i * 2) + 0] = (pcm[i] >> 4) * 2; + converted[(i * 2) + 1] = (pcm[i] & 0x7) * 2; + } + alBufferData(buffer.id, AL_FORMAT_MONO8, converted.data(), converted.size(), wav.samplerate()); + } else if (format == AL_FORMAT_STEREO_MSADPCM_SOFT) { + throw std::runtime_error("Format not implemented"); + } else { + alBufferData(buffer.id, format, pcm.data(), pcm.size(), wav.samplerate()); + } + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot buffer sample data", errcode); + } + } +}; + +#pragma pack(1) +struct SoundBankHead { // sizeof = 18 + uint8_t field_0[14]; + uint32_t field_E; +}; +#pragma pack() + +#pragma pack(1) +struct SoundBankSample { // sizeof = 32 + /** Name of the sound file the sample comes from. */ + char filename[18]; + /** Offset of the sample data. */ + uint32_t field_12; + uint32_t field_16; + /** Size of the sample file. */ + uint32_t data_size; + SoundSFXID sfxid; + uint8_t field_1F; +}; +#pragma pack() + +#pragma pack(1) +struct SoundBankEntry { // sizeof = 16 + uint32_t first_sample_offset; + uint32_t first_data_offset; + uint32_t total_samples_size; + uint32_t field_C; +}; +#pragma pack() + +std::vector load_sound_bank(const char * filename) { + const int directory_index = 2; // a5 was always 1622 + std::ifstream stream(filename, std::ios::in | std::ios::binary); + if (!stream.is_open()) { + throw std::runtime_error("Cannot open sound bank file"); + } + stream.seekg(-4, std::ios::end); + uint32_t head_offset; + stream.read(reinterpret_cast(&head_offset), sizeof(head_offset)); + stream.seekg(head_offset, std::ios::beg); + SoundBankHead bhead; + stream.read(reinterpret_cast(&bhead), sizeof(bhead)); + SoundBankEntry bentries[9]; + stream.read(reinterpret_cast(bentries), sizeof(bentries)); + const auto & directory = bentries[directory_index]; + if (directory.first_sample_offset == 0) { + throw std::runtime_error("Invalid sample offset"); + } else if (directory.total_samples_size < sizeof(SoundBankSample)) { + throw std::runtime_error("Invalid samples size"); + } + const int sample_count = directory.total_samples_size / sizeof(SoundBankSample); + stream.seekg(directory.first_sample_offset, std::ios::beg); + std::vector buffers; + buffers.reserve(sample_count); + SoundBankSample sample; + for (int i = 0; i < sample_count; ++i) { + stream.seekg(directory.first_sample_offset + (sizeof(sample) * i), std::ios::beg); + stream.read(reinterpret_cast(&sample), sizeof(sample)); + stream.seekg(directory.first_data_offset + sample.field_12, std::ios::beg); + buffers.emplace_back(sample.filename, sample.sfxid, wave_file(stream)); + } + JUSTLOG("Loaded %d sound samples from %s", buffers.size(), filename); + return buffers; +} + +std::vector g_sources; +std::array, 2> g_banks; + +void load_sound_banks() { + char snd_fname[2048]; + prepare_file_path_buf(snd_fname, FGrp_LrgSound, "sound.dat"); + // language-specific speech file + char * spc_fname = prepare_file_fmtpath(FGrp_LrgSound, "speech_%s.dat", get_language_lwrstr(install_info.lang_id)); + // default speech file + if (!LbFileExists(spc_fname)) { + spc_fname = prepare_file_path(FGrp_LrgSound, "speech.dat"); + } + // speech file for english + if (!LbFileExists(spc_fname)) { + spc_fname = prepare_file_fmtpath(FGrp_LrgSound, "speech_%s.dat", get_language_lwrstr(1)); + } + g_banks[0] = load_sound_bank(snd_fname); + g_banks[1] = load_sound_bank(spc_fname); +} + +void print_device_info() { + if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) { + const auto devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + JUSTLOG("Available audio devices:"); + for (auto device = devices; device[0] != 0; device += strlen(device)) { + JUSTLOG(" %s", device); + } + const auto default_device = alcGetString(nullptr, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); + JUSTLOG("Default audio device: %s", default_device); + } else if (alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { + const auto devices = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + JUSTLOG("Available audio devices:"); + for (auto device = devices; device[0] != 0; device += strlen(device)) { + JUSTLOG(" %s", device); + } + const auto default_device = alcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER); + JUSTLOG("Default audio device: %s", default_device); + } else { + // Cannot enumerate devices :( + } +} + +} // local + +extern "C" void FreeAudio() { + g_sources.clear(); + g_banks[0].clear(); + g_banks[1].clear(); + g_openal_context = nullptr; + g_openal_device = nullptr; +} + +extern "C" void SetSoundMasterVolume(SoundVolume volume) { + try { + alListenerf(AL_GAIN, float(volume) / FULL_LOUDNESS); + const auto errcode = alGetError(); + if (errcode != AL_NO_ERROR) { + throw openal_error("Cannot set master volume", errcode); + } + g_master_volume = volume; + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } +} + +extern "C" void SetMusicMasterVolume(SoundVolume value) { + g_music_volume = value; +} + +extern "C" TbBool GetSoundInstalled() { + return g_openal_device && g_openal_context; +} + +extern "C" void MonitorStreamedSoundTrack() { + for (auto & source : g_sources) { + try { + if (source.emit_id > 0 && !source.is_playing()) { + source.emit_id = 0; + source.smptbl_id = 0; + source.bank_id = 0; + } + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } +} + +extern "C" void * GetSoundDriver() { + // This just needs to return any non-null pointer. FMV library appears to have standalone audio + static int dummy = 0; + return &dummy; +} + +extern "C" void StopAllSamples() { + for (auto & source : g_sources) { + try { + source.stop(); + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } +} + +extern "C" TbBool InitAudio(const SoundSettings * settings) { + try { + if (SoundDisabled) { + LbWarnLog("Sound is disabled, skipping OpenAL initialization"); + return false; + } + if (g_openal_device || g_openal_context) { + LbWarnLog("OpenAL already initialized"); + return true; + } + print_device_info(); + ALCdevice_ptr device(alcOpenDevice(nullptr)); + if (!device) { + throw openal_error("Cannot open default audio device"); + } + ALCcontext_ptr context(alcCreateContext(device.get(), nullptr)); + if (!context) { + throw openal_error("Cannot create context"); + } else if (!alcMakeContextCurrent(context.get())) { + throw openal_error("Cannot make context current"); + } + g_sources.resize(settings->max_number_of_samples); + for (size_t i = 0; i < g_sources.size(); ++i) { + g_sources[i].mss_id = i + 1; + } + load_sound_banks(); + g_openal_device = std::move(device); + g_openal_context = std::move(context); + return true; + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + SoundDisabled = true; + return false; +} + +extern "C" TbBool IsSamplePlaying(SoundMilesID mss_id) { + try { + for (const auto & source : g_sources) { + if (source.mss_id == mss_id) { + return source.is_playing(); + } + } + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + return false; +} + +extern "C" SoundVolume GetCurrentSoundMasterVolume() { + return g_master_volume; +} + +extern "C" void SetSampleVolume(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundVolume volume) { + for (auto & source : g_sources) { + if (source.emit_id == emit_id && source.smptbl_id == smptbl_id) { + try { + source.gain(volume); + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } + } +} + +extern "C" void SetSamplePan(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundPan pan) { + for (auto & source : g_sources) { + if (source.emit_id == emit_id && source.smptbl_id == smptbl_id) { + try { + source.pan(pan); + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } + } +} + +extern "C" void SetSamplePitch(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundPitch pitch) { + for (auto & source : g_sources) { + if (source.emit_id == emit_id && source.smptbl_id == smptbl_id) { + try { + source.pitch(pitch); + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } + } +} + +extern "C" SoundMilesID play_sample( + SoundEmitterID emit_id, + SoundSmplTblID smptbl_id, + SoundVolume volume, + SoundPan pan, + SoundPitch pitch, + char fild1D, // possible values: -1, 0 + unsigned char ctype, // possible values: 2, 3 + SoundBankID bank_id +) { + if (emit_id <= 0) { + LbErrorLog("Can't play sample %d from bank %u, invalid emitter ID", smptbl_id, bank_id); + return 0; + } else if (bank_id > g_banks.size()) { + LbErrorLog("Can't play sample %d from bank %u, invalid bank ID", smptbl_id, bank_id); + return 0; + } else if (smptbl_id <= 0 || smptbl_id >= g_banks[bank_id].size()) { + LbErrorLog("Can't play sample %d from bank %u, invalid sample ID", smptbl_id, bank_id); + return 0; + } + try { + for (auto & source : g_sources) { + if (source.emit_id == 0) { + source.play(g_banks[bank_id][smptbl_id].buffer); + source.emit_id = emit_id; + source.smptbl_id = smptbl_id; + source.bank_id = bank_id; + return source.mss_id; + } + } + LbErrorLog("Can't play sample %d from bank %u, too many samples playing at once", smptbl_id, bank_id); + return 0; + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + return 0; +} + +extern "C" void stop_sample(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundBankID bank_id) { + for (auto & source : g_sources) { + if (emit_id == source.emit_id && smptbl_id == source.smptbl_id && bank_id == source.bank_id) { + try { + source.stop(); + source.emit_id = 0; + source.smptbl_id = 0; + source.bank_id = 0; + } catch (const std::exception & e) { + LbErrorLog("%s", e.what()); + } + } + } +} + +extern "C" SoundSFXID get_sample_sfxid(SoundSmplTblID smptbl_id, SoundBankID bank_id) { + if (bank_id > 1) { + return 0; + } else if (smptbl_id < 0 || smptbl_id >= g_banks[bank_id].size()) { + return 0; + } + return g_banks[bank_id][smptbl_id].sfx_id; +} diff --git a/src/bflib_sndlib.h b/src/bflib_sndlib.h index 716df8b676..57996df9af 100644 --- a/src/bflib_sndlib.h +++ b/src/bflib_sndlib.h @@ -29,32 +29,6 @@ #ifdef __cplusplus extern "C" { #endif -/******************************************************************************/ -#pragma pack(1) - -// Data structures - -struct SampleInfo { // sizeof = 29 - SoundMilesID mss_id; - unsigned char field_4[4]; - unsigned char field_8; - unsigned char field_9[9]; - SoundSmplTblID smptbl_id; - unsigned char field_14[3]; - unsigned char flags_17; - unsigned long field_18; - unsigned char field_1C; -}; - -#pragma pack() -/******************************************************************************/ -// Exported variables - -/******************************************************************************/ -// Exported functions - -int init_miles_sound_system(); -void unload_miles_sound_system(); void FreeAudio(void); void SetRedbookVolume(SoundVolume); @@ -68,18 +42,13 @@ void MonitorStreamedSoundTrack(void); void StopRedbookTrack(void); void * GetSoundDriver(void); void StopAllSamples(void); -struct SampleInfo * GetFirstSampleInfoStructure(void); -TbBool InitAudio(struct SoundSettings *); -void SetupAudioOptionDefaults(struct SoundSettings *); +TbBool InitAudio(const struct SoundSettings *); TbBool IsSamplePlaying(SoundMilesID); -struct SampleInfo * GetLastSampleInfoStructure(void); SoundVolume GetCurrentSoundMasterVolume(void); -void StopSample(SoundEmitterID, SoundSmplTblID); void SetSampleVolume(SoundEmitterID, SoundSmplTblID, SoundVolume); void SetSamplePan(SoundEmitterID, SoundSmplTblID, SoundPan); void SetSamplePitch(SoundEmitterID, SoundSmplTblID, SoundPitch); -struct SampleInfo * PlaySampleFromAddress(SoundEmitterID, SoundSmplTblID, SoundVolume, SoundPan, SoundPitch, unsigned char a6, unsigned char a7, void * buf, SoundSFXID); -/******************************************************************************/ + #ifdef __cplusplus } #endif diff --git a/src/bflib_sound.c b/src/bflib_sound.c index eb52b18a8b..5a204cdffc 100644 --- a/src/bflib_sound.c +++ b/src/bflib_sound.c @@ -50,14 +50,9 @@ static long deadzone_radius; TbBool SoundDisabled; int atmos_sound_volume = 128; -long samples_in_bank; -long samples_in_bank2; long MaxSoundDistance; struct SoundReceiver Receiver; long Non3DEmitter; -struct SampleTable *sample_table; -struct SampleTable *sample_table2; -unsigned char using_two_banks; long SpeechEmitter; /******************************************************************************/ // Internal routines @@ -750,51 +745,6 @@ void increment_sample_times(void) } } -struct SampleTable *sample_table_get(SoundSmplTblID smptbl_id, SoundBankID bank_id) -{ - if (bank_id > 0) - { - if ((smptbl_id < 0) || (smptbl_id >= samples_in_bank2)) { - ERRORLOG("Sample %d exceeds bank %d bounds",smptbl_id,2); - return NULL; - } - if (smptbl_id == 0){ - SYNCDBG(9,"No Sample to play"); - return NULL; - } - return &sample_table2[smptbl_id]; - } else - { - if ((smptbl_id < 0) || (smptbl_id >= samples_in_bank)) { - ERRORLOG("Sample %d exceeds bank %d bounds",smptbl_id,1); - return NULL; - } - if (smptbl_id == 0){ - SYNCDBG(9,"No Sample to play"); - return NULL; - } - return &sample_table[smptbl_id]; - } -} - -SoundSFXID get_sample_sfxid(SoundSmplTblID smptbl_id, SoundBankID bank_id) -{ - if ( using_two_banks ) - { - if (bank_id != 0) - return sample_table2[smptbl_id].sfxid; - return sample_table[smptbl_id].sfxid; - } else - { - if (bank_id != 0) - { - ERRORLOG("Trying to use two sound banks when only one has been set up"); - return 0; - } - return sample_table[smptbl_id].sfxid; - } -} - void kick_out_sample(short smpl_id) { struct S3DSample* sample = &SampleList[smpl_id]; @@ -802,43 +752,6 @@ void kick_out_sample(short smpl_id) sample->is_playing = 0; } -struct SampleInfo *play_sample(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundVolume volume, SoundPan pan, SoundPitch pitch, char a6, unsigned char a7, SoundBankID bank_id) -{ - if ((!using_two_banks) && (bank_id > 0)) - { - ERRORLOG("Trying to use two sound banks when only one has been set up"); - return NULL; - } - - struct SampleTable* smp_table = sample_table_get(smptbl_id, bank_id); - if (smp_table == NULL) { - return NULL; - } - // Start the play - struct SampleInfo* smpinfo = PlaySampleFromAddress(emit_id, smptbl_id, volume, pan, pitch, a6, a7, smp_table->snd_buf, smp_table->sfxid); - if (smpinfo == NULL) { - SYNCLOG("Can't start playing sample %d",smptbl_id); - return NULL; - } - smpinfo->flags_17 |= 0x01; - if (bank_id != 0) { - smpinfo->flags_17 |= 0x04; - } - return smpinfo; -} - -void stop_sample(SoundEmitterID emit_id, SoundSmplTblID smptbl_id, SoundBankID bank_id) -{ - SYNCDBG(19,"Starting"); - if ( !using_two_banks ) - { - if (bank_id > 0) { - ERRORLOG("Trying to use two sound banks when only one has been set up"); - } - } - StopSample(emit_id, smptbl_id); -} - TbBool process_sound_samples(void) { for (long i = 0; i < MaxNoSounds; i++) @@ -846,17 +759,13 @@ TbBool process_sound_samples(void) struct S3DSample* sample = &SampleList[i]; if (sample->is_playing != 0) { - if (sample->smpinfo == NULL) + if (sample->mss_id <= 0) { ERRORLOG("Attempt to query invalid sample"); continue; } - if ( IsSamplePlaying(sample->smpinfo->mss_id) ) - { - sample->smpinfo->flags_17 |= 0x02; - } else + if (!IsSamplePlaying(sample->mss_id)) { - sample->smpinfo->flags_17 &= ~0x02; sample->is_playing = 0; } if (sample->emit_ptr != NULL) @@ -951,8 +860,8 @@ long start_emitter_playing(struct SoundEmitter *emit, SoundSmplTblID smptbl_id, volume = (volume * loudness) / 256; if (smpl_idx < 0) return 0; - struct SampleInfo* smpinfo = play_sample(get_emitter_id(emit), smptbl_id, volume, pan, smpitch, fild1D, ctype, bank_id); - if (smpinfo == NULL) { + SoundMilesID mss_id = play_sample(get_emitter_id(emit), smptbl_id, volume, pan, smpitch, fild1D, ctype, bank_id); + if (mss_id <= 0) { return 0; } struct S3DSample* sample = &SampleList[smpl_idx]; @@ -965,7 +874,7 @@ long start_emitter_playing(struct SoundEmitter *emit, SoundSmplTblID smptbl_id, sample->pan = pan; sample->base_pitch = smpitch; sample->is_playing = 1; - sample->smpinfo = smpinfo; + sample->mss_id = mss_id; sample->flags = flags; sample->time_turn = 0; sample->emit_idx = emit->index; diff --git a/src/bflib_sound.h b/src/bflib_sound.h index 4eefe9a2d2..e8d3da54b7 100644 --- a/src/bflib_sound.h +++ b/src/bflib_sound.h @@ -99,7 +99,7 @@ struct S3DSample { // sizeof = 37 unsigned short base_pitch; unsigned short pan; unsigned short volume; - struct SampleInfo *smpinfo; + SoundMilesID mss_id; struct SoundEmitter *emit_ptr; long emit_idx; char field_1D; // signed @@ -119,15 +119,10 @@ struct SampleTable { /******************************************************************************/ // Exported variables extern int atmos_sound_volume; -extern long samples_in_bank; -extern long samples_in_bank2; extern TbBool SoundDisabled; extern long MaxSoundDistance; extern struct SoundReceiver Receiver; extern long Non3DEmitter; -extern struct SampleTable *sample_table; -extern struct SampleTable *sample_table2; -extern unsigned char using_two_banks; extern long SpeechEmitter; #pragma pack() /******************************************************************************/ @@ -158,7 +153,7 @@ void play_non_3d_sample(SoundSmplTblID); void play_non_3d_sample_no_overlap(SoundSmplTblID); void play_atmos_sound(SoundSmplTblID); short sound_emitter_in_use(SoundEmitterID); -struct SampleInfo * play_sample(SoundEmitterID, SoundSmplTblID, SoundVolume, SoundPan, SoundPitch, char fil1D, unsigned char ctype, SoundBankID); +SoundMilesID play_sample(SoundEmitterID, SoundSmplTblID, SoundVolume, SoundPan, SoundPitch, char fil1D, unsigned char ctype, SoundBankID); void stop_sample(SoundEmitterID, SoundSmplTblID, SoundBankID); long speech_sample_playing(void); long play_speech_sample(SoundSmplTblID); @@ -170,6 +165,7 @@ TbBool process_sound_samples(void); struct SoundEmitter* S3DGetSoundEmitter(SoundEmitterID); SoundEmitterID get_emitter_id(struct SoundEmitter *); void kick_out_sample(SoundSmplTblID); +SoundSFXID get_sample_sfxid(SoundSmplTblID smptbl_id, SoundBankID bank_id); /******************************************************************************/ #ifdef __cplusplus diff --git a/src/cdrom.cpp b/src/cdrom.cpp new file mode 100644 index 0000000000..3101139c56 --- /dev/null +++ b/src/cdrom.cpp @@ -0,0 +1,154 @@ +#include "pre_inc.h" +#include "bflib_sndlib.h" + +// SDL completely removed CD-ROM support, go native + +#if defined(__MINGW32__) +// mingw is somewhat broken... +typedef struct LPMSG *MSG; +#endif + +// All the MCI stuff is not part of LEAN_AND_MEAN +#include +#include "post_inc.h" + +namespace { + +MCIDEVICEID g_redbook_device = 0; +SoundVolume g_redbook_volume = 0; + +MCIDEVICEID mci_open(const char * drive) { + MCI_OPEN_PARMS params = {}; + params.lpstrElementName = drive; + params.lpstrDeviceType = "cdaudio"; + const auto flags = MCI_OPEN_TYPE | MCI_OPEN_ELEMENT | MCI_OPEN_SHAREABLE; + mciSendCommand(0, MCI_OPEN, flags, (DWORD_PTR) ¶ms); + return params.wDeviceID; // will be zero on error +} + +bool mci_close(MCIDEVICEID device_id) { + MCI_GENERIC_PARMS params = {}; + const auto result = mciSendCommand(device_id, MCI_CLOSE, 0, (DWORD_PTR) ¶ms); + return result == 0; +} + +bool mci_set_time_format(MCIDEVICEID device_id) { + MCI_SET_PARMS params = {}; + params.dwTimeFormat = MCI_FORMAT_TMSF; + const auto flags = MCI_SET_TIME_FORMAT; + const auto result = mciSendCommand(device_id, MCI_SET, flags, (DWORD_PTR) ¶ms); + return result == 0; +} + +bool mci_play(MCIDEVICEID device_id, int track) { + MCI_PLAY_PARMS params = {}; + params.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0); + params.dwTo = MCI_MAKE_TMSF(track + 1, 0, 0, 0); + params.dwCallback = (DWORD_PTR) GetDesktopWindow(); + const auto flags = MCI_FROM | MCI_TO | MCI_NOTIFY; + const auto result = mciSendCommand(device_id, MCI_PLAY, flags, (DWORD_PTR) ¶ms); + return result == 0; +} + +bool mci_pause(MCIDEVICEID device_id) { + MCI_GENERIC_PARMS params; + const auto result = mciSendCommand(device_id, MCI_PAUSE, 0, (DWORD_PTR) ¶ms); + return result == 0; +} + +bool mci_resume(MCIDEVICEID device_id) { + MCI_GENERIC_PARMS params; + const auto result = mciSendCommand(device_id, MCI_RESUME, 0, (DWORD_PTR) ¶ms); + return result == 0; +} + +bool mci_stop(MCIDEVICEID device_id) { + MCI_GENERIC_PARMS params = {}; + const auto result = mciSendCommand(device_id, MCI_STOP, 0, (DWORD_PTR) ¶ms); + return result == 0; +} + +int mci_status(MCIDEVICEID device_id, int what) { + MCI_STATUS_PARMS params = {}; + params.dwItem = what; + const auto flags = MCI_STATUS_ITEM; + mciSendCommand(device_id, MCI_STATUS, flags, (DWORD_PTR) ¶ms); + return params.dwReturn; // returns zero on error +} + +bool open_redbook_device() { + if (g_redbook_device == 0) { + // find first cdrom device that has a disk in it + char drive[] = "C:\\"; + for (char letter = 'C'; letter <= 'Z'; ++letter) { + drive[0] = letter; + if (GetDriveType(drive) != DRIVE_CDROM) { + continue; + } + if (const auto device_id = mci_open(drive)) { + const auto num_tracks = mci_status(device_id, MCI_STATUS_NUMBER_OF_TRACKS); + if (num_tracks > 0) { + JUSTLOG("Using cdrom drive %s for music", drive); + g_redbook_device = device_id; + mci_set_time_format(device_id); + return true; + } + mci_close(device_id); + } + } + JUSTLOG("No cdrom drives found with a disk in it"); + return false; + } + return true; +} + +} // local + +extern "C" void SetRedbookVolume(SoundVolume value) { + // TODO: Not implemented + g_redbook_volume = value; +} + +extern "C" void PlayRedbookTrack(int track) { + if (open_redbook_device()) { + const auto mode = mci_status(g_redbook_device, MCI_STATUS_MODE); + if (mode == MCI_MODE_OPEN || mode == MCI_MODE_NOT_READY) { + return; // door open or no disk + } + const auto current_track = mci_status(g_redbook_device, MCI_STATUS_CURRENT_TRACK); + if (current_track == track && (mode == MCI_MODE_PLAY || mode == MCI_MODE_SEEK)) { + return; // already playing or seeking to requested track + } + mci_play(g_redbook_device, track); + } +} + +extern "C" void PauseRedbookTrack() { + if (open_redbook_device()) { + const auto mode = mci_status(g_redbook_device, MCI_STATUS_MODE); + if (!(mode == MCI_MODE_PLAY || MCI_MODE_SEEK)) { + return; // not currently playing or about to play + } + mci_pause(g_redbook_device); + } +} + +extern "C" void ResumeRedbookTrack() { + if (open_redbook_device()) { + const auto mode = mci_status(g_redbook_device, MCI_STATUS_MODE); + if (!(mode == MCI_MODE_PAUSE)) { + return; // not currently paused + } + mci_resume(g_redbook_device); + } +} + +extern "C" void StopRedbookTrack() { + if (open_redbook_device()) { + const auto mode = mci_status(g_redbook_device, MCI_STATUS_MODE); + if (!(mode == MCI_MODE_PLAY || mode == MCI_MODE_PAUSE || mode == MCI_MODE_SEEK)) { + return; // not currently playing, paused or about to play + } + mci_stop(g_redbook_device); + } +} diff --git a/src/config_objects.c b/src/config_objects.c index 1941cd758d..3fc717c66e 100644 --- a/src/config_objects.c +++ b/src/config_objects.c @@ -476,7 +476,7 @@ TbBool parse_objects_object_blocks(char *buf, long len, const char *config_textn if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { k = atoi(word_buf); - if ( (!SoundDisabled) && ( (k < 0) || (k > (samples_in_bank - 1)) ) ) + if (k < 0) { CONFWRNLOG("Incorrect value of \"%s\" parameter in [%.*s] block of %s file.", COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); @@ -647,7 +647,7 @@ TbBool parse_objects_object_blocks(char *buf, long len, const char *config_textn if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { n = atoi(word_buf); - if ( (!SoundDisabled) && ( (n < 0) || (n > (samples_in_bank - 1)) ) ) + if (n < 0) { CONFWRNLOG("Incorrect value of \"%s\" parameter in [%.*s] block of %s file.", COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); @@ -660,7 +660,7 @@ TbBool parse_objects_object_blocks(char *buf, long len, const char *config_textn if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { n = atoi(word_buf); - if ((!SoundDisabled) && ((n < 0) || (n > (samples_in_bank - 1)))) + if (n < 0) { objst->effect.sound_range = 1; } diff --git a/src/config_trapdoor.c b/src/config_trapdoor.c index b0a013da21..5375fc1aad 100644 --- a/src/config_trapdoor.c +++ b/src/config_trapdoor.c @@ -939,7 +939,7 @@ TbBool parse_trapdoor_trap_blocks(char *buf, long len, const char *config_textna if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { n = atoi(word_buf); - if ( n < 0 || n > samples_in_bank - 1 ) + if (n < 0) { CONFWRNLOG("Incorrect value of \"%s\" parameter in [%.*s] block of %s file.", COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); @@ -954,7 +954,7 @@ TbBool parse_trapdoor_trap_blocks(char *buf, long len, const char *config_textna if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { n = atoi(word_buf); - if (n < 0 || n > samples_in_bank - 1) + if (n < 0) { CONFWRNLOG("Incorrect value of \"%s\" parameter in [%.*s] block of %s file.", COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); @@ -1553,7 +1553,7 @@ TbBool parse_trapdoor_door_blocks(char *buf, long len, const char *config_textna if (get_conf_parameter_single(buf, &pos, len, word_buf, sizeof(word_buf)) > 0) { n = atoi(word_buf); - if (n < 0 || n > samples_in_bank - 1) + if (n < 0) { CONFWRNLOG("Incorrect value of \"%s\" parameter in [%.*s] block of %s file.", COMMAND_TEXT(cmd_num), blocknamelen, blockname, config_textname); diff --git a/src/front_torture.c b/src/front_torture.c index c57ba52283..f4c03aa931 100644 --- a/src/front_torture.c +++ b/src/front_torture.c @@ -357,7 +357,7 @@ void fronttorture_update(void) { volume = 0; doorsnd->field_4 = 0; - StopSample(0, door->smptbl_id); + stop_sample(0, door->smptbl_id, 0); } else if (volume >= 127) { diff --git a/src/game_heap.c b/src/game_heap.c index 5561211151..099afd1a0c 100644 --- a/src/game_heap.c +++ b/src/game_heap.c @@ -38,8 +38,6 @@ extern "C" { } #endif /******************************************************************************/ -static const char *sound_fname = "sound.dat"; -static const char *speech_fname = "speech.dat"; static unsigned char *heap; static long heap_size; /******************************************************************************/ @@ -216,26 +214,6 @@ TbBool setup_heaps(void) } heap = calloc(heap_size, 1); } - if (!SoundDisabled) - { - // Prepare sound sample bank file names - char snd_fname[2048]; - prepare_file_path_buf(snd_fname, FGrp_LrgSound, sound_fname); - // language-specific speech file - char* spc_fname = prepare_file_fmtpath(FGrp_LrgSound, "speech_%s.dat", get_language_lwrstr(install_info.lang_id)); - // default speech file - if (!LbFileExists(spc_fname)) - spc_fname = prepare_file_path(FGrp_LrgSound,speech_fname); - // speech file for english - if (!LbFileExists(spc_fname)) - spc_fname = prepare_file_fmtpath(FGrp_LrgSound,"speech_%s.dat",get_language_lwrstr(1)); - // Initialize sample banks - if (!init_sound_banks(snd_fname, spc_fname, 1622)) - { - SoundDisabled = true; - ERRORLOG("Unable to initialize sound heap. Sound disabled."); - } - } return true; } diff --git a/src/main.cpp b/src/main.cpp index 136619e914..5d1452f173 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3758,7 +3758,6 @@ static TbBool wait_at_frontend(void) if (!SoundDisabled) { process_3d_sounds(); - process_sound_heap(); MonitorStreamedSoundTrack(); } @@ -4262,9 +4261,6 @@ int LbBullfrogMain(unsigned short argc, char *argv[]) LbSetTitle(PROGRAM_NAME); LbSetIcon(1); LbScreenSetDoubleBuffering(true); - - init_miles_sound_system(); - srand(LbTimerClock()); #ifdef FUNCTESTING @@ -4336,7 +4332,6 @@ int LbBullfrogMain(unsigned short argc, char *argv[]) LbErrorLogClose(); steam_api_shutdown(); - unload_miles_sound_system(); return 0; } diff --git a/src/map_events.c b/src/map_events.c index 7cdcb12043..b5c40d4c4c 100644 --- a/src/map_events.c +++ b/src/map_events.c @@ -661,7 +661,7 @@ void maintain_my_event_list(struct Dungeon *dungeon) { if (is_my_player_number(dungeon->owner)) { struct SoundEmitter* emit = S3DGetSoundEmitter(Non3DEmitter); - StopSample(get_emitter_id(emit), 947); + stop_sample(get_emitter_id(emit), 947, 0); play_non_3d_sample(175); } unsigned char prev_ev_idx = dungeon->event_button_index[i - 1]; diff --git a/src/sounds.c b/src/sounds.c index 56d627c0f5..9e5797b714 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -373,7 +373,6 @@ void update_player_sounds(void) if ( !SoundDisabled ) { if ( (game.turns_fastforward == 0) && (!game.numfield_149F38) ) { MonitorStreamedSoundTrack(); - process_sound_heap(); } } SYNCDBG(9,"Finished"); @@ -387,156 +386,19 @@ void process_3d_sounds(void) process_sound_emitters(); } -// This function marks not playing samples (to remove them from heap MB) -void process_sound_heap(void) -{ - long i = 0; - SYNCDBG(9,"Starting"); - struct SampleInfo* smpinfo_last = GetLastSampleInfoStructure(); - if (smpinfo_last == NULL) { - return; - } - for (struct SampleInfo* smpinfo = GetFirstSampleInfoStructure(); smpinfo <= smpinfo_last; smpinfo++) - { - if ( (smpinfo->mss_id != 0) && ((smpinfo->flags_17 & 0x01) != 0) ) - { - if ( IsSamplePlaying(smpinfo->mss_id) ) - { - i++; - } else - { - smpinfo->flags_17 &= ~0x01; - smpinfo->flags_17 &= ~0x04; - } - } - } - SYNCDBG(9,"Done (%ld playing yet)", i); -} - -void free_sound_bank(struct SampleTable * samples, int sample_count) { - if (samples) { - for (int i = 0; i < sample_count; ++i) { - free(samples[i].snd_buf); - } - free(samples); - } -} - -struct SampleTable * parse_sound_file(TbFileHandle fileh, long * nsamples, long a5) -{ - int directory_index; - switch ( a5 ) - { - case 1610: - directory_index = 5; - break; - case 822: - directory_index = 6; - break; - case 811: - directory_index = 7; - break; - case 800: - directory_index = 8; - break; - case 1611: - directory_index = 4; - break; - case 1620: - directory_index = 3; - break; - case 1622: - directory_index = 2; - break; - case 1640: - directory_index = 1; - break; - case 1644: - directory_index = 0; - break; - default: - return NULL; - } - long fsize = LbFileLengthHandle(fileh); - if (fsize < 4) { - return NULL; - } else if (LbFileSeek(fileh, -4, Lb_FILE_SEEK_END) < 0) { - return NULL; - } - int head_offset; - if (LbFileRead(fileh, &head_offset, sizeof(head_offset)) < sizeof(head_offset)) { - return NULL; - } - head_offset = read_int32_le_buf((unsigned char *) &head_offset); - if (head_offset > fsize) { - return NULL; - } else if (LbFileSeek(fileh, head_offset, Lb_FILE_SEEK_BEGINNING) < 0) { - return NULL; - } - struct SoundBankHead bhead; - if (LbFileRead(fileh, &bhead, sizeof(bhead)) < sizeof(bhead)) { - return NULL; - } - struct SoundBankEntry bentries[9]; - if (LbFileRead(fileh, bentries, sizeof(bentries)) < sizeof(bentries)) { - return NULL; - } - struct SoundBankEntry * directory = &bentries[directory_index]; - if (directory->field_0 == 0) { - return NULL; - } else if (directory->field_8 == 0) { - return NULL; - } - int sample_count = directory->field_8 / sizeof(struct SoundBankSample); - if (sizeof(struct SampleTable) * sample_count >= head_offset) { - return NULL; - } else if (LbFileSeek(fileh, directory->field_0, Lb_FILE_SEEK_BEGINNING) < 0) { - return NULL; - } - struct SampleTable * samples = (struct SampleTable *) calloc(sample_count, sizeof(struct SampleTable)); - struct SoundBankSample sample; - for (int i = 0; i < sample_count; ++i) { - if (LbFileSeek(fileh, directory->field_0 + (sizeof(sample) * i), Lb_FILE_SEEK_BEGINNING) < 0) { - free_sound_bank(samples, sample_count); - return NULL; - } else if (LbFileRead(fileh, &sample, sizeof(sample)) < sizeof(sample)) { - free_sound_bank(samples, sample_count); - return NULL; - } else if (LbFileSeek(fileh, directory->field_4 + sample.field_12, Lb_FILE_SEEK_BEGINNING) < 0) { - free_sound_bank(samples, sample_count); - return NULL; - } - samples[i].snd_buf = (SndData) calloc(sample.data_size, 1); - if (!samples[i].snd_buf) { - free_sound_bank(samples, sample_count); - return NULL; - } - if (LbFileRead(fileh, samples[i].snd_buf, sample.data_size) < sample.data_size) { - free_sound_bank(samples, sample_count); - return NULL; - } - snprintf(samples[i].name, sizeof(samples[i].name), "%s", sample.filename); - samples[i].data_size = sample.data_size; - samples[i].sfxid = sample.sfxid; - } - *nsamples = sample_count; - return samples; -} - TbBool init_sound(void) { SYNCDBG(8,"Starting"); if (SoundDisabled) return false; struct SoundSettings* snd_settng = &game.sound_settings; - SetupAudioOptionDefaults(snd_settng); snd_settng->flags = SndSetting_Sound; snd_settng->sound_type = 1622; snd_settng->sound_data_path = sound_dir; snd_settng->dir3 = sound_dir; snd_settng->field_12 = 1; snd_settng->stereo = 1; - snd_settng->max_number_of_samples = 16; + snd_settng->max_number_of_samples = 100; snd_settng->danger_music = 0; snd_settng->no_load_music = 1; snd_settng->no_load_sounds = 1; @@ -558,58 +420,6 @@ TbBool init_sound(void) return true; } -TbBool init_first_bank(const char * snd_fname, long a5) -{ - TbFileHandle handle = LbFileOpen(snd_fname,Lb_FILE_MODE_READ_ONLY); - if (!handle) { - ERRORLOG("Couldn't open primary sound bank file \"%s\"",snd_fname); - return false; - } - free_sound_bank(sample_table, samples_in_bank); - samples_in_bank = 0; - sample_table = parse_sound_file(handle, &samples_in_bank, a5); - if (!sample_table) { - ERRORLOG("Couldn't parse sound bank file \"%s\"",snd_fname); - return false; - } - SYNCLOG("Loaded %ld sound samples into bank 0", samples_in_bank); - return true; -} - -TbBool init_second_bank(const char * spc_fname, long a5) -{ - TbFileHandle handle = LbFileOpen(spc_fname,Lb_FILE_MODE_READ_ONLY); - if (!handle) { - LbFileClose(handle); - ERRORLOG("Couldn't open secondary sound bank file \"%s\"",spc_fname); - return false; - } - free_sound_bank(sample_table2, samples_in_bank2); - samples_in_bank2 = 0; - sample_table2 = parse_sound_file(handle, &samples_in_bank2, a5); - if (!sample_table2) { - LbFileClose(handle); - ERRORLOG("Couldn't parse sound bank file \"%s\"",spc_fname); - return false; - } - LbFileClose(handle); - SYNCLOG("Loaded %ld sound samples into bank 1", samples_in_bank2); - return true; -} - -TbBool init_sound_banks(char *snd_fname, char *spc_fname, long a5) -{ - SYNCDBG(8,"Starting"); - using_two_banks = 0; - if (!init_first_bank(snd_fname, a5)) { - return false; - } else if (!init_second_bank(spc_fname, a5)) { - return false; - } - using_two_banks = 1; - return true; -} - struct Thing *create_ambient_sound(const struct Coord3d *pos, ThingModel model, PlayerNumber owner) { if ( !i_can_allocate_free_thing_structure(FTAF_FreeEffectIfNoSlots) ) diff --git a/src/sounds.h b/src/sounds.h index 7ee060e3a2..6200409d33 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -55,30 +55,6 @@ struct SoundSettings { unsigned char redbook_enable; }; -struct SoundBankHead { // sizeof = 18 - unsigned char field_0[14]; - unsigned long field_E; -}; - -struct SoundBankSample { // sizeof = 32 - /** Name of the sound file the sample comes from. */ - char filename[18]; - /** Offset of the sample data. */ - unsigned long field_12; - unsigned long field_16; - /** Size of the sample file. */ - unsigned long data_size; - unsigned char sfxid; - unsigned char field_1F; -}; - -struct SoundBankEntry { // sizeof = 16 - unsigned long field_0; - unsigned long field_4; - unsigned long field_8; - unsigned long field_C; -}; - enum SoundSettingsFlags { SndSetting_None = 0x00, SndSetting_MIDI = 0x01, @@ -92,13 +68,11 @@ extern Mix_Chunk* streamed_sample; #pragma pack() /******************************************************************************/ -TbBool init_sound_banks(char *snd_fname, char *spc_fname, long a5); TbBool init_sound(void); void sound_reinit_after_load(void); void update_player_sounds(void); void process_3d_sounds(void); -void process_sound_heap(void); void thing_play_sample(struct Thing *, SoundSmplTblID, SoundPitch, char fil1D, unsigned char ctype, unsigned char flags, long priority, SoundVolume); void play_sound_if_close_to_receiver(struct Coord3d*, SoundSmplTblID);