Load fluidsynth entrypoints dynamically (and make them optional)

This removes the static dependency on fluidsynth being present
at buildtime (even headers aren't needed anymore).

Even though midi is a default format for the RPG XP/VX series,
it has fallen more and more out of use, with VX Ace completely
abandoning it from the RTP and making ogg vorbis the de facto
standard. Midi support is kept for legacy reasons, but isn't
encouraged. On top of all this, fluidsynth together with glib
is a heavy dependency that often times won't even be used.
Making it optional at build time is an attempt to unify and
keep build config fragmentation low.

In RGSS3, fluidsynth / midi is not initialized at all by
default, but rather on demand when either a midi track is
played back or Audio.setup_midi is called.
This commit is contained in:
Jonas Kulla 2014-09-07 05:23:10 +02:00
parent 673a25f811
commit 757a1d5e39
13 changed files with 223 additions and 120 deletions

View File

@ -3,7 +3,7 @@ Project(mkxp)
## Setup options ##
option(MIDI "Enable midi support" ON)
option(SHARED_FLUID "Dynamically link fluidsynth at build time" OFF)
option(WORKDIR_CURRENT "Keep current directory on startup" OFF)
option(FORCE32 "Force 32bit compile on 64bit OS" OFF)
set(BINDING "MRI" CACHE STRING "The Binding Type (MRI, MRUBY, NULL)")
@ -151,6 +151,8 @@ set(MAIN_HEADERS
src/windowvx.h
src/tilemapvx.h
src/tileatlasvx.h
src/sharedmidistate.h
src/fluid-fun.h
)
set(MAIN_SOURCE
@ -193,6 +195,8 @@ set(MAIN_SOURCE
src/tilemapvx.cpp
src/tileatlasvx.cpp
src/autotilesvx.cpp
src/midisource.cpp
src/fluid-fun.cpp
)
source_group("MKXP Source" FILES ${MAIN_SOURCE} ${MAIN_HEADERS})
@ -227,16 +231,10 @@ if (RGSS2)
)
endif()
if (MIDI)
pkg_check_modules(MIDI REQUIRED fluidsynth)
if (SHARED_FLUID)
pkg_check_modules(FLUID REQUIRED fluidsynth)
list(APPEND DEFINES
MIDI
)
list(APPEND MAIN_HEADERS
src/sharedmidistate.h
)
list(APPEND MAIN_SOURCE
src/midisource.cpp
SHARED_FLUID
)
endif()
@ -400,7 +398,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${Boost_INCLUDE_DIR}
${MRI_INCLUDE_DIRS}
${VORBISFILE_INCLUDE_DIRS}
${MIDI_INCLUDE_DIRS}
${FLUID_INCLUDE_DIRS}
${OPENAL_INCLUDE_DIR}
)
@ -415,7 +413,7 @@ target_link_libraries(${PROJECT_NAME}
${Boost_LIBRARIES}
${MRI_LIBRARIES}
${VORBISFILE_LIBRARIES}
${MIDI_LIBRARIES}
${FLUID_LIBRARIES}
${OPENAL_LIBRARY}
${ZLIB_LIBRARY}

View File

@ -60,7 +60,6 @@ This binding only exists for testing purposes and does nothing (the engine quits
* SDL_sound (latest hg, apply provided patches!)
* vorbisfile
* pixman
* fluidsynth (if midi enabled)
* zlib (only ruby bindings)
* OpenGL header (alternatively GLES2 with `DEFINES+=GLES2_HEADER`)
* libiconv (on Windows, optional with INI_ENCODING)
@ -72,7 +71,7 @@ qmake will use pkg-config to locate the respective include/library paths. If you
The exception is boost, which is weird in that it still hasn't managed to pull off pkg-config support (seriously?). *If you installed boost in a non-standard prefix*, you will need to pass its include path via `BOOST_I` and library path via `BOOST_L`, either as direct arguments to qmake (`qmake BOOST_I="/usr/include" ...`) or via environment variables. You can specify a library suffix (eg. "-mt") via `BOOST_LIB_SUFFIX` if needed.
Midi support is enabled by default; you can disable it via `qmake CONFIG+=DISABLE_MIDI`, in which case the fluidsynth dependency is dropped. When building fluidsynth yourself, you can disable almost all options (audio drivers etc.) as they are not used. Note that upstream fluidsynth has support for sharing soundfont data between synthesizers (mkxp uses multiple synths), so if your memory usage is very high, you might want to try compiling fluidsynth from git master.
Midi support is enabled by default and requires fluidsynth to be present at runtime (not needed for building); if mkxp can't find it at runtime, midi playback is disabled. It looks for `libfluidsynth.so.1` on Linux, `libfluidsynth.dylib.1` on OSX and `fluidsynth.dll` on Windows, so make sure to have one of these in your link path. If you still need fluidsynth to be hard linked at buildtime, use `CONFIG+=SHARED_FLUID`. When building fluidsynth yourself, you can disable almost all options (audio drivers etc.) as they are not used. Note that upstream fluidsynth has support for sharing soundfont data between synthesizers (mkxp uses multiple synths), so if your memory usage is very high, you might want to try compiling fluidsynth from git master.
By default, mkxp switches into the directory where its binary is contained and then starts reading the configuration and resolving relative paths. In case this is undesired (eg. when the binary is to be installed to a system global, read-only location), it can be turned off by adding `DEFINES+=WORKDIR_CURRENT` to qmake's arguments.

View File

@ -8,12 +8,6 @@ INCLUDEPATH += . src
CONFIG(release, debug|release): DEFINES += NDEBUG
CONFIG += MIDI
DISABLE_MIDI {
CONFIG -= MIDI
}
isEmpty(BINDING) {
BINDING = MRI
}
@ -50,7 +44,7 @@ unix {
PKGCONFIG += sigc++-2.0 pixman-1 zlib physfs vorbisfile \
sdl2 SDL2_image SDL2_ttf SDL_sound openal
MIDI {
SHARED_FLUID {
PKGCONFIG += fluidsynth
}
@ -135,7 +129,9 @@ HEADERS += \
src/rgssad.h \
src/windowvx.h \
src/tilemapvx.h \
src/tileatlasvx.h
src/tileatlasvx.h \
src/sharedmidistate.h \
src/fluid-fun.h
SOURCES += \
src/main.cpp \
@ -176,7 +172,9 @@ SOURCES += \
src/windowvx.cpp \
src/tilemapvx.cpp \
src/tileatlasvx.cpp \
src/autotilesvx.cpp
src/autotilesvx.cpp \
src/midisource.cpp \
src/fluid-fun.cpp
EMBED = \
shader/transSimple.frag \
@ -200,14 +198,8 @@ EMBED = \
shader/tilemapvx.vert \
assets/liberation.ttf
MIDI {
HEADERS += \
src/sharedmidistate.h
SOURCES += \
src/midisource.cpp
DEFINES += MIDI
SHARED_FLUID {
DEFINES += SHARED_FLUID
}
INI_ENCODING {

View File

@ -61,9 +61,7 @@ ALDataSource *createSDLSource(SDL_RWops &ops,
ALDataSource *createVorbisSource(SDL_RWops &ops,
bool looped);
#ifdef MIDI
ALDataSource *createMidiSource(SDL_RWops &ops,
bool looped);
#endif
#endif // ALDATASOURCE_H

View File

@ -22,8 +22,10 @@
#include "alstream.h"
#include "sharedstate.h"
#include "sharedmidistate.h"
#include "filesystem.h"
#include "aldatasource.h"
#include "fluid-fun.h"
#include <SDL_mutex.h>
#include <SDL_thread.h>
@ -198,14 +200,6 @@ void ALStream::openSource(const std::string &filename)
shState->fileSystem().openRead(srcOps, filename.c_str(), FileSystem::Audio, false, &ext);
needsRewind = false;
bool readSig = rgssVer >= 2;
#ifdef MIDI
readSig = true;
#endif
if (readSig)
{
/* Try to read ogg file signature */
char sig[5] = { 0 };
SDL_RWread(&srcOps, sig, 1, 4);
@ -217,13 +211,15 @@ void ALStream::openSource(const std::string &filename)
return;
}
#ifdef MIDI
if (!strcmp(sig, "MThd"))
{
shState->midiState().initIfNeeded(shState->config());
if (HAVE_FLUID)
{
source = createMidiSource(srcOps, looped);
return;
}
#endif
}
source = createSDLSource(srcOps, ext, STREAM_BUF_SIZE, looped);

View File

@ -24,10 +24,7 @@
#include "audiostream.h"
#include "soundemitter.h"
#include "sharedstate.h"
#ifdef MIDI
#include "sharedmidistate.h"
#endif
#include <string>
@ -317,9 +314,7 @@ void Audio::seStop()
void Audio::setupMidi()
{
#ifdef MIDI
shState->midiState().initDefaultSynths();
#endif
shState->midiState().initIfNeeded(shState->config());
}
float Audio::bgmPos()

View File

@ -340,10 +340,8 @@ FileSystem::FileSystem(const char *argv0,
if (rgssVer >= 2 && !contains(p->extensions[Audio], std::string("ogg")))
p->extensions[Audio].push_back("ogg");
#if MIDI
p->extensions[Audio].push_back("mid");
p->extensions[Audio].push_back("midi");
#endif
/* Font extensions */
p->extensions[Font].push_back("ttf");

73
src/fluid-fun.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "fluid-fun.h"
#include <string.h>
#include <SDL_loadso.h>
#include <SDL_platform.h>
#include "debugwriter.h"
#ifdef SHARED_FLUID
#include <fluidsynth.h>
#endif
#ifdef __LINUX__
#define FLUID_LIB "libfluidsynth.so.1"
#elif __MACOSX__
#define FLUID_LIB "libfluidsynth.dylib.1"
#elif __WINDOWS__
#define FLUID_LIB "fluidsynth.dll"
#else
#error "platform not recognized"
#endif
struct FluidFunctions fluid;
static void *so;
void initFluidFunctions()
{
#ifdef SHARED_FLUID
#define FLUID_FUN(name, type) \
fluid.name = fluid_##name;
#define FLUID_FUN2(name, type, real_name) \
fluid.name = real_name;
#else
so = SDL_LoadObject(FLUID_LIB);
if (!so)
goto fail;
#define FLUID_FUN(name, type) \
fluid.name = (type) SDL_LoadFunction(so, "fluid_" #name); \
if (!fluid.name) \
goto fail;
#define FLUID_FUN2(name, type, real_name) \
fluid.name = (type) SDL_LoadFunction(so, #real_name); \
if (!fluid.name) \
goto fail;
#endif
FLUID_FUNCS
FLUID_FUNCS2
return;
#ifndef SHARED_FLUID
fail:
Debug() << "Failed to load " FLUID_LIB ". Midi playback is disabled.";
memset(&fluid, 0, sizeof(fluid));
finiFluidFunctions();
#endif
}
void finiFluidFunctions()
{
#ifndef SHARED_FLUID
SDL_UnloadObject(so);
#endif
}

60
src/fluid-fun.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef FLUIDFUN_H
#define FLUIDFUN_H
typedef struct _fluid_hashtable_t fluid_settings_t;
typedef struct _fluid_synth_t fluid_synth_t;
typedef fluid_settings_t* (*NEWFLUIDSETTINGSPROC)(void);
typedef int (*FLUIDSETTINGSSETNUMPROC)(fluid_settings_t* settings, const char *name, double val);
typedef int (*FLUIDSETTINGSSETSTRPROC)(fluid_settings_t* settings, const char *name, const char *str);
typedef void (*DELETEFLUIDSETTINGSPROC)(fluid_settings_t* settings);
typedef fluid_synth_t* (*NEWFLUIDSYNTHPROC)(fluid_settings_t* settings);
typedef int (*DELETEFLUIDSYNTHPROC)(fluid_synth_t* synth);
typedef int (*FLUIDSYNTHSFLOADPROC)(fluid_synth_t* synth, const char* filename, int reset_presets);
typedef int (*FLUIDSYNTHSYSTEMRESETPROC)(fluid_synth_t* synth);
typedef int (*FLUIDSYNTHWRITES16PROC)(fluid_synth_t* synth, int len, void* lout, int loff, int lincr, void* rout, int roff, int rincr);
typedef int (*FLUIDSYNTHNOTEONPROC)(fluid_synth_t* synth, int chan, int key, int vel);
typedef int (*FLUIDSYNTHNOTEOFFPROC)(fluid_synth_t* synth, int chan, int key);
typedef int (*FLUIDSYNTHCHANNELPRESSUREPROC)(fluid_synth_t* synth, int chan, int val);
typedef int (*FLUIDSYNTHPITCHBENDPROC)(fluid_synth_t* synth, int chan, int val);
typedef int (*FLUIDSYNTHCCPROC)(fluid_synth_t* synth, int chan, int ctrl, int val);
typedef int (*FLUIDSYNTHPROGRAMCHANGEPROC)(fluid_synth_t* synth, int chan, int program);
#define FLUID_FUNCS \
FLUID_FUN(settings_setnum, FLUIDSETTINGSSETNUMPROC) \
FLUID_FUN(settings_setstr, FLUIDSETTINGSSETSTRPROC) \
FLUID_FUN(synth_sfload, FLUIDSYNTHSFLOADPROC) \
FLUID_FUN(synth_system_reset, FLUIDSYNTHSYSTEMRESETPROC) \
FLUID_FUN(synth_write_s16, FLUIDSYNTHWRITES16PROC) \
FLUID_FUN(synth_noteon, FLUIDSYNTHNOTEONPROC) \
FLUID_FUN(synth_noteoff, FLUIDSYNTHNOTEOFFPROC) \
FLUID_FUN(synth_channel_pressure, FLUIDSYNTHCHANNELPRESSUREPROC) \
FLUID_FUN(synth_pitch_bend, FLUIDSYNTHPITCHBENDPROC) \
FLUID_FUN(synth_cc, FLUIDSYNTHCCPROC) \
FLUID_FUN(synth_program_change, FLUIDSYNTHPROGRAMCHANGEPROC)
/* Functions that don't fit into the default prefix naming scheme */
#define FLUID_FUNCS2 \
FLUID_FUN2(new_settings, NEWFLUIDSETTINGSPROC, new_fluid_settings) \
FLUID_FUN2(new_synth, NEWFLUIDSYNTHPROC, new_fluid_synth) \
FLUID_FUN2(delete_settings, DELETEFLUIDSETTINGSPROC, delete_fluid_settings) \
FLUID_FUN2(delete_synth, DELETEFLUIDSYNTHPROC, delete_fluid_synth)
struct FluidFunctions
{
#define FLUID_FUN(name, type) type name;
#define FLUID_FUN2(name, type, rn) type name;
FLUID_FUNCS
FLUID_FUNCS2
#undef FLUID_FUN
#undef FLUID_FUN2
};
#define HAVE_FLUID fluid.new_synth
extern FluidFunctions fluid;
void initFluidFunctions();
void finiFluidFunctions();
#endif // FLUIDFUN_H

View File

@ -27,8 +27,8 @@
#include "sharedmidistate.h"
#include "util.h"
#include "debugwriter.h"
#include "fluid-fun.h"
#include <fluidsynth.h>
#include <SDL_rwops.h>
#include <assert.h>
@ -648,22 +648,22 @@ struct MidiSource : ALDataSource, MidiReadHandler
switch (e.type)
{
case NoteOn:
fluid_synth_noteon(synth, e.e.note.chan, key, e.e.note.vel);
fluid.synth_noteon(synth, e.e.note.chan, key, e.e.note.vel);
break;
case NoteOff:
fluid_synth_noteoff(synth, e.e.note.chan, key);
fluid.synth_noteoff(synth, e.e.note.chan, key);
break;
case ChanTouch:
fluid_synth_channel_pressure(synth, e.e.chanTouch.chan, e.e.chanTouch.val);
fluid.synth_channel_pressure(synth, e.e.chanTouch.chan, e.e.chanTouch.val);
break;
case PitchBend:
fluid_synth_pitch_bend(synth, e.e.pitchBend.chan, e.e.pitchBend.val);
fluid.synth_pitch_bend(synth, e.e.pitchBend.chan, e.e.pitchBend.val);
break;
case CC:
fluid_synth_cc(synth, e.e.cc.chan, e.e.cc.ctrl, e.e.cc.val);
fluid.synth_cc(synth, e.e.cc.chan, e.e.cc.ctrl, e.e.cc.val);
break;
case PC:
fluid_synth_program_change(synth, e.e.pc.chan, e.e.pc.prog);
fluid.synth_program_change(synth, e.e.pc.chan, e.e.pc.prog);
break;
case Tempo:
updatePlaybackSpeed(e.e.tempo.bpm);
@ -679,7 +679,7 @@ struct MidiSource : ALDataSource, MidiReadHandler
int len = count * TICK_FRAMES;
void *buffer = &synthBuf[bufOffset];
fluid_synth_write_s16(synth, len, buffer, 0, 2, buffer, 1, 2);
fluid.synth_write_s16(synth, len, buffer, 0, 2, buffer, 1, 2);
}
/* MidiReadHandler */
@ -817,7 +817,7 @@ struct MidiSource : ALDataSource, MidiReadHandler
void seekToOffset(float)
{
/* Reset synth */
fluid_synth_system_reset(synth);
fluid.synth_system_reset(synth);
/* Reset runtime variables */
genDeltasCarry = 0;

View File

@ -24,8 +24,7 @@
#include "config.h"
#include "debugwriter.h"
#include <fluidsynth.h>
#include "fluid-fun.h"
#include <assert.h>
#include <vector>
@ -50,58 +49,52 @@ struct SharedMidiState
SharedMidiState(const Config &conf)
: inited(false),
soundFont(conf.midi.soundFont)
{
flSettings = new_fluid_settings();
fluid_settings_setnum(flSettings, "synth.gain", 1.0);
fluid_settings_setnum(flSettings, "synth.sample-rate", SYNTH_SAMPLERATE);
fluid_settings_setstr(flSettings, "synth.chorus.active", conf.midi.chorus ? "yes" : "no");
fluid_settings_setstr(flSettings, "synth.reverb.active", conf.midi.reverb ? "yes" : "no");
}
{}
~SharedMidiState()
{
delete_fluid_settings(flSettings);
if (!HAVE_FLUID || !inited)
return;
fluid.delete_settings(flSettings);
for (size_t i = 0; i < synths.size(); ++i)
{
assert(!synths[i].inUse);
delete_fluid_synth(synths[i].synth);
}
fluid.delete_synth(synths[i].synth);
}
fluid_synth_t *addSynth(bool usedNow)
{
fluid_synth_t *syn = new_fluid_synth(flSettings);
if (!soundFont.empty())
fluid_synth_sfload(syn, soundFont.c_str(), 1);
else
Debug() << "Warning: No soundfont specified, sound might be mute";
Synth synth;
synth.inUse = usedNow;
synth.synth = syn;
synths.push_back(synth);
return syn;
finiFluidFunctions();
}
void initDefaultSynths()
void initIfNeeded(const Config &conf)
{
if (inited)
return;
inited = true;
initFluidFunctions();
if (!HAVE_FLUID)
return;
flSettings = fluid.new_settings();
fluid.settings_setnum(flSettings, "synth.gain", 1.0);
fluid.settings_setnum(flSettings, "synth.sample-rate", SYNTH_SAMPLERATE);
fluid.settings_setstr(flSettings, "synth.chorus.active", conf.midi.chorus ? "yes" : "no");
fluid.settings_setstr(flSettings, "synth.reverb.active", conf.midi.reverb ? "yes" : "no");
for (size_t i = 0; i < SYNTH_INIT_COUNT; ++i)
addSynth(false);
inited = true;
}
fluid_synth_t *allocateSynth()
{
size_t i;
assert(HAVE_FLUID);
assert(inited);
initDefaultSynths();
size_t i;
for (i = 0; i < synths.size(); ++i)
if (!synths[i].inUse)
@ -110,7 +103,7 @@ struct SharedMidiState
if (i < synths.size())
{
fluid_synth_t *syn = synths[i].synth;
fluid_synth_system_reset(syn);
fluid.synth_system_reset(syn);
synths[i].inUse = true;
return syn;
@ -133,6 +126,24 @@ struct SharedMidiState
synths[i].inUse = false;
}
private:
fluid_synth_t *addSynth(bool usedNow)
{
fluid_synth_t *syn = fluid.new_synth(flSettings);
if (!soundFont.empty())
fluid.synth_sfload(syn, soundFont.c_str(), 1);
else
Debug() << "Warning: No soundfont specified, sound might be mute";
Synth synth;
synth.inUse = usedNow;
synth.synth = syn;
synths.push_back(synth);
return syn;
}
};
#endif // SHAREDMIDISTATE_H

View File

@ -36,10 +36,7 @@
#include "quad.h"
#include "binding.h"
#include "exception.h"
#ifdef MIDI
#include "sharedmidistate.h"
#endif
#include <unistd.h>
#include <stdio.h>
@ -74,9 +71,7 @@ struct SharedStatePrivate
RGSSThreadData &rtData;
Config &config;
#ifdef MIDI
SharedMidiState midiState;
#endif
Graphics graphics;
Input input;
@ -109,9 +104,7 @@ struct SharedStatePrivate
eThread(*threadData->ethread),
rtData(*threadData),
config(threadData->config),
#ifdef MIDI
midiState(threadData->config),
#endif
graphics(threadData),
audio(threadData->config),
fontState(threadData->config),
@ -164,10 +157,8 @@ struct SharedStatePrivate
/* RGSS3 games will call setup_midi, so there's
* no need to do it on startup */
#if MIDI
if (rgssVer <= 2)
midiState.initDefaultSynths();
#endif
midiState.initIfNeeded(threadData->config);
}
~SharedStatePrivate()
@ -245,10 +236,7 @@ GSATT(ShaderSet&, shaders)
GSATT(TexPool&, texPool)
GSATT(Quad&, gpQuad)
GSATT(SharedFontState&, fontState)
#ifdef MIDI
GSATT(SharedMidiState&, midiState)
#endif
void SharedState::setBindingData(void *data)
{

View File

@ -49,10 +49,7 @@ class SharedFontState;
struct GlobalIBO;
struct Config;
struct Vec2i;
#ifdef MIDI
struct SharedMidiState;
#endif
struct SharedState
{
@ -83,9 +80,7 @@ struct SharedState
SharedFontState &fontState() const;
Font &defaultFont() const;
#ifdef MIDI
SharedMidiState &midiState() const;
#endif
sigc::signal<void> prepareDraw;