Index: base.tab =================================================================== --- base.tab (revision 9606) +++ base.tab (working copy) @@ -4225,3 +4225,15 @@ #obj=button_text #name=cl_btn_filter_enable #- +#obj=button_text +#name=Select soundfont +#- +#obj=program_text +#name=Soundfonts are located in the music directory. +#- +#obj=program_text +#name=Soundfont not found. Please select a soundfont below. +#- +#obj=error_text +#name=No soundfont found!\n\nMusic won't play until you load a soundfont from the sound options menu. +#- Index: trunk/Makefile =================================================================== --- trunk/Makefile (revision 9606) +++ trunk/Makefile (working copy) @@ -174,6 +174,25 @@ endif endif +ifdef USE_FLUIDSYNTH_MIDI + ifeq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + # windows can have fluidsynth with any backend + ifeq ($(OSTYPE),mingw) + SOURCES += music/fluidsynth.cc + SOURCES += gui/loadsoundfont_frame.cc + # fluidsynth.pc doesn't properly list dependant libraries, unable to use pkg-config. Manually listed below. Only valid for fluidsynth built with options: "-DBUILD_SHARED_LIBS=0 -Denable-aufile=0 -Denable-dbus=0 -Denable-ipv6=0 -Denable-jack=0 -Denable-ladspa=0 -Denable-midishare=0 -Denable-opensles=0 -Denable-oboe=0 -Denable-oss=0 -Denable-readline=0 -Denable-winmidi=0 -Denable-waveout=0 -Denable-libsndfile=0 -Denable-network=0 -Denable-pulseaudio=0 Denable-dsound=1 -Denable-sdl2=0" + LDFLAGS += -lfluidsynth -lglib-2.0 -lintl -liconv -ldsound -lole32 + CFLAGS += -DUSE_FLUIDSYNTH_MIDI + # but linux/mac will need sdl2 + else ifeq ($(BACKEND),sdl2) + SOURCES += music/fluidsynth.cc + SOURCES += gui/loadsoundfont_frame.cc + LDFLAGS += -lfluidsynth + CFLAGS += -DUSE_FLUIDSYNTH_MIDI + endif + endif +endif + ifdef PROFILE ifeq ($(shell expr $(PROFILE) \>= 1), 1) CFLAGS += -pg -DPROFILE @@ -224,6 +243,7 @@ endif endif + CFLAGS += -Wall -Wextra -Wcast-qual -Wpointer-arith -Wcast-align $(FLAGS) CCFLAGS += -ansi -Wstrict-prototypes -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 @@ -568,7 +588,9 @@ ifeq ($(BACKEND),allegro) SOURCES += sys/simsys_d.cc SOURCES += sound/allegro_sound.cc - SOURCES += music/allegro_midi.cc + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/allegro_midi.cc + endif ifeq ($(ALLEGRO_CONFIG),) ALLEGRO_CFLAGS := ALLEGRO_LDFLAGS := -lalleg @@ -582,9 +604,11 @@ ifeq ($(BACKEND),gdi) SOURCES += sys/simsys_w.cc - SOURCES += music/w32_midi.cc SOURCES += sound/win32_sound_xa.cc - LDFLAGS += -lxaudio2_8 + LDFLAGS += -lxaudio2_8 + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/w32_midi.cc + endif endif ifeq ($(BACKEND),sdl) @@ -636,20 +660,28 @@ ifeq ($(shell expr $(AV_FOUNDATION) \>= 1), 1) # Core Audio (AVFoundation) base sound system routines SOURCES += sound/AVF_core-audio_sound.mm - SOURCES += music/AVF_core-audio_midi.mm LIBS += -framework Foundation -framework AVFoundation + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/AVF_core-audio_midi.mm + endif else # Core Audio (Quicktime) base sound system routines SOURCES += sound/core-audio_sound.mm - SOURCES += music/core-audio_midi.mm LIBS += -framework Foundation -framework QTKit + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/core-audio_midi.mm + endif endif else SOURCES += sound/sdl2_sound.cc ifneq ($(OSTYPE),mingw) - SOURCES += music/no_midi.cc + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/no_midi.cc + endif else - SOURCES += music/w32_midi.cc + ifneq ($(shell expr $(USE_FLUIDSYNTH_MIDI) \>= 1), 1) + SOURCES += music/w32_midi.cc + endif endif endif Index: trunk/config.default.in =================================================================== --- trunk/config.default.in (revision 9606) +++ trunk/config.default.in (working copy) @@ -52,6 +52,9 @@ # using zstd compression USE_ZSTD = @zstd@ +# using FluidSynth for MIDI playback (SDL2 backend needed for Linux/MacOS, SDL2 or GDI for MinGW) +USE_FLUIDSYNTH_MIDI = @fluidsynth@ + # use static linking (to be at least somewhat portable) STATIC = 1 Index: trunk/config.template =================================================================== --- trunk/config.template (revision 9606) +++ trunk/config.template (working copy) @@ -42,7 +42,7 @@ #MULTI_THREAD = 1 # Enable multithreading # using freetype for Truetype font support -USE_FREETYPE = 0 +#USE_FREETYPE = 0 # using UPnP for easy server hosting behind routers #USE_UPNP = 0 @@ -50,6 +50,9 @@ # using zstd compression library #USE_ZSTD = 0 +# using FluidSynth for MIDI playback (SDL2 backend needed for Linux/MacOS, SDL2 or GDI for MinGW) +#USE_FLUIDSYNTH_MIDI = 1 + # Define these as empty strings, if you don't have the respective config program #ALLEGRO_CONFIG = allegro-config #PNG_CONFIG = pkg-config libpng @@ -60,7 +63,7 @@ #VERBOSE = 1 # Use static libraries instead -# STATIC = 1 +#STATIC = 1 # The following useful conditional compilation flags exist # Index: trunk/configure.ac =================================================================== --- trunk/configure.ac (revision 9606) +++ trunk/configure.ac (working copy) @@ -60,6 +60,11 @@ [AC_SUBST(freetype, 1)], [AC_SUBST(freetype, 0)], -lpng -lharfbuzz -lgraphite2 -lfreetype ) + # optional fluidsynth + AC_SEARCH_LIBS(new_fluid_settings, fluidsynth, + [AC_SUBST(fluidsynth, 1)], + [AC_SUBST(fluidsynth, 0)], + -lglib-2.0 -lintl -liconv -ldsound -lole32) else # optional upnp AC_SEARCH_LIBS(upnpDiscover, miniupnpc, @@ -70,6 +75,10 @@ [AC_SUBST(freetype, 1)], [AC_SUBST(freetype, 0)], -lpng ) + # optional fluidsynth + AC_SEARCH_LIBS(new_fluid_settings, fluidsynth, + [AC_SUBST(fluidsynth, 1)], + [AC_SUBST(fluidsynth, 0)] ) if uname | grep "Darwin" then AC_LANG_PUSH(Objective C++) Index: trunk/dataobj/environment.cc =================================================================== --- trunk/dataobj/environment.cc (revision 9606) +++ trunk/dataobj/environment.cc (working copy) @@ -72,6 +72,8 @@ uint32 env_t::sound_distance_scaling; sint16 env_t::midi_volume = 127; uint16 env_t::specific_volume[MAX_SOUND_TYPES]; + +std::string env_t::soundfont_filename = ""; bool env_t::global_mute_sound = false; #ifdef __APPLE__ bool env_t::mute_midi = true; @@ -536,6 +538,15 @@ if( file->is_version_atleast( 122, 1 ) ) { file->rdwr_bool( env_t::numpad_always_moves_map ); } + + if( file->is_version_atleast( 122, 2 ) ) { + plainstring str = soundfont_filename.c_str(); + file->rdwr_str( str ); + if( file->is_loading() ) { + soundfont_filename = str ? str.c_str() : ""; + } + } + // server settings are not saved, since they are server specific // and could be different on different servers on the same computers } Index: trunk/dataobj/environment.h =================================================================== --- trunk/dataobj/environment.h (revision 9606) +++ trunk/dataobj/environment.h (working copy) @@ -445,9 +445,11 @@ /// how dast are distant sounds fading (1: very fast 25: very little) static uint32 sound_distance_scaling; + // FluidSynth MIDI parameters + static std::string soundfont_filename; + /// @} - /// if true this will show a softkeyboard only when editing text /// default is off static bool hide_keyboard; Index: trunk/dataobj/settings.cc =================================================================== --- trunk/dataobj/settings.cc (revision 9606) +++ trunk/dataobj/settings.cc (working copy) @@ -1565,6 +1565,11 @@ // Default pak file path objfilename = ltrim(contents.get_string("pak_file_path", "" ) ); + // FluidSynth MIDI parameters + if( *contents.get("soundfont_filename") ) { + env_t::soundfont_filename = ltrim(contents.get("soundfont_filename")); + } + printf("Reading simuconf.tab successful!\n" ); } Index: trunk/gui/loadfont_frame.cc =================================================================== --- trunk/gui/loadfont_frame.cc (revision 9606) +++ trunk/gui/loadfont_frame.cc (working copy) @@ -101,7 +101,7 @@ /** * CHECK FILE - * Check if a file name qualifies to be added tot he item list. + * Check if a file name qualifies to be added to the item list. */ bool loadfont_frame_t::check_file(const char *filename, const char *) { Index: trunk/gui/loadsoundfont_frame.cc =================================================================== --- trunk/gui/loadsoundfont_frame.cc (nonexistent) +++ trunk/gui/loadsoundfont_frame.cc (working copy) @@ -0,0 +1,153 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#include "../simdebug.h" + +#include + +#include "loadsoundfont_frame.h" + +#include "../pathes.h" + +#include "../dataobj/loadsave.h" +#include "../dataobj/translator.h" +#include "../dataobj/environment.h" + +#include "../music/music.h" +#include "../simsound.h" + +#include "gui_theme.h" + +#include "../utils/simstring.h" + +// static, since we keep them over reloading +std::string loadsoundfont_frame_t::old_soundfontname; + +/** + * Action that's started with a button click + */ +bool loadsoundfont_frame_t::item_action(const char *filename) +{ + if( dr_load_sf( filename ) && midi_get_mute() ) { + midi_set_mute( false ); + midi_play( env_t::shuffle_midi ? -1 : 0 ); + } + return false; +} + + +bool loadsoundfont_frame_t::ok_action(const char *filename) +{ + item_action( filename ); + old_soundfontname.clear(); + return true; +} + + +bool loadsoundfont_frame_t::cancel_action(const char *) +{ + dr_load_sf( old_soundfontname.c_str() ); + old_soundfontname.clear(); + return true; +} + + +loadsoundfont_frame_t::loadsoundfont_frame_t() : savegame_frame_t(NULL,false,NULL,false) +{ + set_name( translator::translate("Select soundfont") ); + fnlabel.set_text("Soundfonts are located in the music directory."); + top_frame.remove_component( &input ); + delete_enabled = false; +} + + +const char *loadsoundfont_frame_t::get_info(const char *sfname) +{ + return sfname; +} + + +bool loadsoundfont_frame_t::compare_items ( const dir_entry_t & entry, const char *info, const char *) +{ + return (STRICMP(entry.info, info) > 0); +} + + +/** + * CHECK FILE + * Check if a file name qualifies to be added to the item list. + */ +bool loadsoundfont_frame_t::check_file(const char *filename, const char *) +{ + FILE *test = fopen( filename, "r" ); + if( test == NULL ) { + return false; + } + fclose( test ); + + // just match textension for buildin soundfonts + const char *start_extension = strrchr(filename, '.' ); + if( start_extension && !STRICMP( start_extension, ".sf2" ) ) { + return true; + } + return false; +} + + +// parses the directory +void loadsoundfont_frame_t::fill_list() +{ + add_path( ((std::string)env_t::data_dir + "music/").c_str() ); + add_path( "/usr/share/soundfonts/" ); + add_path( "/usr/share/sounds/sf2/" ); + + if( old_soundfontname.empty() ) { + old_soundfontname = env_t::soundfont_filename; + } + + // do the search ... + savegame_frame_t::fill_list(); + + // mark current fonts + FOR( slist_tpl, const& i, entries ) { + if( i.type == LI_HEADER ) { + continue; + } + i.button->set_typ( button_t::roundbox_state | button_t::flexible ); + } + + // force new resize after we have rearranged the gui + resize( scr_coord(0,0) ); +} + + +void loadsoundfont_frame_t::draw(scr_coord pos, scr_size size) +{ + // mark current fonts + FOR( slist_tpl, const& i, entries) { + if( i.type == LI_HEADER ) { + continue; + } + i.button->pressed = strstr( env_t::soundfont_filename.c_str(), i.info ); + } + savegame_frame_t::draw( pos, size ); +} + + +void loadsoundfont_frame_t::rdwr( loadsave_t *file ) +{ + scr_size size = get_windowsize(); + size.rdwr( file ); + if( file->is_loading() ) { + set_windowsize( size ); + resize( scr_coord(0,0) ); + } +} + + +bool loadsoundfont_frame_t::action_triggered(gui_action_creator_t *component, value_t v) +{ + return savegame_frame_t::action_triggered( component, v ); +} Index: trunk/gui/loadsoundfont_frame.h =================================================================== --- trunk/gui/loadsoundfont_frame.h (nonexistent) +++ trunk/gui/loadsoundfont_frame.h (working copy) @@ -0,0 +1,58 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ + +#ifndef GUI_LOADSOUNDFONT_FRAME_H +#define GUI_LOADSOUNDFONT_FRAME_H + +#include "simwin.h" +#include "savegame_frame.h" + +#include "components/action_listener.h" +#include "components/gui_button.h" + +#include "../tpl/stringhashtable_tpl.h" +#include + + +class loadsoundfont_frame_t : public savegame_frame_t +{ +protected: + static std::string old_soundfontname; + + /** + * Action that's started with a button click + */ + bool item_action (const char *filename) OVERRIDE; + bool ok_action (const char *fullpath) OVERRIDE; + bool cancel_action(const char *) OVERRIDE; + + // returns extra file info + const char *get_info(const char *fname) OVERRIDE; + + // sort with respect to info, which is date + bool compare_items ( const dir_entry_t & entry, const char *info, const char *) OVERRIDE; + + bool check_file( const char *filename, const char *suffix ) OVERRIDE; + + void fill_list() OVERRIDE; + +public: + /** + * Set the window associated helptext + * @return the filename for the helptext, or NULL + */ + const char *get_help_filename() const OVERRIDE { return "load_soundfont.txt"; } + + loadsoundfont_frame_t(); + + void draw(scr_coord pos, scr_size size) OVERRIDE; + + uint32 get_rdwr_id( void ) OVERRIDE { return magic_soundfont; } + void rdwr( loadsave_t *file ) OVERRIDE; + + bool action_triggered(gui_action_creator_t *, value_t v) OVERRIDE; +}; + +#endif Index: trunk/gui/simwin.cc =================================================================== --- trunk/gui/simwin.cc (revision 9606) +++ trunk/gui/simwin.cc (working copy) @@ -58,6 +58,9 @@ #include "themeselector.h" #include "goods_frame_t.h" #include "loadfont_frame.h" +#ifdef USE_FLUIDSYNTH_MIDI +#include "loadsoundfont_frame.h" +#endif #include "scenario_info.h" #include "depot_frame.h" #include "depotlist_frame.h" @@ -582,6 +585,9 @@ case magic_factory_info: w = new fabrik_info_t(); break; case magic_goodslist: w = new goods_frame_t(); break; case magic_font: w = new loadfont_frame_t(); break; +#ifdef USE_FLUIDSYNTH_MIDI + case magic_soundfont: w = new loadsoundfont_frame_t(); break; +#endif case magic_scenario_info: w = new scenario_info_t(); break; case magic_depot: w = new depot_frame_t(); break; case magic_convoi_list: w = new convoi_frame_t(); break; Index: trunk/gui/simwin.h =================================================================== --- trunk/gui/simwin.h (revision 9606) +++ trunk/gui/simwin.h (working copy) @@ -104,6 +104,7 @@ magic_motd, magic_factory_info, // only used to load/save magic_font, + magic_soundfont, // only with USE_FLUIDSYNTH_MIDI magic_edit_groundobj, // magic numbers with big jumps between them Index: trunk/gui/sound_frame.cc =================================================================== --- trunk/gui/sound_frame.cc (revision 9606) +++ trunk/gui/sound_frame.cc (working copy) @@ -11,6 +11,9 @@ #include "../dataobj/translator.h" #include "../dataobj/environment.h" #include "components/gui_divider.h" +#ifdef USE_FLUIDSYNTH_MIDI +#include "loadsoundfont_frame.h" +#endif #define L_KNOB_SIZE (32) @@ -17,15 +20,24 @@ void sound_frame_t::update_song_name() { const int current_midi = get_current_midi(); - - if(current_midi >= 0) { - song_name_label.buf().printf("%d - %s", current_midi+1, sound_get_midi_title(current_midi)); + if( current_midi < 0 ) { + song_name_label.buf().printf( translator::translate("Music playing disabled/not available") ); } +#ifdef USE_FLUIDSYNTH_MIDI + else if( strcmp( env_t::soundfont_filename.c_str(), "Error") == 0 ){ + song_name_label.buf().printf( translator::translate("Soundfont not found. Please select a soundfont below.") ); + } +#endif else { - song_name_label.buf().printf("Music playing disabled/not available" ); + song_name_label.buf().printf("%d - %s", current_midi + 1, sound_get_midi_title( current_midi ) ); } song_name_label.update(); song_name_label.set_size( song_name_label.get_min_size() ); + + // Loadsoundfont dialog may unmute us, update mute status + music_mute_button.pressed = midi_get_mute(); + previous_song_button.enable( !music_mute_button.pressed ); + next_song_button.enable( !music_mute_button.pressed ); } @@ -89,6 +101,11 @@ music_mute_button.init( button_t::square_state, "disable midi"); music_mute_button.pressed = midi_get_mute(); music_mute_button.add_listener( this ); +#ifdef USE_FLUIDSYNTH_MIDI + if( strcmp( env_t::soundfont_filename.c_str(), "Error" ) == 0 ){ + music_mute_button.enable( !music_mute_button.pressed ); + } +#endif add_component(&music_mute_button); add_table(2,0); @@ -110,10 +127,12 @@ { previous_song_button.set_typ( button_t::arrowleft ); previous_song_button.add_listener( this ); + previous_song_button.enable( !music_mute_button.pressed ); add_component( &previous_song_button ); next_song_button.set_typ( button_t::arrowright ); next_song_button.add_listener( this ); + next_song_button.enable( !music_mute_button.pressed ); add_component( &next_song_button ); add_component( &song_name_label ); @@ -126,6 +145,13 @@ shuffle_song_button.add_listener(this); add_component(&shuffle_song_button); +#ifdef USE_FLUIDSYNTH_MIDI + // Soundfont selection + soundfont_button.init( button_t::roundbox_state | button_t::flexible, "Select soundfont" ); + soundfont_button.add_listener(this); + add_component( &soundfont_button ); +#endif + set_resizemode(diagonal_resize); reset_min_windowsize(); } @@ -156,7 +182,8 @@ else if (comp == &music_mute_button) { midi_set_mute( !music_mute_button.pressed ); music_mute_button.pressed = midi_get_mute(); - previous_song_button.enable(!music_mute_button.pressed); + previous_song_button.enable( !music_mute_button.pressed ); + next_song_button.enable( !music_mute_button.pressed ); } else if (comp == &sound_volume_scrollbar) { sound_set_global_volume(p.i); @@ -167,6 +194,11 @@ else if (comp == &sound_range) { env_t::sound_distance_scaling = p.i; } +#ifdef USE_FLUIDSYNTH_MIDI + else if( comp == &soundfont_button ) { + create_win( new loadsoundfont_frame_t(), w_info, magic_soundfont ); + } +#endif else { for( int i = 0; i < MAX_SOUND_TYPES; i++ ) { if( comp == specific_volume_scrollbar[ i ] ) { @@ -187,11 +219,15 @@ { // update song name label update_song_name(); +#ifdef USE_FLUIDSYNTH_MIDI + if( !strcmp(env_t::soundfont_filename.c_str(), "Error") == 0 ){ + music_mute_button.enable( true ); + } +#endif gui_frame_t::draw(pos, size); } - // need to delete scroll bars sound_frame_t::~sound_frame_t() { Index: trunk/gui/sound_frame.h =================================================================== --- trunk/gui/sound_frame.h (revision 9606) +++ trunk/gui/sound_frame.h (working copy) @@ -30,6 +30,9 @@ button_t next_song_button; button_t previous_song_button; button_t shuffle_song_button; +#ifdef USE_FLUIDSYNTH_MIDI + button_t soundfont_button; +#endif gui_label_buf_t song_name_label; void update_song_name(); Index: trunk/music/fluidsynth.cc =================================================================== --- trunk/music/fluidsynth.cc (nonexistent) +++ trunk/music/fluidsynth.cc (working copy) @@ -0,0 +1,220 @@ +/* + * This file is part of the Simutrans project under the Artistic License. + * (see LICENSE.txt) + */ +#include + +#include "../simdebug.h" +#include "../utils/plainstring.h" +#include "../dataobj/environment.h" +#include "music.h" +#ifndef _WIN32 + #include +#endif + +// fluidsynth music routine interfaces +static int midi_number = -1; +static plainstring midi_filenames[MAX_MIDI]; + +fluid_settings_t* settings; +fluid_synth_t* synth; +fluid_audio_driver_t* adriver; +fluid_player_t* player; + +// Predefined list of paths to search for soundfonts +static const char * default_sf[] = { + /* RedHat/Fedora/Arch preferred */ + "/usr/share/soundfonts/sf2/default.sf2", + "/usr/share/soundfonts/sf2/freepats-general-midi.sf2", + "/usr/share/soundfonts/FluidR3_GM.sf2", + + /* Debian/Ubuntu/OpenSUSE preferred */ + "/usr/share/sounds/sf2/default.sf2", + "/usr/share/sounds/sf2/FluidR3_GM.sf2", + + /* Debian/Ubuntu/OpenSUSE alternatives */ + "/usr/share/sounds/sf2/TimGM6mb.sf2", + "/usr/share/sounds/sf2/FluidR3_GS.sf2", + + nullptr +}; + + +/** + * sets midi playback volume + */ +void dr_set_midi_volume(int vol) +{ + /* Allowed range of synth.gain is 0.0 to 10.0 */ + /* fluidsynth's default gain is 0.2, to avoid possible clipping. + * Set gain using Simutrans's volume, as a number between 0 + * and 0.8 to balance with sound effects. + */ + if( fluid_settings_setnum( settings, "synth.gain", 0.8 * vol / 255.0 ) != FLUID_OK ) { + dbg->warning("dr_set_midi_volume()", "FluidSynth: Could not set volume."); + } +} + + +/** + * Loads a MIDI file + */ +int dr_load_midi(const char *filename) +{ + if( midi_number < MAX_MIDI - 1 ) { + const int i = midi_number + 1; + + if( i >= 0 && i < MAX_MIDI && fluid_is_midifile( filename ) ) { + midi_number = i; + midi_filenames[i] = filename; + } + else { + dbg->warning("dr_load_midi()", "FluidSynth: Failed to load MIDI %s.", filename ); + } + } + return midi_number; +} + + +/** + * Plays a MIDI file + */ +void dr_play_midi(int key) +{ + if( dr_midi_pos() != -1 ) { + dr_stop_midi(); + } + if( !(player = new_fluid_player( synth )) ) { + dbg->warning("dr_play_midi()", "FluidSynth: MIDI player setup failed."); + return; + } + if( fluid_player_add( player, midi_filenames[key] ) != FLUID_OK ) { + dbg->warning("dr_play_midi()", "FluidSynth: %s MIDI file load failed.", midi_filenames[key].c_str() ); + return; + } + if( fluid_player_play( player ) != FLUID_OK ) { + dbg->warning("dr_play_midi()", "FluidSynth: MIDI player start failed."); + return; + } +} + + +/** + * Stops playing MIDI file + */ +void dr_stop_midi(void) +{ + if( !player ) { + return; + } + + fluid_player_stop( player ); + if( fluid_player_join( player ) != FLUID_OK ) { + dbg->warning("dr_stop_midi()", "FluidSynth: Player join failed."); + } + fluid_synth_all_notes_off( synth, -1 ); + delete_fluid_player( player ); + player = NULL; +} + + +/** + * Returns the midi_pos variable <- doesn't actually do this + * Simutrans only needs to know whether file has finished (so that it can start the next music) + * Returns -1 if current music has finished, else 0 + */ +sint32 dr_midi_pos(void) +{ + if( !player || fluid_player_get_status( player ) != FLUID_PLAYER_PLAYING ) { + return -1; + } + return 0; +} + + +/** + * Midi shutdown/cleanup + */ +void dr_destroy_midi(void) +{ + dr_stop_midi(); + delete_fluid_audio_driver(adriver); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + midi_number = -1; +} + + +/** + * MIDI initialisation routines + */ +bool dr_load_sf(const char * filename){ + static int previous_id = -1; + + if( synth && fluid_is_soundfont( filename ) ) { + int next_id = fluid_synth_sfload( synth, filename, 1 ); + if( next_id != FLUID_FAILED ) { + if( previous_id != -1 ) { + fluid_synth_sfunload( synth, previous_id, 1 ); + } + previous_id = next_id; + env_t::soundfont_filename = filename; + return true; + } + } + return false; +} + + +bool dr_init_midi() +{ + if( !(settings = new_fluid_settings()) ) { + dbg->warning("dr_init_midi()", "FluidSynth: MIDI settings failed."); + return false; + } + fluid_settings_setint( settings, "synth.cpu-cores", env_t::num_threads ); + fluid_settings_setstr( settings, "synth.midi-bank-select", "gm" ); + +#ifdef _WIN32 + std::string fluidsynth_driver = "dsound"; +#else + std::string fluidsynth_driver = "sdl2"; + + if( !SDL_WasInit(SDL_INIT_AUDIO) ) { + if( SDL_InitSubSystem( SDL_INIT_AUDIO ) != 0 ) { + dbg->warning("dr_init_midi()", "FluidSynth: SDL_INIT_AUDIO failed."); + return false; + } + } +#endif + + if( fluid_settings_setstr( settings, "audio.driver", fluidsynth_driver.c_str() ) != FLUID_OK ) { + dbg->warning("dr_init_midi()", "FluidSynth: Set MIDI driver %s failed.", fluidsynth_driver.c_str()); + return false; + } + + if( !(synth = new_fluid_synth( settings )) ) { + dbg->warning("dr_init_midi()", "FluidSynth: Synth setup failed."); + return false; + } + + if( !(adriver = new_fluid_audio_driver( settings, synth )) ) { + dbg->warning("dr_init_midi()", "FluidSynth: Audio driver setup failed."); + return false; + } + + // User defined font first + if( dr_load_sf( env_t::soundfont_filename.c_str() ) || dr_load_sf( ((std::string)env_t::data_dir + "music/" + env_t::soundfont_filename).c_str() ) ) { + return true; + } + // Then predefined list of soundfonts + for( int i = 0; default_sf[i]; i++ ) { + if( dr_load_sf( default_sf[i] ) ) { + return true; + } + } + + env_t::soundfont_filename = "Error"; + dbg->warning("dr_init_midi()", "FluidSynth: No soundfont was found."); + return true; // MIDI system has been initialed even if no soundfont was loaded. A user can load a soundfont after. +} Index: trunk/music/music.h =================================================================== --- trunk/music/music.h (revision 9606) +++ trunk/music/music.h (working copy) @@ -55,4 +55,12 @@ */ void dr_destroy_midi(); + +/** + * Load a soundfont. Only available if we are using FluidSynth. + */ +#ifdef USE_FLUIDSYNTH_MIDI +bool dr_load_sf(const char * filename); #endif + +#endif Index: trunk/simmain.cc =================================================================== --- trunk/simmain.cc (revision 9606) +++ trunk/simmain.cc (working copy) @@ -22,6 +22,7 @@ #include "display/simview.h" #include "gui/simwin.h" #include "gui/gui_theme.h" +#include "gui/messagebox.h" #include "simhalt.h" #include "display/simimg.h" #include "simcolor.h" @@ -696,9 +697,14 @@ if(simuconf.open(path_to_simuconf)) { // we do not allow to change the global font name std::string old_fontname = env_t::fontname; + std::string old_soundfont_filename = env_t::soundfont_filename; printf("parse_simuconf() at config/simuconf.tab: "); env_t::default_settings.parse_simuconf( simuconf, disp_width, disp_height, fullscreen, env_t::objfilename ); simuconf.close(); + if( (old_soundfont_filename.length() > 0) && (strcmp( old_soundfont_filename.c_str(), "Error" ) != 0) ) { + // We had a valid soundfont saved by the user, let's restore it + env_t::soundfont_filename = old_soundfont_filename; + } env_t::fontname = old_fontname; } } @@ -1297,16 +1303,22 @@ dbg->message("simu_main()","Reading midi data ..."); char pak_dir[PATH_MAX]; sprintf( pak_dir, "%s%s", env_t::data_dir, env_t::objfilename.c_str() ); - if( !midi_init(pak_dir) ) { - if( !midi_init(env_t::user_dir) ) { - if( !midi_init(env_t::data_dir) ) { - dbg->message("simu_main()","Midi disabled ..."); - } - } + if( midi_init( pak_dir ) || midi_init( env_t::user_dir ) || midi_init( env_t::data_dir ) ) { + midi_set_mute( false ); } + else { + midi_set_mute( true ); + dbg->message("simu_main()","Midi disabled ..."); + } if(gimme_arg(argc, argv, "-nomidi", 0)) { midi_set_mute(true); } +#ifdef USE_FLUIDSYNTH_MIDI + // Audio is ok, but we failed to find a soundfont + if( strcmp( env_t::soundfont_filename.c_str(), "Error" ) == 0 ) { + midi_set_mute( true ); + } +#endif } else { dbg->message("simu_main()","Midi disabled ..."); @@ -1484,6 +1496,11 @@ if( !env_t::networkmode && !env_t::server && new_world ) { welt->get_message()->clear(); } +#ifdef USE_FLUIDSYNTH_MIDI + if( strcmp( env_t::soundfont_filename.c_str(), "Error" ) == 0 ) { + create_win( 0,0, new news_img("No soundfont found!\n\nMusic won't play until you load a soundfont from the sound options menu."), w_info, magic_none ); + } +#endif while( !env_t::quit_simutrans ) { // play next tune? check_midi(); Index: trunk/simutrans/config/simuconf.tab =================================================================== --- trunk/simutrans/config/simuconf.tab (revision 9606) +++ trunk/simutrans/config/simuconf.tab (working copy) @@ -599,6 +599,15 @@ #################################system stuff################################# +# Set this for playing MIDI music with your preferred soundfont. +# Need Fluidsynth support. +# A recommended lightweight (30 MB) soundfont is PCLite: +# http://www.personalcopy.com/sfarkfonts1.htm +# https://src.fedoraproject.org/repo/pkgs/PersonalCopy-Lite-soundfont/PCLite.sf2/629732b7552c12a8fae5b046d306273a/ +# But there are many more, including greater quality ones. +# Set either the full path or the name of the .sf2 soundfont saved into the "music" directory +soundfont_filename = PCLite.sf2 + # File format for saved games # Uncompressed formats: # - binary Uncompressed binary data Index: trunk/simversion.h =================================================================== --- trunk/simversion.h (revision 9606) +++ trunk/simversion.h (working copy) @@ -23,8 +23,8 @@ // Beware: SAVEGAME minor is often ahead of version minor when there were patches. // ==> These have no direct connection at all! -#define SIM_SAVE_MINOR 0 -#define SIM_SERVER_MINOR 0 +#define SIM_SAVE_MINOR 2 +#define SIM_SERVER_MINOR 2 // NOTE: increment before next release to enable save/load of new features #define MAKEOBJ_VERSION "60.5"