diff options
-rw-r--r-- | CMakeLists.txt | 28 | ||||
-rw-r--r-- | OpenAL32/alMidi.c | 242 | ||||
-rw-r--r-- | cmake/FindFluidSynth.cmake | 23 | ||||
-rw-r--r-- | config.h.in | 3 |
4 files changed, 291 insertions, 5 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d5b6960..c7fbc095 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ OPTION(ALSOFT_BACKEND_COREAUDIO "Check for CoreAudio backend" ON) OPTION(ALSOFT_BACKEND_OPENSL "Check for OpenSL backend" ON) OPTION(ALSOFT_BACKEND_WAVE "Enable Wave Writer backend" ON) +OPTION(ALSOFT_MIDI_FLUIDSYNTH "Check for FluidSynth MIDI" ON) + OPTION(ALSOFT_REQUIRE_ALSA "Require ALSA backend" OFF) OPTION(ALSOFT_REQUIRE_OSS "Require OSS backend" OFF) OPTION(ALSOFT_REQUIRE_SOLARIS "Require Solaris backend" OFF) @@ -66,6 +68,8 @@ OPTION(ALSOFT_REQUIRE_PULSEAUDIO "Require PulseAudio backend" OFF) OPTION(ALSOFT_REQUIRE_COREAUDIO "Require CoreAudio backend" OFF) OPTION(ALSOFT_REQUIRE_OPENSL "Require OpenSL backend" OFF) +OPTION(ALSOFT_REQUIRE_FLUIDSYNTH "Require FluidSynth MIDI" OFF) + OPTION(ALSOFT_DLOPEN "Check for the dlopen API for loading optional libs" ON) OPTION(ALSOFT_WERROR "Treat compile warnings as errors" OFF) @@ -553,6 +557,23 @@ IF(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) ENDIF() +SET(HAVE_MIDI_SYNTH 0) +# Check for FluidSynth support +IF(ALSOFT_MIDI_FLUIDSYNTH) + FIND_PACKAGE(FluidSynth) + IF(FLUIDSYNTH_FOUND) + SET(HAVE_FLUIDSYNTH 1) + IF(CMAKE_VERSION VERSION_LESS "2.8.8") + INCLUDE_DIRECTORIES(${FLUIDSYNTH_INCLUDE_DIR}) + ENDIF() + SET(EXTRA_LIBS ${FLUIDSYNTH_LIBRARIES} ${EXTRA_LIBS}) + ENDIF() +ENDIF() +IF(ALSOFT_REQUIRE_FLUIDSYNTH AND NOT HAVE_FLUIDSYNTH) + MESSAGE(FATAL_ERROR "Failed to enabled required FluidSynth support") +ENDIF() + + SET(ALC_OBJS ${ALC_OBJS} Alc/backends/base.c # Default backends, always available @@ -847,6 +868,9 @@ IF(WIN32 AND ALSOFT_NO_UID_DEFS) SET_PROPERTY(TARGET ${LIBNAME} APPEND PROPERTY COMPILE_DEFINITIONS AL_NO_UID_DEFS) ENDIF() SET_PROPERTY(TARGET ${LIBNAME} APPEND PROPERTY INCLUDE_DIRECTORIES "${OpenAL_SOURCE_DIR}/OpenAL32/Include" "${OpenAL_SOURCE_DIR}/Alc") +IF(FLUIDSYNTH_FOUND) + SET_PROPERTY(TARGET ${LIBNAME} APPEND PROPERTY INCLUDE_DIRECTORIES ${FLUIDSYNTH_INCLUDE_DIR}) +ENDIF() SET_TARGET_PROPERTIES(${LIBNAME} PROPERTIES VERSION ${LIB_VERSION} SOVERSION ${LIB_MAJOR_VERSION}) IF(WIN32 AND NOT LIBTYPE STREQUAL "STATIC") @@ -880,6 +904,10 @@ MESSAGE(STATUS "") MESSAGE(STATUS "Building with support for CPU extensions:") MESSAGE(STATUS " ${CPU_EXTS}") MESSAGE(STATUS "") +IF(HAVE_FLUIDSYNTH) + MESSAGE(STATUS "FluidSynth support for ALC_SOFT_midi_interface enabled") + MESSAGE(STATUS "") +ENDIF() IF(WIN32) IF(NOT HAVE_DSOUND) diff --git a/OpenAL32/alMidi.c b/OpenAL32/alMidi.c index 3e75b805..16f7fbf4 100644 --- a/OpenAL32/alMidi.c +++ b/OpenAL32/alMidi.c @@ -100,6 +100,222 @@ static ALenum MidiSynth_insertEvent(MidiSynth *self, ALuint64 time, ALuint event } +#ifdef HAVE_FLUIDSYNTH + +#include <fluidsynth.h> + +typedef struct FSynth { + DERIVE_FROM_TYPE(MidiSynth); + + /* NOTE: This rwlock is for setting the soundfont. The EventQueue and + * related must use the device lock as they're used in the mixer thread. + */ + RWLock Lock; + + fluid_settings_t *Settings; + fluid_synth_t *Synth; + int FontID; +} FSynth; + +static void FSynth_Construct(FSynth *self, ALCdevice *device); +static void FSynth_Destruct(FSynth *self); +static ALboolean FSynth_init(FSynth *self, ALCdevice *device); +static void FSynth_setState(FSynth *self, ALenum state); +static void FSynth_update(FSynth *self, ALCdevice *device); +static void FSynth_process(FSynth *self, ALuint SamplesToDo, ALfloat (*restrict DryBuffer)[BUFFERSIZE]); +static void FSynth_Delete(FSynth *self); +DEFINE_MIDISYNTH_VTABLE(FSynth); + + +static void FSynth_Construct(FSynth *self, ALCdevice *device) +{ + MidiSynth_Construct(STATIC_CAST(MidiSynth, self), device); + SET_VTABLE2(FSynth, MidiSynth, self); + + RWLockInit(&self->Lock); + + self->Settings = NULL; + self->Synth = NULL; + self->FontID = FLUID_FAILED; +} + +static void FSynth_Destruct(FSynth *self) +{ + if(self->FontID != FLUID_FAILED) + fluid_synth_sfunload(self->Synth, self->FontID, 0); + self->FontID = FLUID_FAILED; + + if(self->Synth != NULL) + delete_fluid_synth(self->Synth); + self->Synth = NULL; + + if(self->Settings != NULL) + delete_fluid_settings(self->Settings); + self->Settings = NULL; + + MidiSynth_Destruct(STATIC_CAST(MidiSynth, self)); +} + +static ALboolean FSynth_init(FSynth *self, ALCdevice *device) +{ + self->Settings = new_fluid_settings(); + if(!self->Settings) + { + ERR("Failed to create FluidSettings\n"); + return AL_FALSE; + } + + fluid_settings_setint(self->Settings, "synth.reverb.active", 1); + fluid_settings_setint(self->Settings, "synth.chorus.active", 1); + fluid_settings_setint(self->Settings, "synth.polyphony", 256); + fluid_settings_setstr(self->Settings, "synth.midi-bank-select", "mma"); + fluid_settings_setnum(self->Settings, "synth.sample-rate", device->Frequency); + + self->Synth = new_fluid_synth(self->Settings); + if(!self->Synth) + { + ERR("Failed to create FluidSynth\n"); + return AL_FALSE; + } + + return AL_TRUE; +} + +static void FSynth_setState(FSynth *self, ALenum state) +{ + WriteLock(&self->Lock); + if(state == AL_PLAYING) + { + if(self->FontID == FLUID_FAILED) + { + int fontid = FLUID_FAILED; + const char *fname = getenv("FLUID_SOUNDFONT"); + if(fname && fname[0]) + fontid = fluid_synth_sfload(self->Synth, fname, 1); + if(fontid != FLUID_FAILED) + self->FontID = fontid; + else + ERR("Failed to load soundfont '%s'\n", fname?fname:"(nil)"); + } + } + MidiSynth_setState(STATIC_CAST(MidiSynth, self), state); + WriteUnlock(&self->Lock); +} + +static void FSynth_update(FSynth *self, ALCdevice *device) +{ + fluid_settings_setnum(self->Settings, "synth.sample-rate", device->Frequency); + fluid_synth_set_sample_rate(self->Synth, device->Frequency); + MidiSynth_update(STATIC_CAST(MidiSynth, self), device); +} + + +static void FSynth_processQueue(FSynth *self, ALuint64 time) +{ + EvtQueue *queue = &STATIC_CAST(MidiSynth, self)->EventQueue; + + while(queue->pos < queue->size && queue->events[queue->pos].time <= time) + { + const MidiEvent *evt = &queue->events[queue->pos]; + + switch((evt->event&0xF0)) + { + case AL_NOTEOFF_SOFT: + fluid_synth_noteoff(self->Synth, (evt->event&0x0F), evt->param[0]); + break; + case AL_NOTEON_SOFT: + fluid_synth_noteon(self->Synth, (evt->event&0x0F), evt->param[0], evt->param[1]); + break; + case AL_AFTERTOUCH_SOFT: + break; + + case AL_CONTROLLERCHANGE_SOFT: + fluid_synth_cc(self->Synth, (evt->event&0x0F), evt->param[0], evt->param[1]); + break; + case AL_PROGRAMCHANGE_SOFT: + fluid_synth_program_change(self->Synth, (evt->event&0x0F), evt->param[0]); + break; + + case AL_CHANNELPRESSURE_SOFT: + fluid_synth_channel_pressure(self->Synth, (evt->event&0x0F), evt->param[0]); + break; + + case AL_PITCHBEND_SOFT: + fluid_synth_pitch_bend(self->Synth, (evt->event&0x0F), (evt->param[0]&0x7F) | + ((evt->param[1]&0x7F)<<7)); + break; + } + + queue->pos++; + } + + if(queue->pos == queue->size) + { + queue->pos = 0; + queue->size = 0; + } +} + +static void FSynth_process(FSynth *self, ALuint SamplesToDo, ALfloat (*restrict DryBuffer)[BUFFERSIZE]) +{ + MidiSynth *synth = STATIC_CAST(MidiSynth, self); + ALuint total = 0; + + if(synth->State != AL_PLAYING) + { + if(synth->State == AL_PAUSED) + fluid_synth_write_float(self->Synth, SamplesToDo, DryBuffer[FrontLeft], 0, 1, + DryBuffer[FrontRight], 0, 1); + return; + } + + while(total < SamplesToDo) + { + if(synth->SamplesToNext >= 1.0) + { + ALuint todo = minu(SamplesToDo - total, fastf2u(synth->SamplesToNext)); + + fluid_synth_write_float(self->Synth, todo, + &DryBuffer[FrontLeft][total], 0, 1, + &DryBuffer[FrontRight][total], 0, 1); + total += todo; + synth->SamplesSinceLast += todo; + synth->SamplesToNext -= todo; + } + else + { + ALuint64 time = synth->NextEvtTime; + if(time == UINT64_MAX) + { + synth->SamplesSinceLast += SamplesToDo-total; + fluid_synth_write_float(self->Synth, SamplesToDo-total, + &DryBuffer[FrontLeft][total], 0, 1, + &DryBuffer[FrontRight][total], 0, 1); + break; + } + + synth->SamplesSinceLast -= (time - synth->LastEvtTime) * synth->SamplesPerTick; + synth->SamplesSinceLast = maxd(synth->SamplesSinceLast, 0.0); + synth->LastEvtTime = time; + FSynth_processQueue(self, time); + + synth->NextEvtTime = MidiSynth_getNextEvtTime(synth); + if(synth->NextEvtTime != UINT64_MAX) + synth->SamplesToNext += (synth->NextEvtTime - synth->LastEvtTime) * synth->SamplesPerTick; + } + } +} + + +static void FSynth_Delete(FSynth *self) +{ + free(self); +} + + +#endif /* HAVE_FLUIDSYNTH */ + + typedef struct DSynth { DERIVE_FROM_TYPE(MidiSynth); } DSynth; @@ -184,15 +400,31 @@ static void DSynth_Delete(DSynth *self) MidiSynth *SynthCreate(ALCdevice *device) { - DSynth *synth = calloc(1, sizeof(*synth)); - if(!synth) + FSynth *fsynth; + DSynth *dsynth; + + fsynth = calloc(1, sizeof(*fsynth)); + if(!fsynth) + ERR("Failed to allocate FSynth\n"); + else { + FSynth_Construct(fsynth, device); + if(FSynth_init(fsynth, device)) + return STATIC_CAST(MidiSynth, fsynth); + DELETE_OBJ(STATIC_CAST(MidiSynth, fsynth)); + fsynth = NULL; + } + + dsynth = calloc(1, sizeof(*dsynth)); + if(!dsynth) ERR("Failed to allocate DSynth\n"); - return NULL; + else + { + DSynth_Construct(dsynth, device); + return STATIC_CAST(MidiSynth, dsynth); } - DSynth_Construct(synth, device); - return STATIC_CAST(MidiSynth, synth); + return NULL; } diff --git a/cmake/FindFluidSynth.cmake b/cmake/FindFluidSynth.cmake new file mode 100644 index 00000000..7d5cb6a8 --- /dev/null +++ b/cmake/FindFluidSynth.cmake @@ -0,0 +1,23 @@ +# - Find fluidsynth +# Find the native fluidsynth includes and library +# +# FLUIDSYNTH_INCLUDE_DIR - where to find fluidsynth.h +# FLUIDSYNTH_LIBRARIES - List of libraries when using fluidsynth. +# FLUIDSYNTH_FOUND - True if fluidsynth found. + + +IF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + # Already in cache, be silent + SET(FluidSynth_FIND_QUIETLY TRUE) +ENDIF (FLUIDSYNTH_INCLUDE_DIR AND FLUIDSYNTH_LIBRARIES) + +FIND_PATH(FLUIDSYNTH_INCLUDE_DIR fluidsynth.h) + +FIND_LIBRARY(FLUIDSYNTH_LIBRARIES NAMES fluidsynth ) +MARK_AS_ADVANCED( FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR ) + +# handle the QUIETLY and REQUIRED arguments and set FLUIDSYNTH_FOUND to TRUE if +# all listed variables are TRUE +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FluidSynth DEFAULT_MSG FLUIDSYNTH_LIBRARIES FLUIDSYNTH_INCLUDE_DIR) + diff --git a/config.h.in b/config.h.in index e7a73fd8..17c69f3b 100644 --- a/config.h.in +++ b/config.h.in @@ -29,6 +29,9 @@ /* Define if we have ARM Neon CPU extensions */ #cmakedefine HAVE_NEON +/* Define if we have FluidSynth support */ +#cmakedefine HAVE_FLUIDSYNTH + /* Define if we have the ALSA backend */ #cmakedefine HAVE_ALSA |