From f877eb3b562ca10b433c6cee94e84a9116da7c95 Mon Sep 17 00:00:00 2001 From: N00byKing Date: Wed, 9 Mar 2022 22:35:29 +0100 Subject: [PATCH] Port to FAudio --- .github/workflows/ci.yml | 24 +- .gitmodules | 3 + desktop_version/CMakeLists.txt | 50 ++- desktop_version/Dockerfile | 10 +- desktop_version/README.md | 9 +- desktop_version/src/Music.cpp | 543 +++++++++++++++++++++++++++++---- third_party/FAudio | 1 + 7 files changed, 530 insertions(+), 110 deletions(-) create mode 160000 third_party/FAudio diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9058c3af..7017cce0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: submodules: true - name: Install dependencies - run: brew install ninja sdl2 sdl2_mixer + run: brew install ninja sdl2 - name: CMake configure (default version) run: | @@ -118,7 +118,6 @@ jobs: env: SDL_VERSION: 2.0.20 - SDL_MIXER_VERSION: 2.0.4 steps: - uses: actions/checkout@v1 @@ -133,14 +132,6 @@ jobs: path: C:\SDL key: ${{ runner.os }}-build-${{ env.cache-name }} - - name: Cache SDL_mixer - uses: actions/cache@v2 - env: - cache-name: cache-sdl-mixer - with: - path: C:\SDL_mixer - key: ${{ runner.os }}-build-${{ env.cache-name }} - - name: Download SDL if not cached run: | if (-Not (Test-Path C:\SDL)) @@ -149,23 +140,14 @@ jobs: Expand-Archive C:\SDL.zip -DestinationPath C:\ } - - name: Download SDL_mixer if not cached - run: | - if (-Not (Test-Path C:\SDL_mixer)) - { - Invoke-WebRequest "https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-devel-$env:SDL_MIXER_VERSION-VC.zip" -o C:\SDL_mixer.zip - Expand-Archive C:\SDL_mixer.zip -DestinationPath C:\ - } - - name: CMake configure (default version) run: | mkdir $env:SRC_DIR_PATH/build cd $env:SRC_DIR_PATH/build $env:LDFLAGS = "/LIBPATH:C:\SDL2-$env:SDL_VERSION\lib\x86 " - $env:LDFLAGS += "/LIBPATH:C:\SDL2_mixer-$env:SDL_MIXER_VERSION\lib\x86" cmake -G "Visual Studio 17 2022" -A Win32 ` - -DSDL2_INCLUDE_DIRS="C:\SDL2-$env:SDL_VERSION\include;C:\SDL2_mixer-$env:SDL_MIXER_VERSION\include" ` - -DSDL2_LIBRARIES="SDL2;SDL2main;SDL2_mixer" .. + -DSDL2_INCLUDE_DIRS="C:\SDL2-$env:SDL_VERSION\include" ` + -DSDL2_LIBRARIES="SDL2;SDL2main" .. - name: Build (default version) run: | cd $env:SRC_DIR_PATH/build diff --git a/.gitmodules b/.gitmodules index 917fef43..436d1606 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third_party/utfcpp"] path = third_party/utfcpp url = https://github.com/nemtrif/utfcpp +[submodule "third_party/FAudio"] + path = third_party/FAudio + url = https://github.com/FNA-XNA/FAudio diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index 384de9fc..3c1d2ca3 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -135,6 +135,8 @@ if(BUNDLE_DEPENDENCIES) ../third_party/physfs/extras ../third_party/lodepng ../third_party/utfcpp/source + ../third_party/FAudio/include + ../third_party/FAudio/src ) else() target_include_directories( @@ -142,6 +144,7 @@ else() src ../third_party/lodepng ../third_party/physfs/extras + ../third_party/FAudio/src ) endif() @@ -159,6 +162,13 @@ endif() set(XML2_SRC ../third_party/tinyxml2/tinyxml2.cpp ) +set(FAUDIO_SRC + ../third_party/FAudio/src/FAudio.c + ../third_party/FAudio/src/FAudio_internal.c + ../third_party/FAudio/src/FAudio_internal_simd.c + ../third_party/FAudio/src/FAudio_operationset.c + ../third_party/FAudio/src/FAudio_platform_sdl2.c +) set(PFS_SRC ../third_party/physfs/src/physfs.c ../third_party/physfs/src/physfs_archiver_dir.c @@ -289,12 +299,17 @@ if(BUNDLE_DEPENDENCIES) target_compile_definitions(physfs-static PRIVATE -DPHYSFS_SUPPORTS_DEFAULT=0 -DPHYSFS_SUPPORTS_ZIP=1 ) + add_library(faudio-static STATIC ${FAUDIO_SRC}) + target_include_directories( + faudio-static PRIVATE + ../third_party/FAudio/include + ) - target_link_libraries(VVVVVV physfs-static tinyxml2-static lodepng-static) + target_link_libraries(VVVVVV physfs-static tinyxml2-static lodepng-static faudio-static) else() find_package(utf8cpp CONFIG) - target_link_libraries(VVVVVV physfs tinyxml2 utf8cpp lodepng-static) + target_link_libraries(VVVVVV physfs tinyxml2 utf8cpp lodepng-static FAudio) endif() # SDL2 Dependency (Detection pulled from FAudio) @@ -302,24 +317,41 @@ if(DEFINED SDL2_INCLUDE_DIRS AND DEFINED SDL2_LIBRARIES) message(STATUS "Using pre-defined SDL2 variables SDL2_INCLUDE_DIRS and SDL2_LIBRARIES") target_include_directories(VVVVVV SYSTEM PRIVATE "$") target_link_libraries(VVVVVV ${SDL2_LIBRARIES}) + if(BUNDLE_DEPENDENCIES) + target_include_directories(faudio-static SYSTEM PRIVATE "$") + target_link_libraries(faudio-static ${SDL2_LIBRARIES}) + endif() elseif (EMSCRIPTEN) message(STATUS "Using Emscripten SDL2") - target_compile_options(VVVVVV PUBLIC -sUSE_SDL=2 -sUSE_SDL_MIXER=2) - target_link_libraries(VVVVVV -sUSE_SDL=2 -sUSE_SDL_MIXER=2) + target_compile_options(VVVVVV PUBLIC -sUSE_SDL=2) + target_link_libraries(VVVVVV -sUSE_SDL=2) + if(BUNDLE_DEPENDENCIES) + target_compile_options(faudio-static PUBLIC -sUSE_SDL=2) + target_link_libraries(faudio-static -sUSE_SDL=2) + endif() else() # Only try to autodetect if both SDL2 variables aren't explicitly set find_package(SDL2 CONFIG) if(TARGET SDL2::SDL2) message(STATUS "Using TARGET SDL2::SDL2") - target_link_libraries(VVVVVV SDL2::SDL2 SDL2_mixer) + target_link_libraries(VVVVVV SDL2::SDL2) + if(BUNDLE_DEPENDENCIES) + target_link_libraries(faudio-static SDL2::SDL2) + endif() elseif(TARGET SDL2) message(STATUS "Using TARGET SDL2") - target_link_libraries(VVVVVV SDL2 SDL2_mixer) + target_link_libraries(VVVVVV SDL2) + if(BUNDLE_DEPENDENCIES) + target_link_libraries(faudio-static SDL2) + endif() else() message(STATUS "No TARGET SDL2::SDL2, or SDL2, using variables") - find_path(SDL2_MIXER_INCLUDE_DIRS NAMES SDL_mixer.h PATH_SUFFIXES SDL2) - target_include_directories(VVVVVV SYSTEM PRIVATE "$" ${SDL2_MIXER_INCLUDE_DIRS}) - target_link_libraries(VVVVVV ${SDL2_LIBRARIES} SDL2_mixer) + target_include_directories(VVVVVV SYSTEM PRIVATE "$") + target_link_libraries(VVVVVV ${SDL2_LIBRARIES}) + if(BUNDLE_DEPENDENCIES) + target_include_directories(faudio-static SYSTEM PRIVATE "$") + target_link_libraries(faudio-static ${SDL2_LIBRARIES}) + endif() endif() endif() diff --git a/desktop_version/Dockerfile b/desktop_version/Dockerfile index 7a98295e..d1df2a89 100644 --- a/desktop_version/Dockerfile +++ b/desktop_version/Dockerfile @@ -5,9 +5,6 @@ WORKDIR /tmp RUN curl -O https://www.libsdl.org/release/SDL2-2.0.20.tar.gz RUN tar -xf SDL2-2.0.20.tar.gz RUN mkdir SDL2-2.0.20/build -RUN curl -O https://www.libsdl.org/projects/SDL_mixer/release/SDL2_mixer-2.0.4.tar.gz -RUN tar -xf SDL2_mixer-2.0.4.tar.gz -RUN mkdir SDL2_mixer-2.0.4/build # add EPEL (for SDL2) RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm @@ -30,12 +27,7 @@ RUN ../configure RUN make -j $(nproc) RUN make install -WORKDIR /tmp/SDL2_mixer-2.0.4/build -RUN ../configure -RUN make -j $(nproc) -RUN make install - WORKDIR /tmp -RUN rm -rf SDL2-2.0.20.tar.gz SDL2-2.0.20/ SDL2_mixer-2.0.4.tar.gz SDL2_mixer-2.0.4/ +RUN rm -rf SDL2-2.0.20.tar.gz SDL2-2.0.20/ WORKDIR / diff --git a/desktop_version/README.md b/desktop_version/README.md index b4fbccca..209d0680 100644 --- a/desktop_version/README.md +++ b/desktop_version/README.md @@ -6,8 +6,7 @@ VVVVVV's official desktop versions are built with the following environments: - macOS: Xcode CLT, currently targeting 10.9 SDK - GNU/Linux: CentOS 7 -The engine depends solely on [SDL2](https://libsdl.org/) 2.0.20+ and -[SDL2_mixer](https://www.libsdl.org/projects/SDL_mixer/). All other dependencies +The engine depends solely on [SDL2](https://libsdl.org/) 2.0.20+. All other dependencies are statically linked into the engine. The development libraries for Windows can be downloaded from their respective websites, Linux developers can find the dev libraries from their respective repositories, and macOS developers should @@ -22,14 +21,14 @@ current implementation has been tested with Steamworks SDK v1.46. To generate the projects on Windows: ``` -# Put your SDL2/SDL2_mixer folders somewhere nice! +# Put your SDL2 folders somewhere nice! mkdir flibitBuild cd flibitBuild -cmake -A Win32 -G "Visual Studio 10 2010" .. -DSDL2_INCLUDE_DIRS="C:\SDL2-2.0.20\include;C:\SDL2_mixer-2.0.4\include" -DSDL2_LIBRARIES="C:\SDL2-2.0.20\lib\x86\SDL2;C:\SDL2-2.0.20\lib\x86\SDL2main;C:\SDL2_mixer-2.0.4\lib\x86\SDL2_mixer" +cmake -A Win32 -G "Visual Studio 10 2010" .. -DSDL2_INCLUDE_DIRS="C:\SDL2-2.0.20\include" -DSDL2_LIBRARIES="C:\SDL2-2.0.20\lib\x86\SDL2;C:\SDL2-2.0.20\lib\x86\SDL2main" ``` Note that on some systems, the `SDL2_LIBRARIES` list on Windows may need -SDL2/SDL2main/SDL2_mixer to have `.lib` at the end of them. The reason for this +SDL2/SDL2main to have `.lib` at the end of them. The reason for this inconsistency is unknown. Also note that if you're using a Visual Studio later than 2010, you will need to diff --git a/desktop_version/src/Music.cpp b/desktop_version/src/Music.cpp index 34dc2e5a..7578b921 100644 --- a/desktop_version/src/Music.cpp +++ b/desktop_version/src/Music.cpp @@ -2,6 +2,7 @@ #include "Music.h" #include +#include #include #include "BinaryBlob.h" @@ -10,168 +11,560 @@ #include "Graphics.h" #include "Map.h" #include "Script.h" +#include "Unused.h" #include "UtilityClass.h" #include "Vlogging.h" -/* Begin SDL_mixer wrapper */ - -#include #include -#define VVV_MAX_VOLUME MIX_MAX_VOLUME +/* stb_vorbis */ + +#define malloc SDL_malloc +#define realloc SDL_realloc +#define free SDL_free +#ifdef memset /* Thanks, Apple! */ +#undef memset +#endif +#define memset SDL_memset +#ifdef memcpy /* Thanks, Apple! */ +#undef memcpy +#endif +#define memcpy SDL_memcpy +#define memcmp SDL_memcmp + +#define pow SDL_pow +#define log(x) SDL_log(x) +#define sin(x) SDL_sin(x) +#define cos(x) SDL_cos(x) +#define floor SDL_floor +#define abs(x) SDL_abs(x) +#define ldexp(v, e) SDL_scalbn((v), (e)) +#define exp(x) SDL_exp(x) + +#define qsort SDL_qsort + +#define assert SDL_assert + +#define FILE SDL_RWops +#ifdef SEEK_SET +#undef SEEK_SET +#endif +#ifdef SEEK_CUR +#undef SEEK_CUR +#endif +#ifdef SEEK_END +#undef SEEK_END +#endif +#ifdef EOF +#undef EOF +#endif +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#define EOF -1 +#define fopen(path, mode) SDL_RWFromFile(path, mode) +#define fopen_s(io, path, mode) (!(*io = fopen(path, mode))) +#define fclose(io) SDL_RWclose(io) +#define fread(dst, size, count, io) SDL_RWread(io, dst, size, count) +#define fseek(io, offset, whence) SDL_RWseek(io, offset, whence) +#define ftell(io) SDL_RWtell(io) + +#define FAudio_alloca(x) SDL_stack_alloc(uint8_t, x) +#define FAudio_dealloca(x) SDL_stack_free(x) + +#define STB_VORBIS_NO_PUSHDATA_API 1 +#define STB_VORBIS_NO_INTEGER_CONVERSION 1 +#include + +/* End stb_vorbis include */ + +#define VVV_MAX_VOLUME 128 +#define VVV_MAX_CHANNELS 8 + +class SoundTrack; +class MusicTrack; +static std::vector soundTracks; +static std::vector musicTracks; + +static FAudio* faudioctx = NULL; +static FAudioMasteringVoice* masteringvoice = NULL; class SoundTrack { public: SoundTrack(const char* fileName) { - /* SDL_LoadWAV, convert spec to FAudioBuffer */ unsigned char *mem; size_t length; + SDL_AudioSpec spec; + SDL_RWops *fileIn; + SDL_zerop(this); FILESYSTEM_loadAssetToMemory(fileName, &mem, &length, false); if (mem == NULL) { - m_sound = NULL; vlog_error("Unable to load WAV file %s", fileName); SDL_assert(0 && "WAV file missing!"); return; } - SDL_RWops *fileIn = SDL_RWFromConstMem(mem, length); - m_sound = Mix_LoadWAV_RW(fileIn, 1); - FILESYSTEM_freeMemory(&mem); - - if (m_sound == NULL) + fileIn = SDL_RWFromConstMem(mem, length); + if (SDL_LoadWAV_RW(fileIn, 1, &spec, &wav_buffer, &wav_length) == NULL) { - vlog_error("Unable to load WAV file: %s", Mix_GetError()); + vlog_error("Unable to load WAV file %s", fileName); + goto end; } + format.nChannels = spec.channels; + format.nSamplesPerSec = spec.freq; + format.wFormatTag = FAUDIO_FORMAT_PCM; + format.wBitsPerSample = 16; + format.nBlockAlign = format.nChannels * format.wBitsPerSample; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + valid = true; +end: + FILESYSTEM_freeMemory(&mem); } void Dispose() { - /* Destroy all track voices, SDL_free buffer from LoadWAV */ - Mix_FreeChunk(m_sound); + SDL_free(wav_buffer); } void Play() { - /* Fire-and-forget from a per-track FAudioSourceVoice pool */ - if (Mix_PlayChannel(-1, m_sound, 0) == -1) + if (!valid) { - vlog_error("Unable to play WAV file: %s", Mix_GetError()); + return; + } + + for (int i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioVoiceState voicestate; + FAudioSourceVoice_GetState(voices[i], &voicestate, 0); + if (voicestate.BuffersQueued == 0) + { + FAudioVoiceDetails details; + FAudioVoice_GetVoiceDetails(voices[i], &details); + if (details.InputChannels != format.nChannels) + { + FAudioVoice_DestroyVoice(voices[i]); + FAudio_CreateSourceVoice(faudioctx, &voices[i], &format, 0, 2.0f, NULL, NULL, NULL); + } + const FAudioBuffer faudio_buffer = { + FAUDIO_END_OF_STREAM, /* Flags */ + wav_length * 8, /* AudioBytes */ + wav_buffer, /* AudioData */ + 0, /* playbegin */ + 0, /* playlength */ + 0, /* LoopBegin */ + 0, /* LoopLength */ + 0, /* LoopCount */ + NULL + }; + if (FAudioSourceVoice_SubmitSourceBuffer(voices[i], &faudio_buffer, NULL)) + { + vlog_error("Unable to queue sound buffer"); + return; + } + if (FAudioSourceVoice_Start(voices[i], 0, FAUDIO_COMMIT_NOW)) + { + vlog_error("Unable to start voice processing"); + } + return; + } } } static void Init(int audio_rate, int audio_channels) { - const Uint16 audio_format = AUDIO_S16SYS; - const int audio_buffers = 1024; - - /* FAudioCreate, FAudio_CreateMasteringVoice */ - if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0) + if (voices == NULL) { - vlog_error("Unable to initialize audio: %s", Mix_GetError()); - SDL_assert(0 && "Unable to initialize audio!"); + voices = (FAudioSourceVoice**) SDL_malloc(sizeof(FAudioSourceVoice*) * VVV_MAX_CHANNELS); + for (int i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioWaveFormatEx format; + format.nChannels = 1; /* Assume 1 for SoundTracks. Will be recreated if mismatched during play */ + format.nSamplesPerSec = audio_rate; + format.wFormatTag = FAUDIO_FORMAT_PCM; + format.wBitsPerSample = 16; + format.nBlockAlign = format.nChannels * format.wBitsPerSample; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + if (FAudio_CreateSourceVoice(faudioctx, &voices[i], &format, 0, 2.0f, NULL, NULL, NULL)) + { + vlog_error("Unable to create source voice no. %i", i); + return; + } + } } } static void Pause() { - /* FAudio_StopEngine */ - Mix_Pause(-1); + for (size_t i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioSourceVoice_Stop(voices[i], 0, FAUDIO_COMMIT_NOW); + } } static void Resume() { - /* FAudio_StartEngine */ - Mix_Resume(-1); + for (size_t i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioSourceVoice_Start(voices[i], 0, FAUDIO_COMMIT_NOW); + } + } + + static void Destroy() + { + if (voices != NULL) + { + for (int i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioVoice_DestroyVoice(voices[i]); + } + SDL_free(voices); + voices = NULL; + } } static void SetVolume(int soundVolume) { - /* FAudioVoice_SetVolume on all sounds. Yeah, all of them :/ - * If we get desperate we can use a submix and set volume on that, but - * the submix is an extra mix stage so just loop all sounds manually... - */ - Mix_Volume(-1, soundVolume); + float adj_vol = (float) soundVolume / VVV_MAX_VOLUME; + for (size_t i = 0; i < VVV_MAX_CHANNELS; i++) + { + FAudioVoice_SetVolume(voices[i], adj_vol, FAUDIO_COMMIT_NOW); + } } -private: - Mix_Chunk *m_sound; + Uint8 *wav_buffer = NULL; + Uint32 wav_length; + FAudioWaveFormatEx format; + bool valid; + + static FAudioSourceVoice** voices; }; +FAudioSourceVoice** SoundTrack::voices = NULL; class MusicTrack { public: MusicTrack(SDL_RWops *rw) { - /* Open an stb_vorbis handle */ - m_music = Mix_LoadMUS_RW(rw, 1); - if (m_music == NULL) + SDL_zerop(this); + read_buf = (Uint8*) SDL_malloc(rw->size(rw)); + SDL_RWread(rw, read_buf, rw->size(rw), 1); + int err; + stb_vorbis_info vorbis_info; + stb_vorbis_comment vorbis_comment; + vorbis = stb_vorbis_open_memory(read_buf, rw->size(rw), &err, NULL); + if (vorbis == NULL) { - vlog_error("Unable to load Magic Binary Music file: %s", Mix_GetError()); + vlog_error("Unable to create Vorbis handle, error %d", err); + SDL_free(read_buf); + read_buf = NULL; + goto end; } + vorbis_info = stb_vorbis_get_info(vorbis); + format.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = sizeof(float) * 8; + format.nChannels = vorbis_info.channels; + format.nSamplesPerSec = vorbis_info.sample_rate; + format.nBlockAlign = format.nChannels * format.wBitsPerSample; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + channels = format.nChannels; + size = format.nAvgBytesPerSec / 20; + + decoded_buf_playing = (Uint8*) SDL_malloc(size); + decoded_buf_reserve = (Uint8*) SDL_malloc(size); + + loopbegin = 0; + looplength = 0; + vorbis_comment = stb_vorbis_get_comment(vorbis); + parseComments(this, vorbis_comment.comment_list, vorbis_comment.comment_list_length); + valid = true; + +end: + SDL_RWclose(rw); } void Dispose() { - /* Free stb_vorbis */ - Mix_FreeMusic(m_music); + stb_vorbis_close(vorbis); + SDL_free(read_buf); + SDL_free(decoded_buf_playing); + SDL_free(decoded_buf_reserve); + if (!IsHalted()) + { + FAudioVoice_DestroyVoice(musicVoice); + musicVoice = NULL; + } } bool Play(bool loop) { - /* Create/Validate static FAudioSourceVoice, begin streaming */ - if (Mix_PlayMusic(m_music, loop ? -1 : 0) == -1) + if (!valid) { - vlog_error("Mix_PlayMusic: %s", Mix_GetError()); return false; } + + shouldloop = loop; + sample_pos = 0; + stb_vorbis_seek_start(vorbis); + + if (IsHalted()) + { + SDL_zero(callbacks); + callbacks.OnBufferStart = &MusicTrack::refillReserve; + callbacks.OnBufferEnd = &MusicTrack::swapBuffers; + FAudio_CreateSourceVoice(faudioctx, &musicVoice, &format, 0, 2.0f, &callbacks, NULL, NULL); + } + else + { + Pause(); + FAudioSourceVoice_FlushSourceBuffers(musicVoice); + } + + FAudioBuffer faudio_buffer; + SDL_zero(faudio_buffer); + if (looplength == 0) + { + faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(vorbis, channels, (float*) decoded_buf_playing, size / sizeof(float)); + } + else + { + int samples_read = stb_vorbis_get_samples_float_interleaved(vorbis, channels, (float*) decoded_buf_playing, size / sizeof(float)); + faudio_buffer.PlayLength = SDL_min(samples_read, (loopbegin + looplength) - sample_pos); + } + faudio_buffer.AudioBytes = size; + faudio_buffer.pAudioData = decoded_buf_playing; + faudio_buffer.pContext = this; + sample_pos += faudio_buffer.PlayLength; + if (FAudioSourceVoice_SubmitSourceBuffer(musicVoice, &faudio_buffer, NULL)) + { + vlog_error("Unable to queue sound buffer"); + return false; + } + Resume(); return true; } static void Halt() { - /* FAudioVoice_Destroy */ - Mix_HaltMusic(); + if (!IsHalted()) + { + FAudioSourceVoice_FlushSourceBuffers(musicVoice); + FAudioVoice_DestroyVoice(musicVoice); + musicVoice = NULL; + } } static bool IsHalted() { - /* return musicVoice == NULL; */ - return Mix_PausedMusic() == 1; + return musicVoice == NULL; } static void Pause() { - /* FAudioSourceVoice_Pause */ - Mix_PauseMusic(); + if (!IsHalted()) + { + FAudioSourceVoice_Stop(musicVoice, 0, FAUDIO_COMMIT_NOW); + } } static void Resume() { - /* FAudioSourceVoice_Resume */ - Mix_ResumeMusic(); + if (!IsHalted()) + { + FAudioSourceVoice_Start(musicVoice, 0, FAUDIO_COMMIT_NOW); + } } static void SetVolume(int musicVolume) { - /* FAudioSourceVoice_SetVolume */ - Mix_VolumeMusic(musicVolume); + float adj_vol = (float) musicVolume / VVV_MAX_VOLUME; + if (!IsHalted()) + { + FAudioVoice_SetVolume(musicVoice, adj_vol, FAUDIO_COMMIT_NOW); + } } -private: - Mix_Music *m_music; + stb_vorbis* vorbis; + int channels; + Uint32 size; + int loopbegin; + int looplength; + int sample_pos; //stb_vorbis offset not yet functional on pulldata API. TODO Replace when fixed + + FAudioVoiceCallback callbacks; + FAudioWaveFormatEx format; + + Uint8* decoded_buf_playing = NULL; + Uint8* decoded_buf_reserve = NULL; + Uint8* read_buf = NULL; + bool shouldloop; + bool valid; + + static FAudioSourceVoice* musicVoice; + + static void refillReserve(FAudioVoiceCallback* callback, void* ctx) + { + MusicTrack* t = (MusicTrack*) ctx; + FAudioBuffer faudio_buffer; + SDL_zero(faudio_buffer); + UNUSED(callback); + if (t->looplength == 0) + { + faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float)); + } + else + { + int samples_read = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float)); + faudio_buffer.PlayLength = SDL_min(samples_read, (t->loopbegin + t->looplength) - t->sample_pos); + } + faudio_buffer.AudioBytes = t->size; + faudio_buffer.pAudioData = t->decoded_buf_reserve; + faudio_buffer.pContext = t; + if (faudio_buffer.PlayLength == 0) + { + if (t->shouldloop) + { + stb_vorbis_seek(t->vorbis, t->loopbegin); + t->sample_pos = t->loopbegin; + if (t->looplength != 0) + { + int samples_read = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float)); + faudio_buffer.PlayLength = SDL_min(samples_read, (t->loopbegin + t->looplength) - t->sample_pos); + } + else + { + faudio_buffer.PlayLength = stb_vorbis_get_samples_float_interleaved(t->vorbis, t->channels, (float*) t->decoded_buf_reserve, t->size / sizeof(float)); + } + if (faudio_buffer.PlayLength == 0) + { + return; + } + } + else + { + return; + } + } + t->sample_pos += faudio_buffer.PlayLength; + FAudioSourceVoice_SubmitSourceBuffer(musicVoice, &faudio_buffer, NULL); + } + + static void swapBuffers(FAudioVoiceCallback* callback, void* ctx) + { + MusicTrack* t = (MusicTrack*) ctx; + Uint8* tmp = t->decoded_buf_playing; + UNUSED(callback); + t->decoded_buf_playing = t->decoded_buf_reserve; + t->decoded_buf_reserve = tmp; + } + + /* Lifted from SDL_mixer, we used it in 2.3 and previous */ + static void parseComments(MusicTrack* t, char** comments, int comment_list_length) + { + int loopend = 0; + for (int i = 0; i < comment_list_length; i++) + { + char *param = SDL_strdup(comments[i]); + char *argument = param; + char *value = SDL_strchr(param, '='); + if (value == NULL) + { + value = param + SDL_strlen(param); + } + else + { + *(value++) = '\0'; + } + + /* Want to match LOOP-START, LOOP_START, etc. Remove - or _ from + * string if it is present at position 4. */ + char buf[5]; + SDL_strlcpy(buf, argument, sizeof(buf)); + if (SDL_strcasecmp(buf, "LOOP") == 0 && ((argument[4] == '_') || (argument[4] == '-'))) + { + SDL_memmove(argument + 4, argument + 5, SDL_strlen(argument) - 4); + } + + if (SDL_strcasecmp(argument, "LOOPSTART") == 0) + { + t->loopbegin = _Mix_ParseTime(value, t->format.nSamplesPerSec); + } + else if (SDL_strcasecmp(argument, "LOOPLENGTH") == 0) + { + t->looplength = SDL_strtoll(value, NULL, 10); + } + else if (SDL_strcasecmp(argument, "LOOPEND") == 0) + { + loopend = _Mix_ParseTime(value, t->format.nSamplesPerSec); + } + + SDL_free(param); + } + if (loopend != 0) + { + t->looplength = loopend - t->loopbegin; + } + } + + static int _Mix_ParseTime(char *time, long samplerate_hz) + { + char *num_start, *p; + Sint64 result; + char c; + int val; + + /* Time is directly expressed as a sample position */ + if (SDL_strchr(time, ':') == NULL) + { + return SDL_strtoll(time, NULL, 10); + } + + result = 0; + num_start = time; + + for (p = time; *p != '\0'; ++p) + { + if (*p == '.' || *p == ':') + { + c = *p; *p = '\0'; + if ((val = SDL_atoi(num_start)) < 0) + { + return -1; + } + result = result * 60 + val; + num_start = p + 1; + *p = c; + } + + if (*p == '.') + { + double val_f = SDL_atof(p); + if (val_f < 0) + { + return -1; + } + return result * samplerate_hz + (Sint64) (val_f * samplerate_hz); + } + } + + if ((val = SDL_atoi(num_start)) < 0) + { + return -1; + } + return (result * 60 + val) * samplerate_hz; + } }; -static std::vector soundTracks; -static std::vector musicTracks; - -/* End SDL_mixer wrapper */ +FAudioSourceVoice* MusicTrack::musicVoice = NULL; musicclass::musicclass(void) { - SoundTrack::Init(44100, 2); - safeToProcessMusic= false; m_doFadeInVol = false; m_doFadeOutVol = false; @@ -190,6 +583,19 @@ musicclass::musicclass(void) void musicclass::init(void) { + if (FAudioCreate(&faudioctx, 0, FAUDIO_DEFAULT_PROCESSOR)) + { + vlog_error("Unable to initialize FAudio"); + return; + } + if (FAudio_CreateMasteringVoice(faudioctx, &masteringvoice, 2, 44100, 0, 0, NULL)) + { + vlog_error("Unable to create mastering voice"); + return; + } + + SoundTrack::Init(44100, 2); + soundTracks.push_back(SoundTrack( "sounds/jump.wav" )); soundTracks.push_back(SoundTrack( "sounds/jump2.wav" )); soundTracks.push_back(SoundTrack( "sounds/hurt.wav" )); @@ -341,10 +747,7 @@ void musicclass::destroy(void) soundTracks[i].Dispose(); } soundTracks.clear(); - - // Before we free all the music: stop playing music, else SDL2_mixer - // will call SDL_Delay() if we are fading, resulting in no-draw frames - MusicTrack::Halt(); + SoundTrack::Destroy(); for (size_t i = 0; i < musicTracks.size(); ++i) { @@ -354,6 +757,14 @@ void musicclass::destroy(void) pppppp_blob.clear(); mmmmmm_blob.clear(); + if (masteringvoice != NULL) + { + FAudioVoice_DestroyVoice(masteringvoice); + } + if (faudioctx != NULL) + { + FAudio_Release(faudioctx); + } } void musicclass::play(int t) diff --git a/third_party/FAudio b/third_party/FAudio new file mode 160000 index 00000000..1d13854f --- /dev/null +++ b/third_party/FAudio @@ -0,0 +1 @@ +Subproject commit 1d13854fb83ac9868e0a2e5b8d3e58522b5fa66f