From a6348f1425ebe74f33c655e8e149c77689f0cfe5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Mon, 30 Aug 2010 04:47:31 -0700 Subject: [PATCH] Add support for MIDI using FluidSynth A soundfont is selected by setting the FLUID_SOUNDFONT environment variable to the location of an sf2 soundfont. --- CMakeLists.txt | 43 +++++- config.h.in | 7 +- include/main.h | 25 ++++ src/alure.cpp | 52 +++++++ src/streamdec.cpp | 439 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 554 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcf73ad..c3700c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -144,12 +144,13 @@ IF(HAVE_DLFCN_H) ENDIF(HAVE_LIBDL) ENDIF(HAVE_DLFCN_H) -OPTION(DYNLOAD "Dynamically load support libs at run-time" ON) -OPTION(SNDFILE "SoundFile support (for various formats)" ON) -OPTION(VORBIS "VorbisFile support (for Ogg Vorbis)" ON) -OPTION(FLAC "FLAC support (for FLAC and Ogg FLAC)" ON) -OPTION(MPG123 "MPG123 support (for MP1/MP2/MP3)" ON) -OPTION(DUMB "DUMB support (for IT/XM/S3M/MOD)" ON) +OPTION(DYNLOAD "Dynamically load support libs at run-time" ON) +OPTION(SNDFILE "SoundFile support (for various formats)" ON) +OPTION(VORBIS "VorbisFile support (for Ogg Vorbis)" ON) +OPTION(FLAC "FLAC support (for FLAC and Ogg FLAC)" ON) +OPTION(MPG123 "MPG123 support (for MP1/MP2/MP3)" ON) +OPTION(DUMB "DUMB support (for IT/XM/S3M/MOD)" ON) +OPTION(FLUIDSYNTH "FluidSynth support (for MID)" ON) IF(WIN32) ADD_DEFINITIONS(-D_WIN32) @@ -332,9 +333,32 @@ ELSE(DUMB) SET(DUMB_LIBRARIES "") ENDIF(DUMB) +# FluidSynth support +IF(FLUIDSYNTH) + PKG_CHECK_MODULES(FLUIDSYNTH fluidsynth>=1.1.1) + IF(NOT FLUIDSYNTH_FOUND) + CHECK_INCLUDE_FILE(fluidsynth.h HAVE_FLUIDSYNTH_H) + IF(HAVE_FLUIDSYNTH_H) + CHECK_SHARED_LIBRARY_EXISTS(fluidsynth new_fluid_synth "" HAVE_LIBFLUIDSYNTH) + IF(DYNLOAD OR HAVE_LIBFLUIDSYNTH) + SET(HAS_FLUIDSYNTH 1) + IF(HAVE_LIBFLUIDSYNTH) + SET(FLUIDSYNTH_LIBRARIES "fluidsynth") + ENDIF(HAVE_LIBFLUIDSYNTH) + ENDIF(DYNLOAD OR HAVE_LIBFLUIDSYNTH) + ENDIF(HAVE_FLUIDSYNTH_H) + ELSE(NOT FLUIDSYNTH_FOUND) + SET(HAS_FLUIDSYNTH 1) + INCLUDE_DIRECTORIES(${FLUIDSYNTH_INCLUDE_DIRS}) + LINK_DIRECTORIES(${FLUIDSYNTH_LIBRARY_DIRS}) + ENDIF(NOT FLUIDSYNTH_FOUND) +ELSE(FLUIDSYNTH) + SET(FLUIDSYNTH_LIBRARIES "") +ENDIF(FLUIDSYNTH) + IF(NOT DYNLOAD) - SET(EXTRA_LIBS ${SNDFILE_LIBRARIES} ${VORBISFILE_LIBRARIES} ${LIBFLAC_LIBRARIES} ${MPG123_LIBRARIES} ${DUMB_LIBRARIES} ${EXTRA_LIBS}) + SET(EXTRA_LIBS ${SNDFILE_LIBRARIES} ${VORBISFILE_LIBRARIES} ${LIBFLAC_LIBRARIES} ${MPG123_LIBRARIES} ${DUMB_LIBRARIES} ${FLUIDSYNTH_LIBRARIES} ${EXTRA_LIBS}) ELSE(NOT DYNLOAD) ADD_DEFINITIONS(-DDYNLOAD=1) ENDIF(NOT DYNLOAD) @@ -472,4 +496,9 @@ IF(HAS_DUMB) ELSE(HAS_DUMB) MESSAGE(STATUS "DUMB support: disabled") ENDIF(HAS_DUMB) +IF(HAS_FLUIDSYNTH) + MESSAGE(STATUS "FluidSynth support: enabled") +ELSE(HAS_FLUIDSYNTH) + MESSAGE(STATUS "FluidSynth support: disabled") +ENDIF(HAS_FLUIDSYNTH) MESSAGE(STATUS "") diff --git a/config.h.in b/config.h.in index 1db1466..139af0a 100644 --- a/config.h.in +++ b/config.h.in @@ -47,8 +47,5 @@ /* Define if we have DUMB */ #cmakedefine HAS_DUMB -/* Define if we have external Timidity */ -#cmakedefine HAS_TIMIDITY - -/* Define if we have GStreamer */ -#cmakedefine HAS_GSTREAMER +/* Define if we have FluidSynth */ +#cmakedefine HAS_FLUIDSYNTH diff --git a/include/main.h b/include/main.h index c03210c..c4c1f08 100644 --- a/include/main.h +++ b/include/main.h @@ -19,6 +19,9 @@ #ifdef HAS_DUMB #include #endif +#ifdef HAS_DUMB +#include +#endif #ifdef HAVE_SYS_TYPES_H @@ -108,6 +111,7 @@ extern void *flac_handle; extern void *dumb_handle; extern void *mp123_handle; extern void *sndfile_handle; +extern void *fsynth_handle; #define MAKE_FUNC(x) extern typeof(x)* p##x #ifdef HAS_VORBISFILE @@ -162,6 +166,27 @@ MAKE_FUNC(sf_open_virtual); MAKE_FUNC(sf_readf_short); MAKE_FUNC(sf_seek); #endif +#ifdef HAS_FLUIDSYNTH +MAKE_FUNC(fluid_settings_setstr); +MAKE_FUNC(fluid_synth_program_change); +MAKE_FUNC(fluid_synth_sfload); +MAKE_FUNC(fluid_settings_setnum); +MAKE_FUNC(fluid_synth_sysex); +MAKE_FUNC(fluid_synth_cc); +MAKE_FUNC(fluid_synth_pitch_bend); +MAKE_FUNC(fluid_synth_channel_pressure); +MAKE_FUNC(fluid_synth_write_float); +MAKE_FUNC(new_fluid_synth); +MAKE_FUNC(delete_fluid_settings); +MAKE_FUNC(delete_fluid_synth); +MAKE_FUNC(fluid_synth_program_reset); +MAKE_FUNC(fluid_settings_setint); +MAKE_FUNC(new_fluid_settings); +MAKE_FUNC(fluid_synth_write_s16); +MAKE_FUNC(fluid_synth_noteoff); +MAKE_FUNC(fluid_synth_sfunload); +MAKE_FUNC(fluid_synth_noteon); +#endif #undef MAKE_FUNC void SetError(const char *err); diff --git a/src/alure.cpp b/src/alure.cpp index fa0650d..606139b 100644 --- a/src/alure.cpp +++ b/src/alure.cpp @@ -48,6 +48,7 @@ void *flac_handle = NULL; void *dumb_handle = NULL; void *mp123_handle = NULL; void *sndfile_handle = NULL; +void *fsynth_handle = NULL; #define MAKE_FUNC(x) typeof(x)* p##x #ifdef HAS_VORBISFILE @@ -102,6 +103,27 @@ MAKE_FUNC(sf_open_virtual); MAKE_FUNC(sf_readf_short); MAKE_FUNC(sf_seek); #endif +#ifdef HAS_FLUIDSYNTH +MAKE_FUNC(fluid_settings_setstr); +MAKE_FUNC(fluid_synth_program_change); +MAKE_FUNC(fluid_synth_sfload); +MAKE_FUNC(fluid_settings_setnum); +MAKE_FUNC(fluid_synth_sysex); +MAKE_FUNC(fluid_synth_cc); +MAKE_FUNC(fluid_synth_pitch_bend); +MAKE_FUNC(fluid_synth_channel_pressure); +MAKE_FUNC(fluid_synth_write_float); +MAKE_FUNC(new_fluid_synth); +MAKE_FUNC(delete_fluid_settings); +MAKE_FUNC(delete_fluid_synth); +MAKE_FUNC(fluid_synth_program_reset); +MAKE_FUNC(fluid_settings_setint); +MAKE_FUNC(new_fluid_settings); +MAKE_FUNC(fluid_synth_write_s16); +MAKE_FUNC(fluid_synth_noteoff); +MAKE_FUNC(fluid_synth_sfunload); +MAKE_FUNC(fluid_synth_noteon); +#endif #undef MAKE_FUNC #if defined(_WIN32) && !defined(ALURE_STATIC_LIBRARY) @@ -222,18 +244,21 @@ if(!p##x) \ #define DUMB_LIB "libdumb.dll" #define MPG123_LIB "libmpg123.dll" #define SNDFILE_LIB "libsndfile-1.dll" +#define FLUIDSYNTH_LIB "libfluidsynth.dll" #elif defined(__APPLE__) #define VORBISFILE_LIB "libvorbisfile.3.dylib" #define FLAC_LIB "libFLAC.8.dylib" #define DUMB_LIB "libdumb.dylib" #define MPG123_LIB "libmpg123.0.dylib" #define SNDFILE_LIB "libsndfile.1.dylib" +#define FLUIDSYNTH_LIB "libfluidsynth.1.dylib" #else #define VORBISFILE_LIB "libvorbisfile.so.3" #define FLAC_LIB "libFLAC.so.8" #define DUMB_LIB "libdumb.so" #define MPG123_LIB "libmpg123.so.0" #define SNDFILE_LIB "libsndfile.so.1" +#define FLUIDSYNTH_LIB "libfluidsynth.so.1" #endif #ifdef HAS_VORBISFILE @@ -319,6 +344,33 @@ if(!p##x) \ } #endif +#ifdef HAS_FLUIDSYNTH + fsynth_handle = LoadLibraryA(FLUIDSYNTH_LIB); + while(fsynth_handle) + { + LOAD_FUNC(fsynth, fluid_settings_setstr); + LOAD_FUNC(fsynth, fluid_synth_program_change); + LOAD_FUNC(fsynth, fluid_synth_sfload); + LOAD_FUNC(fsynth, fluid_settings_setnum); + LOAD_FUNC(fsynth, fluid_synth_sysex); + LOAD_FUNC(fsynth, fluid_synth_cc); + LOAD_FUNC(fsynth, fluid_synth_pitch_bend); + LOAD_FUNC(fsynth, fluid_synth_channel_pressure); + LOAD_FUNC(fsynth, fluid_synth_write_float); + LOAD_FUNC(fsynth, new_fluid_synth); + LOAD_FUNC(fsynth, delete_fluid_settings); + LOAD_FUNC(fsynth, delete_fluid_synth); + LOAD_FUNC(fsynth, fluid_synth_program_reset); + LOAD_FUNC(fsynth, fluid_settings_setint); + LOAD_FUNC(fsynth, new_fluid_settings); + LOAD_FUNC(fsynth, fluid_synth_write_s16); + LOAD_FUNC(fsynth, fluid_synth_noteoff); + LOAD_FUNC(fsynth, fluid_synth_sfunload); + LOAD_FUNC(fsynth, fluid_synth_noteon); + break; + } +#endif + #undef VORBISFILE_LIB #undef FLAC_LIB #undef DUMB_LIB diff --git a/src/streamdec.cpp b/src/streamdec.cpp index b95a4c1..c1b93bd 100644 --- a/src/streamdec.cpp +++ b/src/streamdec.cpp @@ -1343,6 +1343,437 @@ struct dumbStream : public nullStream { #endif +#ifdef HAS_FLUIDSYNTH +struct fluidStream : public alureStream { +private: + static const ALubyte MIDI_CHANNEL_MASK = 0x0F; + static const ALubyte MIDI_EVENT_MASK = 0xF0; + + static const ALubyte MIDI_NOTEOFF = 0x80; // + note + velocity + static const ALubyte MIDI_NOTEON = 0x90; // + note + velocity + static const ALubyte MIDI_POLYPRESS = 0xA0; // + pressure (2 bytes) + static const ALubyte MIDI_CTRLCHANGE = 0xB0; // + ctrl + value + static const ALubyte MIDI_PRGMCHANGE = 0xC0; // + new patch + static const ALubyte MIDI_CHANPRESS = 0xD0; // + pressure (1 byte) + static const ALubyte MIDI_PITCHBEND = 0xE0; // + pitch bend (2 bytes) + static const ALubyte MIDI_SPECIAL = 0xF0; // Special event + + static const ALubyte MIDI_SYSEX = 0xF0; // SysEx begin + static const ALubyte MIDI_SYSEXEND = 0xF7; // SysEx end + static const ALubyte MIDI_SONGPOS = 0xF2; // Song position + static const ALubyte MIDI_SONGSEL = 0xF3; // Song select + static const ALubyte MIDI_META = 0xFF; // Meta event begin + + static const ALubyte MIDI_META_EOT = 0x2F; // End-of-track + static const ALubyte MIDI_META_TEMPO = 0x51; // Tempo change + + struct MidiTrack { + std::vector data; + size_t Offset; + ALubyte LastEvent; + ALdouble SamplesLeft; + + MidiTrack() : Offset(0), LastEvent(0), SamplesLeft(0.) + { } + void Reset() + { + Offset = 0; + LastEvent = 0; + SamplesLeft = 0.; + } + + MidiTrack& operator=(const MidiTrack &rhs) + { + data = rhs.data; + Offset = rhs.Offset; + LastEvent = rhs.LastEvent; + SamplesLeft = rhs.SamplesLeft; + return *this; + } + + unsigned long ReadVarLen() + { + if(Offset >= data.size()) + return 0; + + unsigned long len = data[Offset]&0x7F; + while((data[Offset]&0x80)) + { + if(++Offset >= data.size()) + return 0; + len = (len<<7) | (data[Offset]&0x7F); + } + Offset++; + + return len; + } + }; + + ALuint Divisions; + std::vector Tracks; + + ALenum format; + ALsizei sampleRate; + ALdouble samplesPerTick; + + fluid_settings_t *fluidSettings; + fluid_synth_t *fluidSynth; + int fontID; + +public: + virtual bool IsValid() + { return fluidSynth != NULL; } + + virtual bool GetFormat(ALenum *fmt, ALuint *frequency, ALuint *blockalign) + { + if(format == AL_NONE) + { + format = GetSampleFormat(2, 32, true); + if(format == AL_NONE) + format = AL_FORMAT_STEREO16; + } + *fmt = format; + *frequency = sampleRate; + *blockalign = 2 * ((format==AL_FORMAT_STEREO16) ? sizeof(ALshort) : + sizeof(ALfloat)); + return true; + } + + virtual ALuint GetData(ALubyte *data, ALuint bytes) + { + ALuint ret; + + if(format == AL_FORMAT_STEREO16) + { + ALshort *ptr = reinterpret_cast(data); + ret = FillBuffer(ptr, bytes/2/sizeof(ALshort)); + ret *= 2 * sizeof(ALshort); + } + else + { + ALfloat *ptr = reinterpret_cast(data); + ret = FillBuffer(ptr, bytes/2/sizeof(ALfloat)); + ret *= 2 * sizeof(ALfloat); + } + + return ret; + } + + virtual bool Rewind() + { + for(std::vector::iterator i = Tracks.begin(), end = Tracks.end();i != end;i++) + { + i->Reset(); + unsigned long val = i->ReadVarLen(); + i->SamplesLeft += val * samplesPerTick; + } + pfluid_synth_program_reset(fluidSynth); + UpdateTempo(500000); + return true; + } + + fluidStream(std::istream *_fstream) + : alureStream(_fstream), Divisions(100), + format(AL_NONE), sampleRate(48000), samplesPerTick(1.), + fluidSettings(NULL), fluidSynth(NULL), fontID(FLUID_FAILED) + { + if(!fsynth_handle) return; + + ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); + if(device) + alcGetIntegerv(device, ALC_FREQUENCY, 1, &sampleRate); + + char hdr[4]; + if(!fstream->read(hdr, 4)) + return; + + if(memcmp(hdr, "MThd", 4) == 0) + { + ALuint len = read_be32(fstream); + if(len != 6) + return; + + int type = read_be16(fstream); + if(type != 0 && type != 1) + return; + + ALuint numtracks = read_be16(fstream); + + Divisions = read_be16(fstream); + UpdateTempo(500000); + + Tracks.resize(numtracks); + for(std::vector::iterator i = Tracks.begin(), end = Tracks.end();i != end;i++) + { + if(!fstream->read(hdr, 4) || memcmp(hdr, "MTrk", 4) != 0) + return; + + ALuint len = read_be32(fstream); + i->data.resize(len); + if(!fstream->read(reinterpret_cast(&i->data[0]), len)) + return; + + unsigned long val = i->ReadVarLen(); + i->SamplesLeft += val * samplesPerTick; + } + SetupSynth(); + } + } + + virtual ~fluidStream() + { + if(fontID != FLUID_FAILED) + pfluid_synth_sfunload(fluidSynth, fontID, true); + fontID = FLUID_FAILED; + + if(fluidSynth != NULL) + pdelete_fluid_synth(fluidSynth); + fluidSynth = NULL; + + if(fluidSettings != NULL) + pdelete_fluid_settings(fluidSettings); + fluidSettings = NULL; + } + +private: + template + ALuint FillBuffer(T *Buffer, ALuint BufferSamples) + { + ALuint SamplesInBuffer = 0; + while(SamplesInBuffer < BufferSamples) + { + // Check if any tracks are still playing and how many samples are waiting to render + size_t TracksPlaying = 0; + ALuint SamplesToDo = BufferSamples - SamplesInBuffer; + for(std::vector::iterator i = Tracks.begin(), + end = Tracks.end();i != end;i++) + { + if(i->Offset < i->data.size()) + { + SamplesToDo = std::min(SamplesToDo, i->SamplesLeft); + TracksPlaying++; + } + } + if(TracksPlaying == 0) + break; + + if(SamplesToDo == 0) + { + ProcessMidi(); + continue; + } + + // Render samples + WriteSamples(SamplesToDo, Buffer); + Buffer += SamplesToDo*2; + SamplesInBuffer += SamplesToDo; + + for(std::vector::iterator i = Tracks.begin(), + end = Tracks.end();i != end;i++) + { + if(i->Offset < i->data.size()) + i->SamplesLeft -= SamplesToDo; + } + } + + return SamplesInBuffer; + } + + void WriteSamples(ALuint count, short *buffer) + { pfluid_synth_write_s16(fluidSynth, count, buffer, 0, 2, buffer, 1, 2); } + void WriteSamples(ALuint count, float *buffer) + { pfluid_synth_write_float(fluidSynth, count, buffer, 0, 2, buffer, 1, 2); } + + void ProcessMidi() + { + ALuint newtempo = 0; + + // Process more events + std::vector::iterator i=Tracks.begin(), end=Tracks.end(); + while(i != end) + { + if(i->Offset >= i->data.size() || i->SamplesLeft >= 1.) + { + i++; + continue; + } + + if(i->data.size() - i->Offset < 3) + { + i->Offset = i->data.size(); + i++; + continue; + } + + ALubyte event = i->data[i->Offset++]; + ALubyte parm1, parm2; + if(!(event&0x80)) + { + event = i->LastEvent; + i->Offset--; + } + if((event&MIDI_EVENT_MASK) != MIDI_SPECIAL) + i->LastEvent = event; + parm1 = i->data[i->Offset]; + parm2 = i->data[i->Offset+1]; + + int channel = event&MIDI_CHANNEL_MASK; + switch(event&MIDI_EVENT_MASK) + { + case MIDI_NOTEOFF: + pfluid_synth_noteoff(fluidSynth, channel, parm1); + i->Offset += 2; + break; + case MIDI_NOTEON: + pfluid_synth_noteon(fluidSynth, channel, parm1, parm2); + i->Offset += 2; + break; + case MIDI_POLYPRESS: + i->Offset += 2; + break; + + case MIDI_CTRLCHANGE: + pfluid_synth_cc(fluidSynth, channel, parm1, parm2); + i->Offset += 2; + break; + case MIDI_PRGMCHANGE: + pfluid_synth_program_change(fluidSynth, channel, parm1); + i->Offset += 1; + break; + + case MIDI_CHANPRESS: + pfluid_synth_channel_pressure(fluidSynth, channel, parm1); + i->Offset += 1; + break; + + case MIDI_PITCHBEND: + pfluid_synth_pitch_bend(fluidSynth, channel, (parm1&0x7F) | ((parm2&0x7F)<<7)); + i->Offset += 2; + break; + + case MIDI_SPECIAL: + switch(event) + { + case MIDI_SYSEX: + case MIDI_SYSEXEND: + { + unsigned long len = i->ReadVarLen(); + + if(i->data.size() - i->Offset < len) + { + i->Offset = i->data.size(); + break; + } + + if(len > 1 && i->data[len-1] == MIDI_SYSEXEND) + { + char *data = reinterpret_cast(&i->data[i->Offset]); + pfluid_synth_sysex(fluidSynth, data, len-1, NULL, NULL, NULL, false); + } + i->Offset += len; + break; + } + + case MIDI_SONGPOS: + i->Offset += 2; + break; + + case MIDI_SONGSEL: + i->Offset += 1; + break; + + case MIDI_META: + { + ALubyte metatype = i->data[i->Offset++]; + unsigned long val = i->ReadVarLen(); + + if(i->data.size() - i->Offset < val) + { + i->Offset = i->data.size(); + break; + } + + if(metatype == MIDI_META_EOT) + { + i->Offset = i->data.size(); + break; + } + + if(metatype == MIDI_META_TEMPO && val >= 3) + { + newtempo = (i->data[i->Offset] << 16) | + (i->data[i->Offset+1] << 8) | + (i->data[i->Offset+2]); + } + + i->Offset += val; + break; + } + + default: + /* The rest of the special events don't have any + * data bytes */ + break; + } + break; + + default: + /* Shouldn't ever get to here */ + break; + } + + unsigned long val = i->ReadVarLen(); + i->SamplesLeft += val * samplesPerTick; + } + if(newtempo) + UpdateTempo(newtempo); + } + + void UpdateTempo(ALuint tempo) + { + ALdouble sampletickrate = sampleRate / (1000000. / tempo) / Divisions; + + for(std::vector::iterator i = Tracks.begin(), + end = Tracks.end();i != end;i++) + { + if(i->Offset >= i->data.size()) + continue; + i->SamplesLeft = i->SamplesLeft / samplesPerTick * sampletickrate; + } + samplesPerTick = sampletickrate; + } + + void SetupSynth() + { + fluidSettings = pnew_fluid_settings(); + if(fluidSettings) + { + pfluid_settings_setnum(fluidSettings, "synth.gain", 0.5); + pfluid_settings_setstr(fluidSettings, "synth.reverb.active", "yes"); + pfluid_settings_setstr(fluidSettings, "synth.chorus.active", "yes"); + pfluid_settings_setint(fluidSettings, "synth.polyphony", 256); + pfluid_settings_setnum(fluidSettings, "synth.sample-rate", (double)sampleRate); + + fluidSynth = pnew_fluid_synth(fluidSettings); + if(fluidSynth) + { + const char *soundfont = getenv("FLUID_SOUNDFONT"); + if((fontID=pfluid_synth_sfload(fluidSynth, soundfont, true)) == FLUID_FAILED) + { + pdelete_fluid_synth(fluidSynth); + fluidSynth = NULL; + } + } + } + } +}; +#else +struct fluidStream : public nullStream { + fluidStream(std::istream*){} +}; +#endif + + template alureStream *get_stream_decoder(const T &fdata) { @@ -1389,6 +1820,14 @@ alureStream *get_stream_decoder(const T &fdata) return stream; delete stream; + // Try FluidSynth + file->clear(); + file->seekg(0, std::ios_base::beg); + stream = new fluidStream(file); + if(stream->IsValid()) + return stream; + delete stream; + // Try DUMB file->clear(); file->seekg(0, std::ios_base::beg); -- 2.11.4.GIT