mirror of
https://github.com/TerryCavanagh/VVVVVV.git
synced 2024-12-22 01:29:43 +01:00
Port to FAudio
This commit is contained in:
parent
a8feba029f
commit
f877eb3b56
7 changed files with 530 additions and 110 deletions
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -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
|
||||
|
|
|
@ -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 "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>")
|
||||
target_link_libraries(VVVVVV ${SDL2_LIBRARIES})
|
||||
if(BUNDLE_DEPENDENCIES)
|
||||
target_include_directories(faudio-static SYSTEM PRIVATE "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>")
|
||||
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 "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>" ${SDL2_MIXER_INCLUDE_DIRS})
|
||||
target_link_libraries(VVVVVV ${SDL2_LIBRARIES} SDL2_mixer)
|
||||
target_include_directories(VVVVVV SYSTEM PRIVATE "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>")
|
||||
target_link_libraries(VVVVVV ${SDL2_LIBRARIES})
|
||||
if(BUNDLE_DEPENDENCIES)
|
||||
target_include_directories(faudio-static SYSTEM PRIVATE "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>")
|
||||
target_link_libraries(faudio-static ${SDL2_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
|
|
@ -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 /
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "Music.h"
|
||||
|
||||
#include <SDL.h>
|
||||
#include <FAudio.h>
|
||||
#include <physfsrwops.h>
|
||||
|
||||
#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 <SDL_mixer.h>
|
||||
#include <vector>
|
||||
|
||||
#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 <stb_vorbis.h>
|
||||
|
||||
/* End stb_vorbis include */
|
||||
|
||||
#define VVV_MAX_VOLUME 128
|
||||
#define VVV_MAX_CHANNELS 8
|
||||
|
||||
class SoundTrack;
|
||||
class MusicTrack;
|
||||
static std::vector<SoundTrack> soundTracks;
|
||||
static std::vector<MusicTrack> 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<SoundTrack> soundTracks;
|
||||
static std::vector<MusicTrack> 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)
|
||||
|
|
1
third_party/FAudio
vendored
Submodule
1
third_party/FAudio
vendored
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1d13854fb83ac9868e0a2e5b8d3e58522b5fa66f
|
Loading…
Reference in a new issue