From ae145daf72649265ea005af2f67b4ebd15d8cfd5 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sun, 1 Nov 2020 23:48:49 +0100 Subject: [PATCH 01/55] Remove FLUIDSYNTH_API and FLUID_DEPRECATED macros from documentation --- doc/Doxyfile.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 75b19e1b..cf1a2c28 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -195,12 +195,14 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = -PREDEFINED = __DOXYGEN__ +PREDEFINED = __DOXYGEN__ \ + FLUIDSYNTH_API \ + FLUID_DEPRECATED EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- From 93b3375fe37a626e6ebc2c158573214b4276833d Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sun, 1 Nov 2020 23:50:39 +0100 Subject: [PATCH 02/55] Remove "References" and "Referenced by" links from doc They auto generated links are quite long on some functions, making the documentation harder to read. --- doc/Doxyfile.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index cf1a2c28..cec8a2c9 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -103,8 +103,8 @@ FILTER_SOURCE_FILES = NO SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = NO From cfa9e7bedc971eeccecafc99144194bb027cd451 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 3 Nov 2020 21:30:25 +0100 Subject: [PATCH 03/55] Enable navigation sidebar --- doc/Doxyfile.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index cec8a2c9..3ee7bd7a 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -138,8 +138,8 @@ BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 350 FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output From d1237c46783ff3b7e42059ce0837a0aa760ae642 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 3 Nov 2020 21:51:04 +0100 Subject: [PATCH 04/55] Make larger enums easier to read --- doc/Doxyfile.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 3ee7bd7a..f2ba37ad 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -137,7 +137,7 @@ CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 +ENUM_VALUES_PER_LINE = 1 GENERATE_TREEVIEW = YES TREEVIEW_WIDTH = 350 FORMULA_FONTSIZE = 10 From 339b30c9461276284c99446ebc999761360729a1 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 3 Nov 2020 22:12:50 +0100 Subject: [PATCH 05/55] Move doxygen customizations into separate directory --- doc/Doxyfile.cmake | 2 +- doc/{doxy_formula.css => doxygen/custom.css} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename doc/{doxy_formula.css => doxygen/custom.css} (100%) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index f2ba37ad..f60c53f6 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -122,7 +122,7 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = -HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxy_formula.css +HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl @CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf @CMAKE_SOURCE_DIR@/doc/FluidMixer.jpg GENERATE_HTMLHELP = NO diff --git a/doc/doxy_formula.css b/doc/doxygen/custom.css similarity index 100% rename from doc/doxy_formula.css rename to doc/doxygen/custom.css From d00849b556ea6fc06f6af4e9c5c0ad20085f18ec Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 4 Nov 2020 20:42:45 +0100 Subject: [PATCH 06/55] Restructure devdocs into separate pages --- doc/fluidsynth-v20-devdoc.txt | 95 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 3222b8ba..2e257049 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -17,45 +17,13 @@ All the source code examples in this document are in the public domain; you can FluidSynth is a software synthesizer based on the SoundFont 2 specifications. The synthesizer is available as a shared object that can easily be reused in any application that wants to use wave-table synthesis. This document explains the basic usage of FluidSynth. Some of the more advanced features are not yet discussed but will be added in future versions. -\section Contents Table of Contents - -- \ref Disclaimer -- \ref Introduction -- \ref NewIn2_2_0 -- \ref NewIn2_1_4 -- \ref NewIn2_1_1 -- \ref NewIn2_1_0 -- \ref NewIn2_0_8 -- \ref NewIn2_0_7 -- \ref NewIn2_0_6 -- \ref NewIn2_0_5 -- \ref NewIn2_0_3 -- \ref NewIn2_0_2 -- \ref NewIn2_0_0 -- \ref CreatingSettings -- \ref CreatingSynth -- \ref CreatingAudioDriver -- \ref UsingSynth -- \ref LoadingSoundfonts -- \ref SendingMIDI -- \ref RealtimeMIDI -- \ref MIDIPlayer -- \ref FileRenderer -- \ref MIDIPlayerMem -- \ref MIDIRouter -- \ref Sequencer -- \ref Shell -- \ref Multi-channel -- \ref synth-context -- \ref Advanced - \section Disclaimer This documentation may be partly incomplete. As always, the source code is the final reference. SoundFont(R) is a registered trademark of E-mu Systems, Inc. -\section Introduction +\page Introduction Introduction What is FluidSynth? @@ -71,6 +39,8 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org + +\page RecentChanges Recent Changes \section NewIn2_2_0 What's new in 2.2.0? - #fluid_file_callbacks_t now uses long long as file-offset type (see #fluid_long_long_t). This is a breaking change, which allows to load SoundFonts bigger than 2GiB on Windows. This change required to bump fluidsynth's SOVERSION. @@ -216,7 +186,26 @@ FluidSynths major version was bumped. The API was reworked, deprecated functions - FLUID_MIDI_ROUTER_RULE_COUNT -\section CreatingSettings Creating and changing the settings +\page UsageGuide Usage Guide + - \subpage CreatingSettings + - \subpage CreatingSynth + - \subpage CreatingAudioDriver + - \subpage UsingSynth + - \subpage LoadingSoundfonts + - \subpage SendingMIDI + - \subpage RealtimeMIDI + - \subpage MIDIPlayer + - \subpage FileRenderer + - \subpage MIDIPlayerMem + - \subpage MIDIRouter + - \subpage Sequencer + - \subpage Shell + - \subpage Multi-channel + - \subpage synth-context + - \subpage Advanced + + +\page CreatingSettings Creating and changing the settings Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. @@ -237,7 +226,7 @@ int main(int argc, char** argv) The API contains the functions to query the type, the current value, the default value, the range and the "hints" of a setting. The range is the minimum and maximum value of the setting. The hints gives additional information about a setting. For example, whether a string represents a filename. Or whether a number should be interpreted on on a logarithmic scale. Check the settings.h API documentation for a description of all functions. -\section CreatingSynth Creating the synthesizer +\page CreatingSynth Creating the synthesizer To create the synthesizer, you pass it the settings object, as in the following example: @@ -263,7 +252,7 @@ For a full list of available synthesizer settings, please refer -\section CreatingAudioDriver Creating the Audio Driver +\page CreatingAudioDriver Creating the Audio Driver The synthesizer itself does not write any audio to the audio output. This allows application developers to manage the audio output themselves if they wish. The next section describes the use of the synthesizer without an audio driver in more detail. @@ -312,13 +301,13 @@ There are a number of general audio driver settings. The audio.driver settings d *Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done before the first call to new_fluid_settings()! Also make sure to call SDL_Quit() after all fluidsynth instances have been destroyed. -\section UsingSynth Using the synthesizer without an audio driver +\page UsingSynth Using the synthesizer without an audio driver It is possible to use the synthesizer object without creating an audio driver. This is desirable if the application using FluidSynth manages the audio output itself. The synthesizer has several API functions that can be used to obtain the audio output: fluid_synth_write_s16() fills two buffers (left and right channel) with samples coded as signed 16 bits (the endian-ness is machine dependent). fluid_synth_write_float() fills a left and right audio buffer with 32 bits floating point samples. The function fluid_synth_process() is the generic interface for synthesizing audio, which is also capable of multi channel audio output. -\section LoadingSoundfonts Loading and managing SoundFonts +\page LoadingSoundfonts Loading and managing SoundFonts Before any sound can be produced, the synthesizer needs a SoundFont. @@ -330,7 +319,7 @@ The fluid_synth_sfload() function returns the unique identifier of the loaded So Additional API functions are provided to get the number of loaded SoundFonts and to get a pointer to the SoundFont. -\section SendingMIDI Sending MIDI Events +\page SendingMIDI Sending MIDI Events Once the synthesizer is up and running and a SoundFont is loaded, most people will want to do something useful with it. Make noise, for example. MIDI messages can be sent using the fluid_synth_noteon(), fluid_synth_noteoff(), fluid_synth_cc(), fluid_synth_pitch_bend(), fluid_synth_pitch_wheel_sens(), and fluid_synth_program_change() functions. For convenience, there's also a fluid_synth_bank_select() function (the bank select message is normally sent using a control change message). @@ -373,7 +362,7 @@ protected: }; \endcode -\section RealtimeMIDI Creating a Real-time MIDI Driver +\page RealtimeMIDI Creating a Real-time MIDI Driver FluidSynth can process real-time MIDI events received from hardware MIDI ports or other applications. To do so, the client must create a MIDI input driver. It is a very similar process to the creation of the audio driver: you initialize some properties in a settings instance and call the new_fluid_midi_driver() function providing a callback function that will be invoked when a MIDI event is received. The following MIDI drivers are currently supported: @@ -411,7 +400,7 @@ the MIDI subsystems used. For a full list of available midi driver setti -\section MIDIPlayer Loading and Playing a MIDI file +\page MIDIPlayer Loading and Playing a MIDI file FluidSynth can be used to play MIDI files, using the MIDI File Player interface. It follows a high level implementation, though its implementation is currently incomplete. After initializing the synthesizer, create the player passing the synth instance to new_fluid_player(). Then, you can add some SMF file names to the player using fluid_player_add(), and finally call fluid_player_play() to start the playback. You can check if the player has finished by calling fluid_player_get_status(), or wait for the player to terminate using fluid_player_join(). @@ -457,7 +446,7 @@ A list of available MIDI player settings can be found in FluidSettings Documentation for more \c audio.file\.\* options. -\section MIDIPlayerMem Playing a MIDI file from memory +\page MIDIPlayerMem Playing a MIDI file from memory FluidSynth can be also play MIDI files directly from a buffer in memory. If you need to play a file from a stream (such as stdin, a network, or a high-level file interface), you can load the entire file into a buffer first, and then use this approach. Use the same technique as above, but rather than calling fluid_player_add(), load it into memory and call fluid_player_add_mem() instead. Once you have passed a buffer to fluid_player_add_mem(), it is copied, so you may use it again or free it immediately (it is your responsibility to free it if you allocated it). @@ -565,7 +554,8 @@ int main(int argc, char** argv) } \endcode -\section MIDIRouter Real-time MIDI router + +\page MIDIRouter Real-time MIDI router The MIDI router is one more processing layer directly behind the MIDI input. It processes incoming MIDI events and generates control events for the synth. It can be used to filter or modify events prior to sending them to the synthesizer. When created, the MIDI router is transparent and simply passes all MIDI events. Router "rules" must be added to actually make use of its capabilities. @@ -631,7 +621,7 @@ int main(int argc, char** argv) -\section Sequencer +\page Sequencer Sequencer FluidSynth's sequencer can be used to play MIDI events in a more flexible way than using the MIDI file player, which expects the events to be stored as Standard MIDI Files. Using the sequencer, you can provide the events one by one, with an optional timestamp for scheduling. @@ -751,11 +741,13 @@ int main(void) { } \endcode -\section Shell Shell interface + +\page Shell Shell interface The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, type @c help in the fluidsynth command line shell. For a full list of available command line settings, please refer to FluidSettings Documentation. -\section Multi-channel Multi-Channel audio rendering + +\page Multi-channel Multi-Channel audio rendering FluidSynth is capable of rendering all audio and all effects from all MIDI channels to separate stereo buffers. Refer to the documentation of fluid_synth_process() and review the different use-cases in the example file for information on how to do that: \ref fluidsynth_process.c @@ -767,7 +759,8 @@ The following chart illustrates how the voices (produced by MIDI NoteOns) are di \endhtmlonly -\section synth-context Understanding the "synthesis context" + +\page synth-context Understanding the "synthesis context" When reading through the functions exposed via our API, you will often read the note: "May or may not be called from synthesis context." @@ -786,7 +779,11 @@ automatically becomes the "synthesis thread". The terms "synthesis context" and - fluid_player_set_playback_callback() - fluid_sequencer_register_client() -\section Advanced Advanced features, not yet documented. API reference may contain more info. + +\page Advanced Advanced features + +The following features are not yet fully documented. Some information can be +found in the API reference and in the GitHub Wiki. - Accessing low-level voice parameters - Reverb settings From a11806aa32dbc0cce226ac3f7f91bc4fdf3d554d Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 4 Nov 2020 21:09:13 +0100 Subject: [PATCH 07/55] Change files into groups / modules --- include/fluidsynth/audio.h | 7 +++++-- include/fluidsynth/event.h | 6 +++++- include/fluidsynth/gen.h | 5 ++++- include/fluidsynth/ladspa.h | 6 +++++- include/fluidsynth/log.h | 5 ++++- include/fluidsynth/midi.h | 6 +++++- include/fluidsynth/misc.h | 5 ++++- include/fluidsynth/mod.h | 6 +++++- include/fluidsynth/seq.h | 6 +++++- include/fluidsynth/seqbind.h | 6 +++++- include/fluidsynth/settings.h | 9 ++++++--- include/fluidsynth/sfont.h | 6 +++++- include/fluidsynth/shell.h | 5 ++++- include/fluidsynth/synth.h | 5 ++++- include/fluidsynth/types.h | 6 +++++- include/fluidsynth/version.h.in | 5 ++++- include/fluidsynth/voice.h | 6 +++++- 17 files changed, 80 insertions(+), 20 deletions(-) diff --git a/include/fluidsynth/audio.h b/include/fluidsynth/audio.h index eba327ce..f02b24ab 100644 --- a/include/fluidsynth/audio.h +++ b/include/fluidsynth/audio.h @@ -26,9 +26,8 @@ extern "C" { #endif /** - * @file audio.h + * @defgroup Audio Audio Output * @brief Functions for audio driver output. - * @defgroup AudioFunctions Functions for audio output * * Defines functions for creating audio driver output. Use * new_fluid_audio_driver() to create a new audio driver for a given synth @@ -37,6 +36,8 @@ extern "C" { * audio driver (although it is not as efficient). * * @sa @ref CreatingAudioDriver + * + * @{ */ /** @@ -77,6 +78,8 @@ FLUIDSYNTH_API int fluid_file_set_encoding_quality(fluid_file_renderer_t *dev, d FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/event.h b/include/fluidsynth/event.h index 7fe829b1..9bbab02d 100644 --- a/include/fluidsynth/event.h +++ b/include/fluidsynth/event.h @@ -26,10 +26,12 @@ extern "C" { #endif /** - * @file event.h + * @defgroup SequencerEvents Sequencer Events * @brief Sequencer event functions and defines. * * Functions and constants for creating/processing sequencer events. + * + * @{ */ /** @@ -127,6 +129,8 @@ FLUIDSYNTH_API short fluid_event_get_bank(fluid_event_t *evt); FLUIDSYNTH_API int fluid_event_get_pitch(fluid_event_t *evt); FLUIDSYNTH_API unsigned int fluid_event_get_sfont_id(fluid_event_t *evt); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/gen.h b/include/fluidsynth/gen.h index 1f46fe2a..6e9a9a45 100644 --- a/include/fluidsynth/gen.h +++ b/include/fluidsynth/gen.h @@ -26,8 +26,10 @@ extern "C" { #endif /** - * @file gen.h + * @defgroup Generators SoundFont Generators * @brief Functions and defines for SoundFont generator effects. + * + * @{ */ /** @@ -122,6 +124,7 @@ enum fluid_gen_type #endif }; +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/ladspa.h b/include/fluidsynth/ladspa.h index 4752a893..6de87a69 100644 --- a/include/fluidsynth/ladspa.h +++ b/include/fluidsynth/ladspa.h @@ -26,7 +26,7 @@ extern "C" { #endif /** - * @file ladspa.h + * @defgroup LADSPA LADSPA Interface * @brief Functions for manipulating the ladspa effects unit * * This header defines useful functions for programmatically manipulating the ladspa @@ -34,6 +34,8 @@ extern "C" { * * Using any of those functions requires fluidsynth to be compiled with ladspa support. * Else all of those functions are useless dummies. + * + * @{ */ FLUIDSYNTH_API int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx); @@ -57,6 +59,8 @@ FLUIDSYNTH_API int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const FLUIDSYNTH_API int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/log.h b/include/fluidsynth/log.h index 3ea74b26..05043948 100644 --- a/include/fluidsynth/log.h +++ b/include/fluidsynth/log.h @@ -28,7 +28,7 @@ extern "C" { /** - * @file log.h + * @defgroup Logging Logging * @brief Logging interface * * The default logging function of the fluidsynth prints its messages @@ -47,6 +47,8 @@ extern "C" { * fluid_set_log_function(FLUID_WARN, NULL, NULL); * fluid_set_log_function(FLUID_DBG, NULL, NULL); * @endcode + * + * @{ */ /** @@ -83,6 +85,7 @@ __attribute__ ((format (printf, 2, 3))) #endif ; +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/midi.h b/include/fluidsynth/midi.h index 9ddeef05..945dc49d 100644 --- a/include/fluidsynth/midi.h +++ b/include/fluidsynth/midi.h @@ -26,8 +26,10 @@ extern "C" { #endif /** - * @file midi.h + * @defgroup MIDI MIDI * @brief Functions for MIDI events, drivers and MIDI file playback. + * + * @{ */ FLUIDSYNTH_API fluid_midi_event_t *new_fluid_midi_event(void); @@ -152,6 +154,8 @@ FLUIDSYNTH_API int fluid_player_seek(fluid_player_t *player, int ticks); /// +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/misc.h b/include/fluidsynth/misc.h index e2f5d3f8..80b32a3b 100644 --- a/include/fluidsynth/misc.h +++ b/include/fluidsynth/misc.h @@ -28,8 +28,10 @@ extern "C" { /** - * @file misc.h + * @defgroup Misc Miscellaneous * @brief Miscellaneous utility functions and defines + * + * @{ */ /** @@ -64,6 +66,7 @@ FLUIDSYNTH_API int fluid_is_soundfont(const char *filename); FLUIDSYNTH_API int fluid_is_midifile(const char *filename); FLUIDSYNTH_API void fluid_free(void* ptr); +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/mod.h b/include/fluidsynth/mod.h index 5ea5f89d..154607d2 100644 --- a/include/fluidsynth/mod.h +++ b/include/fluidsynth/mod.h @@ -26,8 +26,10 @@ extern "C" { #endif /** - * @file mod.h + * @defgroup Modulators SoundFont Modulators * @brief SoundFont modulator functions and constants. + * + * @{ */ @@ -91,6 +93,8 @@ FLUIDSYNTH_API int fluid_mod_has_dest(const fluid_mod_t *mod, int gen); FLUIDSYNTH_API void fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/seq.h b/include/fluidsynth/seq.h index 846c7f48..f58fe43b 100644 --- a/include/fluidsynth/seq.h +++ b/include/fluidsynth/seq.h @@ -26,8 +26,10 @@ extern "C" { #endif /** - * @file seq.h + * @defgroup Sequencer Sequencer * @brief MIDI event sequencer. + * + * @{ */ /** @@ -64,6 +66,8 @@ FLUIDSYNTH_API unsigned int fluid_sequencer_get_tick(fluid_sequencer_t *seq); FLUIDSYNTH_API void fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale); FLUIDSYNTH_API double fluid_sequencer_get_time_scale(fluid_sequencer_t *seq); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/seqbind.h b/include/fluidsynth/seqbind.h index 63859566..c158f24c 100644 --- a/include/fluidsynth/seqbind.h +++ b/include/fluidsynth/seqbind.h @@ -28,8 +28,11 @@ extern "C" { #endif /** - * @file seqbind.h + * @defgroup SequencerBind Sequencer Binding + * @ingroup Sequencer * @brief Functions for binding sequencer objects to other subsystems. + * + * @{ */ FLUIDSYNTH_API @@ -37,6 +40,7 @@ fluid_seq_id_t fluid_sequencer_register_fluidsynth(fluid_sequencer_t *seq, fluid FLUIDSYNTH_API int fluid_sequencer_add_midi_event_to_buffer(void *data, fluid_midi_event_t *event); +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/settings.h b/include/fluidsynth/settings.h index aa7f0831..d1552c00 100644 --- a/include/fluidsynth/settings.h +++ b/include/fluidsynth/settings.h @@ -26,9 +26,8 @@ extern "C" { #endif /** - * @file settings.h - * @brief Synthesizer settings - * @defgroup SettingsFunctions Functions for settings management + * @defgroup Settings Settings + * @brief Functions for settings management * * To create a synthesizer object you will have to specify its * settings. These settings are stored in a fluid_settings_t object. @@ -49,6 +48,8 @@ extern "C" { * } * @endcode * @sa @ref CreatingSettings + * + * @{ */ /** @@ -181,6 +182,8 @@ FLUIDSYNTH_API void fluid_settings_foreach(fluid_settings_t *settings, void *data, fluid_settings_foreach_t func); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/sfont.h b/include/fluidsynth/sfont.h index 12cae5f7..bd32bf2b 100644 --- a/include/fluidsynth/sfont.h +++ b/include/fluidsynth/sfont.h @@ -27,7 +27,7 @@ extern "C" { /** - * @file sfont.h + * @defgroup SoundFont SoundFont Loader * @brief SoundFont plugins * * It is possible to add new SoundFont loaders to the @@ -59,6 +59,8 @@ extern "C" { * generator, use fluid_voice_gen_set() or fluid_voice_gen_incr(). When you are * finished initializing the voice call fluid_voice_start() to * start playing the synthesis voice. + * + * @{ */ /** @@ -311,6 +313,8 @@ FLUIDSYNTH_API int fluid_sample_set_sound_data(fluid_sample_t *sample, FLUIDSYNTH_API int fluid_sample_set_loop(fluid_sample_t *sample, unsigned int loop_start, unsigned int loop_end); FLUIDSYNTH_API int fluid_sample_set_pitch(fluid_sample_t *sample, int root_key, int fine_tune); +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/shell.h b/include/fluidsynth/shell.h index f9c17e0f..02430536 100644 --- a/include/fluidsynth/shell.h +++ b/include/fluidsynth/shell.h @@ -28,12 +28,14 @@ extern "C" { /** - * @file shell.h + * @defgroup Shell Command Shell * @brief Command shell interface * * The shell interface allows you to send simple textual commands to * the synthesizer, to parse a command file, or to read commands * from the stdin or other input streams. + * + * @{ */ FLUIDSYNTH_API fluid_istream_t fluid_get_stdin(void); @@ -89,6 +91,7 @@ FLUIDSYNTH_API void delete_fluid_server(fluid_server_t *server); FLUIDSYNTH_API int fluid_server_join(fluid_server_t *server); +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index 55c83da4..881bb3e3 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -28,7 +28,7 @@ extern "C" { /** - * @file synth.h + * @defgroup Synth Synthesizer * @brief Embeddable SoundFont synthesizer * * You create a new synthesizer with new_fluid_synth() and you destroy @@ -43,6 +43,8 @@ extern "C" { * * The API for sending MIDI events is probably what you expect: * fluid_synth_noteon(), fluid_synth_noteoff(), ... + * + * @{ */ @@ -398,6 +400,7 @@ FLUIDSYNTH_API int fluid_synth_set_breath_mode(fluid_synth_t *synth, FLUIDSYNTH_API int fluid_synth_get_breath_mode(fluid_synth_t *synth, int chan, int *breathmode); +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/types.h b/include/fluidsynth/types.h index 5034d47e..4352b4c5 100644 --- a/include/fluidsynth/types.h +++ b/include/fluidsynth/types.h @@ -29,8 +29,10 @@ extern "C" { /** - * @file types.h + * @defgroup Types Types * @brief Type declarations + * + * @{ */ typedef struct _fluid_hashtable_t fluid_settings_t; /**< Configuration settings instance */ @@ -74,6 +76,8 @@ typedef __int64 fluid_long_long_t; // even on 32bit windows typedef long long fluid_long_long_t; #endif +/* @} */ + #ifdef __cplusplus } #endif diff --git a/include/fluidsynth/version.h.in b/include/fluidsynth/version.h.in index 716678ce..27b09bbe 100644 --- a/include/fluidsynth/version.h.in +++ b/include/fluidsynth/version.h.in @@ -27,8 +27,10 @@ extern "C" { #endif /** - * @file version.h + * @defgroup Version Library Version * @brief Library version functions and defines + * + * @{ */ #define FLUIDSYNTH_VERSION @FLUIDSYNTH_VERSION@ /**< String constant of libfluidsynth version. */ @@ -39,6 +41,7 @@ extern "C" { FLUIDSYNTH_API void fluid_version(int *major, int *minor, int *micro); FLUIDSYNTH_API char* fluid_version_str(void); +/* @} */ #ifdef __cplusplus } diff --git a/include/fluidsynth/voice.h b/include/fluidsynth/voice.h index f0644718..3013aadc 100644 --- a/include/fluidsynth/voice.h +++ b/include/fluidsynth/voice.h @@ -26,13 +26,15 @@ extern "C" { #endif /** - * @file voice.h + * @defgroup VoiceManipulation Voice Manipulation * @brief Synthesis voice manipulation functions. * * The interface to the synthesizer's voices. * Examples on using them can be found in fluid_defsfont.c. * Most of these functions should only be called from within synthesis context, * such as the SoundFont loader's noteon method. + * + * @{ */ @@ -64,6 +66,8 @@ FLUIDSYNTH_API int fluid_voice_is_sostenuto(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_optimize_sample(fluid_sample_t *s); FLUIDSYNTH_API void fluid_voice_update_param(fluid_voice_t *voice, int gen); +/* @} */ + #ifdef __cplusplus } From 272cc5edada99561b8d85b66f6569c541d22834e Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 7 Nov 2020 15:41:38 +0100 Subject: [PATCH 08/55] Some additional subgrouping --- doc/Doxyfile.cmake | 2 +- include/fluidsynth/synth.h | 144 ++++++++++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 19 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index f60c53f6..edf5dda2 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -34,7 +34,7 @@ CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = NO +SUBGROUPING = YES TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index 881bb3e3..bb2550b0 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -52,7 +52,14 @@ FLUIDSYNTH_API fluid_synth_t *new_fluid_synth(fluid_settings_t *settings); FLUIDSYNTH_API void delete_fluid_synth(fluid_synth_t *synth); FLUIDSYNTH_API fluid_settings_t *fluid_synth_get_settings(fluid_synth_t *synth); -/* MIDI channel messages */ +/* @} */ + +/** + * @defgroup MIDIChannel MIDI channel messages + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_noteon(fluid_synth_t *synth, int chan, int key, int vel); FLUIDSYNTH_API int fluid_synth_noteoff(fluid_synth_t *synth, int chan, int key); @@ -86,8 +93,15 @@ FLUIDSYNTH_API int fluid_synth_system_reset(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_all_notes_off(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_all_sounds_off(fluid_synth_t *synth, int chan); +/* @} */ -/* Dynamic sample loading */ + +/** + * @defgroup DynamicSampleLoading Dynamic sample loading + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); @@ -95,6 +109,9 @@ int fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int FLUIDSYNTH_API int fluid_synth_unpin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); +/* @} */ + + /** * The midi channel type used by fluid_synth_set_channel_type() */ @@ -115,8 +132,12 @@ FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t *synth, unsigned int id, FLUIDSYNTH_API int fluid_synth_stop(fluid_synth_t *synth, unsigned int id); -/* SoundFont management */ - +/** + * @defgroup SoundFontManagement SoundFont management + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_sfload(fluid_synth_t *synth, const char *filename, int reset_presets); FLUIDSYNTH_API int fluid_synth_sfreload(fluid_synth_t *synth, int id); @@ -130,11 +151,21 @@ FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont_by_name(fluid_synth_t *synth const char *name); FLUIDSYNTH_API int fluid_synth_set_bank_offset(fluid_synth_t *synth, int sfont_id, int offset); FLUIDSYNTH_API int fluid_synth_get_bank_offset(fluid_synth_t *synth, int sfont_id); +/* @} */ -/* Reverb */ +/** + * @defgroup Effects Effects + * @ingroup Synth + */ +/** + * @defgroup Reverb Reverb + * @ingroup Effects + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, double damping, double width, double level); FLUIDSYNTH_API int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize); @@ -147,9 +178,15 @@ FLUIDSYNTH_API double fluid_synth_get_reverb_roomsize(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_damp(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_width(fluid_synth_t *synth); +/* @} */ -/* Chorus */ +/** + * @defgroup Chrous Chrous + * @ingroup Effects + * + * @{ + */ /** * Chorus modulation waveform type. @@ -174,18 +211,29 @@ FLUIDSYNTH_API double fluid_synth_get_chorus_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_speed(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_depth(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t *synth); /* see fluid_chorus_mod */ +/* @} Chorus */ -/* Audio and MIDI channels */ - +/** + * @defgroup ChannelManagement Audio and MIDI channels + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_count_midi_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_audio_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_audio_groups(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_groups(fluid_synth_t *synth); +/* @} */ -/* Synthesis parameters */ +/** + * @defgroup SynthParams Synthesis parameters + * @ingroup Synth + * + * @{ + */ FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate); FLUIDSYNTH_API void fluid_synth_set_gain(fluid_synth_t *synth, float gain); @@ -212,14 +260,28 @@ enum fluid_interp FLUID_INTERP_HIGHEST = FLUID_INTERP_7THORDER, /**< Highest interpolation method */ }; -/* Generator interface */ +/* @} */ + +/** + * @defgroup Generator Generator interface + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_set_gen(fluid_synth_t *synth, int chan, int param, float value); FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param); +/* @} */ -/* Tuning */ + +/** + * @defgroup Tuning Tuning + * @ingroup Synth + * + * @{ + */ FLUIDSYNTH_API int fluid_synth_activate_key_tuning(fluid_synth_t *synth, int bank, int prog, @@ -241,13 +303,25 @@ int fluid_synth_tuning_iteration_next(fluid_synth_t *synth, int *bank, int *prog FLUIDSYNTH_API int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int prog, char *name, int len, double *pitch); -/* Misc */ +/* @} */ + +/** + * @addtogroup Misc + * + * @{ + */ FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t *synth); FLUID_DEPRECATED FLUIDSYNTH_API const char *fluid_synth_error(fluid_synth_t *synth); +/* @} */ -/* Default modulators */ +/** + * @defgroup DefaultModulators Default modulators + * @ingroup Synth + * + * @{ + */ /** * Enum used with fluid_synth_add_default_mod() to specify how to handle duplicate modulators. @@ -261,13 +335,18 @@ enum fluid_synth_add_mod FLUIDSYNTH_API int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode); FLUIDSYNTH_API int fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod); +/* @} */ -/* - * Synthesizer plugin + +/** + * @defgroup SynthPlugin Synthesizer plugin + * @ingroup Synth * * To create a synthesizer plugin, create the synthesizer as * explained above. Once the synthesizer is created you can call * any of the functions below to get the audio. + * + * @{ */ FLUIDSYNTH_API int fluid_synth_write_s16(fluid_synth_t *synth, int len, @@ -283,8 +362,15 @@ FLUIDSYNTH_API int fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[]); +/* @} */ -/* Synthesizer's interface to handle SoundFont loaders */ + +/** + * @defgroup SynthSoundFontLoader SoundFont Loader + * @brief Synthesizer's interface to handle SoundFont loaders + * + * @{ + */ FLUIDSYNTH_API void fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader); FLUIDSYNTH_API fluid_voice_t *fluid_synth_alloc_voice(fluid_synth_t *synth, @@ -295,6 +381,16 @@ FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t *synth, fluid_voice_t *buf[], int bufsize, int ID); FLUIDSYNTH_API int fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event); +/* @} */ + + +/** + * @defgroup CustomIIR IIR Filter + * @ingroup Effects + * + * @{ + */ + /** * Specifies the type of filter to use for the custom IIR filter */ @@ -318,13 +414,25 @@ enum fluid_iir_filter_flags FLUIDSYNTH_API int fluid_synth_set_custom_filter(fluid_synth_t *, int type, int flags); +/* @} */ -/* LADSPA */ +/** + * @addtogroup LADSPA + * @ingroup Effects + * + * @{ + */ FLUIDSYNTH_API fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth); +/* @} */ -/* API: Poly mono mode */ +/** + * @defgroup ChannelModes Channel modes + * @ingroup Synth + * + * @{ + */ /** Interface to poly/mono mode variables * From c5b50455d77a77c4b02a413d758378e0e8d3537b Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 7 Nov 2020 21:14:19 +0100 Subject: [PATCH 09/55] Use xsltproc to include settings in API documentation --- doc/CMakeLists.txt | 13 ++++++ doc/Doxyfile.cmake | 2 +- doc/doxygen/fluidsettings.xsl | 77 +++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 doc/doxygen/fluidsettings.xsl diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index c7f84f77..a6940dcc 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -29,6 +29,19 @@ if ( DOXYGEN_FOUND ) ) endif ( DOXYGEN_FOUND ) +find_package ( LibXslt ) +if ( LIBXSLT_XSLTPROC_EXECUTABLE ) + add_custom_target ( doxygen_settings + ${LIBXSLT_XSLTPROC_EXECUTABLE} + --output ${CMAKE_CURRENT_BINARY_DIR}/fluidsettings.txt + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen/fluidsettings.xsl + ${CMAKE_CURRENT_SOURCE_DIR}/fluidsettings.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_dependencies(doxygen doxygen_settings) +endif ( LIBXSLT_XSLTPROC_EXECUTABLE ) + + if ( UNIX ) install ( FILES fluidsynth.1 DESTINATION ${MAN_INSTALL_DIR} ) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index edf5dda2..2852fc88 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -82,7 +82,7 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt @CMAKE_SOURCE_DIR@/include @CMAKE_SOURCE_DIR@/include/fluidsynth @CMAKE_SOURCE_DIR@/src @CMAKE_BINARY_DIR@/include/fluidsynth +INPUT = @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt @CMAKE_SOURCE_DIR@/include @CMAKE_SOURCE_DIR@/include/fluidsynth @CMAKE_SOURCE_DIR@/src @CMAKE_BINARY_DIR@/include/fluidsynth @CMAKE_BINARY_DIR@/doc/fluidsettings.txt INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h RECURSIVE = YES diff --git a/doc/doxygen/fluidsettings.xsl b/doc/doxygen/fluidsettings.xsl new file mode 100644 index 00000000..0f880164 --- /dev/null +++ b/doc/doxygen/fluidsettings.xsl @@ -0,0 +1,77 @@ + + + + + + +/*! +\page FluidSettings FluidSynth Settings + + + + + + + +*/ + + + + + +- \subpage + + + + + +\page + + + + + + + + +\section + + +\par Type + + Boolean (int) + Integer (int) + + + Selection (str) + String (str) + + + Float (num) + + + + + \par Options + + \par Values + 0, 1 + \par Min - Max + - + + +\par Default +\htmlonly + +\endhtmlonly + + +\deprecated This setting is deprecated and might be removed in a future version of FluidSynth. + + +\htmlonly + +\endhtmlonly + + + From 8617e53a00079ca0acd78eeebf3af8c8922d5124 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 7 Nov 2020 21:34:48 +0100 Subject: [PATCH 10/55] Replace all links to fluidsettings.xml with proper \ref's --- doc/fluidsynth-v20-devdoc.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 2e257049..487ed5be 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -64,7 +64,7 @@ What is FluidSynth? \section NewIn2_1_0 What's new in 2.1.0? - refrain from using fluid_synth_set_sample_rate() -- synth.sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() +- \ref settings_synth_sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() - new reverb engine - chorus is now stereophonic - smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) @@ -89,7 +89,7 @@ What is FluidSynth? \section NewIn2_0_5 What's new in 2.0.5? - fluid_synth_process() omitted audio samples when called with arbitrary sample counts that were not a multiple of fluid_synth_get_internal_bufsize() -- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if "synth.dynamic-sample-loading" was set to FALSE +- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if \ref settings_synth_dynamic-sample-loading was set to FALSE \section NewIn2_0_3 What's new in 2.0.3? @@ -129,14 +129,14 @@ FluidSynths major version was bumped. The API was reworked, deprecated functions New Features and API additions: -- add "midi.autoconnect" a setting for automatically connecting fluidsynth to available MIDI input ports -- add "synth.overflow.important" and "synth.overflow.important-channels" settings to take midi channels during overflow calculation into account that are considered to be "important" -- add "synth.dynamic-sample-loading" a setting for enabling on demand sample loading +- add \ref settings_midi_autoconnect a setting for automatically connecting fluidsynth to available MIDI input ports +- add \ref settings_synth_overflow_important and \ref settings_synth_overflow_important-channels settings to take midi channels during overflow calculation into account that are considered to be "important" +- add \ref settings_synth_dynamic-sample-loading a setting for enabling on demand sample loading - add support for polyphonic key pressure events, see fluid_event_key_pressure() and fluid_synth_key_pressure() - add fluid_synth_add_default_mod() and fluid_synth_remove_default_mod() for manipulating default modulators - add individual reverb setters: fluid_synth_set_reverb_roomsize(), fluid_synth_set_reverb_damp(), fluid_synth_set_reverb_width(), fluid_synth_set_reverb_level() - add individual chorus setters: fluid_synth_set_chorus_nr(), fluid_synth_set_chorus_level(), fluid_synth_set_chorus_speed(), fluid_synth_set_chorus_depth(), fluid_synth_set_chorus_type() -- add realtime settings for reverb and chorus parameters +- add realtime settings for \ref settings_synth_reverb_damp and \ref settings_synth_chorus_depth parameters - add seek support to midi-player, see fluid_player_seek() - expose functions to manipulate the ladspa effects unit (see ladspa.h) - add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() @@ -248,7 +248,7 @@ int main(int argc, char** argv) } \endcode -For a full list of available synthesizer settings, please refer to FluidSettings Documentation. +For a full list of available synthesizer settings, please refer to the \ref settings_synth documentation. @@ -297,7 +297,7 @@ void init() As soon as the audio driver is created, it will start playing. The audio driver creates a separate thread that uses the synthesizer object to generate the audio. -There are a number of general audio driver settings. The audio.driver settings define the audio subsystem that will be used. The audio.periods and audio.period-size settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, please refer to FluidSettings Documentation. +There are a number of general audio driver settings. The audio.driver settings define the audio subsystem that will be used. The audio.periods and audio.period-size settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, please refer to the \ref settings_audio documentation. *Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done before the first call to new_fluid_settings()! Also make sure to call SDL_Quit() after all fluidsynth instances have been destroyed. @@ -396,7 +396,7 @@ int main(int argc, char** argv) There are a number of general MIDI driver settings. The midi.driver setting defines the MIDI subsystem that will be used. There are additional settings for -the MIDI subsystems used. For a full list of available midi driver settings, please refer to FluidSettings Documentation. +the MIDI subsystems used. For a full list of available midi driver settings, please refer to the \ref settings_midi documentation. @@ -442,7 +442,7 @@ int main(int argc, char** argv) \endcode -A list of available MIDI player settings can be found in FluidSettings Documentation. +A list of available MIDI player settings can be found in the \ref settings_player documentation. @@ -497,7 +497,7 @@ delete_fluid_synth(synth); delete_fluid_settings(settings); \endcode -Various output files types are supported, if compiled with libsndfile. Those can be specified via the \c settings object as well. Refer to the FluidSettings Documentation for more \c audio.file\.\* options. +Various output files types are supported, if compiled with libsndfile. Those can be specified via the \c settings object as well. Refer to the \ref settings_audio documentation for more \c audio.file\.\* options. \page MIDIPlayerMem Playing a MIDI file from memory @@ -744,7 +744,7 @@ int main(void) { \page Shell Shell interface -The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, type @c help in the fluidsynth command line shell. For a full list of available command line settings, please refer to FluidSettings Documentation. +The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, type @c help in the fluidsynth command line shell. For a full list of available command line settings, please refer to the \ref settings_shell documentation. \page Multi-channel Multi-Channel audio rendering From af682886286bbc4bb43b6cae15ea6e6203143d8a Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 10 Nov 2020 23:39:33 +0100 Subject: [PATCH 11/55] Command Shell group for all shell related commands With subgroups for command handler, shell and server. --- include/fluidsynth/shell.h | 70 +++++++++++++++++++++++++++----------- src/bindings/fluid_cmd.c | 27 ++++++++++++--- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/include/fluidsynth/shell.h b/include/fluidsynth/shell.h index 02430536..63ca23a8 100644 --- a/include/fluidsynth/shell.h +++ b/include/fluidsynth/shell.h @@ -28,25 +28,35 @@ extern "C" { /** - * @defgroup Shell Command Shell - * @brief Command shell interface + * @defgroup command_interface Command Interface * - * The shell interface allows you to send simple textual commands to + * Control and configuration interface + * + * The command interface allows you to send textual commands to * the synthesizer, to parse a command file, or to read commands - * from the stdin or other input streams. + * from the stdin or other input streams (like a TCP socket). + * + * For a full list of available commands, type \c help in the + * \ref command_shell or send the same command via a command handler. + * Further documentation can be found at + * https://github.com/FluidSynth/fluidsynth/wiki/UserManual#shell-commands * * @{ */ - FLUIDSYNTH_API fluid_istream_t fluid_get_stdin(void); FLUIDSYNTH_API fluid_ostream_t fluid_get_stdout(void); - FLUIDSYNTH_API char *fluid_get_userconf(char *buf, int len); FLUIDSYNTH_API char *fluid_get_sysconf(char *buf, int len); +/* @} */ -/* The command handler */ - +/** + * @defgroup command_handler Command Handler + * @ingroup command_interface + * @brief Handles text commands and reading of configuration files + * + * @{ + */ FLUIDSYNTH_API fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_router_t *router); @@ -56,33 +66,54 @@ void delete_fluid_cmd_handler(fluid_cmd_handler_t *handler); FLUIDSYNTH_API void fluid_cmd_handler_set_synth(fluid_cmd_handler_t *handler, fluid_synth_t *synth); - - -/* Command function */ - FLUIDSYNTH_API int fluid_command(fluid_cmd_handler_t *handler, const char *cmd, fluid_ostream_t out); FLUIDSYNTH_API int fluid_source(fluid_cmd_handler_t *handler, const char *filename); +/* @} */ + +/** + * @defgroup command_shell Command Shell + * @ingroup command_interface + * + * Interactive shell to control and configure a synthesizer instance. + * + * If you need a platform independent way to get the standard input + * and output streams, use fluid_get_stdin() and fluid_get_stdout(). + * + * For a full list of available commands, type \c help in the shell. + * + * @{ + */ FLUIDSYNTH_API void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler); - -/* Shell */ - FLUIDSYNTH_API fluid_shell_t *new_fluid_shell(fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out, int thread); FLUIDSYNTH_API void delete_fluid_shell(fluid_shell_t *shell); +/* @} */ - -/* TCP/IP server */ - - +/** + * @defgroup command_server Command Server + * @ingroup command_interface + * + * TCP socket server for a command handler. + * + * The socket server will open the TCP port set by \ref settings_shell_port + * (default 9800) and starts a new thread and \ref command_handler for each + * incoming connection. + * + * @note The server is only available if libfluidsynth has been compiled + * with network support (enable-network). Without network support, all related + * functions will return FLUID_FAILED or NULL. + * + * @{ + */ FLUIDSYNTH_API fluid_server_t *new_fluid_server(fluid_settings_t *settings, fluid_synth_t *synth, fluid_midi_router_t *router); @@ -90,7 +121,6 @@ fluid_server_t *new_fluid_server(fluid_settings_t *settings, FLUIDSYNTH_API void delete_fluid_server(fluid_server_t *server); FLUIDSYNTH_API int fluid_server_join(fluid_server_t *server); - /* @} */ #ifdef __cplusplus diff --git a/src/bindings/fluid_cmd.c b/src/bindings/fluid_cmd.c index 847ec7f0..968f53dc 100644 --- a/src/bindings/fluid_cmd.c +++ b/src/bindings/fluid_cmd.c @@ -386,12 +386,14 @@ static const fluid_cmd_t fluid_commands[] = /** * Process a string command. - * NOTE: FluidSynth 1.0.8 and above no longer modifies the 'cmd' string. + * * @param handler FluidSynth command handler * @param cmd Command string (NOTE: Gets modified by FluidSynth prior to 1.0.8) * @param out Output stream to display command response to * @return Integer value corresponding to: -1 on command error, 0 on success, * 1 if 'cmd' is a comment or is empty and -2 if quit was issued + * + * @note FluidSynth 1.0.8 and above no longer modifies the 'cmd' string. */ int fluid_command(fluid_cmd_handler_t *handler, const char *cmd, fluid_ostream_t out) @@ -418,6 +420,7 @@ fluid_command(fluid_cmd_handler_t *handler, const char *cmd, fluid_ostream_t out /** * Create a new FluidSynth command shell. + * * @param settings Setting parameters to use with the shell * @param handler Command handler * @param in Input stream @@ -550,8 +553,12 @@ fluid_shell_run(void *data) /** * A convenience function to create a shell interfacing to standard input/output * console streams. + * * @param settings Settings instance for the shell * @param handler Command handler callback + * + * The shell is run in the current thread, this function will only + * return after the \c quit command has been issued. */ void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler) @@ -563,6 +570,7 @@ fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler) /** * Execute shell commands in a file. + * * @param handler Command handler callback * @param filename File name * @return 0 on success, a negative value on error @@ -600,11 +608,12 @@ fluid_source(fluid_cmd_handler_t *handler, const char *filename) /** * Get the user specific FluidSynth command file name. * - * On Windows this is currently @c "%USERPROFILE%\fluidsynth.cfg". - * For anything else (except MACOS9) @c "$HOME/.fluidsynth". * @param buf Caller supplied string buffer to store file name to. * @param len Length of \a buf * @return Returns \a buf pointer or NULL if no user command file for this system type. + * + * On Windows this is currently @c "%USERPROFILE%\fluidsynth.cfg". + * For anything else (except MACOS9) @c "$HOME/.fluidsynth". */ char * fluid_get_userconf(char *buf, int len) @@ -635,11 +644,12 @@ fluid_get_userconf(char *buf, int len) /** * Get the system FluidSynth command file name. * - * Windows and MACOS9 do not have a system-wide config file currently. For anything else it - * returns @c "/etc/fluidsynth.conf". * @param buf Caller supplied string buffer to store file name to. * @param len Length of \a buf * @return Returns \a buf pointer or NULL if no system command file for this system type. + * + * Windows and MACOS9 do not have a system-wide config file currently. For anything else it + * returns @c "/etc/fluidsynth.conf". */ char * fluid_get_sysconf(char *buf, int len) @@ -4151,6 +4161,7 @@ fluid_cmd_handler_destroy_hash_value(void *value) /** * Create a new command handler. + * * @param synth If not NULL, all the default synthesizer commands will be added to the new handler. * @param router If not NULL, all the default midi_router commands will be added to the new handler. * @return New command handler, or NULL if alloc failed @@ -4200,6 +4211,7 @@ fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_rout /** * Delete a command handler. + * * @param handler Command handler to delete */ void @@ -4213,6 +4225,7 @@ delete_fluid_cmd_handler(fluid_cmd_handler_t *handler) /** * Register a new command to the handler. + * * @param handler Command handler instance * @param cmd Command info (gets copied) * @return #FLUID_OK if command was inserted, #FLUID_FAILED otherwise @@ -4227,6 +4240,7 @@ fluid_cmd_handler_register(fluid_cmd_handler_t *handler, const fluid_cmd_t *cmd) /** * Unregister a command from a command handler. + * * @param handler Command handler instance * @param cmd Name of the command * @return TRUE if command was found and unregistered, FALSE otherwise @@ -4415,6 +4429,7 @@ void delete_fluid_client(fluid_client_t *client) /** * Create a new TCP/IP command shell server. + * * @param settings Settings instance to use for the shell * @param synth If not NULL, the synth instance for the command handler to be used by the client * @param router If not NULL, the midi_router instance for the command handler to be used by the client @@ -4464,6 +4479,7 @@ new_fluid_server(fluid_settings_t *settings, /** * Delete a TCP/IP shell server. + * * @param server Shell server instance */ void @@ -4480,6 +4496,7 @@ delete_fluid_server(fluid_server_t *server) /** * Join a shell server thread (wait until it quits). + * * @param server Shell server instance * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ From 66b7c198936cc7f07689d247356996959de80868 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 10 Nov 2020 23:53:54 +0100 Subject: [PATCH 12/55] Audio output group With subgroups Audio Driver and File Renderer --- include/fluidsynth/audio.h | 63 ++++++++++++++++++++++++------- src/bindings/fluid_filerenderer.c | 18 +++++---- src/drivers/fluid_adriver.c | 14 ++++--- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/include/fluidsynth/audio.h b/include/fluidsynth/audio.h index f02b24ab..0ab5e596 100644 --- a/include/fluidsynth/audio.h +++ b/include/fluidsynth/audio.h @@ -26,12 +26,27 @@ extern "C" { #endif /** - * @defgroup Audio Audio Output - * @brief Functions for audio driver output. + * @defgroup audio_output Audio Output + * + * Functions for managing audio drivers and file renderers. + * + * The file renderer is used for fast rendering of MIDI files to + * audio files. The audio drivers are a high-level interface to + * connect the synthesizer with external audio sinks or to render + * real-time audio to files. + */ + +/** + * @defgroup audio_driver Audio Driver + * @ingroup audio_output + * + * Functions for managing audio drivers. * * Defines functions for creating audio driver output. Use * new_fluid_audio_driver() to create a new audio driver for a given synth - * and configuration settings. The function new_fluid_audio_driver2() can be + * and configuration settings. + * + * The function new_fluid_audio_driver2() can be * used if custom audio processing is desired before the audio is sent to the * audio driver (although it is not as efficient). * @@ -42,14 +57,8 @@ extern "C" { /** * Callback function type used with new_fluid_audio_driver2() to allow for - * custom user audio processing before the audio is sent to the driver. This - * function is responsible for rendering audio to the buffers. - * The buffers passed to this function are allocated and owned by the respective - * audio driver and are only valid during that specific call (do not cache them). - * For further details please refer to fluid_synth_process(). - * @note Whereas fluid_synth_process() allows aliasing buffers, there is the guarentee that @p out - * and @p fx buffers provided by fluidsynth's audio drivers never alias. This prevents downstream - * applications from e.g. applying a custom effect accidentially to the same buffer multiple times. + * custom user audio processing before the audio is sent to the driver. + * * @param data The user data parameter as passed to new_fluid_audio_driver2(). * @param len Count of audio frames to synthesize. * @param nfx Count of arrays in \c fx. @@ -57,6 +66,16 @@ extern "C" { * @param nout Count of arrays in \c out. * @param out Array of buffers to store (dry) audio to. Buffers may alias with buffers of \c fx. * @return Should return #FLUID_OK on success, #FLUID_FAILED if an error occurred. + * + * This function is responsible for rendering audio to the buffers. + * The buffers passed to this function are allocated and owned by the respective + * audio driver and are only valid during that specific call (do not cache them). + * For further details please refer to fluid_synth_process(). + * + * @note Whereas fluid_synth_process() allows aliasing buffers, there is the guarentee that @p out + * and @p fx buffers provided by fluidsynth's audio drivers never alias. This prevents downstream + * applications from e.g. applying a custom effect accidentially to the same buffer multiple times. + * */ typedef int (*fluid_audio_func_t)(void *data, int len, int nfx, float *fx[], @@ -71,13 +90,29 @@ FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver2(fluid_settings_t *s FLUIDSYNTH_API void delete_fluid_audio_driver(fluid_audio_driver_t *driver); +FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); +/* @} */ + +/** + * @defgroup file_renderer File Renderer + * @ingroup audio_output + * + * Functions for managing file renderers and triggering the rendering. + * + * The file renderer is only used to render a MIDI file to audio as fast + * as possible. Please see \ref FileRenderer for a full example. + * + * If you are looking for a way to write audio generated + * from real-time events (for example from an external sequencer or a MIDI controller) to a file, + * please have a look at the \c file \ref audio_driver instead. + * + * + * @{ + */ FLUIDSYNTH_API fluid_file_renderer_t *new_fluid_file_renderer(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_file_renderer_process_block(fluid_file_renderer_t *dev); FLUIDSYNTH_API void delete_fluid_file_renderer(fluid_file_renderer_t *dev); FLUIDSYNTH_API int fluid_file_set_encoding_quality(fluid_file_renderer_t *dev, double q); - -FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); - /* @} */ #ifdef __cplusplus diff --git a/src/bindings/fluid_filerenderer.c b/src/bindings/fluid_filerenderer.c index 83805da0..8c244386 100644 --- a/src/bindings/fluid_filerenderer.c +++ b/src/bindings/fluid_filerenderer.c @@ -171,22 +171,24 @@ fluid_file_renderer_settings(fluid_settings_t *settings) /** * Create a new file renderer and open the file. + * * @param synth The synth that creates audio data. * @return the new object, or NULL on failure - * @since 1.1.0 * - * NOTE: Available file types and formats depends on if libfluidsynth was + * @note Available file types and formats depends on if libfluidsynth was * built with libsndfile support or not. If not then only RAW 16 bit output is * supported. * * Uses the following settings from the synth object: - * - audio.file.name: Output filename - * - audio.file.type: File type, "auto" tries to determine type from filename + * - \ref settings_audio_file_name : Output filename + * - \ref settings_audio_file_type : File type, "auto" tries to determine type from filename * extension with fallback to "wav". - * - audio.file.format: Audio format - * - audio.file.endian: Endian byte order, "auto" for file type's default byte order - * - audio.period-size: Size of audio blocks to process - * - synth.sample-rate: Sample rate to use + * - \ref settings_audio_file_format : Audio format + * - \ref settings_audio_file_endian : Endian byte order, "auto" for file type's default byte order + * - \ref settings_audio_period-size : Size of audio blocks to process + * - \ref settings_synth_sample-rate : Sample rate to use + * + * @since 1.1.0 */ fluid_file_renderer_t * new_fluid_file_renderer(fluid_synth_t *synth) diff --git a/src/drivers/fluid_adriver.c b/src/drivers/fluid_adriver.c index c2d1f093..369e23f5 100644 --- a/src/drivers/fluid_adriver.c +++ b/src/drivers/fluid_adriver.c @@ -288,6 +288,7 @@ find_fluid_audio_driver(fluid_settings_t *settings) /** * Create a new audio driver. + * * @param settings Configuration settings used to select and create the audio * driver. * @param synth Synthesizer instance for which the audio driver is created for. @@ -326,6 +327,7 @@ new_fluid_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) /** * Create a new audio driver. + * * @param settings Configuration settings used to select and create the audio * driver. * @param func Function called to fill audio buffers for audio playback @@ -378,6 +380,7 @@ new_fluid_audio_driver2(fluid_settings_t *settings, fluid_audio_func_t func, voi /** * Deletes an audio driver instance. + * * @param driver Audio driver instance to delete * * Shuts down an audio driver and deletes its instance. @@ -391,7 +394,11 @@ delete_fluid_audio_driver(fluid_audio_driver_t *driver) /** - * @brief Registers audio drivers to use + * Registers audio drivers to use + * + * @param adrivers NULL-terminated array of audio drivers to register. Pass NULL to register all available drivers. + * @return #FLUID_OK if all the audio drivers requested by the user are supported by fluidsynth and have been + * successfully registered. Otherwise #FLUID_FAILED is returned and this function has no effect. * * When creating a settings instance with new_fluid_settings(), all audio drivers are initialized once. * In the past this has caused segfaults and application crashes due to buggy soundcard drivers. @@ -407,11 +414,8 @@ delete_fluid_audio_driver(fluid_audio_driver_t *driver) * are alive (e.g. as it would be the case right after fluidsynth's initial creation). Else the behaviour is undefined. * Furtermore any attempt of using audio drivers that have not been registered is undefined behaviour! * - * @param adrivers NULL-terminated array of audio drivers to register. Pass NULL to register all available drivers. - * @return #FLUID_OK if all the audio drivers requested by the user are supported by fluidsynth and have been - * successfully registered. Otherwise #FLUID_FAILED is returned and this function has no effect. - * * @note This function is not thread safe and will never be! + * * @since 1.1.9 */ int fluid_audio_driver_register(const char **adrivers) From bae3dec78acef58fb7eb7deeefae6ca0bb20ee2a Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Tue, 10 Nov 2020 23:58:44 +0100 Subject: [PATCH 13/55] Logging interface --- include/fluidsynth/log.h | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/include/fluidsynth/log.h b/include/fluidsynth/log.h index 05043948..ec553ba4 100644 --- a/include/fluidsynth/log.h +++ b/include/fluidsynth/log.h @@ -28,26 +28,30 @@ extern "C" { /** - * @defgroup Logging Logging - * @brief Logging interface + * @defgroup logging Logging * - * The default logging function of the fluidsynth prints its messages - * to the stderr. The synthesizer uses five level of messages: #FLUID_PANIC, + * Logging interface + * + * The default logging function of the fluidsynth prints its messages to the + * stderr. The synthesizer uses five level of messages: #FLUID_PANIC, * #FLUID_ERR, #FLUID_WARN, #FLUID_INFO, and #FLUID_DBG. * - * A client application can install a new log function to handle the - * messages differently. In the following example, the application - * sets a callback function to display #FLUID_PANIC messages in a dialog, - * and ignores all other messages by setting the log function to - * NULL: + * A client application can install a new log function to handle the messages + * differently. In the following example, the application sets a callback + * function to display #FLUID_PANIC messages in a dialog, and ignores all other + * messages by setting the log function to NULL: * * @code - * fluid_set_log_function(FLUID_PANIC, show_dialog, (void*) root_window); - * fluid_set_log_function(FLUID_ERR, NULL, NULL); - * fluid_set_log_function(FLUID_WARN, NULL, NULL); - * fluid_set_log_function(FLUID_DBG, NULL, NULL); + * fluid_set_log_function(FLUID_PANIC, show_dialog, (void*) root_window); + * fluid_set_log_function(FLUID_ERR, NULL, NULL); + * fluid_set_log_function(FLUID_WARN, NULL, NULL); + * fluid_set_log_function(FLUID_DBG, NULL, NULL); * @endcode * + * @note The logging configuration is global and not tied to a specific + * synthesizer instance. That means that all synthesizer instances created in + * the same process share the same logging configuration. + * * @{ */ @@ -61,13 +65,13 @@ enum fluid_log_level FLUID_WARN, /**< Warning */ FLUID_INFO, /**< Verbose informational messages */ FLUID_DBG, /**< Debugging messages */ -#ifndef __DOXYGEN__ - LAST_LOG_LEVEL /**< @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ -#endif + LAST_LOG_LEVEL /**< @internal This symbol is not part of the public API and ABI + stability guarantee and may change at any time! */ }; /** * Log function handler callback type used by fluid_set_log_function(). + * * @param level Log level (#fluid_log_level) * @param message Log message text * @param data User data pointer supplied to fluid_set_log_function(). @@ -84,7 +88,6 @@ FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))) #endif ; - /* @} */ #ifdef __cplusplus From 4185b25d6f4e8e627b2f6495b897d7c950939b5d Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 00:39:35 +0100 Subject: [PATCH 14/55] MIDI input group Contains MIDI Driver, MIDI Router, MIDI Player and MIDI Events --- include/fluidsynth/midi.h | 106 +++++++++++++++++++++++++++-------- src/drivers/fluid_mdriver.c | 5 +- src/midi/fluid_midi.c | 31 ++++++---- src/midi/fluid_midi_router.c | 39 +++++++++---- 4 files changed, 136 insertions(+), 45 deletions(-) diff --git a/include/fluidsynth/midi.h b/include/fluidsynth/midi.h index 945dc49d..43253df0 100644 --- a/include/fluidsynth/midi.h +++ b/include/fluidsynth/midi.h @@ -26,12 +26,51 @@ extern "C" { #endif /** - * @defgroup MIDI MIDI - * @brief Functions for MIDI events, drivers and MIDI file playback. + * @defgroup midi_input MIDI Input + * + * MIDI Input Subsystem + * + * There are multiple ways to send MIDI events to the synthesizer. They can come + * from MIDI files, from external MIDI sequencers or raw MIDI event sources, + * can be modified via MIDI routers and also generated manually. + * + * The interface connecting all sources and sinks of MIDI events in libfluidsynth + * is \ref handle_midi_event_func_t. * * @{ */ +/** + * Generic callback function for MIDI event handler. + * + * @param data User defined data pointer + * @param event The MIDI event + * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * This callback is used to pass MIDI events + * - from \ref midi_player, \ref midi_router or \ref midi_driver + * - to \ref midi_router via fluid_midi_router_handle_midi_event() + * - or to \ref synth via fluid_synth_handle_midi_event(). + * + * Additionally, there is a translation layer to pass MIDI events to + * a \ref sequencer via fluid_sequencer_add_midi_event_to_buffer(). + */ +typedef int (*handle_midi_event_func_t)(void *data, fluid_midi_event_t *event); +/* @} */ + +/** + * @defgroup midi_events MIDI Events + * @ingroup midi_input + * + * Functions to create, modify, query and delete MIDI events. + * + * These functions are intended to be used in MIDI routers and other filtering + * and processing functions in the MIDI event path. If you want to simply + * send MIDI messages to the synthesizer, you can use the more convenient + * \ref midi_messages interface. + * + * @{ + */ FLUIDSYNTH_API fluid_midi_event_t *new_fluid_midi_event(void); FLUIDSYNTH_API void delete_fluid_midi_event(fluid_midi_event_t *event); @@ -61,9 +100,20 @@ FLUIDSYNTH_API int fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic); FLUIDSYNTH_API int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, void **data, int *size); +/* @} */ + +/** + * @defgroup midi_router MIDI Router + * @ingroup midi_input + * + * Rule based tranformation and filtering of MIDI events. + * + * @{ + */ /** * MIDI router rule type. + * * @since 1.1.0 */ typedef enum @@ -74,24 +124,11 @@ typedef enum FLUID_MIDI_ROUTER_RULE_PITCH_BEND, /**< MIDI pitch bend rule */ FLUID_MIDI_ROUTER_RULE_CHANNEL_PRESSURE, /**< MIDI channel pressure rule */ FLUID_MIDI_ROUTER_RULE_KEY_PRESSURE, /**< MIDI key pressure rule */ -#ifndef __DOXYGEN__ - FLUID_MIDI_ROUTER_RULE_COUNT /**< @internal Total count of rule types @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time!*/ -#endif + FLUID_MIDI_ROUTER_RULE_COUNT /**< @internal Total count of rule types. This symbol + is not part of the public API and ABI stability + guarantee and may change at any time!*/ } fluid_midi_router_rule_type; -/** - * Generic callback function for MIDI events. - * @param data User defined data pointer - * @param event The MIDI event - * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise - * - * Will be used between - * - MIDI driver and MIDI router - * - MIDI router and synth - * to communicate events. - * In the not-so-far future... - */ -typedef int (*handle_midi_event_func_t)(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API fluid_midi_router_t *new_fluid_midi_router(fluid_settings_t *settings, handle_midi_event_func_t handler, @@ -112,15 +149,43 @@ FLUIDSYNTH_API void fluid_midi_router_rule_set_param2(fluid_midi_router_rule_t * FLUIDSYNTH_API int fluid_midi_router_handle_midi_event(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API int fluid_midi_dump_prerouter(void *data, fluid_midi_event_t *event); FLUIDSYNTH_API int fluid_midi_dump_postrouter(void *data, fluid_midi_event_t *event); +/* @} */ - +/** + * @defgroup midi_driver MIDI Driver + * @ingroup midi_input + * + * Functions for managing MIDI drivers. + * + * The available MIDI drivers depend on your platform. See \ref settings_midi for all + * available configuration options. + * + * To create a MIDI driver, you need to specify a source for the MIDI events to be + * forwarded to via the \ref fluid_midi_event_t callback. Normally this will be + * either a \ref midi_router via fluid_midi_router_handle_midi_event() or the synthesizer + * via fluid_synth_handle_midi_event(). + * + * But you can also write your own handler function that preprocesses the events and + * forwards them on to the router or synthesizer instead. + * + * @{ + */ FLUIDSYNTH_API fluid_midi_driver_t *new_fluid_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); FLUIDSYNTH_API void delete_fluid_midi_driver(fluid_midi_driver_t *driver); +/* @} */ +/** + * @defgroup midi_player MIDI Player + * @ingroup midi_input + * + * Parse standard MIDI files and emit MIDI events. + * + * @{ + */ /** * MIDI player status enum. @@ -151,9 +216,6 @@ FLUIDSYNTH_API int fluid_player_get_total_ticks(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_bpm(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_get_midi_tempo(fluid_player_t *player); FLUIDSYNTH_API int fluid_player_seek(fluid_player_t *player, int ticks); - -/// - /* @} */ #ifdef __cplusplus diff --git a/src/drivers/fluid_mdriver.c b/src/drivers/fluid_mdriver.c index 79b20970..12b69204 100644 --- a/src/drivers/fluid_mdriver.c +++ b/src/drivers/fluid_mdriver.c @@ -135,11 +135,14 @@ void fluid_midi_driver_settings(fluid_settings_t *settings) /** * Create a new MIDI driver instance. - * @param settings Settings used to configure new MIDI driver. + * + * @param settings Settings used to configure new MIDI driver. See \ref settings_midi for available options. * @param handler MIDI handler callback (for example: fluid_midi_router_handle_midi_event() * for MIDI router) * @param event_handler_data Caller defined data to pass to 'handler' * @return New MIDI driver instance or NULL on error + * + * Which MIDI driver is actually created depends on the \ref settings_midi_driver option. */ fluid_midi_driver_t *new_fluid_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data) { diff --git a/src/midi/fluid_midi.c b/src/midi/fluid_midi.c index c9a0c4ef..fc61460e 100644 --- a/src/midi/fluid_midi.c +++ b/src/midi/fluid_midi.c @@ -1798,16 +1798,18 @@ fluid_player_add_track(fluid_player_t *player, fluid_track_t *track) } /** - * Change the MIDI callback function. This is usually set to - * fluid_synth_handle_midi_event(), but can optionally be changed - * to a user-defined function instead, for intercepting all MIDI - * messages sent to the synth. You can also use a midi router as - * the callback function to modify the MIDI messages before sending - * them to the synth. + * Change the MIDI callback function. + * * @param player MIDI player instance * @param handler Pointer to callback function * @param handler_data Parameter sent to the callback function * @returns FLUID_OK + * + * This is usually set to fluid_synth_handle_midi_event(), but can optionally + * be changed to a user-defined function instead, for intercepting all MIDI + * messages sent to the synth. You can also use a midi router as the callback + * function to modify the MIDI messages before sending them to the synth. + * * @since 1.1.4 */ int @@ -2140,9 +2142,10 @@ fluid_player_play(fluid_player_t *player) /** * Pauses the MIDI playback. * - * It will not rewind to the beginning of the file, use fluid_player_seek() for this purpose. * @param player MIDI player instance * @return Always returns #FLUID_OK + * + * It will not rewind to the beginning of the file, use fluid_player_seek() for this purpose. */ int fluid_player_stop(fluid_player_t *player) @@ -2167,15 +2170,17 @@ fluid_player_get_status(fluid_player_t *player) /** * Seek in the currently playing file. * - * The actual seek will be performed when the synth calls back the player (i.e. a few - * levels above the player's callback set with fluid_player_set_playback_callback()). - * If the player's status is #FLUID_PLAYER_PLAYING and a previous seek operation has - * not been completed yet, #FLUID_FAILED is returned. * @param player MIDI player instance * @param ticks the position to seek to in the current file * @return #FLUID_FAILED if ticks is negative or after the latest tick of the file * [or, since 2.1.3, if another seek operation is currently in progress], * #FLUID_OK otherwise. + * + * The actual seek will be performed when the synth calls back the player (i.e. a few + * levels above the player's callback set with fluid_player_set_playback_callback()). + * If the player's status is #FLUID_PLAYER_PLAYING and a previous seek operation has + * not been completed yet, #FLUID_FAILED is returned. + * * @since 2.0.0 */ int fluid_player_seek(fluid_player_t *player, int ticks) @@ -2210,13 +2215,15 @@ int fluid_player_seek(fluid_player_t *player, int ticks) /** * Enable looping of a MIDI player + * * @param player MIDI player instance * @param loop Times left to loop the playlist. -1 means loop infinitely. * @return Always returns #FLUID_OK - * @since 1.1.0 * * For example, if you want to loop the playlist twice, set loop to 2 * and call this function before you start the player. + * + * @since 1.1.0 */ int fluid_player_set_loop(fluid_player_t *player, int loop) { diff --git a/src/midi/fluid_midi_router.c b/src/midi/fluid_midi_router.c index f946b837..de6092cb 100644 --- a/src/midi/fluid_midi_router.c +++ b/src/midi/fluid_midi_router.c @@ -68,12 +68,15 @@ struct _fluid_midi_router_rule_t /** - * Create a new midi router. The default rules will pass all events unmodified. + * Create a new midi router. + * * @param settings Settings used to configure MIDI router * @param handler MIDI event callback. * @param event_handler_data Caller defined data pointer which gets passed to 'handler' * @return New MIDI router instance or NULL on error * + * The new router will start with default rules and therefore pass all events unmodified. + * * The MIDI handler callback should process the possibly filtered/modified MIDI * events from the MIDI router and forward them on to a synthesizer for example. * The function fluid_synth_handle_midi_event() can be used for \a handle and @@ -151,10 +154,13 @@ delete_fluid_midi_router(fluid_midi_router_t *router) } /** - * Set a MIDI router to use default "unity" rules. Such a router will pass all - * events unmodified. + * Set a MIDI router to use default "unity" rules. + * * @param router Router to set to default rules. * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * Such a router will pass all events unmodified. + * * @since 1.1.0 */ int @@ -244,10 +250,13 @@ fluid_midi_router_set_default_rules(fluid_midi_router_t *router) } /** - * Clear all rules in a MIDI router. Such a router will drop all events until - * rules are added. + * Clear all rules in a MIDI router. + * * @param router Router to clear all rules from * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * An empty router will drop all events until rules are added. + * * @since 1.1.0 */ int @@ -357,11 +366,13 @@ fluid_midi_router_add_rule(fluid_midi_router_t *router, fluid_midi_router_rule_t /** * Create a new MIDI router rule. + * * @return Newly allocated router rule or NULL if out of memory. - * @since 1.1.0 * * The new rule is a "unity" rule which will accept any values and wont modify * them. + * + * @since 1.1.0 */ fluid_midi_router_rule_t * new_fluid_midi_router_rule(void) @@ -396,11 +407,13 @@ new_fluid_midi_router_rule(void) /** * Free a MIDI router rule. + * * @param rule Router rule to free - * @since 1.1.0 * * Note that rules which have been added to a router are managed by the router, * so this function should seldom be needed. + * + * @since 1.1.0 */ void delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule) @@ -411,12 +424,12 @@ delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule) /** * Set the channel portion of a rule. + * * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's channel value (1.0 to not modify) * @param add Value which is added to matching event's channel value (0 to not modify) - * @since 1.1.0 * * The \a min and \a max parameters define a channel range window to match * incoming events to. If \a min is less than or equal to \a max then an event @@ -426,6 +439,8 @@ delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule) * * The \a mul and \a add values are used to modify event channel values prior to * sending the event, if the rule matches. + * + * @since 1.1.0 */ void fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max, @@ -440,12 +455,12 @@ fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max /** * Set the first parameter portion of a rule. + * * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's 1st parameter value (1.0 to not modify) * @param add Value which is added to matching event's 1st parameter value (0 to not modify) - * @since 1.1.0 * * The 1st parameter of an event depends on the type of event. For note events * its the MIDI note #, for CC events its the MIDI control number, for program @@ -464,6 +479,8 @@ fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max * * The \a mul and \a add values are used to modify event 1st parameter values prior to * sending the event, if the rule matches. + * + * @since 1.1.0 */ void fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, int min, int max, @@ -478,12 +495,12 @@ fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, int min, int m /** * Set the second parameter portion of a rule. + * * @param rule MIDI router rule * @param min Minimum value for rule match * @param max Maximum value for rule match * @param mul Value which is multiplied by matching event's 2nd parameter value (1.0 to not modify) * @param add Value which is added to matching event's 2nd parameter value (0 to not modify) - * @since 1.1.0 * * The 2nd parameter of an event depends on the type of event. For note events * its the MIDI velocity, for CC events its the control value and for key pressure @@ -499,6 +516,8 @@ fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, int min, int m * * The \a mul and \a add values are used to modify event 2nd parameter values prior to * sending the event, if the rule matches. + * + * @since 1.1.0 */ void fluid_midi_router_rule_set_param2(fluid_midi_router_rule_t *rule, int min, int max, From 39ae70793a155bc803f84385aa34454cb250d84a Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 22:09:43 +0100 Subject: [PATCH 15/55] MIDI Seqencer documentation --- include/fluidsynth/event.h | 13 +++++----- include/fluidsynth/seq.h | 12 +++++++--- include/fluidsynth/seqbind.h | 6 +---- src/midi/fluid_seq.c | 43 ++++++++++++++++++++++++++------- src/midi/fluid_seqbind.c | 46 ++++++++++++++++++++---------------- 5 files changed, 75 insertions(+), 45 deletions(-) diff --git a/include/fluidsynth/event.h b/include/fluidsynth/event.h index 9bbab02d..d5638f32 100644 --- a/include/fluidsynth/event.h +++ b/include/fluidsynth/event.h @@ -26,10 +26,10 @@ extern "C" { #endif /** - * @defgroup SequencerEvents Sequencer Events - * @brief Sequencer event functions and defines. + * @defgroup sequencer_events Sequencer Events + * @ingroup sequencer * - * Functions and constants for creating/processing sequencer events. + * Create, modify, query and destroy sequencer events. * * @{ */ @@ -61,9 +61,9 @@ enum fluid_seq_event_type FLUID_SEQ_KEYPRESSURE, /**< Polyphonic aftertouch event @since 2.0.0 */ FLUID_SEQ_SYSTEMRESET, /**< System reset event @since 1.1.0 */ FLUID_SEQ_UNREGISTERING, /**< Called when a sequencer client is being unregistered. @since 1.1.0 */ -#ifndef __DOXYGEN__ - FLUID_SEQ_LASTEVENT /**< @internal Defines the count of events enums @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ -#endif + FLUID_SEQ_LASTEVENT /**< @internal Defines the count of events enums @warning This symbol + is not part of the public API and ABI stability guarantee and + may change at any time! */ }; /* Event alloc/free */ @@ -128,7 +128,6 @@ FLUIDSYNTH_API unsigned int fluid_event_get_duration(fluid_event_t *evt); FLUIDSYNTH_API short fluid_event_get_bank(fluid_event_t *evt); FLUIDSYNTH_API int fluid_event_get_pitch(fluid_event_t *evt); FLUIDSYNTH_API unsigned int fluid_event_get_sfont_id(fluid_event_t *evt); - /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/seq.h b/include/fluidsynth/seq.h index f58fe43b..5b335744 100644 --- a/include/fluidsynth/seq.h +++ b/include/fluidsynth/seq.h @@ -26,14 +26,21 @@ extern "C" { #endif /** - * @defgroup Sequencer Sequencer - * @brief MIDI event sequencer. + * @defgroup sequencer MIDI Sequencer + * + * MIDI event sequencer. + * + * The MIDI sequencer can be used to play MIDI events in a more flexible way than + * using the MIDI file player, which expects the events to be stored as + * Standard MIDI Files. Using the sequencer, you can provide the events one by + * one, with an optional timestamp for scheduling. * * @{ */ /** * Event callback prototype for destination clients. + * * @param time Current sequencer tick value (see fluid_sequencer_get_tick()). * @param event The event being received * @param seq The sequencer instance @@ -65,7 +72,6 @@ void fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source FLUIDSYNTH_API unsigned int fluid_sequencer_get_tick(fluid_sequencer_t *seq); FLUIDSYNTH_API void fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale); FLUIDSYNTH_API double fluid_sequencer_get_time_scale(fluid_sequencer_t *seq); - /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/seqbind.h b/include/fluidsynth/seqbind.h index c158f24c..ddf1cb9d 100644 --- a/include/fluidsynth/seqbind.h +++ b/include/fluidsynth/seqbind.h @@ -28,18 +28,14 @@ extern "C" { #endif /** - * @defgroup SequencerBind Sequencer Binding - * @ingroup Sequencer - * @brief Functions for binding sequencer objects to other subsystems. + * @addtogroup sequencer * * @{ */ - FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_register_fluidsynth(fluid_sequencer_t *seq, fluid_synth_t *synth); FLUIDSYNTH_API int fluid_sequencer_add_midi_event_to_buffer(void *data, fluid_midi_event_t *event); - /* @} */ #ifdef __cplusplus diff --git a/src/midi/fluid_seq.c b/src/midi/fluid_seq.c index 89a94822..b3c9b6cc 100644 --- a/src/midi/fluid_seq.c +++ b/src/midi/fluid_seq.c @@ -82,10 +82,13 @@ typedef struct _fluid_sequencer_client_t /* API implementation */ /** - * Create a new sequencer object which uses the system timer. Use - * new_fluid_sequencer2() to specify whether the system timer or - * fluid_sequencer_process() is used to advance the sequencer. + * Create a new sequencer object which uses the system timer. + * * @return New sequencer instance + * + * Use new_fluid_sequencer2() to specify whether the system timer or + * fluid_sequencer_process() is used to advance the sequencer. + * * @deprecated As of fluidsynth 2.1.1 the use of the system timer has been deprecated. */ fluid_sequencer_t * @@ -96,12 +99,15 @@ new_fluid_sequencer(void) /** * Create a new sequencer object. + * * @param use_system_timer If TRUE, sequencer will advance at the rate of the * system clock. If FALSE, call fluid_sequencer_process() to advance * the sequencer. * @return New sequencer instance - * @since 1.1.0 + * * @note As of fluidsynth 2.1.1 the use of the system timer has been deprecated. + * + * @since 1.1.0 */ fluid_sequencer_t * new_fluid_sequencer2(int use_system_timer) @@ -142,8 +148,10 @@ new_fluid_sequencer2(int use_system_timer) /** * Free a sequencer object. - * @note Before fluidsynth 2.1.1 registered sequencer clients may not be fully freed by this function. + * * @param seq Sequencer to delete + * + * @note Before fluidsynth 2.1.1 registered sequencer clients may not be fully freed by this function. */ void delete_fluid_sequencer(fluid_sequencer_t *seq) @@ -165,10 +173,13 @@ delete_fluid_sequencer(fluid_sequencer_t *seq) /** * Check if a sequencer is using the system timer or not. + * * @param seq Sequencer object * @return TRUE if system timer is being used, FALSE otherwise. - * @since 1.1.0 + * * @deprecated As of fluidsynth 2.1.1 the usage of the system timer has been deprecated. + * + * @since 1.1.0 */ int fluid_sequencer_get_use_system_timer(fluid_sequencer_t *seq) @@ -183,6 +194,7 @@ fluid_sequencer_get_use_system_timer(fluid_sequencer_t *seq) /** * Register a sequencer client. + * * @param seq Sequencer object * @param name Name of sequencer client * @param callback Sequencer client callback or NULL for a source client. @@ -235,9 +247,10 @@ fluid_sequencer_register_client(fluid_sequencer_t *seq, const char *name, /** * Unregister a previously registered client. * - * The client's callback function will receive a FLUID_SEQ_UNREGISTERING event right before it is being unregistered. * @param seq Sequencer object * @param id Client ID as returned by fluid_sequencer_register_client(). + * + * The client's callback function will receive a FLUID_SEQ_UNREGISTERING event right before it is being unregistered. */ void fluid_sequencer_unregister_client(fluid_sequencer_t *seq, fluid_seq_id_t id) @@ -287,6 +300,7 @@ fluid_sequencer_unregister_client(fluid_sequencer_t *seq, fluid_seq_id_t id) /** * Count a sequencers registered clients. + * * @param seq Sequencer object * @return Count of sequencer clients. */ @@ -303,6 +317,7 @@ fluid_sequencer_count_clients(fluid_sequencer_t *seq) /** * Get a client ID from its index (order in which it was registered). + * * @param seq Sequencer object * @param index Index of register client * @return Client ID or #FLUID_FAILED if not found @@ -329,6 +344,7 @@ fluid_seq_id_t fluid_sequencer_get_client_id(fluid_sequencer_t *seq, int index) /** * Get the name of a registered client. + * * @param seq Sequencer object * @param id Client ID * @return Client name or NULL if not found. String is internal and should not @@ -360,6 +376,7 @@ fluid_sequencer_get_client_name(fluid_sequencer_t *seq, fluid_seq_id_t id) /** * Check if a client is a destination client. + * * @param seq Sequencer object * @param id Client ID * @return TRUE if client is a destination client, FALSE otherwise or if not found @@ -390,6 +407,7 @@ fluid_sequencer_client_is_dest(fluid_sequencer_t *seq, fluid_seq_id_t id) /** * Send an event immediately. + * * @param seq Sequencer object * @param evt Event to send (not copied, used directly) */ @@ -434,6 +452,7 @@ fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt) /** * Schedule an event for sending at a later time. + * * @param seq Sequencer object * @param evt Event to send (will be copied into internal queue) * @param time Time value in ticks (in milliseconds with the default time scale of 1000). @@ -479,6 +498,7 @@ fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, /** * Remove events from the event queue. + * * @param seq Sequencer object * @param source Source client ID to match or -1 for wildcard * @param dest Destination client ID to match or -1 for wildcard @@ -517,6 +537,7 @@ fluid_sequencer_get_tick_LOCAL(fluid_sequencer_t *seq, unsigned int cur_msec) /** * Get the current tick of the sequencer scaled by the time scale currently set. + * * @param seq Sequencer object * @return Current tick value */ @@ -528,6 +549,7 @@ fluid_sequencer_get_tick(fluid_sequencer_t *seq) /** * Set the time scale of a sequencer. + * * @param seq Sequencer object * @param scale Sequencer scale value in ticks per second * (default is 1000 for 1 tick per millisecond) @@ -563,6 +585,7 @@ fluid_sequencer_set_time_scale(fluid_sequencer_t *seq, double scale) /** * Get a sequencer's time scale. + * * @param seq Sequencer object. * @return Time scale value in ticks per second. */ @@ -576,11 +599,13 @@ fluid_sequencer_get_time_scale(fluid_sequencer_t *seq) /** * Advance a sequencer. * + * @param seq Sequencer object + * @param msec Time to advance sequencer to (absolute time since sequencer start). + * * If you have registered the synthesizer as client (fluid_sequencer_register_fluidsynth()), the synth * will take care of calling fluid_sequencer_process(). Otherwise it is up to the user to * advance the sequencer manually. - * @param seq Sequencer object - * @param msec Time to advance sequencer to (absolute time since sequencer start). + * * @since 1.1.0 */ void diff --git a/src/midi/fluid_seqbind.c b/src/midi/fluid_seqbind.c index 2df23ce9..9fb01ffa 100644 --- a/src/midi/fluid_seqbind.c +++ b/src/midi/fluid_seqbind.c @@ -78,6 +78,10 @@ delete_fluid_seqbind(fluid_seqbind_t *seqbind) /** * Registers a synthesizer as a destination client of the given sequencer. * + * @param seq Sequencer instance + * @param synth Synthesizer instance + * @returns Sequencer client ID, or #FLUID_FAILED on error. + * * A convenience wrapper function around fluid_sequencer_register_client(), that allows you to * easily process and render enqueued sequencer events with fluidsynth's synthesizer. * The client being registered will be named @c fluidsynth. @@ -85,26 +89,23 @@ delete_fluid_seqbind(fluid_seqbind_t *seqbind) * @note Implementations are encouraged to explicitly unregister this client either by calling * fluid_sequencer_unregister_client() or by sending an unregistering event to the sequencer. Before * fluidsynth 2.1.1 this was mandatory to avoid memory leaks. -@code{.cpp} -fluid_seq_id_t seqid = fluid_sequencer_register_fluidsynth(seq, synth); - -// ... do work - -fluid_event_t* evt = new_fluid_event(); -fluid_event_set_source(evt, -1); -fluid_event_set_dest(evt, seqid); -fluid_event_unregistering(evt); - -// unregister the "fluidsynth" client immediately -fluid_sequencer_send_now(seq, evt); -delete_fluid_event(evt); -delete_fluid_synth(synth); -delete_fluid_sequencer(seq); -@endcode * - * @param seq Sequencer instance - * @param synth Synthesizer instance - * @returns Sequencer client ID, or #FLUID_FAILED on error. + * @code{.cpp} + * fluid_seq_id_t seqid = fluid_sequencer_register_fluidsynth(seq, synth); + * + * // ... do work + * + * fluid_event_t* evt = new_fluid_event(); + * fluid_event_set_source(evt, -1); + * fluid_event_set_dest(evt, seqid); + * fluid_event_unregistering(evt); + * + * // unregister the "fluidsynth" client immediately + * fluid_sequencer_send_now(seq, evt); + * delete_fluid_event(evt); + * delete_fluid_synth(synth); + * delete_fluid_sequencer(seq); + * @endcode */ fluid_seq_id_t fluid_sequencer_register_fluidsynth(fluid_sequencer_t *seq, fluid_synth_t *synth) @@ -352,12 +353,15 @@ static fluid_seq_id_t get_fluidsynth_dest(fluid_sequencer_t *seq) } /** - * Transforms an incoming midi event (from a midi driver or midi router) to a + * Transforms an incoming MIDI event (from a MIDI driver or MIDI router) to a * sequencer event and adds it to the sequencer queue for sending as soon as possible. - * The signature of this function is of type #handle_midi_event_func_t. + * * @param data The sequencer, must be a valid #fluid_sequencer_t * @param event MIDI event * @return #FLUID_OK or #FLUID_FAILED + * + * The signature of this function is of type #handle_midi_event_func_t. + * * @since 1.1.0 */ int From 4a40695541b7c47645f6178559e3cf73c234b7e3 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 22:17:06 +0100 Subject: [PATCH 16/55] Settings documentation --- include/fluidsynth/settings.h | 8 +++++--- src/utils/fluid_settings.c | 22 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/include/fluidsynth/settings.h b/include/fluidsynth/settings.h index d1552c00..2cfdb1f2 100644 --- a/include/fluidsynth/settings.h +++ b/include/fluidsynth/settings.h @@ -26,8 +26,9 @@ extern "C" { #endif /** - * @defgroup Settings Settings - * @brief Functions for settings management + * @defgroup settings Settings + * + * Functions for settings management * * To create a synthesizer object you will have to specify its * settings. These settings are stored in a fluid_settings_t object. @@ -154,6 +155,7 @@ int fluid_settings_getint_range(fluid_settings_t *settings, const char *name, /** * Callback function type used with fluid_settings_foreach_option() + * * @param data User defined data pointer * @param name Setting name * @param option A string option for this setting (iterates through the list) @@ -172,6 +174,7 @@ FLUIDSYNTH_API char *fluid_settings_option_concat(fluid_settings_t *settings, /** * Callback function type used with fluid_settings_foreach() + * * @param data User defined data pointer * @param name Setting name * @param type Setting type (#fluid_types_enum) @@ -181,7 +184,6 @@ typedef void (*fluid_settings_foreach_t)(void *data, const char *name, int type) FLUIDSYNTH_API void fluid_settings_foreach(fluid_settings_t *settings, void *data, fluid_settings_foreach_t func); - /* @} */ #ifdef __cplusplus diff --git a/src/utils/fluid_settings.c b/src/utils/fluid_settings.c index 75248f42..9e750ad5 100644 --- a/src/utils/fluid_settings.c +++ b/src/utils/fluid_settings.c @@ -255,6 +255,7 @@ delete_fluid_set_setting(fluid_setting_node_t *node) /** * Create a new settings object + * * @return the pointer to the settings object */ fluid_settings_t * @@ -278,6 +279,7 @@ new_fluid_settings(void) /** * Delete the provided settings object + * * @param settings a settings object */ void @@ -1015,15 +1017,17 @@ error_recovery: /** * Copy the value of a string setting into the provided buffer (thread safe) + * * @param settings a settings object * @param name a setting's name * @param str Caller supplied buffer to copy string value to * @param len Size of 'str' buffer (no more than len bytes will be written, which * will always include a zero terminator) * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise - * @since 1.1.0 * * @note A size of 256 should be more than sufficient for the string buffer. + * + * @since 1.1.0 */ int fluid_settings_copystr(fluid_settings_t *settings, const char *name, @@ -1075,14 +1079,16 @@ fluid_settings_copystr(fluid_settings_t *settings, const char *name, /** * Duplicate the value of a string setting + * * @param settings a settings object * @param name a setting's name * @param str Location to store pointer to allocated duplicate string * @return #FLUID_OK if the value exists and was successfully duplicated, #FLUID_FAILED otherwise - * @since 1.1.0 * * Like fluid_settings_copystr() but allocates a new copy of the string. Caller * owns the string and should free it with fluid_free() when done using it. + * + * @since 1.1.0 */ int fluid_settings_dupstr(fluid_settings_t *settings, const char *name, char **str) @@ -1194,13 +1200,14 @@ fluid_settings_str_equal(fluid_settings_t *settings, const char *name, const cha } /** - * Get the default value of a string setting. Note that the returned string is - * not owned by the caller and should not be modified or freed. + * Get the default value of a string setting. * * @param settings a settings object * @param name a setting's name * @param def the default string value of the setting if it exists * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * @note The returned string is* not owned by the caller and should not be modified or freed. */ int fluid_settings_getstr_default(fluid_settings_t *settings, const char *name, char **def) @@ -1240,6 +1247,7 @@ fluid_settings_getstr_default(fluid_settings_t *settings, const char *name, char /** * Add an option to a string setting (like an enumeration value). + * * @param settings a settings object * @param name a setting's name * @param s option string to add @@ -1277,6 +1285,7 @@ fluid_settings_add_option(fluid_settings_t *settings, const char *name, const ch /** * Remove an option previously assigned by fluid_settings_add_option(). + * * @param settings a settings object * @param name a setting's name * @param s option string to remove @@ -1602,6 +1611,7 @@ fluid_settings_getint(fluid_settings_t *settings, const char *name, int *val) /** * Get the range of values of an integer setting + * * @param settings a settings object * @param name a setting's name * @param min setting's range lower limit @@ -1727,10 +1737,12 @@ fluid_settings_foreach_option(fluid_settings_t *settings, const char *name, /** * Count option string values for a string setting. + * * @param settings a settings object * @param name Name of setting * @return Count of options for this string setting (0 if none, -1 if not found * or not a string setting) + * * @since 1.1.0 */ int @@ -1758,11 +1770,13 @@ fluid_settings_option_count(fluid_settings_t *settings, const char *name) /** * Concatenate options for a string setting together with a separator between. + * * @param settings Settings object * @param name Settings name * @param separator String to use between options (NULL to use ", ") * @return Newly allocated string or NULL on error (out of memory, not a valid * setting \p name or not a string setting). Free the string when finished with it by using fluid_free(). + * * @since 1.1.0 */ char * From 7f1ac22869b6040fc6de0603e60dc230e33d503b Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 22:40:20 +0100 Subject: [PATCH 17/55] Miscellaneous group --- include/fluidsynth/misc.h | 12 +++++++----- src/utils/fluid_sys.c | 11 +++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/include/fluidsynth/misc.h b/include/fluidsynth/misc.h index 80b32a3b..8da368d9 100644 --- a/include/fluidsynth/misc.h +++ b/include/fluidsynth/misc.h @@ -28,15 +28,15 @@ extern "C" { /** - * @defgroup Misc Miscellaneous - * @brief Miscellaneous utility functions and defines + * @defgroup misc Miscellaneous + * + * Miscellaneous utility functions and defines * * @{ */ /** * Value that indicates success, used by most libfluidsynth functions. - * @since 1.1.0 * * @note This was not publicly defined prior to libfluidsynth 1.1.0. When * writing code which should also be compatible with older versions, something @@ -50,14 +50,17 @@ extern "C" { * #define FLUID_FAILED (-1) * #endif * @endcode + * + * @since 1.1.0 */ #define FLUID_OK (0) /** * Value that indicates failure, used by most libfluidsynth functions. - * @since 1.1.0 * * @note See #FLUID_OK for more details. + * + * @since 1.1.0 */ #define FLUID_FAILED (-1) @@ -65,7 +68,6 @@ extern "C" { FLUIDSYNTH_API int fluid_is_soundfont(const char *filename); FLUIDSYNTH_API int fluid_is_midifile(const char *filename); FLUIDSYNTH_API void fluid_free(void* ptr); - /* @} */ #ifdef __cplusplus diff --git a/src/utils/fluid_sys.c b/src/utils/fluid_sys.c index bfb1fa3e..daa27170 100644 --- a/src/utils/fluid_sys.c +++ b/src/utils/fluid_sys.c @@ -219,8 +219,15 @@ void* fluid_alloc(size_t len) /** * Convenience wrapper for free() that satisfies at least C90 requirements. - * Especially useful when using fluidsynth with programming languages that do not provide malloc() and free(). - * @note Only use this function when the API documentation explicitly says so. Otherwise use adequate \c delete_fluid_* functions. + * + * @param ptr Pointer to memory region that should be freed + * + * Especially useful when using fluidsynth with programming languages that do not + * provide malloc() and free(). + * + * @note Only use this function when the API documentation explicitly says so. Otherwise use + * adequate \c delete_fluid_* functions. + * * @since 2.0.7 */ void fluid_free(void* ptr) From 9248032feb437704c5c777a72a3bc33af304b0c6 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 23:39:30 +0100 Subject: [PATCH 18/55] SoundFont API Includes Generators, Modulators, Loader etc --- include/fluidsynth/gen.h | 15 +++--- include/fluidsynth/mod.h | 8 ++-- include/fluidsynth/sfont.h | 55 ++++++++++++++++----- include/fluidsynth/voice.h | 13 ++--- src/sfloader/fluid_sfont.c | 27 +++++++---- src/synth/fluid_mod.c | 25 ++++++++-- src/synth/fluid_synth.c | 38 ++++++++------- src/synth/fluid_voice.c | 97 +++++++++++++++++++++++++++----------- 8 files changed, 193 insertions(+), 85 deletions(-) diff --git a/include/fluidsynth/gen.h b/include/fluidsynth/gen.h index 6e9a9a45..13b07c05 100644 --- a/include/fluidsynth/gen.h +++ b/include/fluidsynth/gen.h @@ -26,8 +26,10 @@ extern "C" { #endif /** - * @defgroup Generators SoundFont Generators - * @brief Functions and defines for SoundFont generator effects. + * @defgroup generators SoundFont Generators + * @ingroup soundfonts + * + * Functions and defines for SoundFont generator effects. * * @{ */ @@ -98,7 +100,7 @@ enum fluid_gen_type GEN_OVERRIDEROOTKEY, /**< Sample root note override */ /** - * @brief Initial Pitch + * Initial Pitch * * @note This is not "standard" SoundFont generator, because it is not * mentioned in the list of generators in the SF2 specifications. @@ -119,11 +121,10 @@ enum fluid_gen_type GEN_CUSTOM_FILTERFC, /**< Custom filter cutoff frequency */ GEN_CUSTOM_FILTERQ, /**< Custom filter Q */ -#ifndef __DOXYGEN__ - GEN_LAST /**< @internal Value defines the count of generators (#fluid_gen_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ -#endif + GEN_LAST /**< @internal Value defines the count of generators (#fluid_gen_type) + @warning This symbol is not part of the public API and ABI + stability guarantee and may change at any time! */ }; - /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/mod.h b/include/fluidsynth/mod.h index 154607d2..b6c7f168 100644 --- a/include/fluidsynth/mod.h +++ b/include/fluidsynth/mod.h @@ -26,13 +26,14 @@ extern "C" { #endif /** - * @defgroup Modulators SoundFont Modulators - * @brief SoundFont modulator functions and constants. + * @defgroup modulators SoundFont Modulators + * @ingroup soundfonts + * + * SoundFont modulator functions and constants. * * @{ */ - /** * Flags defining the polarity, mapping function and type of a modulator source. * Compare with SoundFont 2.04 PDF section 8.2. @@ -92,7 +93,6 @@ FLUIDSYNTH_API int fluid_mod_has_source(const fluid_mod_t *mod, int cc, int ctrl FLUIDSYNTH_API int fluid_mod_has_dest(const fluid_mod_t *mod, int gen); FLUIDSYNTH_API void fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src); - /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/sfont.h b/include/fluidsynth/sfont.h index bd32bf2b..d41c6e70 100644 --- a/include/fluidsynth/sfont.h +++ b/include/fluidsynth/sfont.h @@ -25,10 +25,21 @@ extern "C" { #endif +/** + * @defgroup soundfonts SountFonts + * + * SoundFont related functions + * + * This part of the API contains functions, defines and types that are mostly + * only used by internal or custom SoundFont loaders or client code that + * modifies loaded presets, SoundFonts or voices directly. + */ /** - * @defgroup SoundFont SoundFont Loader - * @brief SoundFont plugins + * @defgroup soundfont_loader SoundFont Loader + * @ingroup soundfonts + * + * Create custom SoundFont loaders * * It is possible to add new SoundFont loaders to the * synthesizer. This API allows for virtual SoundFont files to be loaded @@ -77,6 +88,7 @@ enum /** * Indicates the type of a sample used by the _fluid_sample_t::sampletype field. + * * This enum corresponds to the \c SFSampleLink enum in the SoundFont spec. * One \c flag may be bit-wise OR-ed with one \c value. */ @@ -94,6 +106,7 @@ enum fluid_sample_type /** * Method to load an instrument file (does not actually need to be a real file name, * could be another type of string identifier that the \a loader understands). + * * @param loader SoundFont loader * @param filename File name or other string identifier * @return The loaded instrument file (SoundFont) or NULL if an error occurred. @@ -102,10 +115,14 @@ typedef fluid_sfont_t *(*fluid_sfloader_load_t)(fluid_sfloader_t *loader, const /** * The free method should free the memory allocated for a fluid_sfloader_t instance in - * addition to any private data. Any custom user provided cleanup function must ultimately call + * addition to any private data. + * + * @param loader SoundFont loader + * + * Any custom user provided cleanup function must ultimately call * delete_fluid_sfloader() to ensure proper cleanup of the #fluid_sfloader_t struct. If no private data * needs to be freed, setting this to delete_fluid_sfloader() is sufficient. - * @param loader SoundFont loader + * */ typedef void (*fluid_sfloader_free_t)(fluid_sfloader_t *loader); @@ -117,9 +134,10 @@ FLUIDSYNTH_API fluid_sfloader_t *new_fluid_defsfloader(fluid_settings_t *setting /** * Opens the file or memory indicated by \c filename in binary read mode. - * \c filename matches the string provided during the fluid_synth_sfload() call. * * @return returns a file handle on success, NULL otherwise + * + * \c filename matches the string provided during the fluid_synth_sfload() call. */ typedef void *(* fluid_sfloader_callback_open_t)(const char *filename); @@ -134,7 +152,6 @@ typedef int (* fluid_sfloader_callback_read_t)(void *buf, fluid_long_long_t coun * Same purpose and behaviour as fseek. * * @param origin either \c SEEK_SET, \c SEEK_CUR or \c SEEK_END - * * @return returns #FLUID_OK if the seek was successfully performed while not seeking beyond a buffer or file, #FLUID_FAILED otherwise */ typedef int (* fluid_sfloader_callback_seek_t)(void *handle, fluid_long_long_t offset, int origin); @@ -164,6 +181,7 @@ FLUIDSYNTH_API void *fluid_sfloader_get_data(fluid_sfloader_t *loader); /** * Method to return the name of a virtual SoundFont. + * * @param sfont Virtual SoundFont * @return The name of the virtual SoundFont. */ @@ -171,6 +189,7 @@ typedef const char *(*fluid_sfont_get_name_t)(fluid_sfont_t *sfont); /** * Get a virtual SoundFont preset by bank and program numbers. + * * @param sfont Virtual SoundFont * @param bank MIDI bank number (0-16383) * @param prenum MIDI preset number (0-127) @@ -181,6 +200,7 @@ typedef fluid_preset_t *(*fluid_sfont_get_preset_t)(fluid_sfont_t *sfont, int ba /** * Start virtual SoundFont preset iteration method. + * * @param sfont Virtual SoundFont * * Starts/re-starts virtual preset iteration in a SoundFont. @@ -189,6 +209,7 @@ typedef void (*fluid_sfont_iteration_start_t)(fluid_sfont_t *sfont); /** * Virtual SoundFont preset iteration function. + * * @param sfont Virtual SoundFont * @return NULL when no more presets are available, otherwise the a pointer to the current preset * @@ -198,13 +219,16 @@ typedef void (*fluid_sfont_iteration_start_t)(fluid_sfont_t *sfont); typedef fluid_preset_t *(*fluid_sfont_iteration_next_t)(fluid_sfont_t *sfont); /** - * Method to free a virtual SoundFont bank. Any custom user provided cleanup function must ultimately call - * delete_fluid_sfont() to ensure proper cleanup of the #fluid_sfont_t struct. If no private data - * needs to be freed, setting this to delete_fluid_sfont() is sufficient. + * Method to free a virtual SoundFont bank. + * * @param sfont Virtual SoundFont to free. * @return Should return 0 when it was able to free all resources or non-zero * if some of the samples could not be freed because they are still in use, * in which case the free will be tried again later, until success. + * + * Any custom user provided cleanup function must ultimately call + * delete_fluid_sfont() to ensure proper cleanup of the #fluid_sfont_t struct. If no private data + * needs to be freed, setting this to delete_fluid_sfont() is sufficient. */ typedef int (*fluid_sfont_free_t)(fluid_sfont_t *sfont); @@ -228,6 +252,7 @@ FLUIDSYNTH_API fluid_preset_t *fluid_sfont_iteration_next(fluid_sfont_t *sfont); /** * Method to get a virtual SoundFont preset name. + * * @param preset Virtual SoundFont preset * @return Should return the name of the preset. The returned string must be * valid for the duration of the virtual preset (or the duration of the @@ -237,6 +262,7 @@ typedef const char *(*fluid_preset_get_name_t)(fluid_preset_t *preset); /** * Method to get a virtual SoundFont preset MIDI bank number. + * * @param preset Virtual SoundFont preset * @param return The bank number of the preset */ @@ -244,6 +270,7 @@ typedef int (*fluid_preset_get_banknum_t)(fluid_preset_t *preset); /** * Method to get a virtual SoundFont preset MIDI program number. + * * @param preset Virtual SoundFont preset * @param return The program number of the preset */ @@ -251,6 +278,7 @@ typedef int (*fluid_preset_get_num_t)(fluid_preset_t *preset); /** * Method to handle a noteon event (synthesize the instrument). + * * @param preset Virtual SoundFont preset * @param synth Synthesizer instance * @param chan MIDI channel number of the note on event @@ -274,11 +302,14 @@ typedef int (*fluid_preset_get_num_t)(fluid_preset_t *preset); typedef int (*fluid_preset_noteon_t)(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel); /** - * Method to free a virtual SoundFont preset. Any custom user provided cleanup function must ultimately call - * delete_fluid_preset() to ensure proper cleanup of the #fluid_preset_t struct. If no private data - * needs to be freed, setting this to delete_fluid_preset() is sufficient. + * Method to free a virtual SoundFont preset. + * * @param preset Virtual SoundFont preset * @return Should return 0 + * + * Any custom user provided cleanup function must ultimately call + * delete_fluid_preset() to ensure proper cleanup of the #fluid_preset_t struct. If no private data + * needs to be freed, setting this to delete_fluid_preset() is sufficient. */ typedef void (*fluid_preset_free_t)(fluid_preset_t *preset); diff --git a/include/fluidsynth/voice.h b/include/fluidsynth/voice.h index 3013aadc..ead4f710 100644 --- a/include/fluidsynth/voice.h +++ b/include/fluidsynth/voice.h @@ -26,18 +26,21 @@ extern "C" { #endif /** - * @defgroup VoiceManipulation Voice Manipulation - * @brief Synthesis voice manipulation functions. + * @defgroup voices Voice Manipulation + * @ingroup soundfonts + * + * Synthesis voice manipulation functions. * * The interface to the synthesizer's voices. - * Examples on using them can be found in fluid_defsfont.c. + * Examples on using them can be found in the source code of the default SoundFont + * loader (fluid_defsfont.c). + * * Most of these functions should only be called from within synthesis context, * such as the SoundFont loader's noteon method. * * @{ */ - /** * Enum used with fluid_voice_add_mod() to specify how to handle duplicate modulators. */ @@ -65,10 +68,8 @@ FLUIDSYNTH_API int fluid_voice_is_sustained(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_is_sostenuto(const fluid_voice_t *voice); FLUIDSYNTH_API int fluid_voice_optimize_sample(fluid_sample_t *s); FLUIDSYNTH_API void fluid_voice_update_param(fluid_voice_t *voice, int gen); - /* @} */ - #ifdef __cplusplus } #endif diff --git a/src/sfloader/fluid_sfont.c b/src/sfloader/fluid_sfont.c index be996819..f5de0a5b 100644 --- a/src/sfloader/fluid_sfont.c +++ b/src/sfloader/fluid_sfont.c @@ -156,8 +156,6 @@ void *fluid_sfloader_get_data(fluid_sfloader_t *loader) /** * Set custom callbacks to be used upon soundfont loading. * - * Useful for loading a soundfont from memory, see \a doc/fluidsynth_sfload_mem.c as an example. - * * @param loader The SoundFont loader instance. * @param open A function implementing #fluid_sfloader_callback_open_t. * @param read A function implementing #fluid_sfloader_callback_read_t. @@ -165,6 +163,9 @@ void *fluid_sfloader_get_data(fluid_sfloader_t *loader) * @param tell A function implementing #fluid_sfloader_callback_tell_t. * @param close A function implementing #fluid_sfloader_callback_close_t. * @return #FLUID_OK if the callbacks have been successfully set, #FLUID_FAILED otherwise. + * + * Useful for loading a soundfont from memory, see \a doc/fluidsynth_sfload_mem.c as an example. + * */ int fluid_sfloader_set_callbacks(fluid_sfloader_t *loader, fluid_sfloader_callback_open_t open, @@ -196,6 +197,7 @@ int fluid_sfloader_set_callbacks(fluid_sfloader_t *loader, /** * Creates a new virtual SoundFont instance structure. + * * @param get_name A function implementing #fluid_sfont_get_name_t. * @param get_preset A function implementing #fluid_sfont_get_preset_t. * @param iter_start A function implementing #fluid_sfont_iteration_start_t, or NULL if preset iteration not needed. @@ -285,8 +287,8 @@ const char *fluid_sfont_get_name(fluid_sfont_t *sfont) } /** - * Retrieve the preset assigned the a SoundFont instance - * for the given bank and preset number. + * Retrieve the preset assigned the a SoundFont instance for the given bank and preset number. + * * @param sfont The SoundFont instance. * @param bank bank number of the preset * @param prenum program number of the preset @@ -300,6 +302,7 @@ fluid_preset_t *fluid_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenu /** * Starts / re-starts virtual preset iteration in a SoundFont. + * * @param sfont Virtual SoundFont instance */ void fluid_sfont_iteration_start(fluid_sfont_t *sfont) @@ -329,10 +332,11 @@ fluid_preset_t *fluid_sfont_iteration_next(fluid_sfont_t *sfont) /** * Destroys a SoundFont instance created with new_fluid_sfont(). * - * Implements #fluid_sfont_free_t. - * * @param sfont The SoundFont instance to destroy. * @return Always returns 0. + * + * Implements #fluid_sfont_free_t. + * */ int delete_fluid_sfont(fluid_sfont_t *sfont) { @@ -467,9 +471,10 @@ fluid_sfont_t *fluid_preset_get_sfont(fluid_preset_t *preset) /** * Destroys a SoundFont preset instance created with new_fluid_preset(). * + * @param preset The SoundFont preset instance to destroy. + * * Implements #fluid_preset_free_t. * - * @param preset The SoundFont preset instance to destroy. */ void delete_fluid_preset(fluid_preset_t *preset) { @@ -480,6 +485,7 @@ void delete_fluid_preset(fluid_preset_t *preset) /** * Create a new sample instance. + * * @return The sample on success, NULL otherwise. */ fluid_sample_t * @@ -502,6 +508,7 @@ new_fluid_sample() /** * Destroy a sample instance previously created with new_fluid_sample(). + * * @param sample The sample to destroy. */ void @@ -521,10 +528,10 @@ delete_fluid_sample(fluid_sample_t *sample) /** * Returns the size of the fluid_sample_t structure. * - * Useful in low latency scenarios e.g. to allocate a pool of samples. - * * @return Size of fluid_sample_t in bytes * + * Useful in low latency scenarios e.g. to allocate a pool of samples. + * * @note It is recommend to zero initialize the memory before using the object. * * @warning Do NOT allocate samples on the stack and assign them to a voice! @@ -536,6 +543,7 @@ size_t fluid_sample_sizeof() /** * Set the name of a SoundFont sample. + * * @param sample SoundFont sample * @param name Name to assign to sample (20 chars in length + zero terminator) * @return #FLUID_OK on success, #FLUID_FAILED otherwise @@ -551,6 +559,7 @@ int fluid_sample_set_name(fluid_sample_t *sample, const char *name) /** * Assign sample data to a SoundFont sample. + * * @param sample SoundFont sample * @param data Buffer containing 16 bit (mono-)audio sample data * @param data24 If not NULL, pointer to the least significant byte counterparts of each sample data point in order to create 24 bit audio samples diff --git a/src/synth/fluid_mod.c b/src/synth/fluid_mod.c index 47547b5b..effa2027 100644 --- a/src/synth/fluid_mod.c +++ b/src/synth/fluid_mod.c @@ -24,8 +24,10 @@ /** * Clone the modulators destination, sources, flags and amount. + * * @param mod the modulator to store the copy to * @param src the source modulator to retrieve the information from + * * @note The \c next member of \c mod will be left unchanged. */ void @@ -41,6 +43,7 @@ fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src) /** * Set a modulator's primary source controller and flags. + * * @param mod The modulator instance * @param src Modulator source (#fluid_mod_src or a MIDI controller number) * @param flags Flags determining mapping function and whether the source @@ -56,6 +59,7 @@ fluid_mod_set_source1(fluid_mod_t *mod, int src, int flags) /** * Set a modulator's secondary source controller and flags. + * * @param mod The modulator instance * @param src Modulator source (#fluid_mod_src or a MIDI controller number) * @param flags Flags determining mapping function and whether the source @@ -71,6 +75,7 @@ fluid_mod_set_source2(fluid_mod_t *mod, int src, int flags) /** * Set the destination effect of a modulator. + * * @param mod The modulator instance * @param dest Destination generator (#fluid_gen_type) */ @@ -82,6 +87,7 @@ fluid_mod_set_dest(fluid_mod_t *mod, int dest) /** * Set the scale amount of a modulator. + * * @param mod The modulator instance * @param amount Scale amount to assign */ @@ -93,6 +99,7 @@ fluid_mod_set_amount(fluid_mod_t *mod, double amount) /** * Get the primary source value from a modulator. + * * @param mod The modulator instance * @return The primary source value (#fluid_mod_src or a MIDI CC controller value). */ @@ -104,6 +111,7 @@ fluid_mod_get_source1(const fluid_mod_t *mod) /** * Get primary source flags from a modulator. + * * @param mod The modulator instance * @return The primary source flags (#fluid_mod_flags). */ @@ -115,6 +123,7 @@ fluid_mod_get_flags1(const fluid_mod_t *mod) /** * Get the secondary source value from a modulator. + * * @param mod The modulator instance * @return The secondary source value (#fluid_mod_src or a MIDI CC controller value). */ @@ -126,6 +135,7 @@ fluid_mod_get_source2(const fluid_mod_t *mod) /** * Get secondary source flags from a modulator. + * * @param mod The modulator instance * @return The secondary source flags (#fluid_mod_flags). */ @@ -137,6 +147,7 @@ fluid_mod_get_flags2(const fluid_mod_t *mod) /** * Get destination effect from a modulator. + * * @param mod The modulator instance * @return Destination generator (#fluid_gen_type) */ @@ -148,6 +159,7 @@ fluid_mod_get_dest(const fluid_mod_t *mod) /** * Get the scale amount from a modulator. + * * @param mod The modulator instance * @return Scale amount */ @@ -466,6 +478,7 @@ fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) /** * Create a new uninitialized modulator structure. + * * @return New allocated modulator or NULL if out of memory */ fluid_mod_t * @@ -484,6 +497,7 @@ new_fluid_mod() /** * Free a modulator structure. + * * @param mod Modulator to free */ void @@ -495,9 +509,9 @@ delete_fluid_mod(fluid_mod_t *mod) /** * Returns the size of the fluid_mod_t structure. * - * Useful in low latency scenarios e.g. to allocate a modulator on the stack. - * * @return Size of fluid_mod_t in bytes + * + * Useful in low latency scenarios e.g. to allocate a modulator on the stack. */ size_t fluid_mod_sizeof() { @@ -518,13 +532,14 @@ fluid_mod_is_src1_none(const fluid_mod_t *mod) /** * Checks if modulators source other than CC source is invalid. - * (specs SF 2.01 7.4, 7.8, 8.2.1) * * @param mod, modulator. * @param src1_select, source input selection to check. * 1 to check src1 source. * 0 to check src2 source. * @return FALSE if selected modulator source other than cc is invalid, TRUE otherwise. + * + * (specs SF 2.01 7.4, 7.8, 8.2.1) */ static int fluid_mod_check_non_cc_source(const fluid_mod_t *mod, unsigned char src1_select) @@ -556,6 +571,7 @@ fluid_mod_check_non_cc_source(const fluid_mod_t *mod, unsigned char src1_select) /** * Checks if modulator CC source is invalid (specs SF 2.01 7.4, 7.8, 8.2.1). + * * @param mod, modulator. * @src1_select, source input selection: * 1 to check src1 source or @@ -599,6 +615,7 @@ fluid_mod_check_cc_source(const fluid_mod_t *mod, unsigned char src1_select) /** * Checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1) + * * @param mod, modulator. * @param name,if not NULL, pointer on a string displayed as a warning. * @return TRUE if modulator sources src1, src2 are valid, FALSE otherwise. @@ -677,6 +694,7 @@ int fluid_mod_check_sources(const fluid_mod_t *mod, char *name) /** * Checks if two modulators are identical in sources, flags and destination. + * * @param mod1 First modulator * @param mod2 Second modulator * @return TRUE if identical, FALSE otherwise @@ -720,6 +738,7 @@ int fluid_mod_has_source(const fluid_mod_t *mod, int cc, int ctrl) /** * Check if the modulator has the given destination. + * * @param mod The modulator instance * @param gen The destination generator of type #fluid_gen_type to check for * @return TRUE if the modulator has the given destination, FALSE otherwise. diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 5aaca720..c3863f9f 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -3021,15 +3021,6 @@ fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, /** * Pins all samples of the given preset. * - * This function will attempt to pin all samples of the given preset and - * load them into memory, if they are currently unloaded. "To pin" in this - * context means preventing them from being unloaded by an upcoming channel - * prog change. - * - * @note This function is only useful if synth.dynamic-sample-loading is enabled. - * By default, dynamic-sample-loading is disabled and all samples are kept in memory. - * Furthermore, this is only useful for presets which support dynamic-sample-loading (currently, - * only preset loaded with the default soundfont loader do). * @param synth FluidSynth instance * @param sfont_id ID of a loaded SoundFont * @param bank_num MIDI bank number @@ -3038,6 +3029,17 @@ fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, * into memory successfully. #FLUID_FAILED otherwise. Note that #FLUID_OK * is returned, even if synth.dynamic-sample-loading is disabled or * the preset doesn't support dynamic-sample-loading. + * + * This function will attempt to pin all samples of the given preset and + * load them into memory, if they are currently unloaded. "To pin" in this + * context means preventing them from being unloaded by an upcoming channel + * prog change. + * + * @note This function is only useful if \ref settings_synth_dynamic-sample-loading is enabled. + * By default, dynamic-sample-loading is disabled and all samples are kept in memory. + * Furthermore, this is only useful for presets which support dynamic-sample-loading (currently, + * only preset loaded with the default soundfont loader do). + * * @since 2.2.0 */ int @@ -3070,16 +3072,18 @@ fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int pre /** * Unpin all samples of the given preset. * - * This function undoes the effect of fluid_synth_pin_preset(). If the preset is - * not currently used, its samples will be unloaded. - * - * @note Only useful for presets loaded with the default soundfont loader and - * only if synth.dynamic-sample-loading is enabled. * @param synth FluidSynth instance * @param sfont_id ID of a loaded SoundFont * @param bank_num MIDI bank number * @param preset_num MIDI program number * @return #FLUID_OK if preset was found, #FLUID_FAILED otherwise + * + * This function undoes the effect of fluid_synth_pin_preset(). If the preset is + * not currently used, its samples will be unloaded. + * + * @note Only useful for presets loaded with the default soundfont loader and + * only if \ref settings_synth_dynamic-sample-loading is enabled. + * * @since 2.2.0 */ int @@ -7047,7 +7051,7 @@ static void fluid_synth_handle_important_channels(void *data, const char *name, } -/** API legato mode *********************************************************/ +/* API legato mode *********************************************************/ /** * Sets the legato mode of a channel. @@ -7100,7 +7104,7 @@ int fluid_synth_get_legato_mode(fluid_synth_t *synth, int chan, int *legatomode) FLUID_API_RETURN(FLUID_OK); } -/** API portamento mode *********************************************************/ +/* API portamento mode *********************************************************/ /** * Sets the portamento mode of a channel. @@ -7153,7 +7157,7 @@ int fluid_synth_get_portamento_mode(fluid_synth_t *synth, int chan, FLUID_API_RETURN(FLUID_OK); } -/** API breath mode *********************************************************/ +/* API breath mode *********************************************************/ /** * Sets the breath mode of a channel. diff --git a/src/synth/fluid_voice.c b/src/synth/fluid_voice.c index 90f94558..60a0b3b5 100644 --- a/src/synth/fluid_voice.c +++ b/src/synth/fluid_voice.c @@ -367,6 +367,7 @@ fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample, /** * Update sample rate. + * * @note If the voice is active, it will be turned off. */ void @@ -385,6 +386,7 @@ fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value) /** * Set the value of a generator. + * * @param voice Voice instance * @param i Generator ID (#fluid_gen_type) * @param val Generator value @@ -403,6 +405,7 @@ fluid_voice_gen_set(fluid_voice_t *voice, int i, float val) /** * Offset the value of a generator. + * * @param voice Voice instance * @param i Generator ID (#fluid_gen_type) * @param val Value to add to the existing value @@ -416,6 +419,7 @@ fluid_voice_gen_incr(fluid_voice_t *voice, int i, float val) /** * Get the value of a generator. + * * @param voice Voice instance * @param gen Generator ID (#fluid_gen_type) * @return Current generator value @@ -734,13 +738,14 @@ calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base, * NRPN system. fluid_voice_gen_value(voice, generator_enumerator) returns the sum * of all three. */ + /** - * Update all the synthesis parameters, which depend on generator \a gen. + * Update all the synthesis parameters which depend on generator \a gen. + * * @param voice Voice instance * @param gen Generator id (#fluid_gen_type) * - * This is only necessary after changing a generator of an already operating voice. - * Most applications will not need this function. + * Calling this function is only necessary after changing a generator of an already playing voice. */ void fluid_voice_update_param(fluid_voice_t *voice, int gen) @@ -1140,6 +1145,7 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) /** * Recalculate voice parameters for a given control. + * * @param voice the synthesis voice * @param cc flag to distinguish between a continuous control and a channel control (pitch bend, ...) * @param ctrl the control number: @@ -1236,17 +1242,18 @@ int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) } /** - * Update all the modulators. This function is called after a - * ALL_CTRL_OFF MIDI message has been received (CC 121). + * Update all the modulators. * - * All destination of all modulators must be updated. + * This function is called after a ALL_CTRL_OFF MIDI message has been received (CC 121). + * All destinations of all modulators will be updated. */ int fluid_voice_modulate_all(fluid_voice_t *voice) { return fluid_voice_modulate(voice, 0, -1); } -/** legato update functions --------------------------------------------------*/ +/* legato update functions --------------------------------------------------*/ + /* Updates voice portamento parameters * * @voice voice the synthesis voice @@ -1456,6 +1463,7 @@ fluid_voice_stop(fluid_voice_t *voice) /** * Adds a modulator to the voice if the modulator has valid sources. + * * @param voice Voice instance. * @param mod Modulator info (copied). * @param mode Determines how to handle an existing identical modulator. @@ -1543,15 +1551,16 @@ fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int /** * Get the unique ID of the noteon-event. + * * @param voice Voice instance * @return Note on unique ID * - * A SoundFont loader may store the voice processes it has created for + * A SoundFont loader may store pointers to voices it has created for * real-time control during the operation of a voice (for example: parameter - * changes in SoundFont editor). The synth uses a pool of voices, which are + * changes in SoundFont editor). The synth uses a pool of voices internally which are * 'recycled' and never deallocated. * - * Before modifying an existing voice, check + * However, before modifying an existing voice, check * - that its state is still 'playing' * - that the ID is still the same * @@ -1563,9 +1572,13 @@ unsigned int fluid_voice_get_id(const fluid_voice_t *voice) } /** - * Check if a voice is producing sound. This is also true after a voice received a noteoff as it may be playing in release phase. + * Check if a voice is producing sound. + * * @param voice Voice instance * @return TRUE if playing, FALSE otherwise + * + * This might also return TRUE after the voice received a noteoff event, as it may + * still be playing in release phase. */ int fluid_voice_is_playing(const fluid_voice_t *voice) { @@ -1576,9 +1589,13 @@ int fluid_voice_is_playing(const fluid_voice_t *voice) } /** - * Check if a voice is ON. A voice is ON, if it has not yet received a noteoff event. + * Check if a voice is ON. + * * @param voice Voice instance * @return TRUE if on, FALSE otherwise + * + * A voice is ON if it has not yet received a noteoff event. + * * @since 1.1.7 */ int fluid_voice_is_on(const fluid_voice_t *voice) @@ -1588,8 +1605,10 @@ int fluid_voice_is_on(const fluid_voice_t *voice) /** * Check if a voice keeps playing after it has received a noteoff due to being held by sustain. + * * @param voice Voice instance * @return TRUE if sustained, FALSE otherwise + * * @since 1.1.7 */ int fluid_voice_is_sustained(const fluid_voice_t *voice) @@ -1599,8 +1618,10 @@ int fluid_voice_is_sustained(const fluid_voice_t *voice) /** * Check if a voice keeps playing after it has received a noteoff due to being held by sostenuto. + * * @param voice Voice instance * @return TRUE if sostenuto, FALSE otherwise + * * @since 1.1.7 */ int fluid_voice_is_sostenuto(const fluid_voice_t *voice) @@ -1609,9 +1630,13 @@ int fluid_voice_is_sostenuto(const fluid_voice_t *voice) } /** - * If the voice is playing, gets the midi channel the voice is playing on. Else the result is undefined. + * Return the MIDI channel the voice is playing on. + * * @param voice Voice instance * @return The channel assigned to this voice + * + * @note The result of this function is only valid if the voice is playing. + * * @since 1.1.7 */ int fluid_voice_get_channel(const fluid_voice_t *voice) @@ -1620,11 +1645,16 @@ int fluid_voice_get_channel(const fluid_voice_t *voice) } /** - * If the voice is playing, gets the midi key the voice is actually playing at. Else the result is undefined. - * If the voice was started from an instrument which uses a fixed key generator, it returns that. - * Else returns the same as \c fluid_voice_get_key. + * Return the effective MIDI key of the playing voice. + * * @param voice Voice instance - * @return The midi key this voice is playing at + * @return The MIDI key this voice is playing at + * + * If the voice was started from an instrument which uses a fixed key generator, it returns that. + * Otherwise returns the same value as \c fluid_voice_get_key. + * + * @note The result of this function is only valid if the voice is playing. + * * @since 1.1.7 */ int fluid_voice_get_actual_key(const fluid_voice_t *voice) @@ -1642,10 +1672,13 @@ int fluid_voice_get_actual_key(const fluid_voice_t *voice) } /** - * If the voice is playing, gets the midi key from the noteon event, by which the voice was initially turned on with. - * Else the result is undefined. + * Return the MIDI key from the starting noteon event. + * * @param voice Voice instance - * @return The midi key of the noteon event that originally turned on this voice + * @return The MIDI key of the noteon event that originally turned on this voice + * + * @note The result of this function is only valid if the voice is playing. + * * @since 1.1.7 */ int fluid_voice_get_key(const fluid_voice_t *voice) @@ -1654,11 +1687,16 @@ int fluid_voice_get_key(const fluid_voice_t *voice) } /** - * If the voice is playing, gets the midi velocity the voice is actually playing at. Else the result is undefined. - * If the voice was started from an instrument which uses a fixed velocity generator, it returns that. - * Else returns the same as \c fluid_voice_get_velocity. + * Return the effective MIDI velocity of the playing voice. + * * @param voice Voice instance - * @return The midi velocity this voice is playing at + * @return The MIDI velocity this voice is playing at + * + * If the voice was started from an instrument which uses a fixed velocity generator, it returns that. + * Otherwise it returns the same value as \c fluid_voice_get_velocity. + * + * @note The result of this function is only valid if the voice is playing. + * * @since 1.1.7 */ int fluid_voice_get_actual_velocity(const fluid_voice_t *voice) @@ -1676,10 +1714,13 @@ int fluid_voice_get_actual_velocity(const fluid_voice_t *voice) } /** - * If the voice is playing, gets the midi velocity from the noteon event, by which the voice was initially - * turned on with. Else the result is undefined. + * Return the MIDI velocity from the starting noteon event. + * * @param voice Voice instance - * @return The midi velocity which originally turned on this voice + * @return The MIDI velocity which originally turned on this voice + * + * @note The result of this function is only valid if the voice is playing. + * * @since 1.1.7 */ int fluid_voice_get_velocity(const fluid_voice_t *voice) @@ -1819,8 +1860,10 @@ int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain) * - Calculate, what factor will make the loop inaudible * - Store in sample */ + /** * Calculate the peak volume of a sample for voice off optimization. + * * @param s Sample to optimize * @return #FLUID_OK on success, #FLUID_FAILED otherwise * From 6780293d0c6a643d67ff43b3726e810bc7185f63 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 11 Nov 2020 23:40:00 +0100 Subject: [PATCH 19/55] Add version defines and functions to misc group --- include/fluidsynth/version.h.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/fluidsynth/version.h.in b/include/fluidsynth/version.h.in index 27b09bbe..34db6ea7 100644 --- a/include/fluidsynth/version.h.in +++ b/include/fluidsynth/version.h.in @@ -27,8 +27,7 @@ extern "C" { #endif /** - * @defgroup Version Library Version - * @brief Library version functions and defines + * @addtogroup misc * * @{ */ From 4ea5d64eb792f6207aca7277196abc65862fae9c Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 20:35:47 +0100 Subject: [PATCH 20/55] Rename setting reference page name to lowercase, for consistency --- doc/doxygen/fluidsettings.xsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/doxygen/fluidsettings.xsl b/doc/doxygen/fluidsettings.xsl index 0f880164..9375a18d 100644 --- a/doc/doxygen/fluidsettings.xsl +++ b/doc/doxygen/fluidsettings.xsl @@ -5,7 +5,7 @@ /*! -\page FluidSettings FluidSynth Settings +\page fluidsettings Settings Reference From a9135faf70ce86458be4b30aa143f77deb0e4f7e Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 21:11:42 +0100 Subject: [PATCH 21/55] Structure the large synth header into subgroups Also include version.h and ladspa.h in the Synthesizer group. --- include/fluidsynth/ladspa.h | 10 +- include/fluidsynth/synth.h | 323 +++++++++++++++----------------- include/fluidsynth/version.h.in | 2 - src/bindings/fluid_ladspa.c | 77 ++++---- src/sfloader/fluid_sffile.c | 3 +- src/synth/fluid_synth.c | 130 +++++++++---- 6 files changed, 292 insertions(+), 253 deletions(-) diff --git a/include/fluidsynth/ladspa.h b/include/fluidsynth/ladspa.h index 6de87a69..05b1942d 100644 --- a/include/fluidsynth/ladspa.h +++ b/include/fluidsynth/ladspa.h @@ -26,18 +26,19 @@ extern "C" { #endif /** - * @defgroup LADSPA LADSPA Interface - * @brief Functions for manipulating the ladspa effects unit + * @defgroup ladspa Effect - LADSPA + * @ingroup synth + * + * Functions for configuring the LADSPA effects unit * * This header defines useful functions for programmatically manipulating the ladspa * effects unit of the synth that can be retrieved via fluid_synth_get_ladspa_fx(). * - * Using any of those functions requires fluidsynth to be compiled with ladspa support. + * Using any of those functions requires fluidsynth to be compiled with LADSPA support. * Else all of those functions are useless dummies. * * @{ */ - FLUIDSYNTH_API int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_activate(fluid_ladspa_fx_t *fx); FLUIDSYNTH_API int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx); @@ -58,7 +59,6 @@ FLUIDSYNTH_API int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const const char *port_name, float val); FLUIDSYNTH_API int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name); - /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index bb2550b0..14346fcc 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -28,8 +28,9 @@ extern "C" { /** - * @defgroup Synth Synthesizer - * @brief Embeddable SoundFont synthesizer + * @defgroup synth Synthesizer + * + * SoundFont synthesizer * * You create a new synthesizer with new_fluid_synth() and you destroy * it with delete_fluid_synth(). Use the fluid_settings_t structure to specify @@ -46,21 +47,23 @@ extern "C" { * * @{ */ - - FLUIDSYNTH_API fluid_synth_t *new_fluid_synth(fluid_settings_t *settings); FLUIDSYNTH_API void delete_fluid_synth(fluid_synth_t *synth); -FLUIDSYNTH_API fluid_settings_t *fluid_synth_get_settings(fluid_synth_t *synth); +FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API const char *fluid_synth_error(fluid_synth_t *synth); /* @} */ /** - * @defgroup MIDIChannel MIDI channel messages - * @ingroup Synth + * @defgroup midi_messages MIDI Channel Messages + * @ingroup synth + * + * The MIDI channel message functions are mostly directly named after their + * counterpart MIDI messages. They are a high-level interface to controlling + * the synthesizer, playing notes and changing note and channel parameters. * * @{ */ - FLUIDSYNTH_API int fluid_synth_noteon(fluid_synth_t *synth, int chan, int key, int vel); FLUIDSYNTH_API int fluid_synth_noteoff(fluid_synth_t *synth, int chan, int key); FLUIDSYNTH_API int fluid_synth_cc(fluid_synth_t *synth, int chan, int ctrl, int val); @@ -93,48 +96,35 @@ FLUIDSYNTH_API int fluid_synth_system_reset(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_all_notes_off(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_all_sounds_off(fluid_synth_t *synth, int chan); -/* @} */ +FLUIDSYNTH_API int fluid_synth_set_gen(fluid_synth_t *synth, int chan, + int param, float value); +FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param); +/* @} MIDI Channel Messages */ /** - * @defgroup DynamicSampleLoading Dynamic sample loading - * @ingroup Synth + * @defgroup voice_control Synthesis Voice Control + * @ingroup synth * * @{ */ - -FLUIDSYNTH_API -int fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); - -FLUIDSYNTH_API -int fluid_synth_unpin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); - -/* @} */ - - -/** - * The midi channel type used by fluid_synth_set_channel_type() - */ -enum fluid_midi_channel_type -{ - CHANNEL_TYPE_MELODIC = 0, /**< Melodic midi channel */ - CHANNEL_TYPE_DRUM = 1 /**< Drum midi channel */ -}; - -FLUIDSYNTH_API int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type); - - -/* Low level access */ -FLUIDSYNTH_API fluid_preset_t *fluid_synth_get_channel_preset(fluid_synth_t *synth, int chan); FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t *synth, unsigned int id, fluid_preset_t *preset, int audio_chan, int midi_chan, int key, int vel); FLUIDSYNTH_API int fluid_synth_stop(fluid_synth_t *synth, unsigned int id); +FLUIDSYNTH_API fluid_voice_t *fluid_synth_alloc_voice(fluid_synth_t *synth, + fluid_sample_t *sample, + int channum, int key, int vel); +FLUIDSYNTH_API void fluid_synth_start_voice(fluid_synth_t *synth, fluid_voice_t *voice); +FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t *synth, + fluid_voice_t *buf[], int bufsize, int ID); +/* @} Voice Control */ + /** - * @defgroup SoundFontManagement SoundFont management - * @ingroup Synth + * @defgroup soundfont_management SoundFont Management + * @ingroup synth * * @{ */ @@ -151,18 +141,14 @@ FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont_by_name(fluid_synth_t *synth const char *name); FLUIDSYNTH_API int fluid_synth_set_bank_offset(fluid_synth_t *synth, int sfont_id, int offset); FLUIDSYNTH_API int fluid_synth_get_bank_offset(fluid_synth_t *synth, int sfont_id); -/* @} */ +/* @} Soundfont Management */ /** - * @defgroup Effects Effects - * @ingroup Synth - */ - - -/** - * @defgroup Reverb Reverb - * @ingroup Effects + * @defgroup reverb_effect Effect - Reverb + * @ingroup synth + * + * Functions for configuring the built-in reverb effect * * @{ */ @@ -178,12 +164,14 @@ FLUIDSYNTH_API double fluid_synth_get_reverb_roomsize(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_damp(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_reverb_width(fluid_synth_t *synth); -/* @} */ +/* @} Reverb */ /** - * @defgroup Chrous Chrous - * @ingroup Effects + * @defgroup chorus_effect Effect - Chorus + * @ingroup synth + * + * Functions for configuring the built-in chorus effect * * @{ */ @@ -211,12 +199,12 @@ FLUIDSYNTH_API double fluid_synth_get_chorus_level(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_speed(fluid_synth_t *synth); FLUIDSYNTH_API double fluid_synth_get_chorus_depth(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t *synth); /* see fluid_chorus_mod */ -/* @} Chorus */ +/* @} Chrous */ /** - * @defgroup ChannelManagement Audio and MIDI channels - * @ingroup Synth + * @defgroup synthesis_params Synthesis Parameters + * @ingroup synth * * @{ */ @@ -225,15 +213,6 @@ FLUIDSYNTH_API int fluid_synth_count_audio_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_audio_groups(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_channels(fluid_synth_t *synth); FLUIDSYNTH_API int fluid_synth_count_effects_groups(fluid_synth_t *synth); -/* @} */ - - -/** - * @defgroup SynthParams Synthesis parameters - * @ingroup Synth - * - * @{ - */ FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate); FLUIDSYNTH_API void fluid_synth_set_gain(fluid_synth_t *synth, float gain); @@ -260,29 +239,28 @@ enum fluid_interp FLUID_INTERP_HIGHEST = FLUID_INTERP_7THORDER, /**< Highest interpolation method */ }; -/* @} */ - /** - * @defgroup Generator Generator interface - * @ingroup Synth - * - * @{ + * Enum used with fluid_synth_add_default_mod() to specify how to handle duplicate modulators. */ +enum fluid_synth_add_mod +{ + FLUID_SYNTH_OVERWRITE, /**< Overwrite any existing matching modulator */ + FLUID_SYNTH_ADD, /**< Sum up modulator amounts */ +}; -FLUIDSYNTH_API int fluid_synth_set_gen(fluid_synth_t *synth, int chan, - int param, float value); -FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param); - -/* @} */ +FLUIDSYNTH_API int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode); +FLUIDSYNTH_API int fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod); +/* @} Synthesis Parameters */ /** - * @defgroup Tuning Tuning - * @ingroup Synth + * @defgroup tuning MIDI Tuning + * @ingroup synth + * + * The functions in this section implement the MIDI Tuning Standard interface. * * @{ */ - FLUIDSYNTH_API int fluid_synth_activate_key_tuning(fluid_synth_t *synth, int bank, int prog, const char *name, const double *pitch, int apply); @@ -302,53 +280,23 @@ FLUIDSYNTH_API int fluid_synth_tuning_iteration_next(fluid_synth_t *synth, int *bank, int *prog); FLUIDSYNTH_API int fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int prog, char *name, int len, double *pitch); - -/* @} */ +/* @} MIDI Tuning */ /** - * @addtogroup Misc + * @defgroup audio_rendering Audio Rendering + * @ingroup synth + * + * The functions in this section can be used to render audio directly to + * memory buffers. They are used internally by the \ref audio_driver and \ref file_renderer, + * but can also be used manually for custom processing of the rendered audio. + * + * @note Please note that all following functions block during rendering. If your goal is to + * render real-time audio, ensure that you call these functions from a high-priority + * thread with little to no other duties other than calling the rendering functions. * * @{ */ -FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t *synth); -FLUID_DEPRECATED FLUIDSYNTH_API const char *fluid_synth_error(fluid_synth_t *synth); -/* @} */ - - -/** - * @defgroup DefaultModulators Default modulators - * @ingroup Synth - * - * @{ - */ - -/** - * Enum used with fluid_synth_add_default_mod() to specify how to handle duplicate modulators. - */ -enum fluid_synth_add_mod -{ - FLUID_SYNTH_OVERWRITE, /**< Overwrite any existing matching modulator */ - FLUID_SYNTH_ADD, /**< Sum up modulator amounts */ -}; - -FLUIDSYNTH_API int fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode); -FLUIDSYNTH_API int fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod); - -/* @} */ - - -/** - * @defgroup SynthPlugin Synthesizer plugin - * @ingroup Synth - * - * To create a synthesizer plugin, create the synthesizer as - * explained above. Once the synthesizer is created you can call - * any of the functions below to get the audio. - * - * @{ - */ - FLUIDSYNTH_API int fluid_synth_write_s16(fluid_synth_t *synth, int len, void *lout, int loff, int lincr, void *rout, int roff, int rincr); @@ -361,32 +309,14 @@ FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_nwrite_float(fluid_synth_t *synt FLUIDSYNTH_API int fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], int nout, float *out[]); - -/* @} */ +/* @} Audio Rendering */ /** - * @defgroup SynthSoundFontLoader SoundFont Loader - * @brief Synthesizer's interface to handle SoundFont loaders + * @defgroup iir_filter Effect - IIR Filter + * @ingroup synth * - * @{ - */ - -FLUIDSYNTH_API void fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader); -FLUIDSYNTH_API fluid_voice_t *fluid_synth_alloc_voice(fluid_synth_t *synth, - fluid_sample_t *sample, - int channum, int key, int vel); -FLUIDSYNTH_API void fluid_synth_start_voice(fluid_synth_t *synth, fluid_voice_t *voice); -FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t *synth, - fluid_voice_t *buf[], int bufsize, int ID); -FLUIDSYNTH_API int fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event); - -/* @} */ - - -/** - * @defgroup CustomIIR IIR Filter - * @ingroup Effects + * Functions for configuring the built-in IIR filter effect * * @{ */ @@ -413,29 +343,43 @@ enum fluid_iir_filter_flags }; FLUIDSYNTH_API int fluid_synth_set_custom_filter(fluid_synth_t *, int type, int flags); +/* @} IIR Filter */ + -/* @} */ /** - * @addtogroup LADSPA - * @ingroup Effects + * @defgroup channel_setup MIDI Channel Setup + * @ingroup synth * - * @{ - */ -FLUIDSYNTH_API fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth); -/* @} */ - - -/** - * @defgroup ChannelModes Channel modes - * @ingroup Synth + * The functions in this section provide interfaces to change the channel type + * and to configure basic channels, legato and portamento setups. * * @{ */ -/** Interface to poly/mono mode variables - * +/** @name Channel Type + * @{ + */ + +/** + * The midi channel type used by fluid_synth_set_channel_type() + */ +enum fluid_midi_channel_type +{ + CHANNEL_TYPE_MELODIC = 0, /**< Melodic midi channel */ + CHANNEL_TYPE_DRUM = 1 /**< Drum midi channel */ +}; + +FLUIDSYNTH_API int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type); +/** @} Channel Type */ + + +/** @name Basic Channel Mode + * @{ + */ + +/** * Channel mode bits OR-ed together so that it matches with the midi spec: poly omnion (0), mono omnion (1), poly omnioff (2), mono omnioff (3) */ enum fluid_channel_mode_flags @@ -444,15 +388,9 @@ enum fluid_channel_mode_flags FLUID_CHANNEL_OMNI_OFF = 0x02, /**< if flag is set, the basic channel is in omni off state, if not set omni is on */ }; -/** Indicates the breath mode a channel is set to */ -enum fluid_channel_breath_flags -{ - FLUID_CHANNEL_BREATH_POLY = 0x10, /**< when channel is poly, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath to initial attenuation modulator */ - FLUID_CHANNEL_BREATH_MONO = 0x20, /**< when channel is mono, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath modulator */ - FLUID_CHANNEL_BREATH_SYNC = 0x40, /**< when channel is mono, this flag indicates that the breath controller(MSB)triggers noteon/noteoff on the running note */ -}; - -/** Indicates the mode a basic channel is set to */ +/** + * Indicates the mode a basic channel is set to + */ enum fluid_basic_channel_modes { FLUID_CHANNEL_MODE_MASK = (FLUID_CHANNEL_OMNI_OFF | FLUID_CHANNEL_POLY_OFF), /**< Mask Poly and Omni bits of #fluid_channel_mode_flags, usually only used internally */ @@ -471,8 +409,13 @@ FLUIDSYNTH_API int fluid_synth_get_basic_channel(fluid_synth_t *synth, int chan int *basic_val_out); FLUIDSYNTH_API int fluid_synth_set_basic_channel(fluid_synth_t *synth, int chan, int mode, int val); -/** Interface to mono legato mode - * +/** @} Basic Channel Mode */ + +/** @name Legato Mode + * @{ + */ + +/** * Indicates the legato mode a channel is set to * n1,n2,n3,.. is a legato passage. n1 is the first note, and n2,n3,n4 are played legato with previous note. */ enum fluid_channel_legato_mode @@ -484,9 +427,13 @@ enum fluid_channel_legato_mode FLUIDSYNTH_API int fluid_synth_set_legato_mode(fluid_synth_t *synth, int chan, int legatomode); FLUIDSYNTH_API int fluid_synth_get_legato_mode(fluid_synth_t *synth, int chan, int *legatomode); +/** @} Legato Mode */ -/** Interface to portamento mode - * +/** @name Portamento Mode + * @{ + */ + +/** * Indicates the portamento mode a channel is set to */ enum fluid_channel_portamento_mode @@ -494,21 +441,61 @@ enum fluid_channel_portamento_mode FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE, /**< Mode 0 - Portamento on each note (staccato or legato) */ FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY, /**< Mode 1 - Portamento only on legato note */ FLUID_CHANNEL_PORTAMENTO_MODE_STACCATO_ONLY, /**< Mode 2 - Portamento only on staccato note */ - FLUID_CHANNEL_PORTAMENTO_MODE_LAST /**< @internal Value defines the count of portamento modes (#fluid_channel_portamento_mode) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ + FLUID_CHANNEL_PORTAMENTO_MODE_LAST /**< @internal Value defines the count of portamento modes + @warning This symbol is not part of the public API and ABI + stability guarantee and may change at any time! */ }; FLUIDSYNTH_API int fluid_synth_set_portamento_mode(fluid_synth_t *synth, int chan, int portamentomode); FLUIDSYNTH_API int fluid_synth_get_portamento_mode(fluid_synth_t *synth, int chan, int *portamentomode); +/** @} Portamento Mode */ + +/**@name Breath Mode + * @{ + */ + +/** + * Indicates the breath mode a channel is set to + */ +enum fluid_channel_breath_flags +{ + FLUID_CHANNEL_BREATH_POLY = 0x10, /**< when channel is poly, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath to initial attenuation modulator */ + FLUID_CHANNEL_BREATH_MONO = 0x20, /**< when channel is mono, this flag indicates that the default velocity to initial attenuation modulator is replaced by a breath modulator */ + FLUID_CHANNEL_BREATH_SYNC = 0x40, /**< when channel is mono, this flag indicates that the breath controller(MSB)triggers noteon/noteoff on the running note */ +}; -/* Interface to breath mode */ FLUIDSYNTH_API int fluid_synth_set_breath_mode(fluid_synth_t *synth, int chan, int breathmode); FLUIDSYNTH_API int fluid_synth_get_breath_mode(fluid_synth_t *synth, int chan, int *breathmode); +/** @} Breath Mode */ +/* @} MIDI Channel Setup */ -/* @} */ + +/** @ingroup settings */ +FLUIDSYNTH_API fluid_settings_t *fluid_synth_get_settings(fluid_synth_t *synth); + +/** @ingroup soundfont_loader */ +FLUIDSYNTH_API void fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader); + +/** @ingroup soundfont_loader */ +FLUIDSYNTH_API fluid_preset_t *fluid_synth_get_channel_preset(fluid_synth_t *synth, int chan); + +/** @ingroup midi_input */ +FLUIDSYNTH_API int fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event); + +/** @ingroup soundfonts */ +FLUIDSYNTH_API +int fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); + +/** @ingroup soundfonts */ +FLUIDSYNTH_API +int fluid_synth_unpin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num); + +/** @ingroup ladspa */ +FLUIDSYNTH_API fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth); #ifdef __cplusplus } diff --git a/include/fluidsynth/version.h.in b/include/fluidsynth/version.h.in index 34db6ea7..6eee0b5d 100644 --- a/include/fluidsynth/version.h.in +++ b/include/fluidsynth/version.h.in @@ -31,7 +31,6 @@ extern "C" { * * @{ */ - #define FLUIDSYNTH_VERSION @FLUIDSYNTH_VERSION@ /**< String constant of libfluidsynth version. */ #define FLUIDSYNTH_VERSION_MAJOR @FLUIDSYNTH_VERSION_MAJOR@ /**< libfluidsynth major version integer constant. */ #define FLUIDSYNTH_VERSION_MINOR @FLUIDSYNTH_VERSION_MINOR@ /**< libfluidsynth minor version integer constant. */ @@ -39,7 +38,6 @@ extern "C" { FLUIDSYNTH_API void fluid_version(int *major, int *minor, int *micro); FLUIDSYNTH_API char* fluid_version_str(void); - /* @} */ #ifdef __cplusplus diff --git a/src/bindings/fluid_ladspa.c b/src/bindings/fluid_ladspa.c index 5f68de4a..65587f36 100644 --- a/src/bindings/fluid_ladspa.c +++ b/src/bindings/fluid_ladspa.c @@ -227,15 +227,16 @@ error_recovery: } /** - * Destroys and frees a LADSPA effects unit previously created - * with new_fluid_ladspa_fx. + * Destroy and free a LADSPA effects unit previously created + * with new_fluid_ladspa_fx(). + * + * @param fx LADSPA effects instance * * @note This function does not check the engine state for * possible users, so make sure that you only call this * if you are sure nobody is using the engine anymore (especially * that nobody calls fluid_ladspa_run) * - * @param fx LADSPA effects instance */ void delete_fluid_ladspa_fx(fluid_ladspa_fx_t *fx) { @@ -268,15 +269,16 @@ void delete_fluid_ladspa_fx(fluid_ladspa_fx_t *fx) /** * Add host buffers to the LADSPA engine. * - * @note The size of the buffers pointed to by the buffers array must be - * at least as large as the buffer size given to new_fluid_ladspa_fx. - * * @param fx LADSPA fx instance * @param prefix common name prefix for the created nodes * @param num_buffers number of of buffers buffer array * @param buffers array of sample buffers * @param buf_stride number of samples contained in one buffer * @return FLUID_OK on success, otherwise FLUID_FAILED + * + * @note The size of the buffers pointed to by the buffers array must be + * at least as large as the buffer size given to new_fluid_ladspa_fx. + * */ int fluid_ladspa_add_host_ports(fluid_ladspa_fx_t *fx, const char *prefix, int num_buffers, fluid_real_t buffers[], int buf_stride) @@ -319,12 +321,13 @@ int fluid_ladspa_add_host_ports(fluid_ladspa_fx_t *fx, const char *prefix, /** * Set the sample rate of the LADSPA effects. * - * Resets the LADSPA effects if the sample rate is different from the - * previous sample rate. - * * @param fx LADSPA fx instance * @param sample_rate new sample rate * @return FLUID_OK on success, otherwise FLUID_FAILED + * + * Resets the LADSPA effects if the sample rate is different from the + * previous sample rate. + * */ int fluid_ladspa_set_sample_rate(fluid_ladspa_fx_t *fx, fluid_real_t sample_rate) { @@ -357,13 +360,13 @@ int fluid_ladspa_set_sample_rate(fluid_ladspa_fx_t *fx, fluid_real_t sample_rate /** * Check if the LADSPA engine is currently used to render audio * + * @param fx LADSPA fx instance + * @return TRUE if LADSPA effects engine is active, otherwise FALSE + * * If an engine is active, the only allowed user actions are deactivation or * setting values of control ports on effects. Anything else, especially * adding or removing effects, buffers or ports, is only allowed in deactivated * state. - * - * @param fx LADSPA fx instance - * @return TRUE if LADSPA effects engine is active, otherwise FALSE */ int fluid_ladspa_is_active(fluid_ladspa_fx_t *fx) { @@ -424,10 +427,10 @@ int fluid_ladspa_activate(fluid_ladspa_fx_t *fx) /** * Deactivate a LADSPA fx instance and all configured effects. * - * @note This function may sleep. - * * @param fx LADSPA fx instance * @return FLUID_OK if deactivation succeeded, otherwise FLUID_FAILED + * + * @note This function may sleep. */ int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx) { @@ -468,11 +471,13 @@ int fluid_ladspa_deactivate(fluid_ladspa_fx_t *fx) } /** - * Reset the LADSPA effects engine: Deactivate LADSPA if currently active, remove all - * effects, remove all user nodes and unload all libraries. + * Reset the LADSPA effects engine. * * @param fx LADSPA fx instance * @return FLUID_OK on success, otherwise FLUID_FAILED + * + * Deactivate LADSPA if currently active, remove all + * effects, remove all user nodes and unload all libraries. */ int fluid_ladspa_reset(fluid_ladspa_fx_t *fx) { @@ -496,15 +501,15 @@ int fluid_ladspa_reset(fluid_ladspa_fx_t *fx) /** * Processes audio data via the LADSPA effects unit. * + * @param fx LADSPA effects instance + * @param block_count number of blocks to render + * @param block_size number of samples in a block + * * FluidSynth calls this function during main output mixing, just after * the internal reverb and chorus effects have been processed. * * It copies audio data from the supplied buffers, runs all effects and copies the * resulting audio back into the same buffers. - * - * @param fx LADSPA effects instance - * @param block_count number of blocks to render - * @param block_size number of samples in a block */ void fluid_ladspa_run(fluid_ladspa_fx_t *fx, int block_count, int block_size) { @@ -862,7 +867,7 @@ int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const char *effect_na } /** - * Create an effect, i.e. an instance of a LADSPA plugin + * Create an instance of a LADSPA plugin as an effect * * @param fx LADSPA effects instance * @param effect_name name of the effect @@ -922,14 +927,14 @@ int fluid_ladspa_add_effect(fluid_ladspa_fx_t *fx, const char *effect_name, /** * Connect an effect port to a host port or buffer * - * @note There is no corresponding disconnect function. If the connections need to be changed, - * clear everything with fluid_ladspa_reset and start again from scratch. - * * @param fx LADSPA effects instance * @param effect_name name of the effect * @param port_name the port name to connect to (case-insensitive prefix match) * @param name the host port or buffer to connect to (case-insensitive) * @return FLUID_OK on success, otherwise FLUID_FAILED + * + * @note There is no corresponding disconnect function. If the connections need to be changed, + * clear everything with fluid_ladspa_reset and start again from scratch. */ int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name, const char *name) @@ -1009,13 +1014,13 @@ int fluid_ladspa_effect_link(fluid_ladspa_fx_t *fx, const char *effect_name, /** * Do a sanity check for problems in the LADSPA setup * - * If the check detects problems and the err pointer is not NULL, a description of the first found - * problem is written to that string (up to err_size - 1 characters). - * * @param fx LADSPA fx instance * @param err externally provided buffer for the error message. Set to NULL if you don't need an error message. * @param err_size size of the err buffer * @return FLUID_OK if setup is valid, otherwise FLUID_FAILED (err will contain the error message) + * + * If the check detects problems and the err pointer is not NULL, a description of the first found + * problem is written to that string (up to err_size - 1 characters). */ int fluid_ladspa_check(fluid_ladspa_fx_t *fx, char *err, int err_size) { @@ -1139,13 +1144,13 @@ static fluid_ladspa_node_t *get_node(fluid_ladspa_fx_t *fx, const char *name) /** * Return a LADSPA effect port index by name, using a 'fuzzy match'. * - * Returns the first effect port which matches the name. If no exact match is - * found, returns the port that starts with the specified name, but only if there is - * only one such match. - * * @param effect pointer to fluid_ladspa_effect_t * @param name the port name * @return index of the port in the effect or -1 on error + * + * Returns the first effect port which matches the name. If no exact match is + * found, returns the port that starts with the specified name, but only if there is + * only one such match. */ static int get_effect_port_idx(const fluid_ladspa_effect_t *effect, const char *name) { @@ -1178,11 +1183,11 @@ static int get_effect_port_idx(const fluid_ladspa_effect_t *effect, const char * /** * Return a LADSPA descriptor structure for a plugin in a LADSPA library. * - * If name is optional if the library contains only one plugin. - * * @param lib pointer to the dynamically loaded library * @param name name (LADSPA Label) of the plugin * @return pointer to LADSPA_Descriptor, NULL on error or if not found + * + * The name is optional if the library contains only one plugin. */ static const LADSPA_Descriptor *get_plugin_descriptor(fluid_module_t *lib, const char *name) { @@ -1233,13 +1238,13 @@ static const LADSPA_Descriptor *get_plugin_descriptor(fluid_module_t *lib, const * Instantiate a LADSPA plugin from a library and set up the associated * control structures needed by the LADSPA fx engine. * - * If the library contains only one plugin, then the name is optional. - * Plugins are identified by their "Label" in the plugin descriptor structure. - * * @param fx LADSPA fx instance * @param lib_name file path of the plugin library * @param plugin_name (optional) string name of the plugin (the LADSPA Label) * @return pointer to the new ladspa_plugin_t structure or NULL on error + * + * If the library contains only one plugin, then the name is optional. + * Plugins are identified by their "Label" in the plugin descriptor structure. */ static fluid_ladspa_effect_t * new_fluid_ladspa_effect(fluid_ladspa_fx_t *fx, const char *lib_name, const char *plugin_name) diff --git a/src/sfloader/fluid_sffile.c b/src/sfloader/fluid_sffile.c index d03ca033..339de53f 100644 --- a/src/sfloader/fluid_sffile.c +++ b/src/sfloader/fluid_sffile.c @@ -331,10 +331,11 @@ static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int en /** * Check if a file is a SoundFont file. * - * If fluidsynth was built with DLS support, this function will also identify DLS files. * @param filename Path to the file to check * @return TRUE if it could be a SF2, SF3 or DLS file, FALSE otherwise * + * If fluidsynth was built with DLS support, this function will also identify DLS files. + * * @note This function only checks whether header(s) in the RIFF chunk are present. * A call to fluid_synth_sfload() might still fail. */ diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index c3863f9f..bf3ac21a 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -600,7 +600,7 @@ static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_syn * @note The @p settings parameter is used directly and should freed after * the synth has been deleted. Further note that you may modify FluidSettings of the * @p settings instance. However, only those FluidSettings marked as 'realtime' will - * affect the synth immediately. + * affect the synth immediately. See the \ref fluidsettings for more details. */ fluid_synth_t * new_fluid_synth(fluid_settings_t *settings) @@ -3461,7 +3461,8 @@ fluid_synth_program_reset(fluid_synth_t *synth) } /** - * Synthesize a block of floating point audio to separate audio buffers (multichannel rendering). First effect channel used by reverb, second for chorus. + * Synthesize a block of floating point audio to separate audio buffers (multi-channel rendering). + * * @param synth FluidSynth instance * @param len Count of audio frames to synthesize * @param left Array of float buffers to store left channel of planar audio (as many as \c synth.audio-channels buffers, each of \c len in size) @@ -3470,9 +3471,13 @@ fluid_synth_program_reset(fluid_synth_t *synth) * @param fx_right Since 1.1.7: If not \c NULL, array of float buffers to store right effect channels (size: dito) * @return #FLUID_OK on success, #FLUID_FAILED otherwise * + * First effect channel used by reverb, second for chorus. + * * @note Should only be called from synthesis thread. * - * @deprecated fluid_synth_nwrite_float() is deprecated and will be removed in a future release. It may continue to work or it may return #FLUID_FAILED in the future. Consider using the more powerful and flexible fluid_synth_process(). + * @deprecated fluid_synth_nwrite_float() is deprecated and will be removed in a future release. + * It may continue to work or it may return #FLUID_FAILED in the future. Consider using the more + * powerful and flexible fluid_synth_process(). * * Usage example: * @code{.cpp} @@ -3484,14 +3489,17 @@ fluid_synth_program_reset(fluid_synth_t *synth) // we need twice as many (mono-)buffers channels *= 2; - // fluid_synth_nwrite_float renders planar audio, e.g. if synth.audio-channels==16: each midi channel gets rendered to its own stereo buffer, rather than having one buffer and interleaved PCM + // fluid_synth_nwrite_float renders planar audio, e.g. if synth.audio-channels==16: + // each midi channel gets rendered to its own stereo buffer, rather than having + // one buffer and interleaved PCM float** mix_buf = new float*[channels]; for(int i = 0; i < channels; i++) { mix_buf[i] = new float[FramesToRender]; } - // retrieve number of (stereo) effect channels (internally hardcoded to reverb (first chan) and chrous (second chan)) + // retrieve number of (stereo) effect channels (internally hardcoded to reverb (first chan) + // and chrous (second chan)) fluid_settings_getint(settings, "synth.effects-channels", &channels); channels *= 2; @@ -3709,7 +3717,34 @@ static FLUID_INLINE void fluid_synth_mix_single_buffer(float *FLUID_RESTRICT out } /** - * @brief Synthesize floating point audio to stereo audio channels (implements the default interface #fluid_audio_func_t). + * Synthesize floating point audio to stereo audio channels + * (implements the default interface #fluid_audio_func_t). + * + * @param synth FluidSynth instance + * + * @param len Count of audio frames to synthesize and store in every single buffer provided by \p out and \p fx. + * Zero value is permitted, the function does nothing and return FLUID_OK. + * + * @param nfx Count of arrays in \c fx. Must be a multiple of 2 (because of stereo). + * and in the range 0 <= nfx/2 <= (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups()). + * Note that zero value is valid and allows to skip mixing effects in all fx output buffers. + * + * @param fx Array of buffers to store effects audio to. Buffers may + * alias with buffers of \c out. Individual NULL buffers are permitted and will cause to skip mixing any audio into that buffer. + * + * @param nout Count of arrays in \c out. Must be a multiple of 2 + * (because of stereo) and in the range 0 <= nout/2 <= fluid_synth_count_audio_channels(). + * Note that zero value is valid and allows to skip mixing dry audio in all out output buffers. + * + * @param out Array of buffers to store (dry) audio to. Buffers may + * alias with buffers of \c fx. Individual NULL buffers are permitted and will cause to skip mixing any audio into that buffer. + * + * @return #FLUID_OK on success, + * #FLUID_FAILED otherwise, + * - fx == NULL while nfx > 0, or out == NULL while nout > 0. + * - \c nfx or \c nout not multiple of 2. + * - len < 0. + * - \c nfx or \c nout exceed the range explained above. * * Synthesize and mix audio to a given number of planar audio buffers. * Therefore pass nout = N*2 float buffers to \p out in order to render @@ -3757,26 +3792,6 @@ fx[ ((k * fluid_synth_count_effects_channels() + j) * 2 + 1) % nfx ] = right_bu * * k = chan % fluid_synth_count_effects_groups() * - * @param synth FluidSynth instance - * @param len Count of audio frames to synthesize and store in every single buffer provided by \p out and \p fx. - * Zero value is permitted, the function does nothing and return FLUID_OK. - * @param nfx Count of arrays in \c fx. Must be a multiple of 2 (because of stereo). - * and in the range 0 <= nfx/2 <= (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups()). - Note that zero value is valid and allows to skip mixing effects in all fx output buffers. - * @param fx Array of buffers to store effects audio to. Buffers may -alias with buffers of \c out. Individual NULL buffers are permitted and will cause to skip mixing any audio into that buffer. - * @param nout Count of arrays in \c out. Must be a multiple of 2 -(because of stereo) and in the range 0 <= nout/2 <= fluid_synth_count_audio_channels(). - Note that zero value is valid and allows to skip mixing dry audio in all out output buffers. - * @param out Array of buffers to store (dry) audio to. Buffers may -alias with buffers of \c fx. Individual NULL buffers are permitted and will cause to skip mixing any audio into that buffer. - * @return #FLUID_OK on success, - * #FLUID_FAILED otherwise, - * - fx == NULL while nfx > 0, or out == NULL while nout > 0. - * - \c nfx or \c nout not multiple of 2. - * - len < 0. - * - \c nfx or \c nout exceed the range explained above. - * * @parblock * @note The owner of the sample buffers must zero them out before calling this * function, because any synthesized audio is mixed (i.e. added) to the buffers. @@ -5488,6 +5503,7 @@ fluid_synth_set_reverb_preset(fluid_synth_t *synth, unsigned int num) /** * Set reverb parameters. + * * @param synth FluidSynth instance * @param roomsize Reverb room size value (0.0-1.0) * @param damping Reverb damping value (0.0-1.0) @@ -5507,7 +5523,10 @@ fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, double damping, } /** - * Set reverb roomsize. See fluid_synth_set_reverb() for further info. + * Set reverb roomsize. + * + * @param synth FluidSynth instance + * @param roomsize Reverb room size value (0.0-1.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize) @@ -5516,7 +5535,10 @@ int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize) } /** - * Set reverb damping. See fluid_synth_set_reverb() for further info. + * Set reverb damping. + * + * @param synth FluidSynth instance + * @param damping Reverb damping value (0.0-1.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping) @@ -5525,7 +5547,10 @@ int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping) } /** - * Set reverb width. See fluid_synth_set_reverb() for further info. + * Set reverb width. + * + * @param synth FluidSynth instance + * @param width Reverb width value (0.0-100.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width) @@ -5534,7 +5559,10 @@ int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width) } /** - * Set reverb level. See fluid_synth_set_reverb() for further info. + * Set reverb level. + * + * @param synth FluidSynth instance + * @param level Reverb level value (0.0-1.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level) @@ -5544,6 +5572,7 @@ int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level) /** * Set one or more reverb parameters. + * * @param synth FluidSynth instance * @param set Flags indicating which parameters should be set (#fluid_revmodel_set_t) * @param roomsize Reverb room size value (0.0-1.2) @@ -5684,8 +5713,8 @@ fluid_synth_set_chorus_on(fluid_synth_t *synth, int on) } /** - * Set chorus parameters. It should be turned on with fluid_synth_set_chorus_on(). - * Keep in mind, that the needed CPU time is proportional to 'nr'. + * Set chorus parameters. + * * @param synth FluidSynth instance * @param nr Chorus voice count (0-99, CPU time consumption proportional to * this value) @@ -5695,6 +5724,9 @@ fluid_synth_set_chorus_on(fluid_synth_t *synth, int on) * 0.0-21.0 is safe for sample-rate values up to 96KHz) * @param type Chorus waveform type (#fluid_chorus_mod) * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * It should be turned on with fluid_synth_set_chorus_on(). + * Keep in mind, that the needed CPU time is proportional to 'nr'. */ int fluid_synth_set_chorus(fluid_synth_t *synth, int nr, double level, double speed, double depth_ms, int type) @@ -5704,7 +5736,11 @@ int fluid_synth_set_chorus(fluid_synth_t *synth, int nr, double level, } /** - * Set the chorus voice count. See fluid_synth_set_chorus() for further info. + * Set the chorus voice count. + * + * @param synth FluidSynth instance + * @param nr Chorus voice count (0-99, CPU time consumption proportional to + * this value) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr) @@ -5713,7 +5749,10 @@ int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr) } /** - * Set the chorus level. See fluid_synth_set_chorus() for further info. + * Set the chorus level. + * + * @param synth FluidSynth instance + * @param level Chorus level (0.0-10.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level) @@ -5722,7 +5761,10 @@ int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level) } /** - * Set the chorus speed. See fluid_synth_set_chorus() for further info. + * Set the chorus speed. + * + * @param synth FluidSynth instance + * @param speed Chorus speed in Hz (0.1-5.0) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed) @@ -5731,7 +5773,11 @@ int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed) } /** - * Set the chorus depth. See fluid_synth_set_chorus() for further info. + * Set the chorus depth. + * + * @param synth FluidSynth instance + * @param depth_ms Chorus depth (max value depends on synth sample-rate, + * 0.0-21.0 is safe for sample-rate values up to 96KHz) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms) @@ -5740,7 +5786,10 @@ int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms) } /** - * Set the chorus type. See fluid_synth_set_chorus() for further info. + * Set the chorus type. + * + * @param synth FluidSynth instance + * @param type Chorus waveform type (#fluid_chorus_mod) * @return #FLUID_OK on success, #FLUID_FAILED otherwise */ int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type) @@ -6938,14 +6987,13 @@ fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) /** * Configure a general-purpose IIR biquad filter. * - * This is an optional, additional filter that operates independently from the default low-pass filter required by the Soundfont2 standard. - * By default this filter is off (#FLUID_IIR_DISABLED). - * * @param synth FluidSynth instance * @param type Type of the IIR filter to use (see #fluid_iir_filter_type) * @param flags Additional flags to customize this filter or zero to stay with the default (see #fluid_iir_filter_flags) - * * @return #FLUID_OK if the settings have been successfully applied, otherwise #FLUID_FAILED + * + * This is an optional, additional filter that operates independently from the default low-pass filter required by the Soundfont2 standard. + * By default this filter is off (#FLUID_IIR_DISABLED). */ int fluid_synth_set_custom_filter(fluid_synth_t *synth, int type, int flags) { From 48761ea724d8ac69c32a06a7703fb677b7b11b93 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 21:12:06 +0100 Subject: [PATCH 22/55] Consistent capitalization of usage guide section names --- doc/fluidsynth-v20-devdoc.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 487ed5be..04ef8fc5 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -189,9 +189,9 @@ FluidSynths major version was bumped. The API was reworked, deprecated functions \page UsageGuide Usage Guide - \subpage CreatingSettings - \subpage CreatingSynth + - \subpage LoadingSoundfonts - \subpage CreatingAudioDriver - \subpage UsingSynth - - \subpage LoadingSoundfonts - \subpage SendingMIDI - \subpage RealtimeMIDI - \subpage MIDIPlayer @@ -252,7 +252,7 @@ For a full list of available synthesizer settings, please refer -\page CreatingAudioDriver Creating the Audio Driver +\page CreatingAudioDriver Creating the audio driver The synthesizer itself does not write any audio to the audio output. This allows application developers to manage the audio output themselves if they wish. The next section describes the use of the synthesizer without an audio driver in more detail. @@ -319,7 +319,7 @@ The fluid_synth_sfload() function returns the unique identifier of the loaded So Additional API functions are provided to get the number of loaded SoundFonts and to get a pointer to the SoundFont. -\page SendingMIDI Sending MIDI Events +\page SendingMIDI Sending MIDI events Once the synthesizer is up and running and a SoundFont is loaded, most people will want to do something useful with it. Make noise, for example. MIDI messages can be sent using the fluid_synth_noteon(), fluid_synth_noteoff(), fluid_synth_cc(), fluid_synth_pitch_bend(), fluid_synth_pitch_wheel_sens(), and fluid_synth_program_change() functions. For convenience, there's also a fluid_synth_bank_select() function (the bank select message is normally sent using a control change message). @@ -362,7 +362,7 @@ protected: }; \endcode -\page RealtimeMIDI Creating a Real-time MIDI Driver +\page RealtimeMIDI Creating a real-time MIDI driver FluidSynth can process real-time MIDI events received from hardware MIDI ports or other applications. To do so, the client must create a MIDI input driver. It is a very similar process to the creation of the audio driver: you initialize some properties in a settings instance and call the new_fluid_midi_driver() function providing a callback function that will be invoked when a MIDI event is received. The following MIDI drivers are currently supported: @@ -400,7 +400,7 @@ the MIDI subsystems used. For a full list of available midi driver setti -\page MIDIPlayer Loading and Playing a MIDI file +\page MIDIPlayer Loading and playing a MIDI file FluidSynth can be used to play MIDI files, using the MIDI File Player interface. It follows a high level implementation, though its implementation is currently incomplete. After initializing the synthesizer, create the player passing the synth instance to new_fluid_player(). Then, you can add some SMF file names to the player using fluid_player_add(), and finally call fluid_player_play() to start the playback. You can check if the player has finished by calling fluid_player_get_status(), or wait for the player to terminate using fluid_player_join(). @@ -621,7 +621,7 @@ int main(int argc, char** argv) -\page Sequencer Sequencer +\page Sequencer Using the MIDI sequencer FluidSynth's sequencer can be used to play MIDI events in a more flexible way than using the MIDI file player, which expects the events to be stored as Standard MIDI Files. Using the sequencer, you can provide the events one by one, with an optional timestamp for scheduling. @@ -747,7 +747,7 @@ int main(void) { The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, type @c help in the fluidsynth command line shell. For a full list of available command line settings, please refer to the \ref settings_shell documentation. -\page Multi-channel Multi-Channel audio rendering +\page Multi-channel Multi-channel audio rendering FluidSynth is capable of rendering all audio and all effects from all MIDI channels to separate stereo buffers. Refer to the documentation of fluid_synth_process() and review the different use-cases in the example file for information on how to do that: \ref fluidsynth_process.c @@ -792,7 +792,7 @@ found in the API reference and in the GitHub Wiki. - Voice overflow settings - LADSPA effects unit - MIDI tunings -*/ + /*! \example example.c From 512f39b1a186a215454fc2c3c3584bce942bd808 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 21:12:44 +0100 Subject: [PATCH 23/55] Some more brief message abbreviation hints --- doc/Doxyfile.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 2852fc88..9c1e37b4 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -11,7 +11,7 @@ CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the +ABBREVIATE_BRIEF = "Functions for" "Functions to" "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO From f5900a261fe530e4dcd72192d069ed726888bf84 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 7 Nov 2020 23:43:23 +0100 Subject: [PATCH 24/55] Custom doxygen layout to rename modules to API Reference --- doc/Doxyfile.cmake | 1 + doc/doxygen/layout.xml | 195 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 doc/doxygen/layout.xml diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 9c1e37b4..2e3ced7a 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -69,6 +69,7 @@ SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = +LAYOUT_FILE = @CMAKE_SOURCE_DIR@/doc/doxygen/layout.xml #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- diff --git a/doc/doxygen/layout.xml b/doc/doxygen/layout.xml new file mode 100644 index 00000000..647cfa32 --- /dev/null +++ b/doc/doxygen/layout.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d4741b7d5e4b87f69c118c27d31cb28ac89457ba Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 7 Nov 2020 21:52:47 +0100 Subject: [PATCH 25/55] Sort groups/modules, briefs and members --- doc/Doxyfile.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 2e3ced7a..df04d275 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -54,9 +54,9 @@ CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO INLINE_INFO = YES -SORT_MEMBER_DOCS = NO -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = YES +SORT_GROUP_NAMES = YES SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO From 921c0e08c00ead8ac416ac0bee73601765687cdf Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 4 Nov 2020 19:46:31 +0100 Subject: [PATCH 26/55] Updated documentation styling --- doc/doxygen/custom.css | 204 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 195 insertions(+), 9 deletions(-) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 1114d839..f44975cc 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -1,12 +1,198 @@ +body +{ + background: #eee; +} + +#titlearea +{ + background: white; +} + +* +{ + text-shadow: none !important; +} + + +/* Reduce width of main content for more readibility */ +div.contents, +div.header +{ + max-width: 60em; + background: white; + margin: 0; + padding: 1em; +} + +#titlearea +{ + border: 0; +} + + +div.headertitle +{ + padding-left: 0; +} + + +/* Hide permalink icon in front of method name and make whole name clickable + * instead (by invisibly streting the permalink over the title). Not ideal, + * as the name can't be selected anymore, but better than the distracting + * icon. */ +span.permalink +{ + display: block; + position: relative; + font-size: unset; +} + +span.permalink a { + position: absolute; + width: 100%; + opacity: 0; +} + + +/* Hide "libfluidsynth" root node of nav tree */ +#nav-tree-contents > ul > li:first-child > .item > .label, +#nav-tree-contents > ul > li:first-child > .item > a +{ + display: none; +} + +#nav-tree +{ + background: #333; +} + +#nav-tree .label a { + color: #fff; +} + +#nav-tree .selected { + background: #666; +} + + +#nav-tree, +div.header, +.ui-resizable-e, +.sm-dox, +.navpath ul, +.memtitle, +.sm-dox a, +.fieldtable th +{ + background-image: none !important; +} + +#main-nav { + background: #DFE5F1; +} + +.memitem, +.memproto, +.memdoc, +.memtitle +{ + box-shadow: none; + text-shadow: none; + border-right: none; + border-bottom: none; + border-bottom-left-radius: 0; + background-image: none; + border-color: #DFE5F1; +} + +.memtitle { + width: 100%; + box-sizing: border-box; + background: #DFE5F1; + border: 0; +} + +.memproto { + background: #fafafa; + border-bottom: 1px solid #DFE5F1; + border-right: 1px solid #DFE5F1; +} + +.mdescLeft, +.mdescRight, +.memItemLeft, +.memItemRight, +.memTemplItemLeft, +.memTemplItemRight, +.memTemplParams +{ + background: none; +} + +.memSeparator { +} code { - background-color: #eeeeee; - text-shadow: none; - color: black; - margin-left: 4px; - margin-right: 4px; - padding-left: 4px; - padding-right: 4px; - border-radius: 3px; - white-space: nowrap; + background-color: #eeeeee; + text-shadow: none; + color: black; + margin-left: 4px; + margin-right: 4px; + padding-left: 4px; + padding-right: 4px; + border-radius: 3px; + white-space: nowrap; +} + +div.fragment { + padding: 5px; +} + +div.fragment .line { + line-height: 1.4; +} + +.memdoc dl { + padding-left: 0; +} + +dl.note { + margin-left: -10px; + padding-left: 5px; + border-left: 5px solid; + border-color: lightblue; +} + +dl.deprecated { + margin-left: -10px; + padding-left: 5px; + border-left: 5px solid; + border-color: orange; +} + +dl.warning { + margin-left: -10px; + padding-left: 5px; + border-left: 5px solid; + border-color: red; +} + +table.directory .arrow { + height: inherit; +} + +table.directory tr td { + padding: 0.4em 6px; +} + +table.directory td.desc { + border: 0; +} + +table.directory tr.even { + background-color: inherit; +} + +table.directory tr { + border-bottom: 1px solid #eee; } From 5a70df1cb79dbf7fd1138714c7fd00118defeba6 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Wed, 4 Nov 2020 20:20:54 +0100 Subject: [PATCH 27/55] Remove footer, as it takes away valuable vertical space --- doc/Doxyfile.cmake | 2 +- doc/doxygen/footer.html | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 doc/doxygen/footer.html diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index df04d275..1757cffa 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -122,7 +122,7 @@ GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = -HTML_FOOTER = +HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/doxygen/footer.html HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl @CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf @CMAKE_SOURCE_DIR@/doc/FluidMixer.jpg diff --git a/doc/doxygen/footer.html b/doc/doxygen/footer.html new file mode 100644 index 00000000..308b1d01 --- /dev/null +++ b/doc/doxygen/footer.html @@ -0,0 +1,2 @@ + + From d2a29ec4c514d48e2babf60e6f94e60ff0626462 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 21:50:35 +0100 Subject: [PATCH 28/55] Make sure libxslt is only searched if doxygen is available as well --- doc/CMakeLists.txt | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index a6940dcc..752c22f4 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -27,19 +27,20 @@ if ( DOXYGEN_FOUND ) ${DOXYGEN} Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) -endif ( DOXYGEN_FOUND ) -find_package ( LibXslt ) -if ( LIBXSLT_XSLTPROC_EXECUTABLE ) - add_custom_target ( doxygen_settings - ${LIBXSLT_XSLTPROC_EXECUTABLE} - --output ${CMAKE_CURRENT_BINARY_DIR}/fluidsettings.txt - ${CMAKE_CURRENT_SOURCE_DIR}/doxygen/fluidsettings.xsl - ${CMAKE_CURRENT_SOURCE_DIR}/fluidsettings.xml - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - add_dependencies(doxygen doxygen_settings) -endif ( LIBXSLT_XSLTPROC_EXECUTABLE ) + find_package ( LibXslt ) + if ( LIBXSLT_XSLTPROC_EXECUTABLE ) + add_custom_target ( doxygen_settings + ${LIBXSLT_XSLTPROC_EXECUTABLE} + --output ${CMAKE_CURRENT_BINARY_DIR}/fluidsettings.txt + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen/fluidsettings.xsl + ${CMAKE_CURRENT_SOURCE_DIR}/fluidsettings.xml + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + add_dependencies(doxygen doxygen_settings) + endif ( LIBXSLT_XSLTPROC_EXECUTABLE ) + +endif ( DOXYGEN_FOUND ) if ( UNIX ) From 1d9d7e9042d9b5ed855399a5d9509956fb88fef8 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Thu, 12 Nov 2020 22:31:19 +0100 Subject: [PATCH 29/55] Also update the styling of the deprecated list --- doc/doxygen/custom.css | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index f44975cc..db728b80 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -94,7 +94,9 @@ div.header, .memitem, .memproto, .memdoc, -.memtitle +.memtitle, +dl.reflist dd, +dl.reflist dt { box-shadow: none; text-shadow: none; @@ -105,7 +107,9 @@ div.header, border-color: #DFE5F1; } -.memtitle { +.memtitle, +dl.reflist dt +{ width: 100%; box-sizing: border-box; background: #DFE5F1; @@ -132,6 +136,15 @@ div.header, .memSeparator { } +dl.reflist { + padding: 0; +} + +dl.reflist dd { + margin-bottom: 1.5em; + padding: 1em; +} + code { background-color: #eeeeee; text-shadow: none; From ee2ac9e1d9c18f3561d7f1663acaf2665af5f898 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Fri, 13 Nov 2020 20:46:02 +0100 Subject: [PATCH 30/55] Mark settings with callbacks as realtime and output this in the generated docs --- doc/doxygen/fluidsettings.xsl | 12 ++++++++++++ doc/fluidsettings.xml | 25 ++++++++++++++++++++++++- doc/fluidsettings.xsl | 16 ++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/doc/doxygen/fluidsettings.xsl b/doc/doxygen/fluidsettings.xsl index 9375a18d..106498a6 100644 --- a/doc/doxygen/fluidsettings.xsl +++ b/doc/doxygen/fluidsettings.xsl @@ -65,6 +65,18 @@ \endhtmlonly + +\par Real-time + + +\htmlonly + +\endhtmlonly + + This setting can be changed during runtime of the synthesizer. + + + \deprecated This setting is deprecated and might be removed in a future version of FluidSynth. diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index ab5c2091..7f66ab2a 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -11,7 +11,9 @@ NOTE: You're not expected to look at this raw XML file. Please open it in a webb https://stackoverflow.com/a/3839054 https://stackoverflow.com/a/6251757 -Developers: Settings can be deprecated by adding: SOME TEXT +Developers: + - Settings can be deprecated by adding: SOME TEXT + - Real-time settings can be marked with SOME OPTIONAL TEXT. --> @@ -38,6 +40,7 @@ Developers: Settings can be deprecated by adding: SOME TEXTchorus.active bool 1 (TRUE) + When set to 1 (TRUE) the chorus effects module is activated. Otherwise, no chorus will be added to the output signal. Note that the amount of signal sent to the chorus module depends on the "chorus send" generator defined in the SoundFont. @@ -47,6 +50,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT8.0 0.0 256.0 + Specifies the modulation depth of the chorus. @@ -56,6 +60,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT2.0 0.0 10.0 + Specifies the output amplitude of the chorus signal. @@ -65,6 +70,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT3 0 99 + Sets the voice count of the chorus. @@ -74,6 +80,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.3 0.1 5.0 + Sets the modulation speed in Hz. @@ -102,6 +109,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0 0 126 + Device identifier used for SYSEX commands, such as MIDI Tuning Standard commands. Only those SYSEX commands destined for this ID or to all devices will be acted upon. @@ -136,6 +144,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.2 0.0 10.0 + The gain is applied to the final or master output of the synthesizer. It is set to a low value by default to avoid the saturation of the output when many notes are played. @@ -191,6 +200,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT1000.0 -10000.0 10000.0 + This score is divided by the number of seconds this voice has been active and is added to the overflow priority. It is usually a positive @@ -204,6 +214,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT5000.0 -50000.0 50000.0 + This score is added to voices on channels marked with the synth.overflow.important-channels setting. @@ -213,6 +224,7 @@ Developers: Settings can be deprecated by adding: SOME TEXToverflow.important-channels str (empty string) + This setting is a comma-separated list of MIDI channel numbers that should be treated as "important" by the overflow calculation, adding the score @@ -229,6 +241,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT4000.0 -10000.0 10000.0 + Sets the overflow priority score added to voices on a percussion channel. This is usually a positive score, to give percussion voices @@ -242,6 +255,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT-2000.0 -10000.0 10000.0 + Sets the overflow priority score added to voices that have already received a note-off event. This is usually a negative score, to give released @@ -255,6 +269,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT-1000.0 -10000.0 10000.0 + Sets the overflow priority score added to voices that are currently sustained. With the default value, sustained voices are considered less @@ -267,6 +282,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT500.0 -10000.0 10000.0 + Sets the overflow priority score added to voices based on their current volume. The voice volume is normalized to a value between 0 and 1 and @@ -281,6 +297,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT256 1 65535 + The polyphony defines how many voices can be played in parallel. A note event produces one or more voices. Its good to set this to a value which the system can handle and will thus limit FluidSynth's CPU usage. When FluidSynth runs out of voices it will begin terminating lower priority voices for new note events. @@ -288,6 +305,7 @@ Developers: Settings can be deprecated by adding: SOME TEXTreverb.active bool 1 (TRUE) + When set to 1 (TRUE) the reverb effects module is activated. Otherwise, no reverb will be added to the output signal. Note that the amount of signal sent to the reverb module depends on the "reverb send" generator defined in the SoundFont. @@ -298,6 +316,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.0 0.0 1.0 + Sets the amount of reverb damping. @@ -307,6 +326,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.9 0.0 1.0 + Sets the reverb output amplitude. @@ -316,6 +336,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.2 0.0 1.0 + Sets the room size (i.e. amount of wet) reverb. @@ -325,6 +346,7 @@ Developers: Settings can be deprecated by adding: SOME TEXT0.5 0.0 100.0 + Sets the stereo spread of the reverb signal. @@ -691,6 +713,7 @@ Developers: Settings can be deprecated by adding: SOME TEXTreset-synth bool 1 (TRUE) + If true, reset the synth before starting a new MIDI song, so the state of a previous song can't affect the new song. Turn it off for seamless looping of a song. diff --git a/doc/fluidsettings.xsl b/doc/fluidsettings.xsl index 196b733f..88185de7 100644 --- a/doc/fluidsettings.xsl +++ b/doc/fluidsettings.xsl @@ -276,6 +276,22 @@ a {
+ +
+
Real-time:
+
+ + + + + + This setting can be changed during runtime of the synthesizer. + + +
+
+
+
This setting is deprecated and might be removed in a future version of FluidSynth. From 2a2027085e980cc4cd05d5826a6ef96ce5f2547a Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Fri, 13 Nov 2020 22:23:29 +0100 Subject: [PATCH 31/55] Separate new_* and delete_* functions from the rest --- doc/Doxyfile.cmake | 4 +++- doc/doxygen/layout.xml | 8 ++++++-- include/fluidsynth/audio.h | 8 +++++++- include/fluidsynth/event.h | 2 ++ include/fluidsynth/midi.h | 17 +++++++++++++++++ include/fluidsynth/mod.h | 3 +++ include/fluidsynth/seq.h | 3 +++ include/fluidsynth/settings.h | 3 ++- include/fluidsynth/sfont.h | 9 +++++++++ include/fluidsynth/shell.h | 15 +++++++++++++-- include/fluidsynth/synth.h | 3 +++ 11 files changed, 68 insertions(+), 7 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 1757cffa..b5b8760e 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -24,7 +24,9 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 -ALIASES = +ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" +ALIASES += startlifecycle="\name Lifecycle Functions\n@{" +ALIASES += endlifecycle="@}" OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO diff --git a/doc/doxygen/layout.xml b/doc/doxygen/layout.xml index 647cfa32..84e1ec9d 100644 --- a/doc/doxygen/layout.xml +++ b/doc/doxygen/layout.xml @@ -1,7 +1,11 @@ - + @@ -150,6 +154,7 @@ + @@ -159,7 +164,6 @@ - diff --git a/include/fluidsynth/audio.h b/include/fluidsynth/audio.h index 0ab5e596..4eca1788 100644 --- a/include/fluidsynth/audio.h +++ b/include/fluidsynth/audio.h @@ -81,6 +81,7 @@ typedef int (*fluid_audio_func_t)(void *data, int len, int nfx, float *fx[], int nout, float *out[]); +/** @startlifecycle{Audio Driver} */ FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); @@ -89,6 +90,7 @@ FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver2(fluid_settings_t *s void *data); FLUIDSYNTH_API void delete_fluid_audio_driver(fluid_audio_driver_t *driver); +/** @endlifecycle */ FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); /* @} */ @@ -109,9 +111,13 @@ FLUIDSYNTH_API int fluid_audio_driver_register(const char **adrivers); * * @{ */ + +/** @startlifecycle{File Renderer} */ FLUIDSYNTH_API fluid_file_renderer_t *new_fluid_file_renderer(fluid_synth_t *synth); -FLUIDSYNTH_API int fluid_file_renderer_process_block(fluid_file_renderer_t *dev); FLUIDSYNTH_API void delete_fluid_file_renderer(fluid_file_renderer_t *dev); +/** @endlifecycle */ + +FLUIDSYNTH_API int fluid_file_renderer_process_block(fluid_file_renderer_t *dev); FLUIDSYNTH_API int fluid_file_set_encoding_quality(fluid_file_renderer_t *dev, double q); /* @} */ diff --git a/include/fluidsynth/event.h b/include/fluidsynth/event.h index d5638f32..3da4ce23 100644 --- a/include/fluidsynth/event.h +++ b/include/fluidsynth/event.h @@ -67,8 +67,10 @@ enum fluid_seq_event_type }; /* Event alloc/free */ +/** @startlifecycle{Sequencer Event} */ FLUIDSYNTH_API fluid_event_t *new_fluid_event(void); FLUIDSYNTH_API void delete_fluid_event(fluid_event_t *evt); +/** @endlifecycle */ /* Initializing events */ FLUIDSYNTH_API void fluid_event_set_source(fluid_event_t *evt, fluid_seq_id_t src); diff --git a/include/fluidsynth/midi.h b/include/fluidsynth/midi.h index 43253df0..a0cd3029 100644 --- a/include/fluidsynth/midi.h +++ b/include/fluidsynth/midi.h @@ -71,8 +71,10 @@ typedef int (*handle_midi_event_func_t)(void *data, fluid_midi_event_t *event); * * @{ */ +/** @startlifecycle{MIDI Event} */ FLUIDSYNTH_API fluid_midi_event_t *new_fluid_midi_event(void); FLUIDSYNTH_API void delete_fluid_midi_event(fluid_midi_event_t *event); +/** @endlifecycle */ FLUIDSYNTH_API int fluid_midi_event_set_type(fluid_midi_event_t *evt, int type); FLUIDSYNTH_API int fluid_midi_event_get_type(fluid_midi_event_t *evt); @@ -130,16 +132,24 @@ typedef enum } fluid_midi_router_rule_type; +/** @startlifecycle{MIDI Router} */ FLUIDSYNTH_API fluid_midi_router_t *new_fluid_midi_router(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); FLUIDSYNTH_API void delete_fluid_midi_router(fluid_midi_router_t *handler); +/** @endlifecycle */ + FLUIDSYNTH_API int fluid_midi_router_set_default_rules(fluid_midi_router_t *router); FLUIDSYNTH_API int fluid_midi_router_clear_rules(fluid_midi_router_t *router); FLUIDSYNTH_API int fluid_midi_router_add_rule(fluid_midi_router_t *router, fluid_midi_router_rule_t *rule, int type); + + +/** @startlifecycle{MIDI Router Rule} */ FLUIDSYNTH_API fluid_midi_router_rule_t *new_fluid_midi_router_rule(void); FLUIDSYNTH_API void delete_fluid_midi_router_rule(fluid_midi_router_rule_t *rule); +/** @endlifecycle */ + FLUIDSYNTH_API void fluid_midi_router_rule_set_chan(fluid_midi_router_rule_t *rule, int min, int max, float mul, int add); FLUIDSYNTH_API void fluid_midi_router_rule_set_param1(fluid_midi_router_rule_t *rule, @@ -170,12 +180,16 @@ FLUIDSYNTH_API int fluid_midi_dump_postrouter(void *data, fluid_midi_event_t *ev * * @{ */ + +/** @startlifecycle{MIDI Driver} */ FLUIDSYNTH_API fluid_midi_driver_t *new_fluid_midi_driver(fluid_settings_t *settings, handle_midi_event_func_t handler, void *event_handler_data); FLUIDSYNTH_API void delete_fluid_midi_driver(fluid_midi_driver_t *driver); +/** @endlifecycle */ + /* @} */ /** @@ -198,8 +212,11 @@ enum fluid_player_status FLUID_PLAYER_DONE /**< Player is finished playing */ }; +/** @startlifecycle{MIDI Player} */ FLUIDSYNTH_API fluid_player_t *new_fluid_player(fluid_synth_t *synth); FLUIDSYNTH_API void delete_fluid_player(fluid_player_t *player); +/** @endlifecycle */ + FLUIDSYNTH_API int fluid_player_add(fluid_player_t *player, const char *midifile); FLUIDSYNTH_API int fluid_player_add_mem(fluid_player_t *player, const void *buffer, size_t len); FLUIDSYNTH_API int fluid_player_play(fluid_player_t *player); diff --git a/include/fluidsynth/mod.h b/include/fluidsynth/mod.h index b6c7f168..98a00b0c 100644 --- a/include/fluidsynth/mod.h +++ b/include/fluidsynth/mod.h @@ -72,8 +72,11 @@ enum fluid_mod_src FLUID_MOD_PITCHWHEELSENS = 16 /**< Pitch wheel sensitivity */ }; +/** @startlifecycle{Modulator} */ FLUIDSYNTH_API fluid_mod_t *new_fluid_mod(void); FLUIDSYNTH_API void delete_fluid_mod(fluid_mod_t *mod); +/** @endlifecycle */ + FLUIDSYNTH_API size_t fluid_mod_sizeof(void); FLUIDSYNTH_API void fluid_mod_set_source1(fluid_mod_t *mod, int src, int flags); diff --git a/include/fluidsynth/seq.h b/include/fluidsynth/seq.h index 5b335744..549c9cfd 100644 --- a/include/fluidsynth/seq.h +++ b/include/fluidsynth/seq.h @@ -50,9 +50,12 @@ typedef void (*fluid_event_callback_t)(unsigned int time, fluid_event_t *event, fluid_sequencer_t *seq, void *data); +/** @startlifecycle{MIDI Sequencer} */ FLUID_DEPRECATED FLUIDSYNTH_API fluid_sequencer_t *new_fluid_sequencer(void); FLUIDSYNTH_API fluid_sequencer_t *new_fluid_sequencer2(int use_system_timer); FLUIDSYNTH_API void delete_fluid_sequencer(fluid_sequencer_t *seq); +/** @endlifecycle */ + FLUIDSYNTH_API int fluid_sequencer_get_use_system_timer(fluid_sequencer_t *seq); FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_register_client(fluid_sequencer_t *seq, const char *name, diff --git a/include/fluidsynth/settings.h b/include/fluidsynth/settings.h index 2cfdb1f2..a8b3cb85 100644 --- a/include/fluidsynth/settings.h +++ b/include/fluidsynth/settings.h @@ -99,9 +99,10 @@ enum fluid_types_enum FLUID_SET_TYPE /**< Set of values */ }; - +/** @startlifecycle{Settings} */ FLUIDSYNTH_API fluid_settings_t *new_fluid_settings(void); FLUIDSYNTH_API void delete_fluid_settings(fluid_settings_t *settings); +/** @endlifecycle */ FLUIDSYNTH_API int fluid_settings_get_type(fluid_settings_t *settings, const char *name); diff --git a/include/fluidsynth/sfont.h b/include/fluidsynth/sfont.h index d41c6e70..b29d8b49 100644 --- a/include/fluidsynth/sfont.h +++ b/include/fluidsynth/sfont.h @@ -127,10 +127,12 @@ typedef fluid_sfont_t *(*fluid_sfloader_load_t)(fluid_sfloader_t *loader, const typedef void (*fluid_sfloader_free_t)(fluid_sfloader_t *loader); +/** @startlifecycle{SoundFont Loader} */ FLUIDSYNTH_API fluid_sfloader_t *new_fluid_sfloader(fluid_sfloader_load_t load, fluid_sfloader_free_t free); FLUIDSYNTH_API void delete_fluid_sfloader(fluid_sfloader_t *loader); FLUIDSYNTH_API fluid_sfloader_t *new_fluid_defsfloader(fluid_settings_t *settings); +/** @endlifecycle */ /** * Opens the file or memory indicated by \c filename in binary read mode. @@ -233,6 +235,7 @@ typedef fluid_preset_t *(*fluid_sfont_iteration_next_t)(fluid_sfont_t *sfont); typedef int (*fluid_sfont_free_t)(fluid_sfont_t *sfont); +/** @startlifecycle{SoundFont} */ FLUIDSYNTH_API fluid_sfont_t *new_fluid_sfont(fluid_sfont_get_name_t get_name, fluid_sfont_get_preset_t get_preset, fluid_sfont_iteration_start_t iter_start, @@ -240,6 +243,7 @@ FLUIDSYNTH_API fluid_sfont_t *new_fluid_sfont(fluid_sfont_get_name_t get_name, fluid_sfont_free_t free); FLUIDSYNTH_API int delete_fluid_sfont(fluid_sfont_t *sfont); +/** @endlifecycle */ FLUIDSYNTH_API int fluid_sfont_set_data(fluid_sfont_t *sfont, void *data); FLUIDSYNTH_API void *fluid_sfont_get_data(fluid_sfont_t *sfont); @@ -313,6 +317,7 @@ typedef int (*fluid_preset_noteon_t)(fluid_preset_t *preset, fluid_synth_t *synt */ typedef void (*fluid_preset_free_t)(fluid_preset_t *preset); +/** @startlifecycle{Preset} */ FLUIDSYNTH_API fluid_preset_t *new_fluid_preset(fluid_sfont_t *parent_sfont, fluid_preset_get_name_t get_name, fluid_preset_get_banknum_t get_bank, @@ -320,6 +325,7 @@ FLUIDSYNTH_API fluid_preset_t *new_fluid_preset(fluid_sfont_t *parent_sfont, fluid_preset_noteon_t noteon, fluid_preset_free_t free); FLUIDSYNTH_API void delete_fluid_preset(fluid_preset_t *preset); +/** @endlifecycle */ FLUIDSYNTH_API int fluid_preset_set_data(fluid_preset_t *preset, void *data); FLUIDSYNTH_API void *fluid_preset_get_data(fluid_preset_t *preset); @@ -329,8 +335,11 @@ FLUIDSYNTH_API int fluid_preset_get_banknum(fluid_preset_t *preset); FLUIDSYNTH_API int fluid_preset_get_num(fluid_preset_t *preset); FLUIDSYNTH_API fluid_sfont_t *fluid_preset_get_sfont(fluid_preset_t *preset); +/** @startlifecycle{Sample} */ FLUIDSYNTH_API fluid_sample_t *new_fluid_sample(void); FLUIDSYNTH_API void delete_fluid_sample(fluid_sample_t *sample); +/** @endlifecycle */ + FLUIDSYNTH_API size_t fluid_sample_sizeof(void); FLUIDSYNTH_API int fluid_sample_set_name(fluid_sample_t *sample, const char *name); diff --git a/include/fluidsynth/shell.h b/include/fluidsynth/shell.h index 63ca23a8..8df704ba 100644 --- a/include/fluidsynth/shell.h +++ b/include/fluidsynth/shell.h @@ -57,11 +57,14 @@ FLUIDSYNTH_API char *fluid_get_sysconf(char *buf, int len); * * @{ */ + +/** @startlifecycle{Command Handler} */ FLUIDSYNTH_API fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_router_t *router); FLUIDSYNTH_API void delete_fluid_cmd_handler(fluid_cmd_handler_t *handler); +/** @endlifecycle */ FLUIDSYNTH_API void fluid_cmd_handler_set_synth(fluid_cmd_handler_t *handler, fluid_synth_t *synth); @@ -87,14 +90,18 @@ int fluid_source(fluid_cmd_handler_t *handler, const char *filename); * * @{ */ -FLUIDSYNTH_API -void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler); +/** @startlifecycle{Command Shell} */ FLUIDSYNTH_API fluid_shell_t *new_fluid_shell(fluid_settings_t *settings, fluid_cmd_handler_t *handler, fluid_istream_t in, fluid_ostream_t out, int thread); +FLUIDSYNTH_API +void fluid_usershell(fluid_settings_t *settings, fluid_cmd_handler_t *handler); + FLUIDSYNTH_API void delete_fluid_shell(fluid_shell_t *shell); +/** @endlifecycle */ + /* @} */ @@ -114,6 +121,8 @@ FLUIDSYNTH_API void delete_fluid_shell(fluid_shell_t *shell); * * @{ */ + +/** @startlifecycle{Command Server} */ FLUIDSYNTH_API fluid_server_t *new_fluid_server(fluid_settings_t *settings, fluid_synth_t *synth, fluid_midi_router_t *router); @@ -121,6 +130,8 @@ fluid_server_t *new_fluid_server(fluid_settings_t *settings, FLUIDSYNTH_API void delete_fluid_server(fluid_server_t *server); FLUIDSYNTH_API int fluid_server_join(fluid_server_t *server); +/** @endlifecycle */ + /* @} */ #ifdef __cplusplus diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index 14346fcc..48aaed37 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -47,8 +47,11 @@ extern "C" { * * @{ */ + +/** @startlifecycle{Synthesizer} */ FLUIDSYNTH_API fluid_synth_t *new_fluid_synth(fluid_settings_t *settings); FLUIDSYNTH_API void delete_fluid_synth(fluid_synth_t *synth); +/** @endlifecycle */ FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t *synth); FLUID_DEPRECATED FLUIDSYNTH_API const char *fluid_synth_error(fluid_synth_t *synth); From 3c68eaa619fe89b735d4bf90c48a889f370d941b Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Fri, 13 Nov 2020 23:21:47 +0100 Subject: [PATCH 32/55] Sync the static Doxyfile with Doxyfile.cmake Still missing is the integration of the generated fluidsettings.txt, as that requires a build script currently not available on the server generating the public API docs. --- doc/Doxyfile | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 25ee20c2..950b9c50 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -11,7 +11,7 @@ CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES -ABBREVIATE_BRIEF = "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the +ABBREVIATE_BRIEF = "Functions for" "Functions to" "The $name class" "The $name widget" "The $name file" is provides specifies contains represents a an the ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = NO @@ -24,7 +24,9 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 -ALIASES = +ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" +ALIASES += startlifecycle="\name Lifecycle Functions\n@{" +ALIASES += endlifecycle="@}" OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO @@ -34,7 +36,7 @@ CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO -SUBGROUPING = NO +SUBGROUPING = YES TYPEDEF_HIDES_STRUCT = NO #--------------------------------------------------------------------------- # Build related configuration options @@ -54,9 +56,9 @@ CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = NO INLINE_INFO = YES -SORT_MEMBER_DOCS = NO -SORT_BRIEF_DOCS = NO -SORT_GROUP_NAMES = NO +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = YES +SORT_GROUP_NAMES = YES SORT_BY_SCOPE_NAME = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO @@ -69,6 +71,7 @@ SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = +LAYOUT_FILE = ../doc/doxygen/layout.xml #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -103,8 +106,8 @@ FILTER_SOURCE_FILES = NO SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = YES -REFERENCES_RELATION = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES USE_HTAGS = NO VERBATIM_HEADERS = NO @@ -121,8 +124,8 @@ GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = -HTML_FOOTER = -HTML_EXTRA_STYLESHEET = ../doc/doxy_formula.css +HTML_FOOTER = ../doc/doxygen/footer.html +HTML_EXTRA_STYLESHEET = ../doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = ../doc/fluidsettings.xml ../doc/fluidsettings.xsl ../doc/FluidMixer.pdf ../doc/FluidMixer.jpg GENERATE_HTMLHELP = NO @@ -137,9 +140,9 @@ CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO DISABLE_INDEX = NO -ENUM_VALUES_PER_LINE = 4 -GENERATE_TREEVIEW = NO -TREEVIEW_WIDTH = 250 +ENUM_VALUES_PER_LINE = 1 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 350 FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output @@ -195,12 +198,14 @@ PERLMOD_MAKEVAR_PREFIX = # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = -PREDEFINED = __DOXYGEN__ +PREDEFINED = __DOXYGEN__ \ + FLUIDSYNTH_API \ + FLUID_DEPRECATED EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- From cc84da2094f04a33ef212d9dec51027bbfbb7b8f Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 13:21:57 +0100 Subject: [PATCH 33/55] Split doxygen INPUT into separate lines, for easier readability --- doc/Doxyfile | 7 ++++++- doc/Doxyfile.cmake | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 950b9c50..6b677a91 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -85,7 +85,12 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = ../doc/fluidsynth-v20-devdoc.txt ../include ../include/fluidsynth ../src +INPUT = \ +../doc/fluidsynth-v20-devdoc.txt \ +../include \ +../include/fluidsynth \ +../src + INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h RECURSIVE = YES diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index b5b8760e..e960ecea 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -85,7 +85,14 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -INPUT = @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt @CMAKE_SOURCE_DIR@/include @CMAKE_SOURCE_DIR@/include/fluidsynth @CMAKE_SOURCE_DIR@/src @CMAKE_BINARY_DIR@/include/fluidsynth @CMAKE_BINARY_DIR@/doc/fluidsettings.txt +INPUT = \ +@CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt \ +@CMAKE_SOURCE_DIR@/include \ +@CMAKE_SOURCE_DIR@/include/fluidsynth \ +@CMAKE_SOURCE_DIR@/src \ +@CMAKE_BINARY_DIR@/include/fluidsynth \ +@CMAKE_BINARY_DIR@/doc/fluidsettings.txt + INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h RECURSIVE = YES From 67cb344a20bad13ad602e6dc70a2b8153218b8aa Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 13:23:55 +0100 Subject: [PATCH 34/55] Move recent changes into separate file --- doc/Doxyfile | 1 + doc/Doxyfile.cmake | 1 + doc/fluidsynth-v20-devdoc.txt | 146 --------------------------------- doc/recent_changes.txt | 148 ++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 146 deletions(-) create mode 100644 doc/recent_changes.txt diff --git a/doc/Doxyfile b/doc/Doxyfile index 6b677a91..bd44143f 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -87,6 +87,7 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- INPUT = \ ../doc/fluidsynth-v20-devdoc.txt \ +../doc/recent_changes.txt \ ../include \ ../include/fluidsynth \ ../src diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index e960ecea..d9d12b99 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -87,6 +87,7 @@ WARN_LOGFILE = #--------------------------------------------------------------------------- INPUT = \ @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt \ +@CMAKE_SOURCE_DIR@/doc/recent_changes.txt \ @CMAKE_SOURCE_DIR@/include \ @CMAKE_SOURCE_DIR@/include/fluidsynth \ @CMAKE_SOURCE_DIR@/src \ diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 04ef8fc5..57f81e0a 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -40,152 +40,6 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org -\page RecentChanges Recent Changes -\section NewIn2_2_0 What's new in 2.2.0? - -- #fluid_file_callbacks_t now uses long long as file-offset type (see #fluid_long_long_t). This is a breaking change, which allows to load SoundFonts bigger than 2GiB on Windows. This change required to bump fluidsynth's SOVERSION. -- fluid_event_any_control_change() has been removed -- various fluid_event_*() functions that received a "value" argument of type short now receive an int argument in preparation for MIDI 2.0 support -- The sequencer has received a major revisal. For you that means: - - The sequencer's queue no longer blocks the synthesizer thread, due to being busy arranging its events internally. - - Events that share the same tick was given a new, documented order, see fluid_sequencer_send_at(). - - The sequencer's scale can now be used for arbitrary tempo changes. Previously, the scale of the sequencer was limited to 1000. The only limitation now is >0. -- The dynamic-sample-loader has learned support to pin samples, see fluid_synth_pin_preset() and fluid_synth_unpin_preset() - -\section NewIn2_1_4 What's new in 2.1.4? - -- a regression introduced in 2.1.3 broke fluid_synth_start() for DLS presets - -\section NewIn2_1_1 What's new in 2.1.1? - -- requirements for explicit sequencer client unregistering have been relaxed: delete_fluid_sequencer() now correctly frees any registered sequencer clients (clients can still be explicitly unregistered) -- using the sequencer with the system timer as timing source has been deprecated - -\section NewIn2_1_0 What's new in 2.1.0? - -- refrain from using fluid_synth_set_sample_rate() -- \ref settings_synth_sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() -- new reverb engine -- chorus is now stereophonic -- smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) -- the following audio drivers were added: - - opensles - - oboe - - sdl2 - - waveout - -\section NewIn2_0_8 What's new in 2.0.8? - -- fluid_sample_set_sound_data() caused broken sound when copying sample data - -\section NewIn2_0_7 What's new in 2.0.7? - -- fluid_free() has been added to allow proper deallocation by programming languages other than C/C++ - -\section NewIn2_0_6 What's new in 2.0.6? - -- the MIDI player did not emit any audio when calling fluid_player_play() after fluid_player_stop() - -\section NewIn2_0_5 What's new in 2.0.5? - -- fluid_synth_process() omitted audio samples when called with arbitrary sample counts that were not a multiple of fluid_synth_get_internal_bufsize() -- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if \ref settings_synth_dynamic-sample-loading was set to FALSE - -\section NewIn2_0_3 What's new in 2.0.3? - -- fix incorrect behaviour of fluid_sample_set_sound_data() -- add missing getters for midi events: - - fluid_midi_event_get_text() - - fluid_midi_event_get_lyrics() - -\section NewIn2_0_2 What's new in 2.0.2? - -- fluid_synth_error() has been deprecated, use fluid_set_log_function() to interfere log messages - -\section NewIn2_0_0 What's new in 2.0.0? - -FluidSynths major version was bumped. The API was reworked, deprecated functions were removed. - -Important changes that may not result in a compilation error but may cause your app to misbehave: - -- all public \c fluid_settings_* functions that return an integer which is not meant to be interpreted as bool consistently return either FLUID_OK or FLUID_FAILED -- fluid_settings_setstr() cannot be used to set integer (toggle) settings with "yes" or "no" values anymore. Use fluid_settings_setint() instead, for example:
fluid_settings_setint(settings, "synth.reverb.active", 0) instead of fluid_settings_setstr(settings, "synth.reverb.active", "no") -- explicit client unregistering is required for fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() (since fluidsynth 2.1.1 not required anymore, but still recommend) -- all public functions consistently receive signed integers for soundfont ids, bank and program numbers -- use unique device names for the "audio.portaudio.device" setting -- fluid_synth_process() received a new more flexible implementation, but now requires zeroed-out sample buffers - -Other changes in FluidSynth 2.0.0 concerning developers: - -- all public \c delete_* functions return void and are safe when called with NULL -- the shell command handler was decoupled internally, as a consequence the param list of new_fluid_server() and new_fluid_cmd_handler() was adapted -- \c fluid_settings_set* functions no longer silently register unknown settings but return an error instead -- reverb: roomsize is now limited to an upper threshold of 1.0 to avoid exponential volume increase -- rename \c fluid_mod_new() and \c fluid_mod_delete() to match naming conventions: new_fluid_mod() and delete_fluid_mod() -- rename chorus getters to match naming conventions: fluid_synth_get_chorus_speed() and fluid_synth_get_chorus_depth() -- fluid_synth_remove_sfont() returns FLUID_OK or FLUID_FAILED -- introduce a separate data type for sequencer client IDs: #fluid_seq_id_t -- fluid_get_userconf() has been implemented for Windows - -New Features and API additions: - -- add \ref settings_midi_autoconnect a setting for automatically connecting fluidsynth to available MIDI input ports -- add \ref settings_synth_overflow_important and \ref settings_synth_overflow_important-channels settings to take midi channels during overflow calculation into account that are considered to be "important" -- add \ref settings_synth_dynamic-sample-loading a setting for enabling on demand sample loading -- add support for polyphonic key pressure events, see fluid_event_key_pressure() and fluid_synth_key_pressure() -- add fluid_synth_add_default_mod() and fluid_synth_remove_default_mod() for manipulating default modulators -- add individual reverb setters: fluid_synth_set_reverb_roomsize(), fluid_synth_set_reverb_damp(), fluid_synth_set_reverb_width(), fluid_synth_set_reverb_level() -- add individual chorus setters: fluid_synth_set_chorus_nr(), fluid_synth_set_chorus_level(), fluid_synth_set_chorus_speed(), fluid_synth_set_chorus_depth(), fluid_synth_set_chorus_type() -- add realtime settings for \ref settings_synth_reverb_damp and \ref settings_synth_chorus_depth parameters -- add seek support to midi-player, see fluid_player_seek() -- expose functions to manipulate the ladspa effects unit (see ladspa.h) -- add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() -- complete rewrite of the soundfont loader API, see sfont.h - - support for 24 bit audio samples, see fluid_sample_set_sound_data() - - expose new_fluid_defsfloader() to support loading soundfonts from memory, see fluid_sfloader_set_callbacks() and fluidsynth_sfload_mem.c - - remove these structs from the public API and provide proper getter and setter functions instead: - - struct _fluid_sfloader_t - - struct _fluid_sample_t - - struct _fluid_sfont_t - - struct _fluid_preset_t -- add an additional general-purpose IIR filter, see fluid_synth_set_custom_filter() -- add a custom sinusoidal modulator mapping function, see #FLUID_MOD_SIN -- implement polymono support according to MIDI specs: - - add basic channel support, see fluid_synth_reset_basic_channel(), fluid_synth_set_basic_channel(), fluid_synth_get_basic_channel() - - implement MIDI modes Omni On, Omni Off, Poly, Mono, see #fluid_basic_channel_modes - - implement portamento control, see fluid_synth_set_portamento_mode(), fluid_synth_get_portamento_mode() - - implement legato control, see fluid_synth_set_legato_mode(), fluid_synth_get_legato_mode() - - implement breath control, see fluid_synth_set_breath_mode(), fluid_synth_get_breath_mode() - -API cleanups: - -- the ramsfont has been removed, because it is unmaintained and believed to be unused; please get in touch with the mailing list if you still need it -- remove deprecated fluid_synth_get_channel_info() in favour of fluid_synth_get_program() and fluid_synth_get_channel_preset() -- remove deprecated fluid_settings_getstr() -- remove deprecated fluid_synth_set_midi_router(), instead supply the midi-router instance when creating a command handler with new_fluid_cmd_handler() -- remove deprecated fluid_get_hinstance() and fluid_set_hinstance() (dsound driver now uses the desktop window) -- remove deprecated fluid_synth_create_key_tuning(), use fluid_synth_activate_key_tuning(synth, bank, prog, name, pitch, FALSE) instead -- remove deprecated fluid_synth_create_octave_tuning(), use fluid_synth_activate_octave_tuning(synth, bank, prog, name, pitch, FALSE) instead -- remove deprecated fluid_synth_select_tuning(), use fluid_synth_activate_tuning(synth, chan, bank, prog, FALSE) instead -- remove deprecated fluid_synth_reset_tuning(), use fluid_synth_deactivate_tuning(synth, chan, FALSE) instead -- remove deprecated FLUID_HINT_INTEGER -- remove deprecated fluid_synth_set_gen2() as there doesn't seem to be a use case for absolute generator values -- remove deprecated "synth.parallel-render" setting -- remove obsolete "audio.[out|in]put-channels" settings -- remove unimplemented "synth.dump" setting -- remove fluid_cmd_handler_register() and fluid_cmd_handler_unregister() from public API, as they seem to be unused downstream -- remove misspelled FLUID_SEQ_PITCHWHHELSENS macro -- remove struct _fluid_mod_t from public API, use the getters and setters of mod.h instead -- remove struct _fluid_gen_t, fluid_gen_set_default_values() and enum fluid_gen_flags from public API -- remove macros fluid_sfont_get_id() and fluid_sample_refcount() from public API -- remove FLUID_NUM_MOD macro from public API -- remove the following deprecated enum values from public API: - - GEN_LAST - - LAST_LOG_LEVEL - - FLUID_SEQ_LASTEVENT - - FLUID_MIDI_ROUTER_RULE_COUNT - - \page UsageGuide Usage Guide - \subpage CreatingSettings - \subpage CreatingSynth diff --git a/doc/recent_changes.txt b/doc/recent_changes.txt new file mode 100644 index 00000000..5acd3b4a --- /dev/null +++ b/doc/recent_changes.txt @@ -0,0 +1,148 @@ +/*! + +\page RecentChanges Recent Changes +\section NewIn2_2_0 What's new in 2.2.0? + +- #fluid_file_callbacks_t now uses long long as file-offset type (see #fluid_long_long_t). This is a breaking change, which allows to load SoundFonts bigger than 2GiB on Windows. This change required to bump fluidsynth's SOVERSION. +- fluid_event_any_control_change() has been removed +- various fluid_event_*() functions that received a "value" argument of type short now receive an int argument in preparation for MIDI 2.0 support +- The sequencer has received a major revisal. For you that means: + - The sequencer's queue no longer blocks the synthesizer thread, due to being busy arranging its events internally. + - Events that share the same tick was given a new, documented order, see fluid_sequencer_send_at(). + - The sequencer's scale can now be used for arbitrary tempo changes. Previously, the scale of the sequencer was limited to 1000. The only limitation now is >0. +- The dynamic-sample-loader has learned support to pin samples, see fluid_synth_pin_preset() and fluid_synth_unpin_preset() + +\section NewIn2_1_4 What's new in 2.1.4? + +- a regression introduced in 2.1.3 broke fluid_synth_start() for DLS presets + +\section NewIn2_1_1 What's new in 2.1.1? + +- requirements for explicit sequencer client unregistering have been relaxed: delete_fluid_sequencer() now correctly frees any registered sequencer clients (clients can still be explicitly unregistered) +- using the sequencer with the system timer as timing source has been deprecated + +\section NewIn2_1_0 What's new in 2.1.0? + +- refrain from using fluid_synth_set_sample_rate() +- \ref settings_synth_sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() +- new reverb engine +- chorus is now stereophonic +- smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) +- the following audio drivers were added: + - opensles + - oboe + - sdl2 + - waveout + +\section NewIn2_0_8 What's new in 2.0.8? + +- fluid_sample_set_sound_data() caused broken sound when copying sample data + +\section NewIn2_0_7 What's new in 2.0.7? + +- fluid_free() has been added to allow proper deallocation by programming languages other than C/C++ + +\section NewIn2_0_6 What's new in 2.0.6? + +- the MIDI player did not emit any audio when calling fluid_player_play() after fluid_player_stop() + +\section NewIn2_0_5 What's new in 2.0.5? + +- fluid_synth_process() omitted audio samples when called with arbitrary sample counts that were not a multiple of fluid_synth_get_internal_bufsize() +- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if \ref settings_synth_dynamic-sample-loading was set to FALSE + +\section NewIn2_0_3 What's new in 2.0.3? + +- fix incorrect behaviour of fluid_sample_set_sound_data() +- add missing getters for midi events: + - fluid_midi_event_get_text() + - fluid_midi_event_get_lyrics() + +\section NewIn2_0_2 What's new in 2.0.2? + +- fluid_synth_error() has been deprecated, use fluid_set_log_function() to interfere log messages + +\section NewIn2_0_0 What's new in 2.0.0? + +FluidSynths major version was bumped. The API was reworked, deprecated functions were removed. + +Important changes that may not result in a compilation error but may cause your app to misbehave: + +- all public \c fluid_settings_* functions that return an integer which is not meant to be interpreted as bool consistently return either FLUID_OK or FLUID_FAILED +- fluid_settings_setstr() cannot be used to set integer (toggle) settings with "yes" or "no" values anymore. Use fluid_settings_setint() instead, for example:
fluid_settings_setint(settings, "synth.reverb.active", 0) instead of fluid_settings_setstr(settings, "synth.reverb.active", "no") +- explicit client unregistering is required for fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() (since fluidsynth 2.1.1 not required anymore, but still recommend) +- all public functions consistently receive signed integers for soundfont ids, bank and program numbers +- use unique device names for the "audio.portaudio.device" setting +- fluid_synth_process() received a new more flexible implementation, but now requires zeroed-out sample buffers + +Other changes in FluidSynth 2.0.0 concerning developers: + +- all public \c delete_* functions return void and are safe when called with NULL +- the shell command handler was decoupled internally, as a consequence the param list of new_fluid_server() and new_fluid_cmd_handler() was adapted +- \c fluid_settings_set* functions no longer silently register unknown settings but return an error instead +- reverb: roomsize is now limited to an upper threshold of 1.0 to avoid exponential volume increase +- rename \c fluid_mod_new() and \c fluid_mod_delete() to match naming conventions: new_fluid_mod() and delete_fluid_mod() +- rename chorus getters to match naming conventions: fluid_synth_get_chorus_speed() and fluid_synth_get_chorus_depth() +- fluid_synth_remove_sfont() returns FLUID_OK or FLUID_FAILED +- introduce a separate data type for sequencer client IDs: #fluid_seq_id_t +- fluid_get_userconf() has been implemented for Windows + +New Features and API additions: + +- add \ref settings_midi_autoconnect a setting for automatically connecting fluidsynth to available MIDI input ports +- add \ref settings_synth_overflow_important and \ref settings_synth_overflow_important-channels settings to take midi channels during overflow calculation into account that are considered to be "important" +- add \ref settings_synth_dynamic-sample-loading a setting for enabling on demand sample loading +- add support for polyphonic key pressure events, see fluid_event_key_pressure() and fluid_synth_key_pressure() +- add fluid_synth_add_default_mod() and fluid_synth_remove_default_mod() for manipulating default modulators +- add individual reverb setters: fluid_synth_set_reverb_roomsize(), fluid_synth_set_reverb_damp(), fluid_synth_set_reverb_width(), fluid_synth_set_reverb_level() +- add individual chorus setters: fluid_synth_set_chorus_nr(), fluid_synth_set_chorus_level(), fluid_synth_set_chorus_speed(), fluid_synth_set_chorus_depth(), fluid_synth_set_chorus_type() +- add realtime settings for \ref settings_synth_reverb_damp and \ref settings_synth_chorus_depth parameters +- add seek support to midi-player, see fluid_player_seek() +- expose functions to manipulate the ladspa effects unit (see ladspa.h) +- add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() +- complete rewrite of the soundfont loader API, see sfont.h + - support for 24 bit audio samples, see fluid_sample_set_sound_data() + - expose new_fluid_defsfloader() to support loading soundfonts from memory, see fluid_sfloader_set_callbacks() and fluidsynth_sfload_mem.c + - remove these structs from the public API and provide proper getter and setter functions instead: + - struct _fluid_sfloader_t + - struct _fluid_sample_t + - struct _fluid_sfont_t + - struct _fluid_preset_t +- add an additional general-purpose IIR filter, see fluid_synth_set_custom_filter() +- add a custom sinusoidal modulator mapping function, see #FLUID_MOD_SIN +- implement polymono support according to MIDI specs: + - add basic channel support, see fluid_synth_reset_basic_channel(), fluid_synth_set_basic_channel(), fluid_synth_get_basic_channel() + - implement MIDI modes Omni On, Omni Off, Poly, Mono, see #fluid_basic_channel_modes + - implement portamento control, see fluid_synth_set_portamento_mode(), fluid_synth_get_portamento_mode() + - implement legato control, see fluid_synth_set_legato_mode(), fluid_synth_get_legato_mode() + - implement breath control, see fluid_synth_set_breath_mode(), fluid_synth_get_breath_mode() + +API cleanups: + +- the ramsfont has been removed, because it is unmaintained and believed to be unused; please get in touch with the mailing list if you still need it +- remove deprecated fluid_synth_get_channel_info() in favour of fluid_synth_get_program() and fluid_synth_get_channel_preset() +- remove deprecated fluid_settings_getstr() +- remove deprecated fluid_synth_set_midi_router(), instead supply the midi-router instance when creating a command handler with new_fluid_cmd_handler() +- remove deprecated fluid_get_hinstance() and fluid_set_hinstance() (dsound driver now uses the desktop window) +- remove deprecated fluid_synth_create_key_tuning(), use fluid_synth_activate_key_tuning(synth, bank, prog, name, pitch, FALSE) instead +- remove deprecated fluid_synth_create_octave_tuning(), use fluid_synth_activate_octave_tuning(synth, bank, prog, name, pitch, FALSE) instead +- remove deprecated fluid_synth_select_tuning(), use fluid_synth_activate_tuning(synth, chan, bank, prog, FALSE) instead +- remove deprecated fluid_synth_reset_tuning(), use fluid_synth_deactivate_tuning(synth, chan, FALSE) instead +- remove deprecated FLUID_HINT_INTEGER +- remove deprecated fluid_synth_set_gen2() as there doesn't seem to be a use case for absolute generator values +- remove deprecated "synth.parallel-render" setting +- remove obsolete "audio.[out|in]put-channels" settings +- remove unimplemented "synth.dump" setting +- remove fluid_cmd_handler_register() and fluid_cmd_handler_unregister() from public API, as they seem to be unused downstream +- remove misspelled FLUID_SEQ_PITCHWHHELSENS macro +- remove struct _fluid_mod_t from public API, use the getters and setters of mod.h instead +- remove struct _fluid_gen_t, fluid_gen_set_default_values() and enum fluid_gen_flags from public API +- remove macros fluid_sfont_get_id() and fluid_sample_refcount() from public API +- remove FLUID_NUM_MOD macro from public API +- remove the following deprecated enum values from public API: + - GEN_LAST + - LAST_LOG_LEVEL + - FLUID_SEQ_LASTEVENT + - FLUID_MIDI_ROUTER_RULE_COUNT + +*/ From 20ec6f3376dfd1d441ccd52a049e9ccdc11169cd Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 13:26:05 +0100 Subject: [PATCH 35/55] Move usage guide pages into separate files in doc/usage --- doc/Doxyfile | 3 +- doc/Doxyfile.cmake | 3 +- doc/fluidsynth-v20-devdoc.txt | 608 ------------------------------- doc/usage/_overview.txt | 21 ++ doc/usage/advanced.txt | 16 + doc/usage/audio_driver.txt | 82 +++++ doc/usage/creating_settings.txt | 24 ++ doc/usage/creating_synth.txt | 29 ++ doc/usage/file_renderer.txt | 60 +++ doc/usage/loading_soundfonts.txt | 34 ++ doc/usage/manual_rendering.txt | 17 + doc/usage/midi_player.txt | 55 +++ doc/usage/midi_player_mem.txt | 64 ++++ doc/usage/midi_router.txt | 76 ++++ doc/usage/multi_channel.txt | 19 + doc/usage/realtime_midi.txt | 47 +++ doc/usage/sending_midi.txt | 53 +++ doc/usage/sequencer.txt | 142 ++++++++ doc/usage/shell.txt | 12 + doc/usage/synth_context.txt | 22 ++ 20 files changed, 777 insertions(+), 610 deletions(-) create mode 100644 doc/usage/_overview.txt create mode 100644 doc/usage/advanced.txt create mode 100644 doc/usage/audio_driver.txt create mode 100644 doc/usage/creating_settings.txt create mode 100644 doc/usage/creating_synth.txt create mode 100644 doc/usage/file_renderer.txt create mode 100644 doc/usage/loading_soundfonts.txt create mode 100644 doc/usage/manual_rendering.txt create mode 100644 doc/usage/midi_player.txt create mode 100644 doc/usage/midi_player_mem.txt create mode 100644 doc/usage/midi_router.txt create mode 100644 doc/usage/multi_channel.txt create mode 100644 doc/usage/realtime_midi.txt create mode 100644 doc/usage/sending_midi.txt create mode 100644 doc/usage/sequencer.txt create mode 100644 doc/usage/shell.txt create mode 100644 doc/usage/synth_context.txt diff --git a/doc/Doxyfile b/doc/Doxyfile index bd44143f..8ce12644 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -88,12 +88,13 @@ WARN_LOGFILE = INPUT = \ ../doc/fluidsynth-v20-devdoc.txt \ ../doc/recent_changes.txt \ +../doc/usage \ ../include \ ../include/fluidsynth \ ../src INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c *.h +FILE_PATTERNS = *.c *.h *.txt RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index d9d12b99..0250a439 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -88,6 +88,7 @@ WARN_LOGFILE = INPUT = \ @CMAKE_SOURCE_DIR@/doc/fluidsynth-v20-devdoc.txt \ @CMAKE_SOURCE_DIR@/doc/recent_changes.txt \ +@CMAKE_SOURCE_DIR@/doc/usage \ @CMAKE_SOURCE_DIR@/include \ @CMAKE_SOURCE_DIR@/include/fluidsynth \ @CMAKE_SOURCE_DIR@/src \ @@ -95,7 +96,7 @@ INPUT = \ @CMAKE_BINARY_DIR@/doc/fluidsettings.txt INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c *.h +FILE_PATTERNS = *.c *.h *.txt RECURSIVE = YES EXCLUDE = EXCLUDE_SYMLINKS = NO diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 57f81e0a..831b65aa 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -40,614 +40,6 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org -\page UsageGuide Usage Guide - - \subpage CreatingSettings - - \subpage CreatingSynth - - \subpage LoadingSoundfonts - - \subpage CreatingAudioDriver - - \subpage UsingSynth - - \subpage SendingMIDI - - \subpage RealtimeMIDI - - \subpage MIDIPlayer - - \subpage FileRenderer - - \subpage MIDIPlayerMem - - \subpage MIDIRouter - - \subpage Sequencer - - \subpage Shell - - \subpage Multi-channel - - \subpage synth-context - - \subpage Advanced - - -\page CreatingSettings Creating and changing the settings - -Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. - -All settings have a name that follows the "dotted-name" notation. For example, "synth.polyphony" refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: - -\code -#include - -int main(int argc, char** argv) -{ - fluid_settings_t* settings = new_fluid_settings(); - fluid_settings_setint(settings, "synth.polyphony", 128); - /* ... */ - delete_fluid_settings(settings); - return 0; -} -\endcode - -The API contains the functions to query the type, the current value, the default value, the range and the "hints" of a setting. The range is the minimum and maximum value of the setting. The hints gives additional information about a setting. For example, whether a string represents a filename. Or whether a number should be interpreted on on a logarithmic scale. Check the settings.h API documentation for a description of all functions. - -\page CreatingSynth Creating the synthesizer - -To create the synthesizer, you pass it the settings object, as in the following example: - -\code -#include - -int main(int argc, char** argv) -{ - fluid_settings_t* settings; - fluid_synth_t* synth; - settings = new_fluid_settings(); - synth = new_fluid_synth(settings); - - /* Do useful things here */ - - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return 0; -} -\endcode - -For a full list of available synthesizer settings, please refer to the \ref settings_synth documentation. - - - -\page CreatingAudioDriver Creating the audio driver - -The synthesizer itself does not write any audio to the audio output. This allows application developers to manage the audio output themselves if they wish. The next section describes the use of the synthesizer without an audio driver in more detail. - -Creating the audio driver is straightforward: set the audio.driver settings and create the driver object. Because the FluidSynth has support for several audio systems, you may want to change which one you want to use. The list below shows the audio systems that are currently supported. It displays the name, as used by the fluidsynth library, and a description. - -- jack: JACK Audio Connection Kit (Linux, Mac OS X, Windows) -- alsa: Advanced Linux Sound Architecture (Linux) -- oss: Open Sound System (Linux, Unix) -- pulseaudio: PulseAudio (Linux, Mac OS X, Windows) -- coreaudio: Apple CoreAudio (Mac OS X) -- dsound: Microsoft DirectSound (Windows) -- portaudio: PortAudio Library (Mac OS 9 & X, Windows, Linux) -- sndman: Apple SoundManager (Mac OS Classic) -- dart: DART sound driver (OS/2) -- opensles: OpenSL ES (Android) -- oboe: Oboe (Android) -- waveout: Microsoft WaveOut, alternative to DirectSound (Windows CE x86, Windows Mobile 2003 for ARMv5, Windows 98 SE, Windows NT 4.0, Windows XP and later) -- file: Driver to output audio to a file -- sdl2*: Simple DirectMedia Layer (Linux, Windows, Mac OS X, iOS, Android, FreeBSD, Haiku, etc.) - -The default audio driver depends on the settings with which FluidSynth was compiled. You can get the default driver with fluid_settings_getstr_default(). To get the list of available drivers use the fluid_settings_foreach_option() function. Finally, you can set the driver with fluid_settings_setstr(). In most cases, the default driver should work out of the box. - -Additional options that define the audio quality and latency are "audio.sample-format", "audio.period-size", and "audio.periods". The details are described later. - -You create the audio driver with the new_fluid_audio_driver() function. This function takes the settings and synthesizer object as arguments. For example: - -\code -void init() -{ - fluid_settings_t* settings; - fluid_synth_t* synth; - fluid_audio_driver_t* adriver; - settings = new_fluid_settings(); - - /* Set the synthesizer settings, if necessary */ - synth = new_fluid_synth(settings); - - fluid_settings_setstr(settings, "audio.driver", "jack"); - adriver = new_fluid_audio_driver(settings, synth); -} -\endcode - -As soon as the audio driver is created, it will start playing. The audio driver creates a separate thread that uses the synthesizer object to generate the audio. - -There are a number of general audio driver settings. The audio.driver settings define the audio subsystem that will be used. The audio.periods and audio.period-size settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, please refer to the \ref settings_audio documentation. - -*Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done before the first call to new_fluid_settings()! Also make sure to call SDL_Quit() after all fluidsynth instances have been destroyed. - -\page UsingSynth Using the synthesizer without an audio driver - -It is possible to use the synthesizer object without creating an audio driver. This is desirable if the application using FluidSynth manages the audio output itself. The synthesizer has several API functions that can be used to obtain the audio output: - -fluid_synth_write_s16() fills two buffers (left and right channel) with samples coded as signed 16 bits (the endian-ness is machine dependent). fluid_synth_write_float() fills a left and right audio buffer with 32 bits floating point samples. The function fluid_synth_process() is the generic interface for synthesizing audio, which is also capable of multi channel audio output. - -\page LoadingSoundfonts Loading and managing SoundFonts - -Before any sound can be produced, the synthesizer needs a SoundFont. - -SoundFonts are loaded with the fluid_synth_sfload() function. The function takes the path to a SoundFont file and a boolean to indicate whether the presets of the MIDI channels should be updated after the SoundFont is loaded. When the boolean value is TRUE, all MIDI channel bank and program numbers will be refreshed, which may cause new instruments to be selected from the newly loaded SoundFont. - -The synthesizer can load any number of SoundFonts. The loaded SoundFonts are treated as a stack, where each new loaded SoundFont is placed at the top of the stack. When selecting presets by bank and program numbers, SoundFonts are searched beginning at the top of the stack. In the case where there are presets in different SoundFonts with identical bank and program numbers, the preset from the most recently loaded SoundFont is used. The fluid_synth_program_select() can be used for unambiguously selecting a preset or bank offsets could be applied to each SoundFont with fluid_synth_set_bank_offset(), to try and ensure that each preset has unique bank and program numbers. - -The fluid_synth_sfload() function returns the unique identifier of the loaded SoundFont, or -1 in case of an error. This identifier is used in subsequent management functions: fluid_synth_sfunload() removes the SoundFont, fluid_synth_sfreload() reloads the SoundFont. When a SoundFont is reloaded, it retains it's ID and position on the SoundFont stack. - -Additional API functions are provided to get the number of loaded SoundFonts and to get a pointer to the SoundFont. - -\page SendingMIDI Sending MIDI events - -Once the synthesizer is up and running and a SoundFont is loaded, most people will want to do something useful with it. Make noise, for example. MIDI messages can be sent using the fluid_synth_noteon(), fluid_synth_noteoff(), fluid_synth_cc(), fluid_synth_pitch_bend(), fluid_synth_pitch_wheel_sens(), and fluid_synth_program_change() functions. For convenience, there's also a fluid_synth_bank_select() function (the bank select message is normally sent using a control change message). - -The following example show a generic graphical button that plays a note when clicked: - -\code -class SoundButton : public SomeButton -{ -public: - - SoundButton() : SomeButton() { - if (!_synth) { - initSynth(); - } - } - - static void initSynth() { - _settings = new_fluid_settings(); - _synth = new_fluid_synth(_settings); - _adriver = new_fluid_audio_driver(_settings, _synth); - } - - /* ... */ - - virtual int handleMouseDown(int x, int y) { - /* Play a note on key 60 with velocity 100 on MIDI channel 0 */ - fluid_synth_noteon(_synth, 0, 60, 100); - } - - virtual int handleMouseUp(int x, int y) { - /* Release the note on key 60 */ - fluid_synth_noteoff(_synth, 0, 60); - } - -protected: - - static fluid_settings_t* _settings; - static fluid_synth_t* _synth; - static fluid_audio_driver_t* _adriver; -}; -\endcode - -\page RealtimeMIDI Creating a real-time MIDI driver - -FluidSynth can process real-time MIDI events received from hardware MIDI ports or other applications. To do so, the client must create a MIDI input driver. It is a very similar process to the creation of the audio driver: you initialize some properties in a settings instance and call the new_fluid_midi_driver() function providing a callback function that will be invoked when a MIDI event is received. The following MIDI drivers are currently supported: - -- jack: JACK Audio Connection Kit MIDI driver (Linux, Mac OS X) -- oss: Open Sound System raw MIDI (Linux, Unix) -- alsa_raw: ALSA raw MIDI interface (Linux) -- alsa_seq: ALSA sequencer MIDI interface (Linux) -- winmidi: Microsoft Windows MM System (Windows) -- midishare: MIDI Share (Linux, Mac OS X) -- coremidi: Apple CoreMIDI (Mac OS X) - -\code -#include - -int handle_midi_event(void* data, fluid_midi_event_t* event) -{ - printf("event type: %d\n", fluid_midi_event_get_type(event)); -} - -int main(int argc, char** argv) -{ - fluid_settings_t* settings; - fluid_midi_driver_t* mdriver; - settings = new_fluid_settings(); - mdriver = new_fluid_midi_driver(settings, handle_midi_event, NULL); - /* ... */ - delete_fluid_midi_driver(mdriver); - return 0; -} -\endcode - -There are a number of general MIDI driver settings. The midi.driver setting -defines the MIDI subsystem that will be used. There are additional settings for -the MIDI subsystems used. For a full list of available midi driver settings, please refer to the \ref settings_midi documentation. - - - -\page MIDIPlayer Loading and playing a MIDI file - -FluidSynth can be used to play MIDI files, using the MIDI File Player interface. It follows a high level implementation, though its implementation is currently incomplete. After initializing the synthesizer, create the player passing the synth instance to new_fluid_player(). Then, you can add some SMF file names to the player using fluid_player_add(), and finally call fluid_player_play() to start the playback. You can check if the player has finished by calling fluid_player_get_status(), or wait for the player to terminate using fluid_player_join(). - -\code -#include - -int main(int argc, char** argv) -{ - int i; - fluid_settings_t* settings; - fluid_synth_t* synth; - fluid_player_t* player; - fluid_audio_driver_t* adriver; - settings = new_fluid_settings(); - synth = new_fluid_synth(settings); - player = new_fluid_player(synth); - /* process command line arguments */ - for (i = 1; i < argc; i++) { - if (fluid_is_soundfont(argv[i])) { - fluid_synth_sfload(synth, argv[1], 1); - } - if (fluid_is_midifile(argv[i])) { - fluid_player_add(player, argv[i]); - } - } - /* start the synthesizer thread */ - adriver = new_fluid_audio_driver(settings, synth); - /* play the midi files, if any */ - fluid_player_play(player); - /* wait for playback termination */ - fluid_player_join(player); - /* cleanup */ - delete_fluid_audio_driver(adriver); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return 0; -} -\endcode - - -A list of available MIDI player settings can be found in the \ref settings_player documentation. - - - -\page FileRenderer Fast file renderer for non-realtime MIDI file rendering - -Instead of creating an audio driver as described in section \ref MIDIPlayer one may chose to use the file renderer, which is the fastest way to synthesize MIDI files. - -\code -fluid_settings_t* settings; -fluid_synth_t* synth; -fluid_player_t* player; -fluid_file_renderer_t* renderer; - -settings = new_fluid_settings(); - -// specify the file to store the audio to -// make sure you compiled fluidsynth with libsndfile to get a real wave file -// otherwise this file will only contain raw s16 stereo PCM -fluid_settings_setstr(settings, "audio.file.name", "/path/to/output.wav"); - -// use number of samples processed as timing source, rather than the system timer -fluid_settings_setstr(settings, "player.timing-source", "sample"); - -// since this is a non-realtime scenario, there is no need to pin the sample data -fluid_settings_setint(settings, "synth.lock-memory", 0); - -synth = new_fluid_synth(settings); - -// *** loading of a soundfont omitted *** - -player = new_fluid_player(synth); -fluid_player_add(player, "/path/to/midifile.mid"); -fluid_player_play(player); - -renderer = new_fluid_file_renderer (synth); - -while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) -{ - if (fluid_file_renderer_process_block(renderer) != FLUID_OK) - { - break; - } -} - -// just for sure: stop the playback explicitly and wait until finished -fluid_player_stop(player); -fluid_player_join(player); - -delete_fluid_file_renderer(renderer); -delete_fluid_player(player); -delete_fluid_synth(synth); -delete_fluid_settings(settings); -\endcode - -Various output files types are supported, if compiled with libsndfile. Those can be specified via the \c settings object as well. Refer to the \ref settings_audio documentation for more \c audio.file\.\* options. - - -\page MIDIPlayerMem Playing a MIDI file from memory - -FluidSynth can be also play MIDI files directly from a buffer in memory. If you need to play a file from a stream (such as stdin, a network, or a high-level file interface), you can load the entire file into a buffer first, and then use this approach. Use the same technique as above, but rather than calling fluid_player_add(), load it into memory and call fluid_player_add_mem() instead. Once you have passed a buffer to fluid_player_add_mem(), it is copied, so you may use it again or free it immediately (it is your responsibility to free it if you allocated it). - -\code -#include -#include -#include - -/* An example midi file */ -const char MIDIFILE[] = { - 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x01, 0x00, 0x01, 0x01, 0xe0, 0x4d, 0x54, - 0x72, 0x6b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x90, - 0x3c, 0x64, 0x87, 0x40, 0x80, 0x3c, 0x7f, 0x00, - 0x90, 0x43, 0x64, 0x87, 0x40, 0x80, 0x43, 0x7f, - 0x00, 0x90, 0x48, 0x64, 0x87, 0x40, 0x80, 0x48, - 0x7f, 0x83, 0x60, 0xff, 0x2f, 0x00 -}; - -int main(int argc, char** argv) -{ - int i; - void* buffer; - size_t buffer_len; - fluid_settings_t* settings; - fluid_synth_t* synth; - fluid_player_t* player; - fluid_audio_driver_t* adriver; - settings = new_fluid_settings(); - synth = new_fluid_synth(settings); - player = new_fluid_player(synth); - adriver = new_fluid_audio_driver(settings, synth); - /* process command line arguments */ - for (i = 1; i < argc; i++) { - if (fluid_is_soundfont(argv[i])) { - fluid_synth_sfload(synth, argv[1], 1); - } - } - /* queue up the in-memory midi file */ - fluid_player_add_mem(player, MIDIFILE, sizeof(MIDIFILE)); - /* play the midi file */ - fluid_player_play(player); - /* wait for playback termination */ - fluid_player_join(player); - /* cleanup */ - delete_fluid_audio_driver(adriver); - delete_fluid_player(player); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return 0; -} -\endcode - - -\page MIDIRouter Real-time MIDI router - -The MIDI router is one more processing layer directly behind the MIDI input. It processes incoming MIDI events and generates control events for the synth. It can be used to filter or modify events prior to sending them to the synthesizer. When created, the MIDI router is transparent and simply passes all MIDI events. Router "rules" must be added to actually make use of its capabilities. - -Some examples of MIDI router usage: - -- Filter messages (Example: Pass sustain pedal CCs only to selected channels) -- Split the keyboard (Example: noteon with notenr < x: to ch 1, >x to ch 2) -- Layer sounds (Example: for each noteon received on ch 1, create a noteon on ch1, ch2, ch3,...) -- Velocity scaling (Example: for each noteon event, scale the velocity by 1.27) -- Velocity switching (Example: v <= 100: "Angel Choir"; v > 100: "Hell's Bells") -- Get rid of aftertouch - -The MIDI driver API has a clean separation between the midi thread and the synthesizer. That opens the door to add a midi router module. - -MIDI events coming from the MIDI player do not pass through the MIDI router. - -\code -#include - -int main(int argc, char** argv) -{ - fluid_settings_t* settings; - fluid_synth_t* synth; - fluid_midi_router_t* router; - fluid_midi_router_rule_t* rule; - - settings = new_fluid_settings(); - synth = new_fluid_synth(settings); - - /* Create the MIDI router and pass events to the synthesizer */ - router = new_fluid_midi_router (settings, fluid_synth_handle_midi_event, synth); - - /* Clear default rules */ - fluid_midi_router_clear_rules (router); - - /* Add rule to map all notes < MIDI note #60 on any channel to channel 4 */ - rule = new_fluid_midi_router_rule (); - fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 4); /* Map all to channel 4 */ - fluid_midi_router_rule_set_param1 (rule, 0, 59, 1.0, 0); /* Match notes < 60 */ - fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); - - /* Add rule to map all notes >= MIDI note #60 on any channel to channel 5 */ - rule = new_fluid_midi_router_rule (); - fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 5); /* Map all to channel 5 */ - fluid_midi_router_rule_set_param1 (rule, 60, 127, 1.0, 0); /* Match notes >= 60 */ - fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); - - /* Add rule to reverse direction of pitch bender on channel 7 */ - rule = new_fluid_midi_router_rule (); - fluid_midi_router_rule_set_chan (rule, 7, 7, 1.0, 0); /* Match channel 7 only */ - fluid_midi_router_rule_set_param1 (rule, 0, 16383, -1.0, 16383); /* Reverse pitch bender */ - fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_PITCH_BEND); - - /* ... Create audio driver, process events, etc ... */ - - /* cleanup */ - delete_fluid_midi_router(router); - delete_fluid_synth(synth); - delete_fluid_settings(settings); - return 0; -} -\endcode - - - -\page Sequencer Using the MIDI sequencer - -FluidSynth's sequencer can be used to play MIDI events in a more flexible way than using the MIDI file player, which expects the events to be stored as Standard MIDI Files. Using the sequencer, you can provide the events one by one, with an optional timestamp for scheduling. - -The client program should first initialize the sequencer instance using the function new_fluid_sequencer2(). There is a complementary function delete_fluid_sequencer() to delete it. After creating the sequencer instance, the destinations can be registered using fluid_sequencer_register_fluidsynth() for the synthesizer destination, and optionally using fluid_sequencer_register_client() for the client destination providing a suitable callback function. It can be unregistered using fluid_sequencer_unregister_client(). After the initialization, events can be sent with fluid_sequencer_send_now() and scheduled to the future with fluid_sequencer_send_at(). The registration functions return identifiers, that can be used as destinations of an event using fluid_event_set_dest(). - -The function fluid_sequencer_get_tick() returns the current playing position. A program may choose a new timescale in milliseconds using fluid_sequencer_set_time_scale(). - -The following example uses the fluidsynth sequencer to implement a sort of music box. FluidSynth internal clock is used to schedule repetitive sequences of notes. The next sequence is scheduled on advance before the end of the current one, using a timer event that triggers a callback function. The scheduling times are always absolute values, to avoid slippage. - -\code -#include "fluidsynth.h" - -fluid_synth_t* synth; -fluid_audio_driver_t* adriver; -fluid_sequencer_t* sequencer; -short synthSeqID, mySeqID; -unsigned int now; -unsigned int seqduration; - -// prototype -void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data); - -void createsynth() -{ - fluid_settings_t* settings; - settings = new_fluid_settings(); - fluid_settings_setint(settings, "synth.reverb.active", 0); - fluid_settings_setint(settings, "synth.chorus.active", 0); - synth = new_fluid_synth(settings); - adriver = new_fluid_audio_driver(settings, synth); - sequencer = new_fluid_sequencer2(0); - - // register synth as first destination - synthSeqID = fluid_sequencer_register_fluidsynth(sequencer, synth); - - // register myself as second destination - mySeqID = fluid_sequencer_register_client(sequencer, "me", seq_callback, NULL); - - // the sequence duration, in ms - seqduration = 1000; -} - -void deletesynth() -{ - delete_fluid_sequencer(sequencer); - delete_fluid_audio_driver(adriver); - delete_fluid_synth(synth); -} - -void loadsoundfont() -{ - int fluid_res; - // put your own path here - fluid_res = fluid_synth_sfload(synth, "Inside:VintageDreamsWaves-v2.sf2", 1); -} - -void sendnoteon(int chan, short key, unsigned int date) -{ - int fluid_res; - fluid_event_t *evt = new_fluid_event(); - fluid_event_set_source(evt, -1); - fluid_event_set_dest(evt, synthSeqID); - fluid_event_noteon(evt, chan, key, 127); - fluid_res = fluid_sequencer_send_at(sequencer, evt, date, 1); - delete_fluid_event(evt); -} - -void schedule_next_callback() -{ - int fluid_res; - // I want to be called back before the end of the next sequence - unsigned int callbackdate = now + seqduration/2; - fluid_event_t *evt = new_fluid_event(); - fluid_event_set_source(evt, -1); - fluid_event_set_dest(evt, mySeqID); - fluid_event_timer(evt, NULL); - fluid_res = fluid_sequencer_send_at(sequencer, evt, callbackdate, 1); - delete_fluid_event(evt); -} - -void schedule_next_sequence() { - // Called more or less before each sequence start - // the next sequence start date - now = now + seqduration; - - // the sequence to play - - // the beat : 2 beats per sequence - sendnoteon(0, 60, now + seqduration/2); - sendnoteon(0, 60, now + seqduration); - - // melody - sendnoteon(1, 45, now + seqduration/10); - sendnoteon(1, 50, now + 4*seqduration/10); - sendnoteon(1, 55, now + 8*seqduration/10); - - // so that we are called back early enough to schedule the next sequence - schedule_next_callback(); -} - -/* sequencer callback */ -void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data) { - schedule_next_sequence(); -} - -int main(void) { - createsynth(); - loadsoundfont(); - - // initialize our absolute date - now = fluid_sequencer_get_tick(sequencer); - schedule_next_sequence(); - - sleep(100000); - deletesynth(); - return 0; -} -\endcode - - -\page Shell Shell interface - -The shell interface allows you to send simple textual commands to the synthesizer, to parse a command file, or to read commands from the stdin or other input streams. To find the list of currently supported commands, type @c help in the fluidsynth command line shell. For a full list of available command line settings, please refer to the \ref settings_shell documentation. - - -\page Multi-channel Multi-channel audio rendering - -FluidSynth is capable of rendering all audio and all effects from all MIDI channels to separate stereo buffers. Refer to the documentation of fluid_synth_process() and review the different use-cases in the example file for information on how to do that: \ref fluidsynth_process.c - -The following chart illustrates how the voices (produced by MIDI NoteOns) are dispatched or mapped to their dry/effects audio buffers. - -\htmlonly - -FluidSynth Mixer Chart - -\endhtmlonly - - -\page synth-context Understanding the "synthesis context" - -When reading through the functions exposed via our API, you will often read the note: "May or may not be called from synthesis context." - -The reason for this is that some functions are intentionally not thread-safe. Or they require to be called from this context to behave correctly. - -FluidSynth's rendering engine is implemented by using the "Dispatcher Thread Pattern". This means that a certain thread @c A which calls one of FluidSynth's rendering functions, namely - -- fluid_synth_process() -- fluid_synth_nwrite_float() -- fluid_synth_write_float() -- fluid_synth_write_s16() - -automatically becomes the "synthesis thread". The terms "synthesis context" and "synthesis thread" are equivalent. A few locations in our API provide hooks that allow you to interfere this "synthesis context". At those locations you can register your own custom functions that will always be called by thread @c A. For this use-case, the following functions are of interest: - -- new_fluid_audio_driver2() -- fluid_player_set_playback_callback() -- fluid_sequencer_register_client() - - -\page Advanced Advanced features - -The following features are not yet fully documented. Some information can be -found in the API reference and in the GitHub Wiki. - -- Accessing low-level voice parameters -- Reverb settings -- Chorus settings -- Interpolation settings (set_gen, get_gen, NRPN) -- Voice overflow settings -- LADSPA effects unit -- MIDI tunings - - /*! \example example.c Example producing short random music with FluidSynth diff --git a/doc/usage/_overview.txt b/doc/usage/_overview.txt new file mode 100644 index 00000000..103c4279 --- /dev/null +++ b/doc/usage/_overview.txt @@ -0,0 +1,21 @@ +/*! + +\page UsageGuide Usage Guide + - \subpage CreatingSettings + - \subpage CreatingSynth + - \subpage LoadingSoundfonts + - \subpage CreatingAudioDriver + - \subpage UsingSynth + - \subpage SendingMIDI + - \subpage RealtimeMIDI + - \subpage MIDIPlayer + - \subpage FileRenderer + - \subpage MIDIPlayerMem + - \subpage MIDIRouter + - \subpage Sequencer + - \subpage Shell + - \subpage Multi-channel + - \subpage synth-context + - \subpage Advanced + +*/ diff --git a/doc/usage/advanced.txt b/doc/usage/advanced.txt new file mode 100644 index 00000000..4259058b --- /dev/null +++ b/doc/usage/advanced.txt @@ -0,0 +1,16 @@ +/*! + +\page Advanced Advanced features + +The following features are not yet fully documented. Some information can be +found in the API reference and in the GitHub Wiki. + +- Accessing low-level voice parameters +- Reverb settings +- Chorus settings +- Interpolation settings (set_gen, get_gen, NRPN) +- Voice overflow settings +- LADSPA effects unit +- MIDI tunings + +*/ diff --git a/doc/usage/audio_driver.txt b/doc/usage/audio_driver.txt new file mode 100644 index 00000000..5007e374 --- /dev/null +++ b/doc/usage/audio_driver.txt @@ -0,0 +1,82 @@ +/*! + +\page CreatingAudioDriver Creating the audio driver + +The synthesizer itself does not write any audio to the audio output. This +allows application developers to manage the audio output themselves if they +wish. The next section describes the use of the synthesizer without an audio +driver in more detail. + +Creating the audio driver is straightforward: set the +audio.driver settings and create the driver object. Because the +FluidSynth has support for several audio systems, you may want to change +which one you want to use. The list below shows the audio systems that are +currently supported. It displays the name, as used by the fluidsynth library, +and a description. + +- jack: JACK Audio Connection Kit (Linux, Mac OS X, Windows) +- alsa: Advanced Linux Sound Architecture (Linux) +- oss: Open Sound System (Linux, Unix) +- pulseaudio: PulseAudio (Linux, Mac OS X, Windows) +- coreaudio: Apple CoreAudio (Mac OS X) +- dsound: Microsoft DirectSound (Windows) +- portaudio: PortAudio Library (Mac OS 9 & X, Windows, Linux) +- sndman: Apple SoundManager (Mac OS Classic) +- dart: DART sound driver (OS/2) +- opensles: OpenSL ES (Android) +- oboe: Oboe (Android) +- waveout: Microsoft WaveOut, alternative to DirectSound (Windows CE x86, + Windows Mobile 2003 for ARMv5, Windows 98 SE, Windows NT 4.0, Windows XP + and later) +- file: Driver to output audio to a file +- sdl2*: Simple DirectMedia Layer (Linux, Windows, Mac OS X, iOS, Android, + FreeBSD, Haiku, etc.) + +The default audio driver depends on the settings with which FluidSynth was +compiled. You can get the default driver with +fluid_settings_getstr_default(). To get the list of available drivers use the +fluid_settings_foreach_option() function. Finally, you can set the driver +with fluid_settings_setstr(). In most cases, the default driver should work +out of the box. + +Additional options that define the audio quality and latency are +"audio.sample-format", "audio.period-size", and "audio.periods". The details +are described later. + +You create the audio driver with the new_fluid_audio_driver() function. This +function takes the settings and synthesizer object as arguments. For example: + +\code +void init() +{ + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_audio_driver_t* adriver; + settings = new_fluid_settings(); + + /* Set the synthesizer settings, if necessary */ + synth = new_fluid_synth(settings); + + fluid_settings_setstr(settings, "audio.driver", "jack"); + adriver = new_fluid_audio_driver(settings, synth); +} +\endcode + +As soon as the audio driver is created, it will start playing. The audio +driver creates a separate thread that uses the synthesizer object to generate +the audio. + +There are a number of general audio driver settings. The audio.driver +settings define the audio subsystem that will be used. The audio.periods and +audio.period-size settings define the latency and robustness against +scheduling delays. There are additional settings for the audio subsystems +used. For a full list of available audio driver settings, +please refer to the \ref settings_audio documentation. + +*Note: In order to use sdl2 as audio driver, the application +is responsible for initializing SDL (e.g. with SDL_Init()). This must be done +before the first call to new_fluid_settings()! +Also make sure to call SDL_Quit() after all fluidsynth instances have been +destroyed. + +*/ diff --git a/doc/usage/creating_settings.txt b/doc/usage/creating_settings.txt new file mode 100644 index 00000000..df4b27d9 --- /dev/null +++ b/doc/usage/creating_settings.txt @@ -0,0 +1,24 @@ +/*! + +\page CreatingSettings Creating and changing the settings + +Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. + +All settings have a name that follows the "dotted-name" notation. For example, "synth.polyphony" refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: + +\code +#include + +int main(int argc, char** argv) +{ + fluid_settings_t* settings = new_fluid_settings(); + fluid_settings_setint(settings, "synth.polyphony", 128); + /* ... */ + delete_fluid_settings(settings); + return 0; +} +\endcode + +The API contains the functions to query the type, the current value, the default value, the range and the "hints" of a setting. The range is the minimum and maximum value of the setting. The hints gives additional information about a setting. For example, whether a string represents a filename. Or whether a number should be interpreted on on a logarithmic scale. Check the settings.h API documentation for a description of all functions. + +*/ diff --git a/doc/usage/creating_synth.txt b/doc/usage/creating_synth.txt new file mode 100644 index 00000000..26475c2a --- /dev/null +++ b/doc/usage/creating_synth.txt @@ -0,0 +1,29 @@ +/*! + +\page CreatingSynth Creating the synthesizer + +To create the synthesizer, you pass it the settings object, as in the +following example: + +\code +#include + +int main(int argc, char** argv) +{ + fluid_settings_t* settings; + fluid_synth_t* synth; + settings = new_fluid_settings(); + synth = new_fluid_synth(settings); + + /* Do useful things here */ + + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return 0; +} +\endcode + +For a full list of available synthesizer settings, please +refer to the \ref settings_synth documentation. + +*/ diff --git a/doc/usage/file_renderer.txt b/doc/usage/file_renderer.txt new file mode 100644 index 00000000..d52a9995 --- /dev/null +++ b/doc/usage/file_renderer.txt @@ -0,0 +1,60 @@ +/*! + +\page FileRenderer Fast file renderer for non-realtime MIDI file rendering + +Instead of creating an audio driver as described in section \ref MIDIPlayer +one may chose to use the file renderer, which is the fastest way to +synthesize MIDI files. + +\code +fluid_settings_t* settings; +fluid_synth_t* synth; +fluid_player_t* player; +fluid_file_renderer_t* renderer; + +settings = new_fluid_settings(); + +// specify the file to store the audio to +// make sure you compiled fluidsynth with libsndfile to get a real wave file +// otherwise this file will only contain raw s16 stereo PCM +fluid_settings_setstr(settings, "audio.file.name", "/path/to/output.wav"); + +// use number of samples processed as timing source, rather than the system timer +fluid_settings_setstr(settings, "player.timing-source", "sample"); + +// since this is a non-realtime scenario, there is no need to pin the sample data +fluid_settings_setint(settings, "synth.lock-memory", 0); + +synth = new_fluid_synth(settings); + +// *** loading of a soundfont omitted *** + +player = new_fluid_player(synth); +fluid_player_add(player, "/path/to/midifile.mid"); +fluid_player_play(player); + +renderer = new_fluid_file_renderer (synth); + +while (fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) +{ + if (fluid_file_renderer_process_block(renderer) != FLUID_OK) + { + break; + } +} + +// just for sure: stop the playback explicitly and wait until finished +fluid_player_stop(player); +fluid_player_join(player); + +delete_fluid_file_renderer(renderer); +delete_fluid_player(player); +delete_fluid_synth(synth); +delete_fluid_settings(settings); +\endcode + +Various output files types are supported, if compiled with libsndfile. Those +can be specified via the \c settings object as well. Refer to the \ref +settings_audio documentation for more \c audio.file\.\* options. + +*/ diff --git a/doc/usage/loading_soundfonts.txt b/doc/usage/loading_soundfonts.txt new file mode 100644 index 00000000..cdc6f4e4 --- /dev/null +++ b/doc/usage/loading_soundfonts.txt @@ -0,0 +1,34 @@ +/*! + +\page LoadingSoundfonts Loading and managing SoundFonts + +Before any sound can be produced, the synthesizer needs a SoundFont. + +SoundFonts are loaded with the fluid_synth_sfload() function. The function +takes the path to a SoundFont file and a boolean to indicate whether the +presets of the MIDI channels should be updated after the SoundFont is loaded. +When the boolean value is TRUE, all MIDI channel bank and program numbers +will be refreshed, which may cause new instruments to be selected from the +newly loaded SoundFont. + +The synthesizer can load any number of SoundFonts. The loaded SoundFonts are +treated as a stack, where each new loaded SoundFont is placed at the top of +the stack. When selecting presets by bank and program numbers, SoundFonts are +searched beginning at the top of the stack. In the case where there are +presets in different SoundFonts with identical bank and program numbers, the +preset from the most recently loaded SoundFont is used. The +fluid_synth_program_select() can be used for unambiguously selecting a preset +or bank offsets could be applied to each SoundFont with +fluid_synth_set_bank_offset(), to try and ensure that each preset has unique +bank and program numbers. + +The fluid_synth_sfload() function returns the unique identifier of the loaded +SoundFont, or -1 in case of an error. This identifier is used in subsequent +management functions: fluid_synth_sfunload() removes the SoundFont, +fluid_synth_sfreload() reloads the SoundFont. When a SoundFont is reloaded, +it retains it's ID and position on the SoundFont stack. + +Additional API functions are provided to get the number of loaded SoundFonts +and to get a pointer to the SoundFont. + +*/ diff --git a/doc/usage/manual_rendering.txt b/doc/usage/manual_rendering.txt new file mode 100644 index 00000000..228b5589 --- /dev/null +++ b/doc/usage/manual_rendering.txt @@ -0,0 +1,17 @@ +/*! + +\page UsingSynth Using the synthesizer without an audio driver + +It is possible to use the synthesizer object without creating an audio +driver. This is desirable if the application using FluidSynth manages the +audio output itself. The synthesizer has several API functions that can be +used to obtain the audio output: + +fluid_synth_write_s16() fills two buffers (left and right channel) with +samples coded as signed 16 bits (the endian-ness is machine dependent). +fluid_synth_write_float() fills a left and right audio buffer with 32 bits +floating point samples. The function fluid_synth_process() is the generic +interface for synthesizing audio, which is also capable of multi channel +audio output. + +*/ diff --git a/doc/usage/midi_player.txt b/doc/usage/midi_player.txt new file mode 100644 index 00000000..ba8bdc76 --- /dev/null +++ b/doc/usage/midi_player.txt @@ -0,0 +1,55 @@ +/*! + +\page MIDIPlayer Loading and playing a MIDI file + +FluidSynth can be used to play MIDI files, using the MIDI File Player +interface. It follows a high level implementation, though its implementation +is currently incomplete. After initializing the synthesizer, create the +player passing the synth instance to new_fluid_player(). Then, you can add +some SMF file names to the player using fluid_player_add(), and finally call +fluid_player_play() to start the playback. You can check if the player has +finished by calling fluid_player_get_status(), or wait for the player to +terminate using fluid_player_join(). + +\code +#include + +int main(int argc, char** argv) +{ + int i; + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_player_t* player; + fluid_audio_driver_t* adriver; + settings = new_fluid_settings(); + synth = new_fluid_synth(settings); + player = new_fluid_player(synth); + /* process command line arguments */ + for (i = 1; i < argc; i++) { + if (fluid_is_soundfont(argv[i])) { + fluid_synth_sfload(synth, argv[1], 1); + } + if (fluid_is_midifile(argv[i])) { + fluid_player_add(player, argv[i]); + } + } + /* start the synthesizer thread */ + adriver = new_fluid_audio_driver(settings, synth); + /* play the midi files, if any */ + fluid_player_play(player); + /* wait for playback termination */ + fluid_player_join(player); + /* cleanup */ + delete_fluid_audio_driver(adriver); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return 0; +} +\endcode + + +A list of available MIDI player settings can be found in the +\ref settings_player documentation. + +*/ diff --git a/doc/usage/midi_player_mem.txt b/doc/usage/midi_player_mem.txt new file mode 100644 index 00000000..c2d3baaf --- /dev/null +++ b/doc/usage/midi_player_mem.txt @@ -0,0 +1,64 @@ +/*! + +\page MIDIPlayerMem Playing a MIDI file from memory + +FluidSynth can be also play MIDI files directly from a buffer in memory. If +you need to play a file from a stream (such as stdin, a network, or a +high-level file interface), you can load the entire file into a buffer first, +and then use this approach. Use the same technique as above, but rather than +calling fluid_player_add(), load it into memory and call +fluid_player_add_mem() instead. Once you have passed a buffer to +fluid_player_add_mem(), it is copied, so you may use it again or free it +immediately (it is your responsibility to free it if you allocated it). + +\code +#include +#include +#include + +/* An example midi file */ +const char MIDIFILE[] = { + 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x01, 0x00, 0x01, 0x01, 0xe0, 0x4d, 0x54, + 0x72, 0x6b, 0x00, 0x00, 0x00, 0x20, 0x00, 0x90, + 0x3c, 0x64, 0x87, 0x40, 0x80, 0x3c, 0x7f, 0x00, + 0x90, 0x43, 0x64, 0x87, 0x40, 0x80, 0x43, 0x7f, + 0x00, 0x90, 0x48, 0x64, 0x87, 0x40, 0x80, 0x48, + 0x7f, 0x83, 0x60, 0xff, 0x2f, 0x00 +}; + +int main(int argc, char** argv) +{ + int i; + void* buffer; + size_t buffer_len; + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_player_t* player; + fluid_audio_driver_t* adriver; + settings = new_fluid_settings(); + synth = new_fluid_synth(settings); + player = new_fluid_player(synth); + adriver = new_fluid_audio_driver(settings, synth); + /* process command line arguments */ + for (i = 1; i < argc; i++) { + if (fluid_is_soundfont(argv[i])) { + fluid_synth_sfload(synth, argv[1], 1); + } + } + /* queue up the in-memory midi file */ + fluid_player_add_mem(player, MIDIFILE, sizeof(MIDIFILE)); + /* play the midi file */ + fluid_player_play(player); + /* wait for playback termination */ + fluid_player_join(player); + /* cleanup */ + delete_fluid_audio_driver(adriver); + delete_fluid_player(player); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return 0; +} +\endcode + +*/ diff --git a/doc/usage/midi_router.txt b/doc/usage/midi_router.txt new file mode 100644 index 00000000..9e014d0c --- /dev/null +++ b/doc/usage/midi_router.txt @@ -0,0 +1,76 @@ +/*! + +\page MIDIRouter Real-time MIDI router + +The MIDI router is one more processing layer directly behind the MIDI input. +It processes incoming MIDI events and generates control events for the synth. +It can be used to filter or modify events prior to sending them to the +synthesizer. When created, the MIDI router is transparent and simply passes +all MIDI events. Router "rules" must be added to actually make use of its +capabilities. + +Some examples of MIDI router usage: + +- Filter messages (Example: Pass sustain pedal CCs only to selected channels) +- Split the keyboard (Example: noteon with notenr < x: to ch 1, >x to ch 2) +- Layer sounds (Example: for each noteon received on ch 1, create a noteon on + ch1, ch2, ch3,...) +- Velocity scaling (Example: for each noteon event, scale the velocity by + 1.27) +- Velocity switching (Example: v <= 100: "Angel Choir"; v > 100: "Hell's + Bells") +- Get rid of aftertouch + +The MIDI driver API has a clean separation between the midi thread and the +synthesizer. That opens the door to add a midi router module. + +MIDI events coming from the MIDI player do not pass through the MIDI router. + +\code +#include + +int main(int argc, char** argv) +{ + fluid_settings_t* settings; + fluid_synth_t* synth; + fluid_midi_router_t* router; + fluid_midi_router_rule_t* rule; + + settings = new_fluid_settings(); + synth = new_fluid_synth(settings); + + /* Create the MIDI router and pass events to the synthesizer */ + router = new_fluid_midi_router (settings, fluid_synth_handle_midi_event, synth); + + /* Clear default rules */ + fluid_midi_router_clear_rules (router); + + /* Add rule to map all notes < MIDI note #60 on any channel to channel 4 */ + rule = new_fluid_midi_router_rule (); + fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 4); /* Map all to channel 4 */ + fluid_midi_router_rule_set_param1 (rule, 0, 59, 1.0, 0); /* Match notes < 60 */ + fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); + + /* Add rule to map all notes >= MIDI note #60 on any channel to channel 5 */ + rule = new_fluid_midi_router_rule (); + fluid_midi_router_rule_set_chan (rule, 0, 15, 0.0, 5); /* Map all to channel 5 */ + fluid_midi_router_rule_set_param1 (rule, 60, 127, 1.0, 0); /* Match notes >= 60 */ + fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_NOTE); + + /* Add rule to reverse direction of pitch bender on channel 7 */ + rule = new_fluid_midi_router_rule (); + fluid_midi_router_rule_set_chan (rule, 7, 7, 1.0, 0); /* Match channel 7 only */ + fluid_midi_router_rule_set_param1 (rule, 0, 16383, -1.0, 16383); /* Reverse pitch bender */ + fluid_midi_router_add_rule (router, rule, FLUID_MIDI_ROUTER_RULE_PITCH_BEND); + + /* ... Create audio driver, process events, etc ... */ + + /* cleanup */ + delete_fluid_midi_router(router); + delete_fluid_synth(synth); + delete_fluid_settings(settings); + return 0; +} +\endcode + +*/ diff --git a/doc/usage/multi_channel.txt b/doc/usage/multi_channel.txt new file mode 100644 index 00000000..0c136c11 --- /dev/null +++ b/doc/usage/multi_channel.txt @@ -0,0 +1,19 @@ +/*! + +\page Multi-channel Multi-channel audio rendering + +FluidSynth is capable of rendering all audio and all effects from all MIDI +channels to separate stereo buffers. Refer to the documentation of +fluid_synth_process() and review the different use-cases in the example file +for information on how to do that: \ref fluidsynth_process.c + +The following chart illustrates how the voices (produced by MIDI NoteOns) are +dispatched or mapped to their dry/effects audio buffers. + +\htmlonly + +FluidSynth Mixer Chart + +\endhtmlonly + +*/ diff --git a/doc/usage/realtime_midi.txt b/doc/usage/realtime_midi.txt new file mode 100644 index 00000000..b37018e6 --- /dev/null +++ b/doc/usage/realtime_midi.txt @@ -0,0 +1,47 @@ +/*! + +\page RealtimeMIDI Creating a real-time MIDI driver + +FluidSynth can process real-time MIDI events received from hardware MIDI +ports or other applications. To do so, the client must create a MIDI input +driver. It is a very similar process to the creation of the audio driver: you +initialize some properties in a settings instance and call the +new_fluid_midi_driver() function providing a callback function that will be +invoked when a MIDI event is received. The following MIDI drivers are +currently supported: + +- jack: JACK Audio Connection Kit MIDI driver (Linux, Mac OS X) +- oss: Open Sound System raw MIDI (Linux, Unix) +- alsa_raw: ALSA raw MIDI interface (Linux) +- alsa_seq: ALSA sequencer MIDI interface (Linux) +- winmidi: Microsoft Windows MM System (Windows) +- midishare: MIDI Share (Linux, Mac OS X) +- coremidi: Apple CoreMIDI (Mac OS X) + +\code +#include + +int handle_midi_event(void* data, fluid_midi_event_t* event) +{ + printf("event type: %d\n", fluid_midi_event_get_type(event)); +} + +int main(int argc, char** argv) +{ + fluid_settings_t* settings; + fluid_midi_driver_t* mdriver; + settings = new_fluid_settings(); + mdriver = new_fluid_midi_driver(settings, handle_midi_event, NULL); + /* ... */ + delete_fluid_midi_driver(mdriver); + return 0; +} +\endcode + +There are a number of general MIDI driver settings. The midi.driver setting +defines the MIDI subsystem that will be used. There are additional settings +for the MIDI subsystems used. For a full list of available +midi driver settings, please refer to the \ref settings_midi +documentation. + +*/ diff --git a/doc/usage/sending_midi.txt b/doc/usage/sending_midi.txt new file mode 100644 index 00000000..19e7c9d9 --- /dev/null +++ b/doc/usage/sending_midi.txt @@ -0,0 +1,53 @@ +/*! + +\page SendingMIDI Sending MIDI events + +Once the synthesizer is up and running and a SoundFont is loaded, most people +will want to do something useful with it. Make noise, for example. MIDI +messages can be sent using the fluid_synth_noteon(), fluid_synth_noteoff(), +fluid_synth_cc(), fluid_synth_pitch_bend(), fluid_synth_pitch_wheel_sens(), +and fluid_synth_program_change() functions. For convenience, there's also a +fluid_synth_bank_select() function (the bank select message is normally sent +using a control change message). + +The following example show a generic graphical button that plays a note when +clicked: + +\code +class SoundButton : public SomeButton +{ +public: + + SoundButton() : SomeButton() { + if (!_synth) { + initSynth(); + } + } + + static void initSynth() { + _settings = new_fluid_settings(); + _synth = new_fluid_synth(_settings); + _adriver = new_fluid_audio_driver(_settings, _synth); + } + + /* ... */ + + virtual int handleMouseDown(int x, int y) { + /* Play a note on key 60 with velocity 100 on MIDI channel 0 */ + fluid_synth_noteon(_synth, 0, 60, 100); + } + + virtual int handleMouseUp(int x, int y) { + /* Release the note on key 60 */ + fluid_synth_noteoff(_synth, 0, 60); + } + +protected: + + static fluid_settings_t* _settings; + static fluid_synth_t* _synth; + static fluid_audio_driver_t* _adriver; +}; +\endcode + +*/ diff --git a/doc/usage/sequencer.txt b/doc/usage/sequencer.txt new file mode 100644 index 00000000..30351b9b --- /dev/null +++ b/doc/usage/sequencer.txt @@ -0,0 +1,142 @@ +/*! + +\page Sequencer Using the MIDI sequencer + +FluidSynth's sequencer can be used to play MIDI events in a more flexible way +than using the MIDI file player, which expects the events to be stored as +Standard MIDI Files. Using the sequencer, you can provide the events one by +one, with an optional timestamp for scheduling. + +The client program should first initialize the sequencer instance using the +function new_fluid_sequencer2(). There is a complementary function +delete_fluid_sequencer() to delete it. After creating the sequencer instance, +the destinations can be registered using +fluid_sequencer_register_fluidsynth() for the synthesizer destination, and +optionally using fluid_sequencer_register_client() for the client destination +providing a suitable callback function. It can be unregistered using +fluid_sequencer_unregister_client(). After the initialization, events can be +sent with fluid_sequencer_send_now() and scheduled to the future with +fluid_sequencer_send_at(). The registration functions return identifiers, +that can be used as destinations of an event using fluid_event_set_dest(). + +The function fluid_sequencer_get_tick() returns the current playing position. +A program may choose a new timescale in milliseconds using +fluid_sequencer_set_time_scale(). + +The following example uses the fluidsynth sequencer to implement a sort of +music box. FluidSynth internal clock is used to schedule repetitive sequences +of notes. The next sequence is scheduled on advance before the end of the +current one, using a timer event that triggers a callback function. The +scheduling times are always absolute values, to avoid slippage. + +\code +#include "fluidsynth.h" + +fluid_synth_t* synth; +fluid_audio_driver_t* adriver; +fluid_sequencer_t* sequencer; +short synthSeqID, mySeqID; +unsigned int now; +unsigned int seqduration; + +// prototype +void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data); + +void createsynth() +{ + fluid_settings_t* settings; + settings = new_fluid_settings(); + fluid_settings_setint(settings, "synth.reverb.active", 0); + fluid_settings_setint(settings, "synth.chorus.active", 0); + synth = new_fluid_synth(settings); + adriver = new_fluid_audio_driver(settings, synth); + sequencer = new_fluid_sequencer2(0); + + // register synth as first destination + synthSeqID = fluid_sequencer_register_fluidsynth(sequencer, synth); + + // register myself as second destination + mySeqID = fluid_sequencer_register_client(sequencer, "me", seq_callback, NULL); + + // the sequence duration, in ms + seqduration = 1000; +} + +void deletesynth() +{ + delete_fluid_sequencer(sequencer); + delete_fluid_audio_driver(adriver); + delete_fluid_synth(synth); +} + +void loadsoundfont() +{ + int fluid_res; + // put your own path here + fluid_res = fluid_synth_sfload(synth, "Inside:VintageDreamsWaves-v2.sf2", 1); +} + +void sendnoteon(int chan, short key, unsigned int date) +{ + int fluid_res; + fluid_event_t *evt = new_fluid_event(); + fluid_event_set_source(evt, -1); + fluid_event_set_dest(evt, synthSeqID); + fluid_event_noteon(evt, chan, key, 127); + fluid_res = fluid_sequencer_send_at(sequencer, evt, date, 1); + delete_fluid_event(evt); +} + +void schedule_next_callback() +{ + int fluid_res; + // I want to be called back before the end of the next sequence + unsigned int callbackdate = now + seqduration/2; + fluid_event_t *evt = new_fluid_event(); + fluid_event_set_source(evt, -1); + fluid_event_set_dest(evt, mySeqID); + fluid_event_timer(evt, NULL); + fluid_res = fluid_sequencer_send_at(sequencer, evt, callbackdate, 1); + delete_fluid_event(evt); +} + +void schedule_next_sequence() { + // Called more or less before each sequence start + // the next sequence start date + now = now + seqduration; + + // the sequence to play + + // the beat : 2 beats per sequence + sendnoteon(0, 60, now + seqduration/2); + sendnoteon(0, 60, now + seqduration); + + // melody + sendnoteon(1, 45, now + seqduration/10); + sendnoteon(1, 50, now + 4*seqduration/10); + sendnoteon(1, 55, now + 8*seqduration/10); + + // so that we are called back early enough to schedule the next sequence + schedule_next_callback(); +} + +/* sequencer callback */ +void seq_callback(unsigned int time, fluid_event_t* event, fluid_sequencer_t* seq, void* data) { + schedule_next_sequence(); +} + +int main(void) { + createsynth(); + loadsoundfont(); + + // initialize our absolute date + now = fluid_sequencer_get_tick(sequencer); + schedule_next_sequence(); + + sleep(100000); + deletesynth(); + return 0; +} +\endcode + +*/ diff --git a/doc/usage/shell.txt b/doc/usage/shell.txt new file mode 100644 index 00000000..e22bafce --- /dev/null +++ b/doc/usage/shell.txt @@ -0,0 +1,12 @@ +/*! + +\page Shell Shell interface + +The shell interface allows you to send simple textual commands to the +synthesizer, to parse a command file, or to read commands from the stdin or +other input streams. To find the list of currently supported commands, type +@c help in the fluidsynth command line shell. For a full list of available +command line settings, please refer to the \ref +settings_shell documentation. + +*/ diff --git a/doc/usage/synth_context.txt b/doc/usage/synth_context.txt new file mode 100644 index 00000000..9c57a004 --- /dev/null +++ b/doc/usage/synth_context.txt @@ -0,0 +1,22 @@ +/*! + +\page synth-context Understanding the "synthesis context" + +When reading through the functions exposed via our API, you will often read the note: "May or may not be called from synthesis context." + +The reason for this is that some functions are intentionally not thread-safe. Or they require to be called from this context to behave correctly. + +FluidSynth's rendering engine is implemented by using the "Dispatcher Thread Pattern". This means that a certain thread @c A which calls one of FluidSynth's rendering functions, namely + +- fluid_synth_process() +- fluid_synth_nwrite_float() +- fluid_synth_write_float() +- fluid_synth_write_s16() + +automatically becomes the "synthesis thread". The terms "synthesis context" and "synthesis thread" are equivalent. A few locations in our API provide hooks that allow you to interfere this "synthesis context". At those locations you can register your own custom functions that will always be called by thread @c A. For this use-case, the following functions are of interest: + +- new_fluid_audio_driver2() +- fluid_player_set_playback_callback() +- fluid_sequencer_register_client() + +*/ From c4512367c499f6dd0b48479cba1864790638e821 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 13:40:46 +0100 Subject: [PATCH 36/55] Move examples into doc/examples directory --- doc/CMakeLists.txt | 9 +----- doc/Doxyfile | 2 +- doc/Doxyfile.cmake | 2 +- doc/examples/CMakeLists.txt | 30 +++++++++++++++++++ doc/{ => examples}/example.c | 0 doc/{ => examples}/fluidsynth_arpeggio.c | 0 doc/{ => examples}/fluidsynth_fx.c | 0 doc/{ => examples}/fluidsynth_metronome.c | 0 doc/{ => examples}/fluidsynth_process.c | 0 .../fluidsynth_register_adriver.c | 0 doc/{ => examples}/fluidsynth_sfload_mem.c | 0 doc/{ => examples}/fluidsynth_simple.c | 1 + 12 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 doc/examples/CMakeLists.txt rename doc/{ => examples}/example.c (100%) rename doc/{ => examples}/fluidsynth_arpeggio.c (100%) rename doc/{ => examples}/fluidsynth_fx.c (100%) rename doc/{ => examples}/fluidsynth_metronome.c (100%) rename doc/{ => examples}/fluidsynth_process.c (100%) rename doc/{ => examples}/fluidsynth_register_adriver.c (100%) rename doc/{ => examples}/fluidsynth_sfload_mem.c (100%) rename doc/{ => examples}/fluidsynth_simple.c (99%) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 752c22f4..47325d45 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -48,11 +48,4 @@ if ( UNIX ) DESTINATION ${MAN_INSTALL_DIR} ) endif ( UNIX ) -include ( FluidUnitTest ) -add_custom_target ( demo ) - -ADD_FLUID_DEMO ( example ) -ADD_FLUID_DEMO ( fluidsynth_arpeggio ) -ADD_FLUID_DEMO ( fluidsynth_fx ) -ADD_FLUID_DEMO ( fluidsynth_metronome ) -ADD_FLUID_DEMO ( fluidsynth_simple ) +add_subdirectory ( examples ) diff --git a/doc/Doxyfile b/doc/Doxyfile index 8ce12644..0f80df9c 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -100,7 +100,7 @@ EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = fluid_*.h EXCLUDE_SYMBOLS = -EXAMPLE_PATH = ../doc +EXAMPLE_PATH = ../doc/examples EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO IMAGE_PATH = diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 0250a439..305ca9db 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -102,7 +102,7 @@ EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = fluid_*.h EXCLUDE_SYMBOLS = -EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/doc +EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/doc/examples EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO IMAGE_PATH = diff --git a/doc/examples/CMakeLists.txt b/doc/examples/CMakeLists.txt new file mode 100644 index 00000000..ec75a521 --- /dev/null +++ b/doc/examples/CMakeLists.txt @@ -0,0 +1,30 @@ +# FluidSynth - A Software Synthesize +# +# Copyright (C) 2003-2010 Peter Hanappe and others. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# as published by the Free Software Foundation; either version 2.1 of +# the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +# 02111-1307, USA + +# CMake based build system. Pedro Lopez-Cabanillas + +include ( FluidUnitTest ) +add_custom_target ( demo ) + +ADD_FLUID_DEMO ( example ) +ADD_FLUID_DEMO ( fluidsynth_arpeggio ) +ADD_FLUID_DEMO ( fluidsynth_fx ) +ADD_FLUID_DEMO ( fluidsynth_metronome ) +ADD_FLUID_DEMO ( fluidsynth_simple ) + diff --git a/doc/example.c b/doc/examples/example.c similarity index 100% rename from doc/example.c rename to doc/examples/example.c diff --git a/doc/fluidsynth_arpeggio.c b/doc/examples/fluidsynth_arpeggio.c similarity index 100% rename from doc/fluidsynth_arpeggio.c rename to doc/examples/fluidsynth_arpeggio.c diff --git a/doc/fluidsynth_fx.c b/doc/examples/fluidsynth_fx.c similarity index 100% rename from doc/fluidsynth_fx.c rename to doc/examples/fluidsynth_fx.c diff --git a/doc/fluidsynth_metronome.c b/doc/examples/fluidsynth_metronome.c similarity index 100% rename from doc/fluidsynth_metronome.c rename to doc/examples/fluidsynth_metronome.c diff --git a/doc/fluidsynth_process.c b/doc/examples/fluidsynth_process.c similarity index 100% rename from doc/fluidsynth_process.c rename to doc/examples/fluidsynth_process.c diff --git a/doc/fluidsynth_register_adriver.c b/doc/examples/fluidsynth_register_adriver.c similarity index 100% rename from doc/fluidsynth_register_adriver.c rename to doc/examples/fluidsynth_register_adriver.c diff --git a/doc/fluidsynth_sfload_mem.c b/doc/examples/fluidsynth_sfload_mem.c similarity index 100% rename from doc/fluidsynth_sfload_mem.c rename to doc/examples/fluidsynth_sfload_mem.c diff --git a/doc/fluidsynth_simple.c b/doc/examples/fluidsynth_simple.c similarity index 99% rename from doc/fluidsynth_simple.c rename to doc/examples/fluidsynth_simple.c index 23bfff3b..0e8180a4 100644 --- a/doc/fluidsynth_simple.c +++ b/doc/examples/fluidsynth_simple.c @@ -17,6 +17,7 @@ int main(int argc, char **argv) { + asdf fluid_settings_t *settings; fluid_synth_t *synth = NULL; fluid_audio_driver_t *adriver = NULL; From cb16f8d32da9b176d55d236a57aba76ee82e418f Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 14:04:05 +0100 Subject: [PATCH 37/55] Split HTML_EXTRA_FILEs into separate lines --- doc/Doxyfile | 7 ++++++- doc/Doxyfile.cmake | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 0f80df9c..c9e6933a 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -134,7 +134,12 @@ HTML_HEADER = HTML_FOOTER = ../doc/doxygen/footer.html HTML_EXTRA_STYLESHEET = ../doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES -HTML_EXTRA_FILES = ../doc/fluidsettings.xml ../doc/fluidsettings.xsl ../doc/FluidMixer.pdf ../doc/FluidMixer.jpg +HTML_EXTRA_FILES = \ +../doc/fluidsettings.xml \ +../doc/fluidsettings.xsl \ +../doc/FluidMixer.pdf \ +../doc/FluidMixer.jpg + GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 305ca9db..1e8f63e6 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -136,7 +136,12 @@ HTML_HEADER = HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/doxygen/footer.html HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES -HTML_EXTRA_FILES = @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl @CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf @CMAKE_SOURCE_DIR@/doc/FluidMixer.jpg +HTML_EXTRA_FILES = \ +@CMAKE_SOURCE_DIR@/doc/fluidsettings.xml \ +@CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl \ +@CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf \ +@CMAKE_SOURCE_DIR@/doc/FluidMixer.jpg + GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" From 0f9aed6f835d9f88f204f88a4bb42a3c53082236 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 14:23:23 +0100 Subject: [PATCH 38/55] Use \image for images and improve quality of FluidMixer image --- doc/Doxyfile | 5 ++--- doc/Doxyfile.cmake | 5 ++--- doc/FluidMixer.jpg | Bin 25200 -> 0 bytes doc/doxygen/custom.css | 5 +++++ doc/images/fluid_mixer.png | Bin 0 -> 67853 bytes doc/usage/multi_channel.txt | 6 +----- 6 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 doc/FluidMixer.jpg create mode 100644 doc/images/fluid_mixer.png diff --git a/doc/Doxyfile b/doc/Doxyfile index c9e6933a..54c2e848 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -103,7 +103,7 @@ EXCLUDE_SYMBOLS = EXAMPLE_PATH = ../doc/examples EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO -IMAGE_PATH = +IMAGE_PATH = ../doc/images INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO @@ -137,8 +137,7 @@ HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ ../doc/fluidsettings.xml \ ../doc/fluidsettings.xsl \ -../doc/FluidMixer.pdf \ -../doc/FluidMixer.jpg +../doc/FluidMixer.pdf GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 1e8f63e6..ce9b790f 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -105,7 +105,7 @@ EXCLUDE_SYMBOLS = EXAMPLE_PATH = @CMAKE_SOURCE_DIR@/doc/examples EXAMPLE_PATTERNS = *.c EXAMPLE_RECURSIVE = NO -IMAGE_PATH = +IMAGE_PATH = @CMAKE_SOURCE_DIR@/doc/images INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO @@ -139,8 +139,7 @@ HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl \ -@CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf \ -@CMAKE_SOURCE_DIR@/doc/FluidMixer.jpg +@CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO diff --git a/doc/FluidMixer.jpg b/doc/FluidMixer.jpg deleted file mode 100644 index fa51a4ab0cb6d5cd435694312f304d24c97c51da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25200 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 F{0HC)*lhp+ diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index db728b80..214ee2c3 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -209,3 +209,8 @@ table.directory tr.even { table.directory tr { border-bottom: 1px solid #eee; } + +.image img { + width: 100%; + height: auto; +} diff --git a/doc/images/fluid_mixer.png b/doc/images/fluid_mixer.png new file mode 100644 index 0000000000000000000000000000000000000000..5387203e559f2e60dd0fd853a10fd6850b527e0a GIT binary patch literal 67853 zcmaHScUV(R_bo*{)Bgo)`l?4H_y|DiRVB8ZAv#BN7q{ zfP{oR5k?Mu!@K#pkA#Hm$UyhD8uXErl$4B&jGUbO%$YM36clIAo;`Q&91I4dq@<*x zqN1jzK7al^4Gj$~EiD}#9UKlvAP^TWT%f0?XJBApWMpJwVq#`yW?^Buc<~}DD=Qlt z8#_BY2L}fyCnpyd7dJOI5{cyD;X$ELmo8o6<>kG6`7$3LA3s0;l`B`SUcD+HARs6x zC?q5#EG#S{A|fg(dhOb^>({S~iHY5~aYI~OTtY%ZQc_Y%O6um#o6^$KGBPr3JMB}ii%1~O3KR0Dk>_fs;X*gYU=9h8X6j!nwnZ#TH4y$IyySKy1KV+-O|(3 z)7RHGFfcGQG&C|YGB!58ef##EJ9kV>OiWEp&CJZq&CT!Ly=!4%VQFb;Wo2b;ZEa&? zbMM|gG#YJdYinm`cmMwV2M-?D+uJ)hI6Qp#@X@13j*gB_PEO9w&Mq!4uCA_bZf+P1 z#@*fB!^6YV)ARA;$6j7u-rn9mK0a71*4Nk9&(F`_-#;KAATTg6C@3g6I5;FEBs4TM zEG#TMJRAUkh=_>D$jGRusOaeEn3$L+PoBia#y)-eG%hag*|TTy@$t`}KY#J!MM6Tt z%a<<`6BCn?l9H2?Q&Lh=Q&ZE@(q6rKm7boSk&%&^nVFT9m7SfPlaurM_3Jlp-n@PL zHa9mnFE8)iyLb8d`2__9g@uJhMMdx5zb`H>E-5J~EiEl8D=RNAuc)Y~tgNi6s;aK8 zuBoZ1t*x!AtE;cC|M1~MLqkJjV&4Gc&WZvvYHE zfByWLpPye?SXf+KTv}RMUS3{VSy^3OU0YjQUtizY*x20M+}hgO-rnBX+1cIQB@&5y zdwcu)`v(UHhlhtpM@J_oC-oId6sHrwR!2jXB$|Rl6Z$}j(KPoYAz{97`a|lF{R5gi zBGX3thl3 zL$;OSgm$Oc0Kpx4P~b|ToYv`MZHS=w1awU{TwYIWSt|h!G615F6k+rr19XAZg&7;b z!T;kzNeTMr={NsgP*6a3oqj_R(SkTWXp4F8T7t2*MEZ9;wFAoCjDKb3dIwKgo2Nmq z!hZui3UzTUStBbqhewhwQlcl`Rm*5CA9plWUWY6aZi0;{p8X5pF+M8)$}OmI;0D+j zGMv5SW9{zW}@=}TU-f!;Rzbwm9JwE?!;y|< zJ_9fAg|;r4jG*67E)dA*ZE7^2k>gPSCZ3YVe%6DDkdqH@CE3<5C$F7(-k12T%-aCY zNk^`W;992^eM_Ehx_WN0K{85pJ;~y@ts|X0AD_ckZUd*iZC`aj1Ap+mY~*Do@eS{+ z1;c^7(MuQK4VGrA5rRhi6y>%@kxONaP!e84-_nGmw#y(WB1aJ)^rbU*Fy%*Qu1Ve# zbt9*|t-1i_hX-$`3tXl;=?k<)2HFNUoCR$w+aocbV#Gxc7UXZ8SFw>{RntZ{OKE*AG z9yIGG!f==aS-Gc(``!2 z1fE5&Iv=cj{w=rDih7Pv?|LCH&~=lCy=r4=_WI;3LzGavP5!jhTSfdaV;Bi~1}Ttm z*Hrs`!ri2+Jz6D6Kb-kQvQJW$W$x6t4^ZCbh_;G!o$=b$B1Vl)&XXzu&4CELV-P^>^1Mw&y|m7d1;LCIlSB_A5R=MrXSC?22P%* zpm8u$T5aIAd|K6YFGjuFd}kfo953MDJBqs)gQs57@b$e&n3Iy|oXLO(h2Ih+&AP); z%izMT5f(;zU?;-VD*OwVa9MfP!diS~3szp_B)-5-CsK5Ygw~K^jXUm!ldN3nb zA*As()Ss<=9{qQ?olmHgLdrLuGisHHO>MYQ=vIPv?Vz4+~{k0=OHg} zrwo1u_MUb)>z`2UJPk$WE1AWr_*(awzawj#QIi;ZccWAK!oRba3%E$@`3umgbDt%r z{`IywhnX<(#=v7kQnJw;A^&w&u1Efe@`Lb3C9r}ThfN}fH!WyIfRI?{u>SkVK zR!AHSdlb#0@XN$oGhnXY^lxlo;?@W+P{VFNH_J#%CI?DOXg}UY7 zyG2Gz) zmtZGM$goSqBbKMJ!kP$gsrwd2%!xmsIjQ|OkRH`)FE*vGoM9@2o1A(#H=uNTf3MY< zUMw#ulimiko#cNBTJtsvpJDZcL53;u%0;}Ur^BZpG1ALbVxYt1-YfK-kfyM;LNk39 z!-!x%OULMp9-}XVzuEFruX!GeQt67puRazC~gD|d`o|#*~tIn%Ah8c03Kd~62Qqe`v_*<{R6ERM`uZcsu{6} z*RI~9Q+)A%k7@I1&XciyU2}(2y+@@7z2e`7lP6A0bTe=3TAqBHtlnzRcDvcru<>3E zOG)gU@z`;B5=V9{SLlV0IQxSOCc|F&KG*xjkA+;5chx3FPPx@652d#`Hy17I+pSpS8~Uj|X3hx~hI2zu=Ls1?e(>c{ zr|#|)sTaB>Vj?I%jmU4(r;K0KIt2hMB!qAh&RXJn$g!_3uAM<#)3ipJAGx%fdx~hz zhpcgx#ZNq+n^2Lib^KBNIJ3HkO>8#XKuL<^@8f6Wz}(Q+J4*jQ9An50#tt#8YQd%9USP&`hpEeZ5jSN4Nm3 zsD_V&$Fby^&eOT;VS6zU-SgAt$!fZUvPzickJc49mrg-;^(L0 zVq*!^aWT%`-)mByW7J6Kcy`}7^mF693wax*GXA83g@GaRsmar3hJ$JC6Kg4d294`U z9AC#?zbJ@>p-(ifc2;jXB`S|yqIv>q(5X*Y48~}uF1Z0yE*jS}IlhWfaJsn!+c;)f zW&jvPxa;Z!M=cm2MxjPPz3i*&mhPi}c^HTB9C@*}5G_Q(xqANJdr$En0RetZ)%+I? zBKQG+!YySyHM7GU#9iVk(GUy*tpNYGx3oJO{~|~TQ--Qw#s7tiYKric@N+Qewo`0M z7J;#Ows!UJ2TJsl5bjaBT?kZS3Xq4pN}NBabB__9PZ)qzzulF<4EQ^kHekw1MNnBv zXfXKh+ANPtS*-uZN5uM;8wvl5m}~`-2)1xn$6(e6`z72q>G2I#2=iGToXA= zU^V{kdLd-a%f|Z$xPv)x8y1=(DAD2E-P#}7a?sg|n?Z+dBiG`<=s6lq0HH1 zlHCT62Ade6ov#o1Xpzdi*Qj z56SyoIx(9!Ovjfg$R%P9yb$SPRhnACJhCji(BL;MQGJ#DEd1*D7pyM9Q9$>_RUo_0 zt9g7jdEu3juyYGZ-hgXjveT6XlFW@3Gbk!lvFj?)xXWhRufO^+f*^~;C{g;rO)Op0 z{wBZJ>Q<_9{&$``RdR{6`#-|vMFkFsKiWi30Oz8H|keO92KyQ$&8 zJwSc-2FVYrfW*#Y=_YOMeGWoihf+OVqkSo*OXv^DYWQ);5Jhw2l<$Yogn(feyKwEr zpi!d~niPE9OnLbcS)wT$N#X|%Vmy@|!P&0vq5Y7%o5wZA2cE^rc^rKJt9o+|xnu*J#3rjN6aBPu zqP9FL=xX4uWQlWh9$O~J!!rW+4%=q4L?}jG6d0mTg9CTBw%^Ew5`LtAd^$!?VXLN1-8B4EV20^s`EHkhQknygT#g~JG@jHb1(axnO*58rK17d%JCJ~QrQ%MJR{mA zX1)^)@C z=~V?^DzzKLK(4A`|F-!N2H45BV;kJQX82S@8TM3`Jl85*f)&$J@cJ`MboUC#OOyhtmQsGkaG1(fU>fbiD0+-6IixwRr@>6A>GaeSh9;o%wq3@p9T zP50>R?3z;CLpJ(Sd}{ZV^_>RGNK8uywg-ng?^zAEHx~*hqe4rJyDavYd{lgfS+AJ4 zGkZ4XXtEL|WOS7`kmsdWnw^&_U(S3(v3noPrchOCW8l_H9gNQ%n6Z~C2|VbcPkyeT z1QqC#2xD%%1baIyYFc{t9?ZfPU#8rAuc1gU^=RR}(~F__x*9`IOEYrxSxi*&q}<0t z5ff!8euO+S*-*htHPwgadUD_GNgt*9udqdMvm^Ff`tA-3dUo+lA;wRJ+{M2oh`xI^ zqvIowVSSeKF)XKqjKEp^QI?v}SPk!?e0isaCxbEa`mp_GQS=%UJK@;bM5WnfCI~-? z!XHM3#LQZy$?klGIcU5#dzaR7>%!OCcot9&FKZGxf2skka32N>`pe^#o~D@XSX~i+ znaU5F<9zOKGUeivLIuWCUIlzxzh7I@PCr-iC|sPKX;6`ZkOu=PK9{mh)2P*R-uszjA&RXF(Zm$dCwwo#F=LOh*C0BL$34T~G6V6tIy7p#og zNwa%a?PGXtJgerS1KBnuAp|9R-X||t@H!0qIem6Vut#&~JF~3*fHRZb^7saywT$Uv z7QdGaLF}+cvx(DZV}$;RdJZkM6Sv@+ld+%hrHR-iIISL#Z6BT-Mh=n_e?AWTu2U3O zDf{T?f%wtLbDX#Lv#~r~pOn-`EFs#pWis-X=_e!1L zTn;V7>pOk*qXC9Z8?wtjWHApg!Zv06hc73h0P<&R0_w{3ln0c)w>TXhgsv%25EOn; zY~4#dY#X)s@pvGih;PUc|Z-a?FAubcv%&CP7iMXzAg$9#Y_r)(MG*vty89I=3DP?^XefH8gU z_+qp1bRAZ~H`TjO*U4H@FQ^b{err|v+E569@tWe5R*5hl^;^d{;4D}uD4=hI#7Um- z<9hW?K)_@vSJ~_X7cWRFIXemO7$H~S8Mr*++Pw~XakrX$2CZujre{aOIl?>qL20wqQ~-EPK- zVlvzTBUAATM%hQhZqG|x-waYdyy0Zza<85WJre|_1%;`F)t+m+_A}9m6ia+|OX|Gf zQbEFSGt-mwGKn3paa=f@76zi8-^T#qNC?^DpRK(!t7vTU_$cVE{-}a!@nY2lLJdyr ziUu%KfEzDX$G)tlxY1>TEFO(#0vCtl6@FT7X1)6H3td+9i&CR?pSTq;J}(D$Ax``L zk_J}(FLoYR#>+O5Jr{M#Ax^WUU#qfa>1=vvg6ux{da)?9{7R{7_Okss;G4xSF*lt{ zs~+{&^6`-Q*-y?TAM8C%^k9EX!2Qhy83&?la z(Q#k2CLB2!?~JDz{&~L>PP4bFAyeBWr|xW#-xpXuS#Z5JA(m7f%lVH-DrN|e0}(2! z6j(f%`CqWFjJ=^j?w`D}mFXF2nzUHYlLu4(UB&&-2k@i>#>l=MLCB=3vC&)u`E6fit*kiheIv4HjQ6W33Utd{M40y}6+^~jc0#b+lYJaO33Eae zbA-!QQ_R^bSH#F36P&klxtDfvId)Od2(fR#c9pk-L~fL~cqS^#_}1QEh^e7Yajtx( z?{t)1H<7g&h@9kG(Xg{46z^x8SC4+dzXUE!eee)>tykSl)uM4|MT8eck6yL zh{SzYg+(!dPiXU3_o(m_Q_&y@LW25Q;r&lX>e z8MHy+4P~BJ{$={rbrP#y0-KCf1aUMsph891aSe`tb;UE>x^mgdE+jQ-T>Q6Ol?i{< zq@O(B?_UWCSpLsCUt1A+7PETY*~CGd@8*ynivS10+Whxb9lqw}K~iBIPer_%wPst8 zo7G4OOQFSBXF!A!R_6?i@LY`;TO9j3tb8~_{OE%Wyr~BRYYDAXV8T!}3&ec2*5=$D z3Am*+mY7&4*F@Fqqe`sb70J*&2Uty0OdyL%GKU><>8c3vJX$WT*A8eA3cpW{)b#>C#bfk@ zs?$VwVp8iLzeVCmguF6<4wl8tX?HHd?PqHiXO;1Htk*7re%G_}VO(X=d~Q^L=l~^K zjvlc|9Y-+Wn34;4vsz{=u+_Vxy@3tGxct*lsh*j}>?WM>a^U-MgCfj0v;3NCcawrc z^7=~Wyhi=1ciE-*16a#9M3!SQo98RvPbreKRNVnt$U_IK(R2Ygc$?1|4$!bRAjTJx z#y-Ta-RQs04hpkxae&g^E2JQE`kQ&RQ20A(v~vAJlx!wSHUhq*0H&-ek5z?TAiO_y z(iji~g(u8p;YlxF9@u|iwrPl3^POoZl(sR>Vw!~6xP-q7hC~Lp$4H=14^L7dlMe5p zBMb|}vk2d^7?&orxIlk{RN-5esN&A^$BI}70=!nxAKbtHc!)96aK=2($3}>o0j*tyDJ(u+nh2 zfib@zJe9ZN^2#?1bFpsMP!wF~{(TO3>28YF2J>&VPt=%n7QkeR!%_Z z*;5dB!pl|c*W|#xM3TqIV#gS!GIgv3ID~*YTbBa;7S(OW3N4(>7AGqUW*#<;bLg2Z zHw#?!+OEHXPbO81i#s(ss{41y+UxM(yBa5Q*tJMzcu(0G12cf_m9D@BjPPBB_|oFF z37<1AtUMT@r`{T|4|ff>D}9!y0IuBD+kLvm12*-6URU{da>>!3AzPT6g0;^$&*=G* z5)QRVBGmBr?_Pp~$Y{*_&er1}vRk-S+6>7{Uw~&`Hv#; zS7`vBy{}jpTQ0XOd6N1P^%?Lf?{+I|41%uAi;eInVQ0O?^KxQT6s+#alJDrFxEB44 zzVY|1Sybv~-RGZ(n6Uu#n1XvirZ&k!CNmi!&=9KvB^pL_P6MhnSR&!>m1OCAQUHw+ zsylu97t0O`%-}r-kP0xc!h#!JS1IOG3`-L=N@wo^Y#-XSXJ_v+FZOso$%m1fL@qmx zb~k-GX}mJMf?XAR(DN8?{0SDDrnF8G7+3B|so3P%i;!oDI)PpYhFWS{3m>eVWE5dN zkAB+^)9F?Fa^8tMW9&_PU9)0p)ihAfNW}*zJnB+l$Hd*r{bDWNZa(iDW>l+URqJes zgx6kowz0b?vMJPBxxK#B+VJE)25%e+(>ZN!n>)k*p8Ni_SlQjd{P%M%Q1d(qmE)SDbK3l`EQ|$>GESBKX9aaafP_h@-GQz!3zv} zu%YM$$twLDzofVi32cP+@zi3o4Zqj$WEOAbdVlQ3FJ2l0)0(lx6>h-?XWN+tS)g}CE4zgQzK&4WRX@`0EQn`e80XW(4KN{I5(Bm>*aa) ztyXmOlx~rW4!WaU9uRoz@e*9Ay)gMQVPWXKX>W7h#vpkN7W)(h5^iSXV}EeOZ~Uhg zz}Xlj2z?aU&~EDbpj#i{ zQXA@RZxHaQh619|^={oC&j71-^fu~KJ=f@ek-rBXh$JcwF(2nE*L%cWW-WYAyWJx& zRABhlS1F#fvvAEADqV5!{@S^}weZ#PAzw>uI-FJsST&R{u;#OPHO#~i^t4FikFwOb zqe54G|VX+i@tcIMDnSNqryt)*Rih@VC*mq@qit%QfK{4iwD$by%zuN zq>lcTITk(bZhcOah3?@XWn+uyQK0MLn0iIr)rXHtLiqp{I3ZH|e6bpV52mir;evWc zaSlY^Vn^sPM`>V_Ldp=djS~n$)@0jTYgMV@TP_+v(kFP3wS1(pA#N*}jwyN~csku$ zF?H2a0hBZ7yDMQolWTth12Be)^#Mi8wXHHc?0qIH<24=|&(l6}EgSm0yJq`5?scgD z<3UPErO#RsBlFWN=O2WdTKmoC#nh(N6l)|@_ZRA!?e7H+Fg#c%AzX4&xY30_wyx59 zYD@p#`RJ@<#R_iBRL=`6n^(jSE@G%gmN3iRImECYFEEZX>1|RphUa>&e#5;4?#F7A zb9>d#{-nxkp}YoOkNbXx3Q)}VcEOCR@51tcdAgA-)=$WtSyk%YXNi3J4Ybd-R6Uox3VmE;c@i%Tz}5rpPz(0$(YzW# z2+s!s7Ay2;R#DDfr^yHorZ~q%%$Ir3>|FA@avH$IF;wlevc^AYHT@osl6@H;Oznmm z#_%>Ji3@ybteMu zh%!K}$h)Yw*|FWw{{lX-O(g5ov}40Xl*d7N@O|ENsuj1hUp7spEf@?3D6r9N#PSqG zX*L=W5LPaCn!-b2lF8I3$__FC$sF$37Dd1+k)&US55nScPR%i;)|ND(<*ys`yUbQ6 z0b;2hz9WH^us7T}`GJb?<=wl_pS|Kfib}W(QgNj8JT%#nFXO>Yys3zkgqz$yO)YT3 zalL8A{!RAAh}e&kVg0(U)}gRQe$oOTJqTR04h7i%;daIa0Y^{DGxKgVS(&S62SqE0 zN?2t0W!KFWo!oLO<0<|Cne%>ekxH3Z$N$tWn7L;(?f{Ql_=7b0)JEXe!1~L>7O#*( zWi{oq;w^ml;S%>0z#uhzPl7ezdssdru;`iiA^QPm<)7X?(!i?7_uU4+={=`H`~n*i z)!p?@JS47R9_GUyqbFo2lNy)2V#>2FkU-=qN($@{f*nwxDRfr2mwbEZ_`%ZaDxWxr z-Hcqn>kp{iX9d>_r!JIj%%)rD%>48LO8m^;$ldq^CcZFx)%s z;$xg$&&J{`yM@#1_cpHM#Js~VYuni;)%D6|rj&}zpiRyvA&A6CTmr+NTEKVCfsIoY zigcj$`CWv!f+hFTbur=}wd~G*p-Z6efI*8@V=WYWg0QiX8mGEmMc67w4FK=q8^qlc zmWDNUpKtaXspRoWdhseTvA;qQxRde_bp@(f~0@AP;hWu^CpQ@X_t#cP=#7i#H8yg2`7V*LW)P7WP9g_*X+SfVzkl?Dk= zG+fg--RLT(2OnJIGW~}nqd;Tzc+Mknq~(2tb^lw9_)mi|HKD5Y3Ni@~I#kV)qJMc43aPi+AF%>Bj zX`GRnOwr`bN2KMe^>vZI_j0zs^E!}KBu+tfePTel4UfR!mT{=>yr?Z75ka64bLh!% z%fu=?hqbgmyZ`bU!i4vY4%CgTNOnJ%16}kv9?mg>1lTVP#`j%>^cU98KPY!|mo~V- zBDRA?!zb(R9|~%o5!Zj)^h<}qg&%MIrHG2>l%-Npa!e|l8{f2s4p8>@9rZ%sdLpl2 z3ZZwxp7)iE__m+|elnm>-Uo;vot=TMB-_0FVvoLI03)j5c**hB(f0?Y;ud_DxhqXO zSv^0(g})Ms`I@3Mc@Sha!4TiJ*5C_>42 zF0cD5B?EYo2TXZ#3H;faB(oe0h=GZG3bL7S35*u;FaP7!caCeP603Oz>->_ne=n0& z&+tu=RDpl*CcP2Uir4`HeJ3D<_z_tw5DflE946uC3=bn*W`2_ zzWKdz5*S^)f@IIA2yDlF@SLMv+}^Qt!XXANIn(P7Rmzqj;v^XjvpIha2%eA?afu@4 zm1ZWjj0{&9-C3F!@)Rr6gw16hS?mk$ew^I0V+uwVKk1A9iUepzRfU}PJ$<?MDhS>-yTk+&s?e zZ}8F&oUh4?p&hv4wYU)c5m2s#7?+J0QkZ_+e$QVi6;3<*kS;ZOSgGUaQ^=Y+feALzeHZLG{)>2C=2zMA^bfhb6Vxpvk1G13vd-JhIHuPYJDwbnm^ONX# zQ`{!+B^fWe%rMdKowV0=W68qHixf6)MN_kbbQP$y884vsJ#JZOn z9wE=#W8*J!I70bNa38UgYwWXJKR{E*fdb-&rQb09(q%bN)wOJxlr!s><|&{C1(|*+ zb;yM(nR(eM7^@Nk_@U^lR`YSMC%?skC?zXY*q@;SvQtD}8XMwg;|CFX?r%{=S=<1P zIp3i*DhN50Ru-^_M6!=kpt-e3ug{((2PKElCiK84qI3@Uc%qNMGAR%WpbM0 z6&_y85H&irs<{OTKwzHz!+@;Qgy+FOPR0plHpqCH-2W0L`n-VTq8KhicEx9*nWCLM zsysil!D)tq0vuu69C{?NIt{Eb^(QjV-%}wbELVJ(A6#s|h@Ru{8!c?K&v+lt#0*1| zqnt%xQO=97lAb3|&EaQ_mbu@$KJ$6``DhSfp$42>H&}!*`+j=1Rx4Vabu=uFF1tXW zW+#*(P$EC^)*Of^l^;07pS+mzXWgdJg9uwoIddAi3t)#bc2U<~_t__dMI!g5VLx6? zD5KR#3tYEXVkt;-Id{<*moA&d&2A@psyxVmLf2H;U6bG|f_jJXsBY~hqt9=Krj}rm zk%u(W7XClo6(RaAa0xt-R3v_YcF66Jvg8ds_u+#J#b0tL(7$|~p0g_kP%d8T}(D|a=vJ~vI`vRf-G z=+Tfygn?zh?-lNEeE>tNAV*8Q(Qpat{1w_v#)~5IxY&7f1^H$k(`w3-)mkDkGN9hf zZl0|1urO_w$9HtKKalMWGzff|4UL`D-oPqp@NGRau29FYHA+xn%0G??p{LPi8$^+4 z$bD$R&|!Xt&^=bK|6SS4KZ?_5VTGOfRW$R$i8dTmhlIrfCn1(Q%DAS)+iVYtGsCN$ zn3PLUw;?~o@x3XtoyJlV?HfS&%YpQN9k^0o4xKoHf{g!^1iO}3g)!>-M<7?TAeJ?f0XyPdnZ0T z0&YG|FDR52pmGPiyvVs)w8VTzHLy_x2xu@fl%P5)ZACiEn5<(zc@Wkzoq38)U5zVN zqq_oqt-f>2-lhOS%OG|L11Pbfzj64{5S&~Pyfy`mkaeEohpcGY8E=ZP{0I#5J) zp(ssu1%;zGd>UUB>Bv`{#+j++>T^`Lij8-DT6G_hA)PTmCKcG9#EV?AUyN?z0rSrfs0y*bg##9& zyJ5P_w_(OIXmXyV+kf?D7vLvDrv7caxt3I~Hy3rq9sBFcGA*uk$^k96OGk9q0f$Sg za;<927keYQLCKa%`5of7l49Fb{sQAI5mgaBWA{5viJI7BIvOj7GhL!b-&<#0y19)>)e z+bYepKK>C6i*i)Ls^=jNa3P|0^Bj!eEl%r2d63Z^FED_h+mc-|hKv{lC^3Thb=}`^ zJUkgbI|&$+-HwpNMjeK*r0`=-vNwy-?Zp_rid#sd!Q9^BJ0qxfDveNwuEjMPYy*lg zW+Y&m^qVF`SH+E$mhij;VXyq_BDCkzk+6@BQ&ly4{XHJ@SZlSPvQ9ZS34OpCoGmrQ z3$&jyX)54ksm&8RPbUT1KW-U4Q!m`CHg)I5%VbRzimaodJHM$fJ5N_RQ#UB%3(w?? zf0%=9x{mD)a|5yGL4o3B(E~`ih8kpITfcj8P*JK;E!K@%9TKFXMT-p{avv@?H2oO4 zwBeDb@Ws?q2#g!G<@NNO$#bow^YFp#4&Um9N1i$)>FHMgYbm0bCQ4`@!6X(wN&O4a zqoay%wQJ~zG-X5t?Yp;0bkQS@S&3?v(@MC`Sk%W%ROEmLUK@e-Rbqz8q(G!`mj$h3 zlem8uat;pFgF2TGc3IC8zPqp@6||7oibL1VN)7lpa(A11sGCKClmz`>8@*>O>Dg!n znH(hA&C&eXPJsd57n4ik9e#SXY{SuMkZWCQkvsM$fhGapMkM_Z$=Cn-W*wa>h0DE?;u+Y)`81T8F>sE)0WygUM5;mpE$Ymsj^%9}N7SIXb6c)GkQzSzbuQPH`B zQ=|8tdy3`*G9~;`h&W^W>W<|(mxBVkBc?0PRjoDu^fCm=oMLA#fEy|Fh%BIyNE96H zI?jG-!qebObIr6|gr8^h+VB-GyL@T(Fuyqt=pd8B%*h$;A|~){yX)9d2-cx-X7D%5V_yWx=d`4llU5~b|OggtPNr6caW>Ti^o@V%b z+|}Xq_Lk4J1mY^`NEjz~w@)b-;i>3M@!I20Hn85WR8O!Y$0)vRO&A)8!s`d)Myu}W z#x-bt;_`y`-MFIqwstuqkux=~O#03BtCEH;Z_gB!pDdn=lkzP%uc~*+tsIEt(jyd3 zo0|*pf6}VKfe^Mk`if6j7U`4jdwy%w=|J4Py>?TBS%zn+eprW!wB(@N3p@N@F>+=Q zD&s7;;+7j8jZxtRSJy6ky!;;38&hFJr4<&agdlW^_ZPD9y(^XOmnpM+s~$JIchc;7 zIZs*cX_a4f4TCk}FtU6|T53Z>&D9o$I!u$B`*y8A?t?*)SvfMSMNHG zrP_7m-keB1$nNI`86JG4BMem|27~QrANqXR>&Te0rz3FIy(%#O8&##hAPDQ0TM>uiRkW*5f?PsAO=1u?;MNf)Z(m&PxfsP{^kS>| zkI$xtStFE3Z$jiBY8hVZCCnT+SG*YZ0#3WU^3$GsqayBZJ_fCJO`G&VZUv3-sbm+8 zQPzHD&PLE^CkXucjL7=>xg{b6;hKs@Y#{{`Bk;;Zz8iy~f913s4#H#AHHE8ODSgVs z?1w*H2uAwy%w6{wf~}{85{Z(*|0CqF!DNEyP~7al#`3@U3aT71ER@jKR^+M^U#t&5 z0c%;&^ufb^Cb~qruyPW*b(>W>e`Z4SqEL@hy)yPX=XCu1cFlNxCo|grbor72lMh1A z_SQXnx&oCfe5b+cj?WwA%x;piAp@L_2N1%i`zNRMzYkHC4EqS-^PohsW%Hk(i2sL( z2Wf5sKv}BG0sX%FKBQitv^L49 z?y~@6jtBy%L(4OH$6y+els7@0A*9#TYa?fKl0F`)VphLZ9mCP8qVT8w+8mD<|HzG_ z8ke!pbz31A$Aj7_ru+|3T1q5=BpazV$Y%<)KD@Rnml@5e;JnxQD?)F@AuUR9UxX(c zXFj#@*XQH5rit!kzG{~}JIj~+4|G<8Y5o~~9(HAL~tH-V;gFW{Y zi48{-97z6oti*xF#4*iUDA9s8cqSzf?L!O|~^6KG;t zK`jOSV-5kH%6k`$6kbC|l6*F;qq4Ae_xZ--+Uvf?pz zCV%C((eJ-p7w*j&yi#r1;`+5r51aqt&tI{y7`m+c0vGJcdN@?-0_Q%w0N+l-OW+s` z`X&Q``3}o3NcOAS=d*ZQLEZcOq$U?)Euj^6i zUFcZO)AC^_n2ZG*H(;T%WN7}IZh<5p*d6|KB2>$SMomy_njx=~H)X}Ih`XyAcMbt?BjGbAlziczJMjGaJ?C?CLY0 z@)fFepldH((ph?5P$J?ry~pE5TW`D~vD!Sr7-2u4`?okm{4Ki%5e5YVJm}T#Vg(`0 zW#975_oVp8WGl?7z|1pgJN+U=vV&=Jx7&>k%45s(XDPw>TF%l{oTG}F+{?eLff+`} z2gI^~Txo4V&leRG4s;5YiHHs%0alwhQYI}}6b-&wnDUT=Kcy#`bU)+BG?B5_B_M@4 zvaTX2A+)|5_w#T$B%AVW2yr`KU++FWn=|bF>FbLki!aXH1ScSvLWxGdUasM0B~yMK^hyamn-$kRVM2@-Jk)2gGZ%_W4BQD% z-%%S7gU&SIxnb!}c3z z=WJ_=`N|T%r0w@8Q2|NRNW)j^M(^eQsBivNmkdT53R=70ghSN_jdn;NoEp6eRn}xI z4(0Q;lf}5{j?Pk{gQKX>ToJ-Rx_@ER8}Gb;nDX!Bj3qJ+=N|6plN6z_BUPk_F}1;I zO{t(mAZ_X!gUMIafWH(fpi1_8gz>TM=4?}MdGGji_BO@UyUbpx=8xgt3b4}(!3?qH zmR#H-?^9C*LcC;x{6i4QmVVN`&@+OfQfe2niiSl35f%p z4AZDy9j0n{DP>~tftUsmQ8&ERG-WgS#_j$Czf|d_A16UCiTEg7fuFsh5_YjW@xG58 z9LatkR)r0IEMhw6^t&KYa$?wZ=O);j6sR$DPAP0HAuar66O&y)bUF9o{bh}*pYQqJ zzaH~CnMh4H?cVXpIdu3@EQzdPgy_uhaa+a(bl1y47u^fM+dLPcczrzFq*w4NEd5*B zm6dZy_Q%kbf87Juht_tF--)XaY3?g>j@9-KXj@z#d-nNmy#K+n)r3rYUES2T)f#X; zJi9ohZGCmSEpo8;L-6wkSBW0Ibz9;E^aFON-o|wwQbxF1wPU1A(jN##pLDpvZ3eeS5Oy^Hx3`+)s@e3fr9FlUAA@GI1{qS zy~*+-+lShnrac#BX$p#mC+fQBw#F%U1|q6ls+jG{vf)g$DN6b}+^-6(h<>Ym0!4Gd zqWGDyww9BDm9>qI7ZV%K^eMqo0iPaG=BKHVpr1TfN^OjN?fu-)JB5yRk=6y+EUMz@ z>2tK5SqKk7Inz#4%wGe+Pf~}H2Fn1WdyU{)#qTKS2>jFVHO=*uQCgWVEKA|7MUf}( zasvwQj8k;lFl_}Z!u%iNn;))k|0Go-es0w#84!ZfkSlXdr?G{8|VWRMt&;xR~ATx1M{6)fzk)LOR7Y;+<5TpT}fimi) zxSK7|33@afSYDaDDD#EC_R~nzotLP?53w7Ln1w5LpR1l**|f3?@JYDjK(Arw^>Ot}Uw_v96)4TJNxfG5_eBydc^%+3%(GjS zFr_;88;|2Zu{wuGaoYOtV;!v@p`lZ}Rnl9f^PHL6!7z5fg24?BDX7;ztM~*@);UXd z+!zao8hbKcxs+;RXKqGo6=K|uO3Qs(LC+pu_~8?ie7qjV?K!;Bz(yg_?g;)n zE)a>~NE@rgiJT-S#$5J#xS@2?-46*Dz&>^7j-!0`p-uFf7|)@uj3T=wbsa;Wc5CF z`HZvJW<(FY~|g>@Y($D;?9A9hC|< zPd0cW{_0|dsjZa0HSOK-Z`=e?!ca<_#8%&laPPs^@ffq>C#Q)sFl&+Fe@~Nu&F5a$ zSg{i)$1$Y{ce1YWtk}%j@@$$)X{zB(^>c7^%L1{SZan`DoiYVGK%m-=E3UoGM)P0D z%Nb69-LsxOWauI)@o%80^=ii+0A^>m>)&Z$X{PDcjrW^1cOU#X|O_!&rVT}@HB`S43btfV`0D^dYmzPkTX4~jW5 zOtw1i_rPPSwQ`&?oN@=c6E?o_=?Dckmbi4=agVNupKIg+Pl-jw(n{lU>O9S10u%#o ze*wiTk^f2jV_*KElmFB7v$YcnN^viE5|yN0WSPC@@KWMpw-2U0ic1tb=1Z z3SOQ#R6Jh3x2B2?YK_6q)A5y=cAc&h!zsOUYK2K-D__rOVc70oryZSSJT7xXSYq=KG-9BXooztV=IcLyoEuqb?t)cb za@=geKtG}F=NER+KsNQ44U*)~K3E^S>y5{0k?BYVQWTrT{Kj-)dvuH4T4%^`%vA;T zIa$fQw^!0A&s<_Rqaz0Gqh(gGOXKBvr~q_#Wd3Q`^;~dmt;B1aRO#asHB4}g(%f?m z@K$&V7&FOw+vnLD`2Vz;<~kBXpAap#OXpkWiM%p6dbAo>m)mkJGB;jB?)BDxes6Kg zJ9Ou{{sp-0NOuQmbhZ!U+D$!uTH4Td7cfWa?D%BCz27rr3CS-xk7Jl=;AFl3$PH0_ zM>YFca(u$GVIoAToUD3>T}(2qH_l(Zq{751DRjc7=BFHJunxd}6JU4lfw-^WqO+O8 zJ4rSKsqDuD>{c{Pw}sI11cUk;Ll`0$^ys6fmBZ12?7AmDUVS9nC1%s1__wRqt`}9i z$+;qf<3zP>5KwCzKJ&wel51N}< zz%$Hz$cC8)&49K)w>Zw|iBcr5pW5*)eeK;IJ2jMPJj{)qD=NVS*?L**IG9RK1;+U7 z8)2Nq8`d-V?`6u!5iZ&4mEC%k%7STnbSZm2IWcH2ExtMXg^E8#x&P85Uf0~i69Pr4 zO?dPa>!=3j3&~p*hk{n$Rs0i1ai))zm}*b2z~O%&;W!hechpRt^*x8WF7~P#?`+3@ z-0(bStSx6Jl_>2wN81!afz=tWnqoM7fMn;8=x>m*oVp`L4;jo{G%H3{yTZ31UIenb zpK8LovFRfWHfw0sAT0|6<@qml_j?#Tt`Oj(tx#0`CMF{jkT%THZ<_b1Sl)~g(rMUt z9+Fh4y$2RwHlKKulh?XzY2$7C+-nA*sOV%7K*MwU{TX?!XYh`X4=9rra^?NG8b0>Wq>>p- zF%d)|XyjduxElnYbSW_7R`@S;+js>L{>vh3cnl|wn5?Jtas6Uz%Lh_Akjk~?@#=Iq zRsRVhFpI3rxcJuI&zj}xY~<}1uBmJuZ(zicx))vSAXm}rgPNEKia8u!XawDrfpP|& zGWMS8-{BT-ubFKI-bdopk4{)`_9HP~Zq@BBa^C=22K_&t%((4a+tC3?eE#YS(!sb- z#J}mFKxDH1rc-+q0~8~sx7T*%#(m+x#irMJGaChhEz6LdbjOl+QEOE%opH;l>da+7|AC|f9ZW8`w zn*3CHO-)?7uW8Q%(lLemK>t%obQ&n}QWy!xy4Df7VdZAiu3y#m(m%avz5|#gM+re! zX(>Uh7~&z3|Df}fv9iV<^6jmf1T?hLU}W=RY=1H%L6yn)0GZ#s*~wPEztom)&%@GO zECk=5hv#uGmK<0_?75xGz>NqBT*G;0?s)f@EjSX10Qt^+zrCnvUrN{cNpQLq_g^riDcWr?zv{3O@6TXI zG;!M7=}8ofvIv7CS)-lk}!fD|&+NLKG^Si3Vd z*cS46xRkvk+x^@?n>Xvy+2`JLy=QVRgaihIR(F0TK|3auCz-~&U2 zC4iH<-p8xo)t>p;`oDj?>Y0KZA9nEObe^{9G%8QkhRp^`C8qnQ^#zgmzO4u;q(aD` zMyq5}j-#Z1Y^G5`;KdYD#RTnmmER)h+ko~`cV|j^Ec2Bh=X16c&~@+fjp0GE zum4(Dqm%xo`x!1~<8)2Dzt2M~!0cK#Oteq?C3m9m8YYjRO4v1Pe~>8Gns#|VNB9a3 zQUpKTPL8L~y0|Zs%L^M!%`hmgPR8N^Vi_RQV7!PW5pruc=4TdYpJG33figmXF_J7Two|Bd%F&dqg7yL(QbFa6X*GGw z0r$hk1$M9c6vh2`CjD~s;z`20WVeLwrQy}!H(YFJniCB(<)ws+PntjS!i37d_SKF! zq#H^%;mDVr_;J`khqvK(?bi#^IGYJs-1`1cRjTu4;P*vCI;lc(j9CwdyuU6+kbiK< z4>tbX4K!=J=v%LWW6USo9FuOGRUIEZ=k;Q!ph*)MMJe*2C>{H@MIj9dl~I<-u#HU4 zV3T64;viE)72{r-B0zu%DSN-M{(HlZD6~p}Art|T+|l%Ihlws{sfpFQ z4(`i~f;7PC!{O@D=T`pIc9uia#I%SLiaj+p86b8NGV5fRrDr9V+XtDCEM7=y#$J{T z{he6*^CQK~A}e(a_TK&pdjD~v*|hD(Wv^||lNi?bK)IGq_Ya9s?bl=3jgChRLXv#f zgk9?Y-u99rwC$fRS%<8+Sls;~vK28F?7bdOPT5iOz5GAhfF648)%m(fu>APs+a;%0 z2II1@u|#G3RXS8yIR&JqZ94xXOz4`WO*CiF{YGUCJt!qShQi3{5g_!R&7-F;!m#|pE$Qt2W)=}K4>it>q2`B zeqgvhyiIwh`^{Va%L*Mg*@yOi+Dk7~v^M7q^q|m3B!zxh(=Kf;3xvpYw8u(h19z;V z4#3KU1NL#+9J(bDH2)YEV_!;$@FncH`WB=fAtay{&t5D6&UOg%{6MVr9b)WGGbYz7 zqHbKw-=vxUt3)QyC4_~l^@8NA`;G6*o<8kzOrLF!3@*jfKM_ptrw5mxSqtZ){_|6s zw>~ezMb!i-#%(~()_Zj&@)>>-)7le;ma08IR#m#2vmtKFYW}ScFLTTLDsq=-)E+<` zN8gnqM;bqAzcGEEKX&8$WBPlU56s?+*^VPM+Uzt24nA*S@}Kg|071TGC>F^M-ua@qtDa()3uL_%`<{mCdvbi5x}Qg0kqnB}7W zb(H2Fwx8#2{!;jV93}V1i&}ZGqqJ7+8?!p*n3MC-j5YWI1J3Bpv6YHRoak+@h&vID zIgyu)m&#(5r17;bt*baOQ(tgAp$6aX7m!(mCseKFW920L@C-RMRX$BzDN`hde;^NDc?9zP_$!(z<1BwtL6PppB*UPm}f9nE5&v)rWC?U>s>F z+>{>MTwhyu4s#NyJ5k+M{Zs9O_&Aaw{oag`>$f)s>_0`C1VRr7Nb_@7J15G%mlGxfs)#vXph3^D<<;5* zskzE3ZnME&UxA5i=ajW!>LDPgx;~z^%)UVZ>8E?9;BIP;;^y6rz#-k1p)a;}ZS=t9 zxi%X!(l?OtywuN~bMz%ZbF0-hvsos$ypH&k2fK>233BRWKy@7nWdeDRLU--(tR8H* zZhJp1ii*h}+ZN^DRaY)^poP58e8?G9Dxn$o>nzNzaf`qyIseqjZ8|xx`P$zKkc92H zb)6n)fNEm})|<1x*w|O4Ao>_FCr`LNt6W+)P0E!_f)^Luf9@OJFToY};YvqVc6Wv~ zQPtC%A+*@2kLL)7@q>bDwgU{EsO6Zci7zc$Lp2|cF5GGt3ZYj$wb9PW?dY!)65QgQd z?Qs99mci?&>KU`i*vOU>s1_*rTra;b%osjh_-#3;k4W zP;s9RUgz%a+!NmzRs=^J4hHbfDx>6>0y(3t@%y|waFv}R0Maq(oOf|L(A{I+jV%T4 zT=^J>-@fI~-f{By;=!z?$DWt8Z#PiWK~N_){v<+_M)+kk)2@2+@?|jmP(O$WQ^O>Z z3fOhFJxwE~N=pwSJoQlVcQaJe8~s>FCkmFw^W6@L=5~ITh*!%G{Cz|gkb`|LJZL}s z@<)sNKfAO%I$e{}3`7^2P920fuBY8#uuglur+@H++vC;4 zT{aAc3;JrvaVo6sOcuQ0I>Gk|2#x>b<9t*!Xd(jq^_PwR`pXK?(1-fvwT$d+c|jd? zZl!JFm&(QnEGreG7PwCKTLN~-2lcP)6 zM7tZb4ybzHKZEz7g67_4Da6HA9f|N&F_9k3;MHx|1Ain*wus>Td@J(zoaEnzItovr z5AX03!?jL?gYV@qk$qbpdUYk9^F2!F5AhoFpF3B6Y>4Ck=XaBRNBY(7S7(Zjn}F$v z;a+zGHGg?T-ITK{8Bct(mY;^kx`)PS!v}73mHGO$nRgP)Q3VqTwt~>YFZwXhlqj6R zb}MZsaqj%*`a7_QcY#wGg4$V~%iIk`2dP;cMw(ad6*UPnZ?bgmWzYmI|^|lGp7mlaimM!*b85_xlU|ZqM zX+nfE!lU~iMjy{r={P)l$hQA|MBvV|Ml`WW|L&nS?YtmmbdJ*zeFb}cH;M@oim)74_v_dXZM>Gu&mIYkczO*B+lgk+4D zqAB*|xf*OWJkIo9Klhzr!)VD z@>UW){YUe-|MXFZ%k7<+E|<6CaJ3Dk+K``3=hy3xCsnw6iX*F#dN6NhXa74nqqkk( zJnmKw)=sPqP8G1j!({aX@d@);?xW}IEi$>289LU*!@dF{>AH<~@kw=tdKFpl^sQEG zHT+}+DP3P^0A!3%>7R)^lKha0VsWdkn?Eh?{EN%vf4L(<&==hRB<7mF6O0x`;k#Fp zK@mu5ZgDFs_q@F7UMS3$$t_cn_WeUr3J1+Ef{}!nkbYqp3Xj6`)%>xQMx%s)@imP4 zi0KFkEz;MV*YT&rDy3#6{g+sd{!e0&@K-`ouu~ktFl?z(YdDVyb$Hl(MhT`R1RH;Z z>-V)hDHCB^9Nm62lWXn9CDmwF$c;vL%`DcJUxZw4{w-=Ora`Alkyb~`Fo%y2aK>hAR zw2Jf|!uS53vJ-jPOh;i7_M;Cd$JiI-+^bN()XB!ZLI(PM+>NgbLj@lNt*e(0Q;LVs znZcBcOdR{p^jA10)sL_wx%2vrC1iz2_TI|b4OjEuRzbJF599ie#A;BH!7?mz(Gf;8 zM6xY9Zl39kG75h*Qt89J!qE!j#N5Ft?*vnv9c;&s>UG~73UQme|C-mjFn)%yM=95e zSq?{HS?Q5yh3Y15hC3NL8SKR!A#>f}eq}S?TvZ>sDg*bA?7QYaaB|_KE|P4EDhV&t zzCI9+zs$jN{~pq^ZdgvtWd?dMmGS{5B`IuY`Yg!#x{rTh|7E1daerCdo|U_V2>0gt z$=ZAVO9x2kq4J(K+bw6hQ+2}_M*0Wd%f3@efK)5NH6JJfhfv`7R*DuUigF+OCb+Zi zTbKI%C*Cc`wMS-7Ol4F(><2knzATE_r}Radw*PnB(1o9O2DpwQUxhWi6oE&?o$Q`H z%9nI)r|Zi)SFq+;n1hJBr|-A%@C|3qs?IZ)Er_AU@nFN3u4g?jTZI3jq<9sG2*sJ=tOxht#MDT$C7`?Fb9smb+YFz zrl+$}KvPQoG`>g(X7{h&jUxJ&)W{E~5f%D?`b}Ki<~NsPal`TVbPS!g-Pgi9es#4! z53oB;vpwEFSxMxayXv}P`Y6q-8x0!bT9=aw(-)qcH4D1K`G;yFDV}GM6#%j#%!@^n zgy^T&KE~-BbzXdMKFo5)c6^ox-VXmA8 z5CcHZ&)j8gfRb8NjjbtOt^ zkIbGKIlT{k9;dTWLcXB>@cEHM!)h9Zt%T$7k?H;)8eS=IQ2Z!pupd}A>u0rJC>eLX z14@;SFLHvr2xvd*<88khgi+(_!>WL_KfeUYF^XLB^z-T|@BWRR=-uSIQVE*~;Y;k! z?j^*7QQb8dwinWFz)U|WG3M#ytLN$26mZY$Pl^k=O~b2h2ve%j)b@0hH-74L!Z-8Z zJ}=V5MlHX`ns>aPgLxzE`??vyamT}{-u~&*ldWK(0mpPgWFH5DR!#mRT!p7#7)c+V z3}QJE3Yhb+_Rqhb-k;mr6fY{o>cU_dRa*C#$f(cuyx$q5+9z7LsD_p%Spq)4<#JZt z3llarP4SmM&!_7YZgX<&ACK;_0D%75*lDK2spe!!OZVV+=BD(!JxUyh{iu!2y8Sod zT?qf~)_6W#%d~vAs*UkD-2L*M^<5Pr@4wNvQ428Yuywq$;o=M$hWxI!w>n!U)q8U{kjYwywgP@zKSzzM#yyjEq{LOej5i;-M{_NY+8ZS{sR{qD_8NM~#bHIUQT-;VWd_W5=n{CWkbg$GtL` z-|PN5G-4AZs3oslD|eVxU;YWb5>T}E+jz=N^73=VP+{&BTJZ9PHjPfbc9{z9@c$dR z8nt9oPpt<8Ip;YgugeMiEZbQfZ^l<^jDGwqFvjU)`)-!xLf(w1lkK^M*yhfzvE; zn|EI_D+MA^jqO~^6xjcA0f^r|&V5u8e~*x|5cTFMyy>2`smZlT%lGNy z#R0jBp5yDTYdcwW@T(p`c=!Vr1qw zE_3m+me^Zy`1@p5LF`P8_IWQaPEcs?nmvDAef>OFbw2766_heW8$el|+%k+*h!Ooo zSpLWX-yljy!w1II6f}q7eF(ab3k-2eX-s1rU&y7T-3~3@_X1{~km+kZ5-P<6&5v>d zY+MZ^Zs3sv=|@HNy$|HLd9Dw~do5xhJ0~!5hliqV?yd5k%+#_)7PVpv`l&26{=h0{1W;5{&{^C|%b-gJ*Fhcs zjfBrz*mnc_{4R*Kkm;RzoAE0u6x{{)F#V=!*4O0sTy=rNClisiaw-$)s0PY~&8)^K zpjbU)c5q?$Eit#&U3zlV3KD!G8;w2q@kHnwt)p$9q zKez-bwk8~<^`jsGq~(<$cBi=sB8@i!sZTuU%){|3addnqVSrpXUNrJo5vK8u+GrB+ zq~Kl{$IPF7t|o5$cf0&ozigArxkTd>vIPm9KCh;Va;KjbaBP&5A(His4@52*fe)}e zZIx-3vZrAeS1=^S-%_HlQqApr+`0uO`S`HqOpwK5wL!(OyUw0MiMI`TVphxDNiojs zIGg3zU#WsG^&g-hSG7ILyK{M&wy)293w~in&hHBacbQv-U2Lmo3;;=D^s z^_Jth<`wDkJ&RiKA zUWn5;QgVpTkCs~BLz?esX>nExiSglFedzwd;QYSF?c6|6@D_PylZ4{Q_KZ+S3{a26 z%+euT>w2nrGLJcfqvykQLo{#LT2zrXbl3@{=G`k^=G;`$tKALhSWop-m(HUJ?yLG~ z=7;a~7pX3M(lOQh1byR{12^%Xu@s=Vm)xzii*~Pi;w!37s(?7GU|dG6_Gik@6zEQn zQ&;ls;b)=90tOH-d4Nn_EK(>Y9-v41qM_l7r&V%8?<$8ItQ3yF1kZk-4+-jLnGD6a z-i^o!UHU?g3l1_k6x@mpc=Upmd3x$LKRB(>f9rhLRX`Bzx5zlO*?@Se)bIC^Tb1)2 z?Ig*Tkw8}PYg8<@&A`%b?bY-i@ryoiJc!KSJUf^+Uk0@z16t7Op@XOU$_3=;F*PYA zxJzK=l*?c%E5i5X^`1OUv)iQaS6rwOJL|*9;Rg>c&*gli-2DSkF@o-a`Hy}Si821< zRbxUL1%$Nfsn>qul4tru9YT${t>@o7FFW9^CVeVpf7@tBWl9$EwuWnWw`Ajo5d2f9 z5kl{7Bu34I9oi9qc96Nj-^vjpltr=S+|Kc>3?`^O)+3hBflQCLB{6l^um+FSMqih# zs0;f(Q@v|q8%=uw#P@aOuN*pa?u&}^i~&96U@j(5NA zF{2C6YI$<;O*COA!{CISak=RL?I@TG-d)zRtc@p{PM^1?MkNA_*~!-xS45$oTy1Nr zdzYAInIIX{){e^eo*h}Ayec{TnQ&1z#VtbgYU`_k;zMYkADYaOter852{f3UMZ-!E zVZt27yidS~7P487x&S(!f&xg6&sLY4EoH-!p1L>_Ez2ijg;cHX>G{8%cXqqHvw~h- z&^q77V{yWBK?`*GqHV#G;VY1X|0xQ~6GRDVuJb-6uz%RTj7USgK9B2v%@k48gObZX zL93)GOVJjg=a>)9CCuf%_2Lyhdf6H!)2soj30_@${4&S6@o-_^tm`Tu5;MYeqj@k{ zbVUSw1-|Vk*Q{tRSC!wFDCtpT;c}MGdDw^ry0hM0n9$p^&yY6%aw`I0#FLhGe<1|h z_@+db+5;91jT@EErdeZ@J9;t-`&5(+*<`_ukCDV0M^bu8u@D8n!3T#-U1q`*zZMWU zfEoA9O$kdjg(T3!skikvjUJ@3AEhee2b@Uqr27fr(bcnJ$e4VYa0JTYS+^M4Y|xSEWe5582kNg)DB_v*_|6fsve!LBgr+^Sy5Ag!F*fbc6k+RBB|f zq}NLm9=`D=$;GI_1s(iX3tBWJqGGZd7YU%DTon`gwkQ>oaGZ=Rrsb=72kJ4MEiEPt z|JC>cBA3Lappz&DK?;Nf#2A`R)*ErAmQ9BGuVxNt4y`)XkRwS~gbN+Jo>!QS?R=WE9dG!!+z4wf?O4TU@B^9DRNLE3U+!mwY{OOXo9 zCc^cKiGB!7*rW8C?k|q=r{@$K7>N@05|znEH9!5LW|eHXDGEt8`e($?Io2{yN*yX- za+4FYaszedYlzJ>&s5KeHs@-K(lF_B*WBt~mybeKpoA1b!iZ?gRPJmGon4YTL!;AY zF90IqCamr)qw$#Z=)Sh0>#s$gZkvri^YQENy5I!L?POh=&_pT*{#o*fSGCMq!!p@> z(S|jxJ=$(&X5ULY>nM+_ZITlaA|>qIAZ1{kP`j%&pOSUT$coQ z>(7Uee3Px$e(+*dea5r#CiV_IS@{PcK>(vHDpTM5+Yesr9&T8IUG&OVJWNBSvnf~m zwSyhEzSETSX@o+fw#hq2;6C*@Q@(=-3lC8A>m6Pg9SBLo%4d5A3PWR8?|puDo^TrB zf}}AIhL+xoEgaRu^E1kc(6c~H_eYBLcmYqcQtN*$^KS;^;}A9i;sM?O4ec>PK@UvWpu#PSQ_(E7gd@LP6M>x#LM#8mk< z`JsXz&PI;x8&w4+g4l_COeA){OF87OaXC9HD`Cp`a_IYrSJJr*ipfvPl$g#Yaw=K0 zYc+n?-k$~&@R-dGeI-vlYB@JFcseSM@iGvc&c>E4)ArH2@yvlfy~Cpp zg_6wqTDc)$sYuVI?PX_yRBHy1kx@J*3|`<5Bis*MQ#vin;k!@LF-pC5?oa(-n|z?& z$}N06HC;6blQ|}|RIaEv%8=`#iV)t4CduVZ^^x6*| zsQ7by$RJ4aG=$q2%zJr#$2X3~p%z~tqrRS+En1zd1fQs!swOV93NQ%q41I$iu~5Oi zt`6#xMCFFuB(i;{y64jI_~g#j8=-%;HnL)|Fl9+h#eZU-`Rv?-xd9=jvWfUpnND!f zEr1rCy>&r|Ywk^7_M=anxZm*VXPn3rt&<9W2t>OsA~3XQq4wABMDu2+f>{iKtPaxGIT+1|F7Y?$t^`60Xit)QZ8}=0=mF!~B|!0#er@U7F}1OA7Os^= zxYhs%&N!T#4V|c!9u2Fo4E86jReS1KFdYeZIaEh+s4;ax2zSV?GJ4@qm^!w|dKu_P zo3nWn?eM;@H%lPA@%Q@SW?TB1v0J8YQh{2z4H!pqWUB&Mo=~JPFro=wOBOxO8wEqQ z^6xKP2{VR`f>9qpP6L|;2yVMRx@idy@h%vub!z0Wx_@m=)nDt$r}<=V=P%?n>E6qc zzktuD{Bly2qZS3!P!@qD&P1|Mv4$w0{!G_Y7_#%oyXX=VV;#j6Vu&dJ?ck3lLe~=V za`O+JVxX=JZjAxt?4tA%Nr}+Q_93vS75>v^^4W!G2|OFD%3i<9g+d*q5!E8%q$L*~*;A3>rIiZvt+QrZPdHa709i8nH1yyS2% zFcGaEUlAjBNJdDLh%V_FU#koJ22#v+?90?YN+z9byX6YL~9LfPz@K8iIPRoZUm3;P*+rn*eyY z+5PbVfCf*%Di!~nw*?*d)J9D~T0IxXFv2qtOVMmfdlq>rCb$sk1pIe*uP3wKYNI$h zySuNnvpPN}?z5|yilLyhT>wD0gDEhKbKs)M+Mzv0B5gZ@BxsepAu#+tRb>qR;OY-` zC{evTP1vmR)j)-&1gJ?+Ligo`9eD*460>^r2Dz+?EaQceX0OEapK8{6;1b&9JfX`f zO%)Rykk9H`VWRtt_T~7}OztB|rS3NK!It=$ELsN{F?!@M6^4_N8ae#O&>`|4f9huv zAZNhi+D|@x<2F-aS>rH}qAjEil)qZ9^*7uoO<$9g(yiyLj>Y@^i5WY3sBq!Q2NnPA z2;|)+fy-{}I5sHiQn40CH@RSE7J~wM|6?E;D80W=U)#z7U0XfoudIy8L-qI|DD{nx zlTs?;GOK|~>GSK>1>_K0&Gqh)xe09HV!s+`u(49Jnd8|#; z)rVfX^U^87y;Hh?Go|I?iyByoSR-J4vJ~_3Pznm7v^tYsu#4+SE48P25}w1&o_!;s z8t^h-NM%f*`xYw%YaomA3qvgL^=5zl~K54fu4!X--GsItUI@UHrh+> z0-d&4?XQg0SZwr$@#~Zh^=5UGL)fv8!%})eSN=Uvxs|~jX%Cv_X_!s&#vujUOoCcj z2I{%^N)*N-0Y=2(R7mI9H4=-pLa)SUi=s+!ag~duMc%QZq^Lw0j1+f+#4iBl%$`@K z4vHPYlWL}UPj6HUl!#V4N9;YB0(ea@Ih3e?VhBtRc|9TS-~Gp z6MXS&?MWkwFPq7%IsVpDTfZMh2FVK>jv6j*B(DxjKm01U<$ij@cw(X)sYh?^6&mub zP!%3%b@C@hT$W>K`t*(h$lgyrRAkUlw@Gq9p)=OFeTy1837w%>&u2mC z@gckBp3F$-)?^g^Mi}x;zbiF>TrOQTUtUkl#8@EQTK%}^jh$cn^Lsy%}lwXYGO0G z%186}3bBmG8tO?82TQ4(d%C{6aE@$-ept^x{IwWcd~bF0PU+Vdn*E~vhIF9~x1&oD zC(iNQ{$GUN6a7|1*)V%Qd5L~UOnn!x0epCj7Fy$qi_r$Mx*g?&cTy;un$fLKt@5eh zlipbg6le1Hs<=V;-}XUk~V`ej((z?8$6m2YD++@k?F&BhMgA#CRTWV z8wFH~zr}9btF_GTmzEh;+V&nS)D2#o_U`SxguX$ee1YmoRq}Nlr@m?u#O1u!!?hhs#6ob>r;% zmXoF%fq{GdozO1)*lh@qV_YD(Pe-5mChj#&_@=2iNaFb3N4Zml%ls3i*G&QL`k!<~ z&kVoUr8>JD-t_P=S&U--fo@3R*zbyv8mH9#ReddJ>WuDFIso&nBZd-^v4E zar_EI1No%$SJN@v=soi?#lfgLA%52~@)4h_5Rz{FjkJe2(WZKaocyxGQ09`c;xG%2 z@fU{gjhZyaPtPVZ>XpFcH=|U@9S?Ax9@DHgYMAqpvF!TO`*I;s-IFFrBce={mANffHmFKlsWLKMs3U4iIG$Ez+|fAJTzA2ZS~0P!M2qa zVmL&Fb5Mo4`7!-KGG@Fr3b3PTe0k9Rd1W#v>htVWW6hXy#yqcDjG}-Lr0Dvk((>E6 z=u{Tbh`gpC;h2f*cb|IeVv;rYzE-h9mLrO$A6BI{07k#dIurf08281y>eBwJ;SVZH zW{Yq2;j30ON0u0@LOvYmpiwJ_fSDm|q5V%hA`8>a*UA6+nHtjA@U@P&*UsG&;q12_fE!~$MHog+RJxHhg|#<3}svUe$SDPD0@JSTL&r!49ed`+R>Hd!+x z@}qP8M9sd?PIn1kSg6%iJR@}x-A1lkU}0PqGhgK-_W}VXXy?d{?&R)o617n&ghFc1 zLucEItCh;5AK}#%#KTu243QXv>}t}w#!+s}U+FrZBWdaelbe?ACye&+m4Hsi(k(iO zTnyb?3Sga?vzT!ZsmDcSMAaY8mpEq^7IF@{tW+0n%}S8?;rSEP1Si$y8>}W&cUeu5 zWV_GB-paHDsti-5gmJVri@EGvRO;4JwQg|-SI6G!J@-@Kpr;Z>V%9bNO~$^}B4Yi~ zTic>x3uL8;>N3E(XpI9t(`y`vi@tthX95ANO*{7yNQAZ*BP;DGDFqulnAbI$m zpxeZ-j_!*1pgMl;!ATeSgbLWmft$Knbh1ateG?tHR~x@T*rE=)2tcE1y}RS&#czPa zUAzVfcf<07t@zYyM$%u^Rz<_IahwDuj#3=Z+7_@3s=2B9<|PE{MY!9EMIF(E9KCb6 z{4h}Q3RYv!r(!^atkp6a@2jA0P?E3lITmZoHNhV_tD!XNn>=n`w)+tHZrU&0uS{K> zYZ3kVpgaRYVzfetv5b?YAMRmg4X~aB3JoTBtpdbV!Jn) z-OQ8UYeSkKtQ8Tp5=*R7-D#vMyY|Xu_mm@quV(E99QA#nH3|drqi@`1g{nXL4T7e{ zQ{0QsO&M%c-Mt*bCM#lRz-BpzJW<5ahjz&BKJ7O}G(C7`P>c2ZDjMi}7ky*7;XbLm zJXL;z0G--s48l_PRD`UlBd~T~@<46aPDv*A!`hvnT3YIFkg(nGI@_IF)~B4(Hvy;A z8nky6O7o`?;JEpYk4!Spci?fM2oo;8L?F0boYVhFh2YMw0FIwc-?K?@oDUd9cCU5# zx=ws1#mOy{pd04vXlZGQbUruFgP*oz_)^eEvyEEsMX(JtAJ)Qgk7U^Pnk+% zYp_Vqv+Rc{c_B9^?u_wLo7n9V2-oo4UP+Jc6u3Drwk;jGALrc^@)TM)GTs(GehbgvyCLXYQz4AMBF-!DY z1g_l?UW7qR(7FVfCF&K9K=C+D7>dN1KK#x}Dh&dMl#n((X64%?S_YT0B*+#HIC;+y z$F|v2sGCHmZm)DgmtJFjfwHi9oSqp^9jgCW$ZWkp$qpf)mP>FFB*R#x`7+lzP|XdN5E-D=GC4yK)+~fkl3c9o}_R zn4ki4*s#BrN~Zx<$$JE5-QUX&$reP4aPimAofJH^N~G}G{B$@!RNmgRc`ELUkDCt- z7~nkduz26NQ9k*+hAkKwi-qwNb7ZI9B;1UMTG3i8_Ey7|WBBD7cFt#nUpDBJ%hX4w zvd7OMcPzoxJ7C3aKlqlY@BuaW`Xur<)oBS3Ysr`5U}s^<5ZQUr5k3lGw?`CZ{tcmU zfm{%QsaXW+Ruh#@i9@1VeTN&*pqN;!;xH&h3nN7EKPiVKRIS%lO{Ts_m7^vy^8g=q zh)m(AZ;cIJNaNJ-+&`1S(H5M!IE;=$cb8p&?n*v|IJ|pUymRgI(m$m9@R%~wzDVRZ-?B897dYkt}7MnZsX2mJLEVs zgd?-vz4q_@O7s5*FDpha)Cz};e{=q7C-IL5RgPLQ*Q>1y#(LkL$hvv1Wnh)sE(}eL!F|M;U1~cN_WPo%30g#84?n(FgPX`x+Ox_2BE1>{$mP&g zKk&ysz!0BSvnty^W6`$I2GJ0Om^Cph2pUS}4mNRNq|j`n#VmS4x-Zwu0;93m#sdXj zl(iHWWK=O{n6-yMb}GTR7q(c|$4`XMTyt|lAoCp@O0uc#GuI2{8y4;fMX>X7$m! zY;2gj)=j#N|8fEL+U4F`q(qaGE~lMWt9``RxAPlu)zPf;WWJXhML14w%>|vnd`lS| zHcm2f@nnxmqV|UP#(l7{=C+?gE@|YW;?K^6ty#$b&yB+RRnxr&jB5+Al^gZ1w*2hq z+%!gmvfgauy`*`U@WhKCkQ-t;iN(r#9C4hfZ#(C*g__AF1yX;N>}=A3b7yKhW;pv# zJpvAD9g&CltZ&_paUzn3z0nI|U_*5UcTT_KjhAo-sC<%*}bJ~ ztn0qnGybl1uD_k+=uO5v(@mlbkChU)q9w9_IKcf+=+h988+aSk`RICBSlQv^Bf1?7 z*x2CH9+YYeJ)lMX^W9-&V}aiB29Zj>#vi-~eU#DWJ0u6yL3EkQX!tPlxL`9MS2f>^ zv3LrfDljfMV&x`Y+kV%%Fxub)!u4tE+FgqAJ`Yx$L(EywGh0vdPog1>WU}r9xn;>u z$mHb0t3@A{9#M@f!fP*`yQkExQ-V!qj{x=@FTih08)#0*r_3UMy9&QNq$8)Fe1xRS zx{eChlfR_uE&HHS04^al08xSox);<>vf zqVF?>Nba*|S-e+LdV2$JUI!VAWEgzz{V5C?%ybvzpLBbWY4(HM$z6~N-XJ<>QJ8-C z9Rt*HQEL7?iiQ2#gM`yV>Yb9&iM;K1c<^{2JB^i>J+&f1;DH7w&L*xJ{bRxuvkHdP_?&IVLq*89Y+5cwREF3< zG|?YckmG>u!Q5H_?o#ps$ax%mbn##DV$3w9lSG-%%~#6Dh}KkbD9(&~8Zs^j$>4(M zv=gt$B0sdje;uH|_G$WDG0IO6^SSiDNq=0OYNNqIt+886?*Wy^vn|MR$`1T`7HiQ$Rct!J_IlVPMK946%z zh!Cnj!&Rsuk)>}I@R@V3YUM<9N)sX3@lc1?%ZpMsEwg-R^jDQ@rm&5&~n4%C1Hy%^C@tzNdkukB8qdbNR*1|3$%8 z9l#hKnMZW4v{CR~t`4jRUL$R}Sut4>SiHhuZ# zKQKiU6KTalU*vggTxJ5|gr{dSyy2hk@q+soq1029<6r1pFQ84I#XLh5%#rty{ia=qiAS~83H~=0%g=0-3RCvm1BY)#o47LGP@_Id%GY>-=qt@;iE(#l_6LDTjWY5~y zCW;9D!02H-8zc_NN8?TSW$;@nD4}vVN+LI7`QwA#xqa_;c4|cDFtRU4dYNFR{c|rt zDFTy-ioqs|$^oqF_R;PO3hJO4q`e&ZMt$@hN=P_$8D=H3c-=~)YpQ*qX0cV9`XE#d zz6|h|VSF8*$1=^IpTYd)s(aVd&vR27&0Uh<=Ifz>QcxGCz5%#D_Zx51BV4J-C=;-T z>L#~%SW$aD>z~YroXX`s#P7=nKWi%1XN8lC_Osou#Om@W78B!PoCp^R^ncx0YIs!q z3CxD$T|{6?Qz11(P;hx2tFkv$b@0f0^`y;dgqXq2s@_F;@Bgv&)?raaUHdR8gVc<4 zGaw}`A>ApZpmYt5bax*@DMd;`5GABbNp(OmTlAiSCan+sr<`RoFb<(B(6t3 zM`|hR5wGmCjriR78Y%d?{k0s~SAPf~urxej|1-9w8lGawY zCn}5&jlGveJ=d40qE>;86ZUh{@{$*0s|2)Q(vXx|WP^9EYOxZ&U#VLTITI0Ik}dgz zPDr9nhsoP*j!C)|Y8bwoEwm0tmFVVbkEqlyt4;RVdBh9GE+butvG}@Jr2DWOAN`h= ziS&kRDiN9j_0AtsdDw9}gv_&YaM*R=8wk=>{SW+fHl$L%)2rRHmp_lR_?B59_Z6(G z-JGmZ7y+G4*k>>qDbd%?|L|#P=E#Tdx(oW}JoS^81}@TlA0aGUmRd;vKiqJDr51&5 zF3wXfUGKDNJUL{)4fO_2}TUGtZ*DQQ= z4+dv|##@CLWx1&4ZMtH#ctNphcxt5#Gms2(^HvndPyK?#e z<%kieQqKyS?v1#bp>TxLGveE{3oL3O8zV^2;_-fKaypKJVe6ORENJIFN1#&w-%Rn? zh7qp*OulIUZk=UP{va%zmZ_~;9am&SxxXg(E5^*@7B@oBJ%f-nnW$Xf%rKAWtQXt84Qq2mfj3YUM zhnc=*HS-7uF`eo8Da+7BvB7~m@kV}q3m^Pdk3$l5ZbH$E3IVEU`GAzwEU6QiqSD!i zg)>ok#f!&y4C~iKJbf)Gi$pF79f!k1yGX&wbjY3lA?G;eNW&8Jk&1%_dBaff}1Xj-9=PX#JRn_xo9im9SNap_aqi^uF!|5;g{(J~;G zdioYOUl;o}C8DYr0xrMgdzg9~W;BV)(?Ar|kM3D@B7@uooY8C0c5oZUV=>_cM|0}| z0~gtZNC%?FHLXvP($~OaDWHa%vu)Xn(XsU!48o2#baF*x(*9^=FbzpbYDVujD&lqu}#D<;BiVaqeIhGf-pFhJwSoR zfCjx-VoVO)!dM;1G=Yb;tF!EW)1KHIspHrkTX5FxXv~KgmJmAh-k4i39a{Kft3Vqg zI{4dQyVC`5Wj(XrsTTJkvtacu=(Z0wkV&Drg>F(rp;?|6qMf}&v;>Ws(pW%?2#?ktF98K6(!gt6Eq@ch_0y_3%1Jf_U)W}o;qQKRAVQ1br;8qZyB*oSF z4tfkDP#v37|6*$?p~l~T`z7&yUHraI#L?g0i4>dfn?&9(DP@sXyUhU%8BOV^eq$Yz zj4}v!hQUgDu-hVGHdZ>#EWl*UEr7mHg6jTK?0&+^7#E%PM0Q=7ph~lEAl5mwzd>Vl zQ=a*`&j8-xB$2i9&N`~P+59J}9lFC5U%niq2aYi}8p(S<N*!z@A$l+oC>$M24E@D9KEwI)0T2vDJY@yEy|`jHGGs~+Ia2iaj z!-7FJv%mC4g8A4J8%tR6gDcl?h?SwmMvU&B`SWlP0Zw~fPh2-ano+K}LQsss&7(pF zdB=|`g`Qhpl?6VX=?2r6`B6eD^eBD?A9K)H7u4Po=7Ub(A;b_fYqw?a>PG%|eFQ#h zn@vJ?DXC!PO6B^HQA=;F(Yphihx4wy&FZ@L&5OB-ynHg^-4r6X(BOR)c*%7$6m z(mOZ(K9C`XE3UZ~>;7P(d%0?*GIJf>e)?BsOeqUMH1So-*PI@6EFl6Twd4fB_phSo zJ%z05t0#UTL##JWt;O+~{BXQ#`G>J_Mx~iImo*v5JF6rNkQrL!m0xs!w3vVdFEcst zrCW`1^LufSZh$j^nnPml|K^Al0QO+VIKhY)s9hBjXmy2NN7b^nd40RqscgjbhzAsd zML$OI0N!;`2@ZD87AjoA#O~}w;-T|X$J~Dkr_|vw*ViW&%hsum`_**an`w(9L<@jQq_+&5 zPaSpSG6Ui)T)x%*XY{0C>XZG?>CVFDh7aBn-8#bs3!0+$zh94(Ye21JBf^rxbBWCl z)lLp;o+>G80@V*;6AGh-u}NuRTm?DjD}W$M(a`;0hrWs8jlL)oG` zIU9F#LdHeu9SBffutKY>24CgtmjQH-z9#ISq4sT)`9OB_94ECv z=>NRdnNdYC=N~aK&(V>)O;5GicFnSI}XmG1|`W{(Yo+| z-xc)zD$hK-(geI)&+;1o3>*1N(&GIiY4Opkjj(XlPgsuCgdmFC{jHQ|4YlZBamr#! z4RkXrJz%pc+!FF%d2c4mLV9=z(KR7}x@Ie1{;e-w3xXskPfgr$lX8-i^xtekNiPV$ zQdXCTHz7t<-NcFO|9}Rh>F<9XT$(V6RX9E{S>&ez*As zy5aFdcF_X8=%c?tHlz+@KoRhO7gBBadz)_<$SFq$GhQh>MVE^viP{9Mt^8t>g2o6B z#Z#z;DeV1k$;Z909j=!4I~r8GO#P|k%n;V>@o~B6W$gum9*)v|!!`yf!g&i;yM1=- zi0J+TZ0kTCnevQwC7XEClbi1ZHOl{&yF!5D!f6>$d6g?r4VrCw81+;s$XX->etx+0 z)g1cQKe&w^9$S(*$2-69C1MK#^+2nP6U=CR_B%`w4tuq_6el8q z@ZY<8qqkHN{zj&!tP3$~ad*<#%se+o@0i4m z_I1%AiAV1T4)I5%&XZoru{R)^fH5>da}LD!H7*&Vhl<3Lf;r^m%l?FnnQ#k5R0 zFo|>T0=%L|u|jREt%G{YjOXoOYVz!oLI~EcFuAULmtPw*H=O}txfjC03HazYTVf;6 z)3O4d&P#^QB2@r?zT6IjIet<60A7ZgSL%Dw%{e`v;+`-Zg$n4vG3`% zy*nL&TCx9g=FXIfz#S!rzImB=LFTx|y^V&DXsAr_=QzKSc}B65+aPu->eG(5nE;B# ztCW?6fjg0aL!sFc9`eESp;gTda0iPCI-8`Ml#gSlEswA<*h#+p6=;HGSzUaVy(jlb zJ4qDaU-MIYGXLVOodj9vCftoielk%5>l-O6*b(8eO6SUo>MgVz$f%svD>X_vhfc>9 z@gCpO%eU`+RlS*1xLafwtMv7t;mw$I4F!5d;^nk;du3DDSyW4 znw`?)1)KP>h_M7#VQla}L2E^Mh73L|(Z(tyJR;&BcAk2k^69&CO2xEHt6$F3YFU%>qM7o`bQw**MQTKc z=l5cF*0x#9c)nV|T^jly@1d-5zeQfgf7=KZHhxDel}3UZf1+I zK03c;Ie+MZ?5z2Wc58C<%gb*9Q+*Z%-qTXG{WtQj!=_`3FCsj=pqmU$g5SI&ME6wS zzc6B?M6ADzxJ&??p1_VNMjKe#Xjh!r0lN`4g#deDs{AaAaUCc}aD*Le3gf+VVz4o1a=gT4A$PYjCgYhySec0nL?Nca`v|Bn2cp%vD zW^%Q)U{CO^TGp#VIixiH3VX7YUN7L`fJDw~Kk=Y0z5cWi@KA(((4vJ7jj+?iwW&NY8Z46#Z89$#xDrFzKlXOiGbD z4Dm7l)q(Fs9;X1g7}cExRvx4eS>8pY;s>||ZD%htB)161yUFYBJWSZ7Nf`ROV3|cF z;0AqHOugbsEw$yYnWjJ?mlLBxIRAvH_@PFG=WS>%UIY6$CIqr+{RbU(tA=ej@W!s| zX!BqE=$c}p>_dqqHD4=2B;N{S*w)Pp;`HjZ?(@W+6}1C#t(7}wL>-|h zo#cI;j57RCkq?Stk%GzS@>~iupN|H;Gtk48KiH30;!Xx;xRkC&o(9*M!rsjAi0i3! z`JFM$stlN7cY8jTgf0EsLZB;=X)_RC77aBlSTMaKnBn|ab-om+Xph<|9Lx#X2X-`Q z4#*(~hXiop^&VW|8}#Ni1;tE-YS563$t2k*;Zrfw0XEDIF@jAS65FoIozV~w_y!uR z;GL)+jlkO2AHCA(?Ylt)P_EN8xcbKw+|rzXqt#`@+-%tDQR8huhDY+NITF5dc;7#D zAK)P;2Q3wvo5MqyRgI9h^#|{KIK0I?v;J#(d;I&UTuR;y=Mhcq9d4y>xre0}W9g~8y0vS#$*MNG|2Aht`E;-9Tq!GEd6UwJo~_BFjvvuj-P zh|$a}4;C)D|E_vyT{iE-)_=%ytWyz_MH4%%z{8C$Kf~9ON z1~aqnx&d-{WU4m4S^*-hT&yAbx9i$lDdTJ65KAtfZ-0F@&yG^$H$TBv#%O+0fZ5CM z3G`lli?;!NmWavJPA+a1X`2(XPNkU*Pynuz_}i+jIQem}CsP8K6pZ6WqLEqpr4ss7dQ;O8Bm4yzQe0r69w5Jq)5d11m-E}LMC@R8=IE1~QnS-gHk4+4sdggs-E?Szoaw3|l<946&PECB zheW@sUxUpvwO9d415bo1?pZF3K=DEc+pg-v+9WR4-Dz00Q=X*=#kC8P1lRBKZ5+~= zt7Y;-F@RdW-!<;KqS0nfzYsu~Y$gaszdOZY1GAE?JXr&KmV{N8dGVBI91u&nSy_Go zr@Q71nYF07+er5J&Og5b+L(#n?rwP zg#?^7B&~Oi$Gz$9#?5d$Bzn9rK+*qg*K6_2XEphhh=y4%EBQplXIKi{7w)_2G1 z{!=D5wre6U9*w?R;Ory$uNI(aQ;TT)Q}NJmpJ71{Z}-cGg@2W~)+LwmcMJ zpX*xOw_9Qy`my^i8U6vXNy5vohL3OGVrm(M$yxVHlyRODV7X^yCKJ>ye}qS90K6qU zDQOM@w*cl*d#OB0?I1!YnbwOBYoP5w!hZtUABy4;{+zj&rul0$8pPeVqEeq>4q8{& zE4l6ej3O5VBg$x>V|`|%K>UD>cKHlWr8z2X-gfhU6Hxl{HV}Qn)>xxB6)7nP6+wSH z7&Sj53Wl)eMaLCIGVcC4c1vXj{~)EFTW_u15C;9L?zb#A9|^pWzudTK*&>2lL7;k! z1wQ-H?k#=gN+1a-QY$o%f5MmNUdRy%ok-r=?flzG$J3eo;q(RV3@A5qtkLX^gu-$* zpHZ%)HPVBwE@lK`7#6i+&LC&&EO&UT)ziH-arLB)3YHUPZV+tr++aBYyG`{#Mf~Nk z`K#)4ZDDYo$lDW3w|3xmp7T`hh7+*J^mo>SucMPfCVvNd2d!8sF54FkMOW1!UzR=; zt%Tc0zXEN}RDj=D)uV3_nAtW1W$_myZXOG2Sgu3mj}BMrisA1XRI?at-jZ>hcVZB- zUH18K6Dm{WLgcVPzTd*;=@jD5xFf@ljbVt5z0w85!5iuJ+^^GZ9zvIEjLbcz3W1a!;!g@f%(Sb3HFg8tASLh>>hodLk%eEhqEgZpUdi`SWEP zZDA3G0G7&}al6NUG==rBj2Ry)Tj*Ys`&Pcut&!d3C$K-QL^!%#23*_|YQI zx4P&C#teba0rLeRh##CR=gx%s&ztI~q2ulq*dPPc-0T*!wLsvr{XFg^r$$fOd>av4Uo4A3JW9eN1*m9^SKYvvyrCbW$~M_4=bMkwIdry1LcHn=dW92^IJeVa|UAvEgQSV&Wh%GxZ#C; z_@#{Zq~rV7F1q91vrKrm?9 zMrSjB-bN8??qvzjNFyxb*8EfthI{@+%B&DsoRTh-ESzmPLGe07ZR=&mMx8`_!WG60 ziOk#;1L!95B&xZ8v38xWZLA_)pw3gF&S9*(>9F_0uYmhA?y{`A39w9o zJe5yf2PoA#ir(?l&H?+V}y z2XfGRrt41s4u|al-P$-cW)YrisLE*i|DV=?^uIzkhfLjzh}Ez#7<24fj71*c zY25=o_b`V9_*368!uG`r!p%Rw9QeZm$z)kV+!FMEBF^!q-G6q@M8j+xllSG`8!t|Z zUtfO?)vwXC$vm=viqapB#^~O8Pyo~P%U}($v`i2^8zO`h5uxbmngb zI7&bv;9P(vRsEP7aF^p3A)UlScg^^hx=NQYf&eBxu-iv_SGBjP_^wwRqk_e)GNC$nU=kUMjLinl|zZ78vf1x>Z1g z7$-Ie}rgD2@%D<&8vbt9_BB6<EBMg3 zy_&a%KewBp`bjYsbz3Kg3-o|~lbHYh3Wip~#>OR5JrP>@P04&9bAz>lqLB?5Kh^k9 zUG*<|YzDw(VS)wnKs9z+dzx#|r%Uzt8ZV<1fW^dA2MNv3RdBP+5^^?B8Ap4+Gf(^{ zS&m%jYPZrs-(zy)uV$Y!XQyD7FAj=+Q6Ik6=k7aw_TUqKL~#be7M(SQ$9Ke70xafy z2mI{Io9>4$khx$7pED1vILW0RjN^u#S99vrOo@iq@y+-wHNC%R3?jg12|4b5Os=}8 zVuZSh_r;{>KSQ~Q+CM{AoRw1ZKNZ4K+QOV$1@NZ$5X?+P@Q1HDD_Tt8ZsN!~z}$mh z@|QcEe#|w=9wrzpY-o41(iiTAUgm&Y3f^$J=yh}8dG?ym!`4SrH!q6DIWI9Pn>wG0 zbX@A=zwL4*Mea$yJD!1tQb0m$T_7 zzx%S;5$SYuS}wwFR1y8yvZ2FKQ+FTaliV0WA$Ov*j<@l z_3lI{y^;Lj53~{PQ|?)9xl;Sz6A6D>%*HBaX{0ODj@-@Brv<}VE=BOpM+~-LTXaFC zLU%5UMxI*9rhe7vp;2CxIW%<;L=q46R&%;zHJl!z^n_q1v)9n0zo-2&zdoN3BRE^O z6RNt4c!|4T_9X{lKtmYWqlLCi;z9hdx0nGHfVCJh{HxvadWr{Z^Tu9keYrs%t8he8 zo;mGH!2{B&V&`CNmrPda99a?E6O)-cxGR^NT}&N6c$JLm)?YDwUPpLI3KrC#s%LPv zhbO7Hxv>C?tY5>N$iYyfNkKftYJ?vrZLqlkO!w7zIkv^~c3k9KR_kW+GZxG%(z%V& z!O;L-idg+-#_xq+GWcNhQG4+N7gMwhN&yx=zbLP^Adl;44vO**jfW$n{c_lJPnWSc zZY+JY`Gq2uucJUe0JH0}Np%qwV}&XSs#qhYNM7SZdQILeMd{RU8ggIXVSe&f!s>tH zYQgqi3vXbX)W1>mBTDS{nnDwRv1X1xUu<*E#mHhzZGZp0X^Qub}&@f;UmAAI6^X~ zQ=(i25cpw{(R?Y^x&PxGgBP3A{V~y)7PV=&8abb?zi2G?y!U@m+7L^jKucG%#$!nC z3%P)!o|c;CB!%XacMMiN-uDtpRZiFKLHA6@To0Uahut3Qgqu z;h>l$bNUcIM-(#bz{%B?xjC4?3cgUBY=4Jggc+yW3dufaF>^0WHC{^I|K5kz&Sb+Z z$IGL)=j_a5^rgYej?bUT!D6CL9a3JHy|#Jp#_XWyz0C8i(`Akj$sG3ruTHs~s*pOV zOVQcOn>jpGDS!P`op?!aiZ1)t_4inOuv1Pf6TB{BcHqnuiP=PVNqga036_)QNqF~P zNA+i`U6Wlr^VS-qFJ|k1nU+Ej1S|Z#kkeBHd(Jgl|9MAVmU=NN?l?>Wf%02h6p27w zC(s`xyv#Mnw#uwVE)cww`RWC@|3bKXni0F_ts{OAehjtODqMd3Tl)RK%)^6 zd<8P66V`|=y&Y^^E}{SRHayMiaDpC-r~;l*^(x%t&7KQv|Jj!M0q3_h(FjRy)9nai z^cI$`i_{U7{8vhlRNwiq*q()znC)0wECNk!9bD72*1gH5aDrp3S?)j+K#V5PCCu3}R{72&dq}_E9AD!x+Elq5>9r5rxWDRfLIH*lWVp$Tmo!H% z@|;?ltFWc)8)^st%-M~*CJjoWf8@pr4h9Va`+kkxMv3M+j@WzqJ27W&*&nWpc?V(6 z;sudSbPY$}Oi^$YH8NLNAXbR_SR#LzXoKh76zuuwGpxI?=X*K#T?YE(Sj3w6G^a4t zWQ*6{)^lI+YYvvaLUcHIds-%UAq{w7Cj2Rj+VV#0B7C!soq( z%ywj@XO(g+uSvjuFx?*VK#j>2_0-xnFMY7{yw=X%lkG~+zFxj8u^tM)Z2aqpn=dn; zC0;vODib@2D+*rJT-9b()O;K+z}=492bLqKBx`&Kz_S7p6v>B9A+`|IULi_1Dt`Zx zFgOJN{**T>nEsd+-6_v0(iir|VHtGa>|8BblG(>X*ne5xkqlG;+VN1D3&9IYEmAg} z7VVDazfmMGy>VoSO{Lq!fj!XkMPwmz|8?pgp0BA@HxP)Wz61HlB&o-+ksC+iWFE_p zbd;N`@KMRLgK8p|eA1II*o#4ebNvbU#d-JRTmm(J%M!~UmZ>p1)roelosBIU#!=u& zNlU2R67w-%dV=ti1PIVnInlEG^Io<`xVnX2{(FliJ>m2Mbcxh$PzLr~Vd5-C&_X$& zn#qJ$@+jg>!haJRT@bQFlCS1+%BeeJ{Pk2HTr16Z*bR5Xttr7dzZCw;JQY$cIq)Tf z>^JOGv^Rw8%R*L8dxFB_I0|Zpam8tyIv){RVU<)?O zg6zfnucnR*%%u5{hVU?>L;RvRqQ=8h^2y~u7-~3@5Wv8Y#oC+-^;3_3qDD`C1~NY+ znBqE{vl(SaDFd>PpZ**z|4hK#^o`!)E)|4SC+#oqY0qDLvZ?+v82jQ-@4;BB%h;Ru zVaYXtj=D`Ox%8E1cl)v*9>OwXaWNe3k6}CE8|;<%U6mUy2oCLTX5qEjru{~bOLn9y z${3*#aD(!gmMz6&ac#MdBmMBw2nYUDNnNwRcQ%-S%v8)(?M^OP&(`#X;YI#@PRJf_ zi6W2en{UV6e1ASZm|lF&2PyIB(X2E}rZ?AL$t*?*(q7Z8dz2mFLEd4Ynu@zZ$b@nX zNq?Gqhh;PBcPn9v6mOTn>f0Rl*FMdkqAx_mc( z9MHR>ZArn%!Em>Ofm;iy& z$cr_)dYwF}5RKVN{2r-JJ&Vm29odoy!9aYUq1Z;T26uuI!uLwW>!Z)|a0+tBM4nFK zq5QfKW@yAkdZ$W5K6iMd`ItlLRW+Nv`&s?=gN=W5|NK7K@2bcNP{uf^p z{2T#+xYdcH!&;7vLK1+YUJzU09f~PEKRL6HtqTIODf=+ZjWoEJ4yt8z+S}xSz~WO1 zF8t?kDD#P!W7Pf+7Bo|#SV*YRE^mxlK+R+pHwBtp8m!lXh4U4A-A400*@r@!v7W!D zjQ4kF(Oy9MS~#uLg;mO>#o8?UFPi%nDMjgkZxgBMeU|=UJc|CVhmiviYko@s)0DEj{*Fs;Tkreh$-&u61_TK~D7& zWv+&e8y>zRSKHxu+OeHRk_qZ!-fJUWhjjCUg=gX=m3sl50(B;7h1MK3)fiDXB!n`5LQKX%U*%~@}qPfP!X ze$K2!(?#922BR)2gtA(H@<1o`zx!6=fB#z2LVDQ0alA6kYju{8JrMtJllrp#g9*2# z_1g4?g_Y-*kP)HIQAsy*50&NMWBwWD zt}5(59Pb+99Kt0|UkN;$$-28{L`?9#D&eh@1H|>SYH9BU{w={nhU$1sM{mEb_UBV9 z`<+#sYLgXXARob*8BKw{&8C(d^7=l!;lj`x(0yR~iVwWJmS&7Jn4CBA&$z0(xD+86 zUf=sr@+V2CmYstf|9%-hlS(rMhFzS#Ynk##A6h2j@PHTwG`-qJVklxVojYa;ZovL_m^1NQLnMR#%Loi zg;u7Y0^k}k`lsMSO93>ik2OyQc$Xaf!oIB=R!{uPiZQXaYE47!6I(uMlmo{(i^enc zR;PVIuzz)iCYhu6s`eQq`ad3F(sI`YQP<5Cn%2S`8rJV|#e;v-JMvUSJZNz^8EpWE-({0(%Qm9Pmrurq;N8 z#;)THpK(C2p~Td(3diy;BBPis0AROy6hwo*s42ep*z*e6=m^7{{A8{PfTOl(*^@Oj z5FC@+2a`jupTI0*V{Zd{+xgaHO*uLRD?iz|k7J#`=GacfucC}>mJ|-v)2%TK3n5f4 zsb@CA@!z-|2bdDy;R72rFJda>7QHK=^LQ4sm=7EA2sMgq18@&-`CYTm+-F2p(TC{# z<50zbPMGH9=@DtRd!{|bfH=B;mA9KCd>5Jqr1XoAlLwx!SwYSf6NKujBk`D8rj0wr+V|O-p`TPd@7%nk~~u0Dgn){UD6skUOdT7hiw? zxEEJn7#b;rk(qdISDNH~7aNbX#X%=gjz)$00s4Iz#n_5+>n9H9WN0nX@SjAL6c<|@ z{ryge(fWevm`Zw03d9njjU8+c-V4i!=!1v-QmD6vPf0cl)o#NL<9A^v;B{1v0&iyY zsOt{`9I)i%g+ugS>UPl}Zf{>VECYYyP!{O9DOK5yD;S&qnZuS;>#1F7iNfNG!|XcG zteEbT*G7)Q8hotp_Rb?a9f0D8&2!`V~~wx)L*(5T$DC1lW74ZsRNj>boi z@V%zifHT2TPs{sxTe?}C_0%$&p99GM zO?UMc=Z{e*sb<^PJmN3fB8ZJ+TSK;Z6`3-~gr7iVaKq8znM9KAI?AXDOd&tYAY=ll+MZ6@HYbdpPpnNj&q2RI~-huP|(i!LcsXV@{ud9p+W+6= z&?tQ#-xg{K;Lzm-I5qb>4u<5q|K}q?U1fx`<_dtFik6I(%ePnt)1#e~V{1N*ggs>q zA(*Rq`*2j95FK8g*79tgYcmZW9bhdLUsPL_{F>}c{)wOy;vnnIkUMdk{CTUX;W1lf z@OU&J{|i zmV%uM)jZ=y{$!jm5B%r{ACPHXX}Z(F_`p1NUk_eAO$aj_Ycdw?5hiH(dA)j_ymz)4BlRoE)YsP2|ADxCK=raRa4A9-JGY#6Et9vdA5t}hMei54 z{76oB3S;8TI~kRplvIq}=Pk8uP|S*{dJv}@GXzzsU`#?SmV4Ia?vIFW)%{uKjQhAO z2HT^o>(sZ34T#7U3vr#chYvh>6egd4ejBLX^i7>lvk7wJR%r^%0i7AD2)H`z+Wwl4 zwku(tX48~j^VWyJog^lvkS6E?A#Rosveh3j&*fhk|0WB<4jBk(==!`n51;zkq{|lip-hpb^cb3cPAv!1 zIu^r8wAkp!wx&r4+LrnRQk-bITiJ|Lcz4fHF$xH*KWbTRkUYecMqYwF+50 zb>2|FFcjcHC@=ndd$N2Mh6=^U7TLSs%aVI1E@-{CBX|!FSa_(EWwv?uCxiC~`@|9- zYKyow)P0sg>e-fcsI8!)5i?X@a&4_7@S^z@V{PD{S9-Bp&u~s`^K|<;Z`$n0o;}Pw z#`tL1C6?^T7XJRy!dmH^4CgP0>&({K4d`BV@$&vsqzAsFEEhGhf{`&2t_ph%2WOxQ zt6``z{UVA9#u#`nM>ODtUS7v4M2cJ1>vcOi*LiEPWcsSkY-^l6&b{6 zr8v^OFIAcp*S|ThMJW}AY3fD6Z#b_SdfU5+-|U$h$Sh-Mwe@VE1Zo77PQHsb9bhfC zaTPr_6`K2=;w#)nJJ7Pb{sRg1ZbuAaS#R>_@CJRlxuw$TYT#CMF(!}P$>i%vZQZL6 zyPI8kSKcM+!r-$A&@||;BA}Ru>WU>1um&EFUEdy_(5FZBzNq?jt+X?N|D1ANN198h z$nHMN?$qDjzhQ%WdLgQ_3N#;1kUVrnX+A^u6^usB7AqZD7&fHplk$CvQcRy9I3V6! zx8yxj@TY@n$#OA&!WI_ZWakYXCV!zE6R5Z0zrA%3qkQlAbHd$*t$rrppg(;2^a)E9 zMaQxo4n{41;3oe=8x!;P1`rM0;(cqdBGG>Jknd9W{UssLbWDM6PfR%EI#L7sy~xhj z-Slife=#Kz)Xq24vg`iCOQWw(^LTCmc9?mG_k-*FOx?R#$$Hks7TmT1G9+mF{(jO8 zL(Gc|wXx^;i=N-&rK+|Dg*yEfKR%gv*qdWQfJ?D87n`tP7&9eG$RJX1XgDi#M%(C- zQ&Fu7ON6}@k`u&ctz-oV#-oUQ%D&|Unbt<{vo6QWO3-KXnSI9r$^&^5p9v@isHUxL zZcq=B=_?LD{_`ND{aI0+V~RCLjkYF)y548Uc|U zrn??}cyt%iA=l5J3f@ZFV_!0s8-47LQuLRB!WBe7af5fLR6mp{p_Avd#{Q5vY3@tWq8|NQmYa$gv<20@cXy7w2*4 z`^w`vf4iw-&USU+9SV3Pph*IXDn!-vyNibAoneXiA897-3kQ*c{+LI6+8L11WQkDD zj1kCznALm{ILGq!giGWbz7I01&G9@rVy>URBDJEhCAVGik!COnOT zx0iD01EY0g8e27gFqc*J*;Vg;)jf}$iRg^sTW0f7C(5RCo8OI(1zX!xRVA$VwNA2H z@q0TSdH6*Lz_i;Tbf0tHj={zLfgRj_R_OGuH1O*6=%KbKz0TXCx-=HvT)t<9UR=Oi zP4AuVZNC);P=u+Yk0m7go2JZ=owf?>j0Fg%xGQ$)ghTT-rbb2HMN08Pm+SH3uaq)l z4R{A1ugt5a^aEV_OI^mVNKb<)P z7A+B~AKK-sA9H*-^Z6!-j?#$TCup#Oz0uf{YKxr9Yk9@^04__&8~^@Y5gsbfCG!qR z#hpCHVHT}=o3dMPSq2ApeD}+s8D|3h$wEHHhQHL%dL!k>E%cLgNMPv3u?C#V-D@8E z(AwIXs%8J%hy*FJ!fQ~!dPh;5r>DwjP}X`tAvLvFbdpgLa=5zkMtpJbFsgzv^D%;g zM=!5|Z))$Uf3nDs9vEGH8>3eF2xi(xr0KrpT7;25p*5unxX6thAeAe$I&ZRrfzJ8Q zL@eJ*nmS=$_ns$AlRaDCFKUzq&5l0v#9mC4&uKLcD#j;$WogruezGv@E>XXZxTkbN z&t0@Ia057X$vmyctbG(?mNTc% zV$2+_<7c)`r`teD_7I6Li!|KowMP%(F@qjU2*=%i-!iYkx6H$*`?rDc?~YNA+$lqUF z%U|y}Q0_1wE1jm0#X+oB)cqA{W{6qde{a~H$&P~>IK$<@@F|MN=ds~V!jp-x1r369 zV`~bCLt%z?dtw{&EQ9zr4W!m5$TmU(2tmZdd9ARwRP79C(jLPRg+7hW$0?^mqlfK= z<@FQ3sc(M_3aP<@$-tb_6nU8MjaWw{uL!T)pAcWwJ930wj6@=90Oyul5wUzghq0&` z-`rm0?;yC2ZU?IK#qeG;8CuSgY&BW&?KNKkJdpgOfU0*=_O!>fTIHi5Rs!6TTbIjT zM|aVZf#(Q&DJoJl0xB9KMD%!tWr3DA2PI~DR)&Xq*r-XAzK`Fn`=gIVtEsH)jY~pS zG`me@N|1mi9{7Ay17){ycjYsE4t^TbJq05hHSt)Fq$F-jBAUygYv_3?7jlBhWQoHr zN@j~F!C(E9{G@arN2Gy3snyWNfObFmR9CkDSQc}cl_FpI9G+bn>46)OJ+`{o#Y|eM z^6D(|Iv7b*(Q5iL5H{joOe@U5he4~;7$`Ke@TAC6JgxYi&zLsq)1@wD{dH<7NpJT% zhSyVDC*!vpoyh0Ujcg8ob`(=SrN3UWgUxfSO52*A)q8%@x^_>8Xvg-s{{Cyo8`Euk zaHzgHO^n<35oO4d>*}IF_R!)f3?+#f(qrW1w2C;{;|KpBlmtxsHdnAKOve7yB7>lRx|3Xb(%dTpjB{XPk6Z-yBR&5zk8$;Et$*FCso}j-N z@a$8dg8}7HWCsB{bb#IUx#ka%vUH14*_E4@>x0`oTj~uYpmj=E9bc7g_02cV_lE41 ziO>NgfYbH>ajgnp+);%gM$QlTXq_ZBO1MAo@|w-3&fHG~b4xmmSPWKvi+lbC3j^l| z2_Dsx@q2rA8uHDQ=jQsfz;S|sxyu(8t>EId2u2$|Ks5PO_Yq`$50*2fR%8fgC~PCg z#H}DGAzOS*HCN)VH=8N?g5U{sMnNkL?3C2v=qHo;HQJ~5y;t77)I({4!L_FZi9v@- z)^EJg2&L!>f-||$HHN8Z!rjxQxkOwvgG=xHKb5_8TvT2AH%bT$JxKQqAR!^rNDm?+ z9TJkGl!SydNNzw$r8`AB1W6?v1f;tK9J(2h6vTI<_x(J-bI$vo&pG@Bi@jIvwbm8i z@3k%@niAizJPboinY)>wbko)PRte9GoIQmpGj_E79l-_}r~99#gO3yMOfIAz#7I92 z9yC+=Xu!rL0jWtyx(N}EuZc$2?ignglMx$cv18Rnan0joiC8ezxhCzMYF&+lssq!)P z3y3|_^Znn$tL>`Ln7Am+1R2b)+as8)-tkN7lNjhR1K@jbO49co?l;HBmjw?Xj5XyL zEfQ$~lrAWKAqSelYf$_!mpcTU?}}+O5y+Q-(uPfw&)vsGBc;F|pM= zgx384&dZ9*`%>%F3$~~j8E4zeB|Xf7G5W){nK&kX4O?)BHsv_yUltkMw64><8x7`) z5%{Rb4^*Wf*?FV0vrMy4qRCh82^2X!KM50PjM)H1IX5t|K(j-#6mavQSTnI?-K{oI zEwU2ZE5EV5Q6o9i%tr``CoT<0+RYvca^ca@Oc_XH`OX8eXMTQ+x*9MUQd1mFG0(CF z^JDyJ{jz8EQQ(so5Q~FGrDTWs0*2m9W18}Bg54Xry zH%O75N3%D(S>>wsg$(+vD%C`fssqwfMs#?4Uzr~2+mJERy8r5Gt`2zi>xolRN#2V( z)vZ_TG(tk(6mHDc^;tibX+Ej@WtId1Zn)!!ef`cnGfdUi8vc5FCRAQRPJEJ0mI?=1 z{$#*x%Fk<>QdT2d?6U+oKpFHS(y2igt1VG)b`54wRB^$|SjRHu<_hB_QA_m)bDR_X zEBQqVh7q=7gmbeKC!QqKS*5=VaFOTbrvd=u7dqmgs`t@kK(_TQ7At6s@0YWN|gD5tm1jT=6go!H_gPQwqmsBuc-)8m~2N~b&;^~8Z3TU zQ%_bZ9JEBB(qf1JP#h}o>&%w1QhE08^#;n5}On-T8p@fJ>oIVfG$*vNI^naRjd}#xa_Fd6R`!D< z4c9tN`dohvQ|MAboQmT#3!jWrPCDh0HIblgWgx_pnL=TpU=S?&7;k!F1X0Fje5ZwK z8mEtfV+}rf0bbL-D~#wjayyw&!b=h#7OS7tF|h9`?-n8+^0#DsYt8KK!mV_ zfyVGB->U`wB$YFM{ zR5}^fu#k@IyRQ+*4pF5OD1-{+AJ|z(KSmfT-`H(`63-(Gyx?AG*6fTq%?scoNBb_G za5Uhc=M>6R4|qfhJWxbM$%;;nLm!nI$zMWa#<&UtL7#zAn6t%e)iFQWuM2n(-_PM) zpoK2*6vD;fuma{guJ^}cm4d`lgcBP{DV7iK$&%c=Q~KKhOZtPW+4<(&H3;o#yRPtI zcWQPhw4x1~By8WHQ=|2tdb}YYulJ0Qp^gf6)piXT#)>{aJ6?>0+?iUGNRJmBw-k4r zHY3X~f&;x=fKvu_0@}6TICT6Vm^@a!^9@m);bxgCJ$TutykY6=4~JorUCw9qb6Ep- z8YHz_9J5msmbRcRnPtmSpVBgJN}J_A$0nX)%A%T8&*bi1wIN6TXN#IDZB15fg zX_nyFdyFk8B62pRCdAvZ@GYrvkhM@$R!~*AG@uiPVarKf{t}b?2G1}y=bI#Nw5EfY z=CT9867{X`v3U`g`{&H#n?_+J=xIS;pWtYy>{8y(9m?I!AWuilf<+l+Ed)ZL$h+m5JNYJ9V(jVF&DnOqTo^*~vk0Cf}J{+_a1&<(EbK)4J8>2Dx77$&(l7Uvz7&i)}EeloH1FF_=!DHtMOWA&Se(h|~rgQ;$&vq&yQ4 zR?FN&m^lCwzuqK6GlI&i*AVuKY^GR zVGoKJ2GIi^mt95~*b`=fVEr6;nxU}on*YHwlT=WP7Gkm3h3Z~XK z4B$7+B8~TN>a86}&A{AlLO!M;mz2An&qQdt^7m*gkWS%#xdfg-$ z%XDBE1^cAP-tV|*202d#=>c^}UuP7Fo{a1-xoJ3!n!Hm610)A)D zV{nkSJ4$dNHk_Oz$TbhBz!&+oj2~mX;8nLvI@pMYyRb5=D$XJ)M8hd`;#&)EBsA$j zM+c($4r@z)vV<6YH>pn{2%}v^8s4X%b%i% zSlelnw^0syKG4r!DwcZc>PVM+=XG52CC%MdsY1%$4gh-02M|9IL`>DgOzafqIzA;( zq24O#R@0GfDxT7NFqg@D-TL$n_g^E_84LaDJFRX`m~(ZCa)Lv`y{|k_eS_8bhd)Jt z8}X(wbI&%gFxt3+Q(-rzVJ1(W^h_c&kHl$V7c6a*`go{}wQ{E%RcM{$n)N!jo11Qb zm~Kgc@P5O1G5kVqd4jN(4Upj`LL;tr0T!^3Ef;VpZsqF+aNwis(e)KaWlhkQWmZfw z5vrKakn25}S5Sy^-Fv(aYqQs~K!6xoYWE!Rbs6MuPJN2T^fBm!-hX-)CMdZoS<934mb`prrz-9+*%(Gqd`sN+!;&JkhE8T_|l-B z9MP4LZGwJrS)lhbRFnl}^ZjYaQ0sV*Gs4j;vIxe9wvoa>2LiL)o=Se048dgHs~sVp zA;{2!F%3%GWfwFn)j49NHu#WuW|?>ljWIwgcfT|u0>7XKBM{mu7WlJ%2d-8l9FxB^ zP83>2VOiOUj+U$q5>S|0%}7_rTV%Bf$jrjD=FX3qA4GWjX&pl+b_kZ%AwZQbBz}Ug zCY1<N_bctp^0 zy>wrA^;mfrV@G&X^HlG7Zdw0((p={`GL*5>i6_z5aPCSj)#=OgW|S{#qNwq)L8_)c z=o2LFJd$|w#OvdG24_W=g;{^^0Cmml1Er#QXy5H^7SEt8Sf$&bnVlGC+UIT7fFv~ zK`4n9zAedrEjbv3IU>oC+4OnGo{l{IMF#Ng5QZgts75JS*q0WpAK!wjQckjDs^NB} z*bMWv3MoH69g(_$$^HlfaExwa@_S5d%zlEP(s)vn;3z$OTPTFIqCf{krPv|lp#vQo zgvLHz4>cQgz28bDN`)f0T%LZ4mG^?SL0!kNKCeaKB2{@UI-@)?s&H}VKtk?67O&W3 zYVtEj;@E)3%Yp}J9td*q_S6srZt@v+Ap>M^LC`Gy*d>P;9*7ebq}_AV!;xEV1uX%6*ul_arjTS09%E?T<>gX8%JXC*m(9#( zl}Sh0LdI{D3vQB;vs(`)4@B^OVs^R6B<)b~G&) z^tHlDshz0Nl&b^$8%rTN;7#NTryh?OPo?>##QD{)b(f16v-hg?vTtPHx{!_z!I*m2 zn#euvU)(uEeLPU{XW03^I!Z(!YqDSSr9h#xbbH#>Vu!%E!`OAAs0oiHxJo>vX%=rj z31%W9oHsU56cCp=e}{5Z-{-b>0l9pG^o2_97>Nf2X-={!8`K|}=_y4-Gm<=6iV|B{ z+zLMn5)nGS7DN1rR3|4og=`$yg(ZT;g9v#eWw57JZWbi}=HT;Y2j zblYBTUn)OJ)VgLkp0V#Kc#Re@U@%3dma*{DV9bFwUB9HouiWF81~WLs=jFs`<+gzD zV+ujzv#!wH$&!fxgBU{K*@yW;!Bkmh(qNDH`4-mil<#$An#vRmZ(-^nXtp$pP2rM4 zDUGGXd8{KN+b^vs+2>2R9UtN}K=ihJOw$a^oCpU+hZ3_Oo&IYS>>HkmZnzExFb{gB zUrm-~{Tv!L<(5k+L3kmsspv%uj104!-^3+A+9C*4{7B`QC(w^qtKSF)#@UG+T5Jb$ zwh$oK6y}a_oP;5?<561K+5((mROG)}H_K+e-2g7FK%|fCcZc)s|6B7a>P8A6>i9hE z^Ws>Ee>folGK9{ok~vx`j{sePG4D424(|&NF;@I8k^4Ss>g6j#3+AE)u21co%AZLG z<)@4l=G<|ebRo3UQQSZ2-JyV7OCN>@;K17+DI3+KDTnF2R*9&`qHFmxlyLDjpjEQ4lc)ahn5PSCSLW= zI<6m#QJ^Y%DS(>G8<<)rq@DR4C}+4Dd=AZwQf#Pis3Lmo26xyecr&u*1xf$HD0jpS zlwaac7!oKPQatO;{QlAgwp|Oi^9icw0U{ml6%BGH(TxOsFyaue<~G?0qYzs5CuliJ zt{n0a2VVz#VrwwviWA# zyR=Ve$cl_~e>@5+PfRETB{mz-IMBAyS)o=iYor66*ZvBv?Z$dfLk$#^j^IKoh|#?k z(8dI(W!SgZ+)$11lI;#;K%no!slqOcG>&l(9RPlOVmZB8wsdgm^%N`TbhCMX7EH#E zG*XL6jgiEZ>*lcXd$A_DOmh}|h@*r}Nc%qYO!L>#^Cvm<+Z*3>yrzr)T7izxi>@dKG&o4bwMGw$G)KzOaSk+23iCr=N+`3`NkD8Zqr4srxK$UwNX zB!rcmp)sp4KeAm4^GrX(HbFg~UCm=Vh#z>UmCq2Cgu3!Mms@ zOQ!QU;0txf<&bL`Y|JoS!aK2{sv>v}dupkxcj&ACI2a$)M zOUnYyx((J?FM8B)82n?V{L~Dy{HvEeFlqf=LW7B8;CHU$p)}o>7RR7rY~VLwcQ7H; z!ZZhM>(lski76jcnA%u}%(cy;$RD~1qu~F5hUJ`*Z{3wDQwUQnZQ88DTaIwj#CLH> zbga@7;1@xsa@^<5>Lsf^bJi><6PI`UW=&qDqjw34d7KywTklAj02trxEO}FF)#%_o zQD~B`+2-l?4XDzN&ttY`vj$Gt;@dtaZM1yo&eSh=n<|s9Q|?W%Sv-KUbnFgG1Ug$k zew;V1Qb#Jak-AJ|OI`ZyS>|#ks4$QqA(w^(s6mOeAbnRTzHR;VZD37)^3_mFU^oVM z5k-_H4mvK_^IGL+ZG%`dKAZsF5aVAYOa*1Lyq{^^a)pW6*RdhK_Ak?(eRo8M3uSU; zg<)oFIQk7rN^0&uAgIZd->7jSGfyjAGB&?`Som35th8Fa2RhGn*>D~Bq0TR^)vQFo zb`!c98GBPkO5LvK!YDY2s33l7W}Xe$c$sRCane7e=hIO%RPlbgSL1sfg{$WF^8CYd z1uQwJJ#V1PqbPsn=y0U>A&G^y=Atcc(mbc4SIP9ZA&@!`nDwd|Yo4m<37>UYW;IRp zcv{4%3P{-`^78I5c>&JDa1sn6Yc4`3NN~(>5rX2v4IW%@#NRuBidD}lP0$EF2*W%| zx{T^T_UDAKm?hCIVxiv3N=-Up`zqYO^`3Ept^y1P+rpo}ytlT^M<|6zSJD}?L2g+3 zI*;4xrt>i`H2s4XQjh72-UY!f)wLXPfZ#71Sq{-? z(5hlUfhVPJ_8dF3?vtDaY5_+_5i2TjK0mJZUeQhyXw)n2cyx>DoCWrc28HzX+JM4!h_A92`nlI(x zop|@|gBGE=(zkGbHXy*p8Y}X-h8T)ZwzlBrN zhuadx#SHKya)PTmW1Ml&Csr?Pb_=fx9Yn^_)QruGz#&?Z)$HyMhw0S!5FFL4ZCu+gnSbdb%L%ABE+wneP0{fQ4 zef3n1G`%DrB2aLtmo5J8;*_nuk$j-jWpe@nwuVAH^;HhNM_4ZcW85YmAG)JA*r7ft zO2HUu+{5nC*4td(C_CPTH!1>(hIKtVus-iK2zpV0j-=99k5~Wphw|Wuj40dH14^?H z!7=kE+@!Qs(Ez z*@S6`of!KgNS8gV@49}B)lx&*3zE%Y3cvUFn|3h4`C|#pq^;EfCU__uRa>SF`-df$ z?S*yAI#)**OOP|~%!p27!im$USimex{$cb}*tXIen8P*f6b*P;-2rpz@^t__sVcb2 zyuh(OM$qL<4%6KE7uK^~m4PUJ-x63!p@gYb|2|oHdW40w<&@s?v1d9_F5rgA7jcZI z$b+10m2H<5reDWf`EmS{W!4qpFq4RVARN(qJ*cV~)DMd19Td8#+$vgySC|`X3XaPc z2t1B@6;lcl^%PRhQZ~k;^To=x`WL;O!83~dXK2gOQJGz$fW!m!#x6QB$0d90_pdF_ zzeIk1=VCrktUmWx;J;tBl@yCkQv$-pD8H{O5jA2KK91!cfsa(Uw8>C3v|5o8>b?P9 zwcf}KQ13Cpw`l+=4CQyr@siR%@ZF8}1eb6ep#AX!glK}y#zK!0L~3%=Yu|sC^Q67+V?WkY?E+n zUHmDh;pd>}t8*+v6I+x{&fct5$};z<2+mS{A)Q^jN#X>&b9rQOY~1k zs^VsJ5)X7fCdS?O)||kF1i&-PJm{D?rJEI%1HjN#Ur2X_DddkWy@>nbq&UL|7G~*w z4OyGG_mv}A2049m&EQYZjn|S}5N@yw5#OUCPdJmw0y|XJoVtnsx1EN4kxx4^vpK9% zgiE=NZ;=P$D_r!kGiu%82nKYu?Xf%y9z{meGG63s4tH&Toe!OGOIt7GfMw`2le~Jd z<2tRQh?bjumxkpJAH?}^>NfWR%>2ECMe~Y$5n4NrMY=Zw>$`AgK|SJK2vQNaa}@D# zpmOXE27(kn6sHyQN*W<&d0t5dEDQxXkDlFt*Eni!mcIfgv^$rsu35h;W6ZlH^^VCn zMhuT7q-rv(XgZx@?G*^7)>6Oq`hb1;)6}q5%FF?yiHdTF8#5;)T$F;cToVMCMCCD( z{D7j-EVD}c&2vLgCTV^gk1w;E4DZy@~LbLZhXNixlB*`~3mlY>BWo5PD zAZNmbl#foOs^Y8uDJS-5^IIW2Tp)?REdtQ(S$b#A_FxD<0L-P2LBc^bzuOS*tW+}u6hG=B4p*mE^yrri1eJf2p4>`|pck*Ac1HCiZR6VK3 z$z*r&vaY7&*86+No>D?Wu9(GS$O75w6ff!?QLriWYKu&>1`> zyopm~Yk*AKS#jvK!a^CFq(vRDjnRuaOkoh@nBhhFQ{Nl4h05@Q-n$Jyak_N7l*srdB#=U5ZiLjX_02ImzY1(Ukxan=@GCHRLYtA11LNqTvv3 zVyNY*LTg?N0_n`0;bVv?0V9q~7mE<*O?Oj>&GcZzKlMs5OeX6}h&-oqJ5T|tXi(!n zeeUgienL7O5A}G>?>qOhyck7FIg85MV+v&j{%w@4D{aV?ooXOYVfE-iq4WvLL_a|PV769r8i zdeo3|(Tj45i?W{eoH}-)$I+~#N&===K8+}l&%A{=J&aX$tNZa~^>N&2bIxv72#6Z# zh)ceuLHSkRe;40|s5dr_fcnR_7|P=ondUH?)1&xO1|QL_(4pwxn~}Zh=qiITunnY8QSJ;ndnVZ;2#d4!0Z1_15RJ=x-MH$p!&9&9TD4Zdzkb>8ACel&Pkh(45W~2YguRWBIX==`Qr$-%bh?2 z6nsi(>W0@sSL2n09K)u!&mK~s9T|&e+C2ro`#%=>sf5%G$zfRtWTilfl6l@+k zbc8I(Yl0r+8feynny~yG2Kmk^A{3sH_hD|JrQ`4IoG+z}_a+EG)q3VriAu?-n#9Lj zHK*f8S6AT8$;_{< zOR*Vd)p$K^aRP~4PrTJdmOlxX5}mjG>X>nlRWwXziVGL)C+NNrNhK3zs~C*=K0+s49g#PL+mdQ%dpsY3&kjS_$kz_WsG%U5L2gv59QG zWItwrE9FD_ZE?DJ{@Ep9{G_=TWb${=#>5PTVghCumgzfc8=i)UE+bkAI5*@|I-F01 zqc}}C=&=cGwRwHs|B}3GT?%tpBRnzEHco|^`NZ$J!5otRwEW`@*z!|Vz&SANnSp}D zxb63MJjxyAl>%e*PTF9rA(&`P4zU#jYEw4GNoUM)kQiJ=$0%=u=^Vhl=vq@RCo?2{ zI@I(Q4ax7C`@HaSG-F*0qu(MEN`WO3F^qgn=xI*{>6K({w0slCzTY*^*Z)yRy?G38 zVwB)*%7kBVx6t?~Z*4b2#XU-Q6?+%AYltaY_%~RmAq0qa{G(&QtvO!#YGbLt)@H(& zBcFF8L$7mY1!FF+v&E=Ck$`-x*pR*bBd<@L>Hx_;0Two$;)*7MiOC(3 zv}tQ}1}Xg&gD`Usq}K0-{apMREjT3!>r6tuq4g&;Xy-ZflTr-GYY0?;J*CkZq$Vf!8N)yGL& zu`%CWe>fJi9R}&D{lG@7IB1qY2TCA$MgmsZqr=1I{%0$6S8PDsQe z;=inpXjGG+Z~lbVA*YmKWtB^N4%~>Gp#D+v#Btrc5BHS0u@Sx`axA!7&(p?fWD=Ap zSJ++-ylfKHIL+QlTsH0^2!#rW0ZJtHV18*co6ZbofS(D!8^!X$eqDvL0nnFuK$UqpXr zp62p3h%8n!E5Q4dp_y1G7mD{4blV*rgsimb#^xje%IkHm9~=I|gDKp7^e+#F0eSNO z;K9JuY{bD1kpkm|$yYB^Tc(I7|W{nQ@Fkx{4az2e9WTu(?%(B%f^$TxQt7Qc!w zH-lj|b4}BpQfJS+!v$GkVYoIeDwlV)}EoL%hsyW z#-3G&ZM~dFuk#uLMj{*L1<#m&vHTNK%z2Kb2q8=|_h03)B*>degbk9{Gt2oOSSBu0$a~>hOrL z){_FoqL&I(3AyzC&nOui_a)rKkH2;iS2a8!K`F5?f5wA|DrPv}uX+4kS<;7F#?0^9 z@MDFBxh!VQIH8VFoxJqWU=?-R8tTiB-?i~pcq=k(!Z7+Cs1lxMO{yMC*su?GQbf%HT{eZ zt~gfs_NaLi>vC{`(^7rR$2vgNFC+c?jee|jEUwOQ+(RvEzZ+kOeygE%xx>2dzW0r9 z&*uY}p4tFkyMOQiwKbiVnR}ZA(PI8g?kYuZq#`tE*KWGZF0SUf_-+V8S8;%}eqVcR zy6ggR*N*jEk|vXD+8~mZM2_dKdEbz3Px#Sxs*{N3x?We8CNFQf0x>$uu>EFNEj2AV z7%4z{(UQU>%&hy+n>cSFbMzIylV*_rhwm>8JJm7O{aBHrgJ$EVsr&9Nu9!$-F6H+; zVJ4hI09Jd>DYlkDj9%X$L%iF6BUQ#y>>OlNAHJHU5hstHRb2?Gx(*w2;G7usT?v5j z_;8zlwn-x?u|3EgrXVCVZat!R1eG-iiI~5d3CIW{CYPn2*%W9DC~A6;$zTM4ct3U9 zaCi1k()ZW_KPW$0O6BEN2~@t(&MFk5Twi;~a9;9x8hib1@{PJLDi>w9Ra~l4wG$O2 z7Gc``GP(*W>EWF_1>x}RS2?@Z&$};I>BgAhCKV6g!80gIiEyMp#=kY#26IRb^I9V~ zr|F%IcJ5sp&!rvau?c0F#qr_V03E^Vm^kvH z$t@dRl(O_?+4otpbYSvjN@Xu}sIg)>LwK-8K#DATmjMD~RnRE$rM!w6fyRNmV zjePLQ+LVp6dzprVzDPX@mA8$R7X^~`t)zj* ztHJ+d0oDeTAO;*)VR&MY^F4N_s&oAc@R|)Y$>Ues=}Khy8nx7`yI%Q{fEk;c<@8T> z4C^8hpe&!h(Z$CR9O7(K2ss)0EAY2*fk7tPPb%&&}x?-V2-K)EK?i2))#C1wuobm5OJ?d&Ru_K|wj|pk7 zke8U93iiSCRsfyZ7;#nistl~#Vx~6Jl&dz;uO0*|rzC3+;v*m4urj`o78y6Y+V)_a zW0iUVczI_uoE z|3X8@k?2l>V72CgxlF*uEO($Kdeesj%Tpe)rC%Jc#yO*UrE~OxvJisK%hCoJOPu}X zyb@R3Z$Ixr&4XVRSI(wrXW=#pBaoc&oXK@c`u&#NZlXH<7OU_0E9GsQXd_Lg_pjfe z$!<>96=Z>1{a=Hu_vU!cZm4~f@8`j}Ql^nK-2`Gq9D()ab36&2>dC{iL+HkO;G&8+t3ndgnS#+je#a4==m zSB7Ld0$hHkDHjHen)olsf(&Q}e1u>W8K| zMkGBUc8?}tCgryt6dYeI&SZGhK^2HbYI|IDs=3(Qb7w}>lddPk=RF^u`)^;`9u4KI z8gFeCdL@L5k{UnszG``!qWEtpZqcG*1Fp|1_hbRf-w@sia^zv3c1JK~MTVQ)-iacB zsgl55*&7Gdnr9PplLgS0Wv`5lkp~O76O47hf58E8U{pk*;Q5lTRiu+V8PWt=4@Ej! z6C0y8>(56vMhkymH-8dDPMrf5A4GpI?MmV|)<|xlsCYpcx>nj-)s{@81wraH(tuIK zWr|l(#N2=&4GbJ}QA8o}7Tybu1^|0eMn$H@B*m9e8h)r!_Tm zwN{e2sp>ocK`}5S+E@zgUVpWS`|9gF`{jw;1>)j>-68Wu6xyniB=)yRe@ATNi}h

;pi zn8%u7Vd06X+*QzfLSbO@`cpJ9|7q{vF7VYuFR!s!Rq|ocyK)^{%lWWYY#NZDY_MAv z#Sdi0pv4l@KZ%Z(Dmq7cZg&GD*7bW6qtO}m{Y1J6^~}Ky*;^2+cjV8r-flg5d;i7i ztajCF{RT7Yy5Y4!`{8kI^P2Gxr+VzK9p+^J%tWxWzX%;`iU{|ZUN%CaRZPz`GyfBR zCDAsLI2xI$)oNb)=(njPC$x#8+D^}kPGqx6%dpTtbJ8G&EO1^L*dslk48~LcJk~98 zkpKP?)8JsTef!$ztamKR7IjQ86CF^1M>jb%{#zUiF}t>OEBx<=%8)+|B8__{5rh$f zTxxKi@n4^cZoH+Am547O!{uZpaS<;23n#G}_&Ef`IGr*CrcqUMk@Sb|#A|-`3J(z^ z;f8U@Fn#w$uONzUD5iYEnJcqo7U`!+a+(B+$7s9>Q6s>(0@}X(^y5FXDh{oC8cqo& z+<8@ITMI!R=z1O}{TyVr?3jhc1@BwY4S0Qgm1G3cTgcH)M%mM>0|!v+iGy~G;ESef z7H{Agqa-wi`bSb~y@iUj_vSA2t5TlXN?zJc%}}7G_&`ERK1-t8*lpnVwFWi@@A=(B(WY?zop+!Z6c(WL`ZO}%?y}z2^jG$$ zPPrvbtrrqvl#2=Pf;sGvL7n5upOMH}s`Pv|V*NWMyf^?WvXX2T#)sL*;q58E3%|I= zsK!~6yFL{IOim~&FW!a>231hzz6hE+TYc66%9%e$Op-68GL&q4oVREFWzynH(*rmo z^ZV-;lm(t9I1+cWJrN<{7O%kQY$4d`#hf)pfIpik^ULk0bHNk;sUZOJn}qYufrHI! z9Q3Wvu^!4N51&8>Du|C-ES)4~h!|FeLxUk$zgyklpZdWF!Z}s-m|ytT_rI0kQ$9`h z;?!#i0h80mfXAQFCETz6_O)oQ^V_2#AeyNgS+}BV`YQzgJ>&CM@czHawbZUhxBx4~ z!cUww`8-@SUD1!J*j>bF>*F;~k`MWZri4SM#k7w?o)XVc{4*_BKzFwXmIYJRxc)0< z7hgby+y1qnDigEI*@C}A1LEsx`iODBHJ<7;e3UV&V2T3td}iN%J-^6qq%9aN>(mhL zub=*#0{CZn5W)XInI$~u_FHS`4d1O&Uoj>-$X4_z%;Sz9xYZ~6?pyB{(wooL9Z75G z{~gsU*P_A>48@=Gl}fBvXXp&f-JpzpbsU8DSL=zKci&$R4gdFVHxWA6O?q -FluidSynth Mixer Chart - -\endhtmlonly +\image html fluid_mixer.png "FluidSynth Mixer Chart" */ From b565b3ebc3aaff1750b60fb1a3c87109b34e2f86 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 14:56:18 +0100 Subject: [PATCH 39/55] Use custom \setting{} alias to link to fluid settings --- doc/Doxyfile | 1 + doc/Doxyfile.cmake | 1 + doc/recent_changes.txt | 14 +++++++------- doc/usage/audio_driver.txt | 8 ++++---- doc/usage/creating_settings.txt | 2 +- doc/usage/creating_synth.txt | 2 +- doc/usage/file_renderer.txt | 4 ++-- doc/usage/midi_player.txt | 2 +- doc/usage/realtime_midi.txt | 5 ++--- 9 files changed, 20 insertions(+), 19 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index 54c2e848..10b8c1f5 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -27,6 +27,7 @@ TAB_SIZE = 8 ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" ALIASES += startlifecycle="\name Lifecycle Functions\n@{" ALIASES += endlifecycle="@}" +ALIASES += setting{1}="\ref settings_\1" OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index ce9b790f..d8f925c0 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -27,6 +27,7 @@ TAB_SIZE = 8 ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" ALIASES += startlifecycle="\name Lifecycle Functions\n@{" ALIASES += endlifecycle="@}" +ALIASES += setting{1}="\ref settings_\1" OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO diff --git a/doc/recent_changes.txt b/doc/recent_changes.txt index 5acd3b4a..725c2fa6 100644 --- a/doc/recent_changes.txt +++ b/doc/recent_changes.txt @@ -24,7 +24,7 @@ \section NewIn2_1_0 What's new in 2.1.0? - refrain from using fluid_synth_set_sample_rate() -- \ref settings_synth_sample-rate is no real-time setting anymore, see note about fluid_synth_set_sample_rate() +- \setting{synth_sample-rate} is no real-time setting anymore, see note about fluid_synth_set_sample_rate() - new reverb engine - chorus is now stereophonic - smallest allowed chorus speed is now 0.1 Hz (previously 0.29 Hz) @@ -49,7 +49,7 @@ \section NewIn2_0_5 What's new in 2.0.5? - fluid_synth_process() omitted audio samples when called with arbitrary sample counts that were not a multiple of fluid_synth_get_internal_bufsize() -- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if \ref settings_synth_dynamic-sample-loading was set to FALSE +- fluid_synth_sfunload() was not releasing sample buffers of SoundFont3 files if \setting{synth_dynamic-sample-loading} was set to FALSE \section NewIn2_0_3 What's new in 2.0.3? @@ -72,7 +72,7 @@ FluidSynths major version was bumped. The API was reworked, deprecated functions - fluid_settings_setstr() cannot be used to set integer (toggle) settings with "yes" or "no" values anymore. Use fluid_settings_setint() instead, for example:
fluid_settings_setint(settings, "synth.reverb.active", 0) instead of fluid_settings_setstr(settings, "synth.reverb.active", "no") - explicit client unregistering is required for fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() (since fluidsynth 2.1.1 not required anymore, but still recommend) - all public functions consistently receive signed integers for soundfont ids, bank and program numbers -- use unique device names for the "audio.portaudio.device" setting +- use unique device names for the \setting{audio_portaudio_device} setting - fluid_synth_process() received a new more flexible implementation, but now requires zeroed-out sample buffers Other changes in FluidSynth 2.0.0 concerning developers: @@ -89,14 +89,14 @@ FluidSynths major version was bumped. The API was reworked, deprecated functions New Features and API additions: -- add \ref settings_midi_autoconnect a setting for automatically connecting fluidsynth to available MIDI input ports -- add \ref settings_synth_overflow_important and \ref settings_synth_overflow_important-channels settings to take midi channels during overflow calculation into account that are considered to be "important" -- add \ref settings_synth_dynamic-sample-loading a setting for enabling on demand sample loading +- add \setting{midi_autoconnect} a setting for automatically connecting fluidsynth to available MIDI input ports +- add \setting{synth_overflow_important} and \setting{synth_overflow_important-channels} settings to take midi channels during overflow calculation into account that are considered to be "important" +- add \setting{synth_dynamic-sample-loading} a setting for enabling on demand sample loading - add support for polyphonic key pressure events, see fluid_event_key_pressure() and fluid_synth_key_pressure() - add fluid_synth_add_default_mod() and fluid_synth_remove_default_mod() for manipulating default modulators - add individual reverb setters: fluid_synth_set_reverb_roomsize(), fluid_synth_set_reverb_damp(), fluid_synth_set_reverb_width(), fluid_synth_set_reverb_level() - add individual chorus setters: fluid_synth_set_chorus_nr(), fluid_synth_set_chorus_level(), fluid_synth_set_chorus_speed(), fluid_synth_set_chorus_depth(), fluid_synth_set_chorus_type() -- add realtime settings for \ref settings_synth_reverb_damp and \ref settings_synth_chorus_depth parameters +- add realtime settings for \setting{synth_reverb_damp} and \setting{synth_chorus_depth} parameters - add seek support to midi-player, see fluid_player_seek() - expose functions to manipulate the ladspa effects unit (see ladspa.h) - add support for text and lyrics midi events, see fluid_midi_event_set_lyrics() and fluid_midi_event_set_text() diff --git a/doc/usage/audio_driver.txt b/doc/usage/audio_driver.txt index 5007e374..f3a6716a 100644 --- a/doc/usage/audio_driver.txt +++ b/doc/usage/audio_driver.txt @@ -40,7 +40,7 @@ with fluid_settings_setstr(). In most cases, the default driver should work out of the box. Additional options that define the audio quality and latency are -"audio.sample-format", "audio.period-size", and "audio.periods". The details +\setting{audio_sample-format}, \setting{audio_period-size}, and \setting{audio_periods}. The details are described later. You create the audio driver with the new_fluid_audio_driver() function. This @@ -67,11 +67,11 @@ driver creates a separate thread that uses the synthesizer object to generate the audio. There are a number of general audio driver settings. The audio.driver -settings define the audio subsystem that will be used. The audio.periods and -audio.period-size settings define the latency and robustness against +settings define the audio subsystem that will be used. The \setting{audio_periods} and +\setting{audio_period-size} settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, -please refer to the \ref settings_audio documentation. +please refer to the \setting{audio} documentation. *Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done diff --git a/doc/usage/creating_settings.txt b/doc/usage/creating_settings.txt index df4b27d9..8f7dabf5 100644 --- a/doc/usage/creating_settings.txt +++ b/doc/usage/creating_settings.txt @@ -4,7 +4,7 @@ Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. -All settings have a name that follows the "dotted-name" notation. For example, "synth.polyphony" refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: +All settings have a name that follows the "dotted-name" notation. For example, \setting{synth_polyphony} refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: \code #include diff --git a/doc/usage/creating_synth.txt b/doc/usage/creating_synth.txt index 26475c2a..3462effa 100644 --- a/doc/usage/creating_synth.txt +++ b/doc/usage/creating_synth.txt @@ -24,6 +24,6 @@ int main(int argc, char** argv) \endcode For a full list of available synthesizer settings, please -refer to the \ref settings_synth documentation. +refer to the \setting{synth} documentation. */ diff --git a/doc/usage/file_renderer.txt b/doc/usage/file_renderer.txt index d52a9995..57df07b4 100644 --- a/doc/usage/file_renderer.txt +++ b/doc/usage/file_renderer.txt @@ -54,7 +54,7 @@ delete_fluid_settings(settings); \endcode Various output files types are supported, if compiled with libsndfile. Those -can be specified via the \c settings object as well. Refer to the \ref -settings_audio documentation for more \c audio.file\.\* options. +can be specified via the \c settings object as well. Refer to the +\setting{audio} documentation for more \c audio.file\.\* options. */ diff --git a/doc/usage/midi_player.txt b/doc/usage/midi_player.txt index ba8bdc76..7d4aafe1 100644 --- a/doc/usage/midi_player.txt +++ b/doc/usage/midi_player.txt @@ -50,6 +50,6 @@ int main(int argc, char** argv) A list of available MIDI player settings can be found in the -\ref settings_player documentation. +\setting{player} documentation. */ diff --git a/doc/usage/realtime_midi.txt b/doc/usage/realtime_midi.txt index b37018e6..ac44f792 100644 --- a/doc/usage/realtime_midi.txt +++ b/doc/usage/realtime_midi.txt @@ -38,10 +38,9 @@ int main(int argc, char** argv) } \endcode -There are a number of general MIDI driver settings. The midi.driver setting +There are a number of general MIDI driver settings. The \setting{midi_driver} setting defines the MIDI subsystem that will be used. There are additional settings for the MIDI subsystems used. For a full list of available -midi driver settings, please refer to the \ref settings_midi -documentation. +midi driver settings, please refer to the \setting{midi} documentation. */ From eebbfb6a62eb6b66f743ab104ac3f60d78aa7d03 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 14:59:29 +0100 Subject: [PATCH 40/55] Smaller cleanup and reformatting of long lines. --- doc/fluidsynth-v20-devdoc.txt | 1 + doc/usage/audio_driver.txt | 14 +++++++------- doc/usage/creating_settings.txt | 20 +++++++++++++++++--- doc/usage/synth_context.txt | 16 ++++++++++++---- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 831b65aa..7cd2bf67 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -39,6 +39,7 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org +*/ /*! \example example.c diff --git a/doc/usage/audio_driver.txt b/doc/usage/audio_driver.txt index f3a6716a..e9b61a97 100644 --- a/doc/usage/audio_driver.txt +++ b/doc/usage/audio_driver.txt @@ -40,8 +40,8 @@ with fluid_settings_setstr(). In most cases, the default driver should work out of the box. Additional options that define the audio quality and latency are -\setting{audio_sample-format}, \setting{audio_period-size}, and \setting{audio_periods}. The details -are described later. +\setting{audio_sample-format}, \setting{audio_period-size}, and +\setting{audio_periods}. The details are described later. You create the audio driver with the new_fluid_audio_driver() function. This function takes the settings and synthesizer object as arguments. For example: @@ -66,12 +66,12 @@ As soon as the audio driver is created, it will start playing. The audio driver creates a separate thread that uses the synthesizer object to generate the audio. -There are a number of general audio driver settings. The audio.driver -settings define the audio subsystem that will be used. The \setting{audio_periods} and +There are a number of general audio driver settings. The audio.driver settings +define the audio subsystem that will be used. The \setting{audio_periods} and \setting{audio_period-size} settings define the latency and robustness against -scheduling delays. There are additional settings for the audio subsystems -used. For a full list of available audio driver settings, -please refer to the \setting{audio} documentation. +scheduling delays. There are additional settings for the audio subsystems used. +For a full list of available audio driver settings, please +refer to the \setting{audio} documentation. *Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done diff --git a/doc/usage/creating_settings.txt b/doc/usage/creating_settings.txt index 8f7dabf5..a7ba6b94 100644 --- a/doc/usage/creating_settings.txt +++ b/doc/usage/creating_settings.txt @@ -2,9 +2,18 @@ \page CreatingSettings Creating and changing the settings -Before you can use the synthesizer, you have to create a settings object. The settings objects is used by many components of the FluidSynth library. It gives a unified API to set the parameters of the audio drivers, the midi drivers, the synthesizer, and so forth. A number of default settings are defined by the current implementation. +Before you can use the synthesizer, you have to create a settings object. The +settings objects is used by many components of the FluidSynth library. It gives +a unified API to set the parameters of the audio drivers, the midi drivers, the +synthesizer, and so forth. A number of default settings are defined by the +current implementation. -All settings have a name that follows the "dotted-name" notation. For example, \setting{synth_polyphony} refers to the number of voices (polyphony) allocated by the synthesizer. The settings also have a type. There are currently three types: strings, numbers (double floats), and integers. You can change the values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), and fluid_settings_setint() functions. For example: +All settings have a name that follows the "dotted-name" notation. For example, +\setting{synth_polyphony} refers to the number of voices (polyphony) allocated +by the synthesizer. The settings also have a type. There are currently three +types: strings, numbers (double floats), and integers. You can change the +values of a setting using the fluid_settings_setstr(), fluid_settings_setnum(), +and fluid_settings_setint() functions. For example: \code #include @@ -19,6 +28,11 @@ int main(int argc, char** argv) } \endcode -The API contains the functions to query the type, the current value, the default value, the range and the "hints" of a setting. The range is the minimum and maximum value of the setting. The hints gives additional information about a setting. For example, whether a string represents a filename. Or whether a number should be interpreted on on a logarithmic scale. Check the settings.h API documentation for a description of all functions. +The API contains the functions to query the type, the current value, the +default value, the range and the "hints" of a setting. The range is the minimum +and maximum value of the setting. The hints gives additional information about +a setting. For example, whether a string represents a filename. Or whether a +number should be interpreted on on a logarithmic scale. Check the settings.h +API documentation for a description of all functions. */ diff --git a/doc/usage/synth_context.txt b/doc/usage/synth_context.txt index 9c57a004..a9705daa 100644 --- a/doc/usage/synth_context.txt +++ b/doc/usage/synth_context.txt @@ -2,18 +2,26 @@ \page synth-context Understanding the "synthesis context" -When reading through the functions exposed via our API, you will often read the note: "May or may not be called from synthesis context." +When reading through the functions exposed via our API, you will often read the +note: "May or may not be called from synthesis context." -The reason for this is that some functions are intentionally not thread-safe. Or they require to be called from this context to behave correctly. +The reason for this is that some functions are intentionally not thread-safe. +Or they require to be called from this context to behave correctly. -FluidSynth's rendering engine is implemented by using the "Dispatcher Thread Pattern". This means that a certain thread @c A which calls one of FluidSynth's rendering functions, namely +FluidSynth's rendering engine is implemented by using the "Dispatcher Thread +Pattern". This means that a certain thread @c A which calls one of FluidSynth's +rendering functions, namely - fluid_synth_process() - fluid_synth_nwrite_float() - fluid_synth_write_float() - fluid_synth_write_s16() -automatically becomes the "synthesis thread". The terms "synthesis context" and "synthesis thread" are equivalent. A few locations in our API provide hooks that allow you to interfere this "synthesis context". At those locations you can register your own custom functions that will always be called by thread @c A. For this use-case, the following functions are of interest: +automatically becomes the "synthesis thread". The terms "synthesis context" and +"synthesis thread" are equivalent. A few locations in our API provide hooks +that allow you to interfere this "synthesis context". At those locations you +can register your own custom functions that will always be called by thread +@c A. For this use-case, the following functions are of interest: - new_fluid_audio_driver2() - fluid_player_set_playback_callback() From 3c1c796396ddfac559ab2a6a337d174592e24904 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 15:10:57 +0100 Subject: [PATCH 41/55] Add generated fluidsettings.txt for fluidsynth.org API doc build Probably not the final solution, but works for now. --- doc/Doxyfile | 3 +- doc/doxygen/fluidsettings.txt | 1677 +++++++++++++++++++++++++++++++++ doc/fluidsettings.xml | 5 + 3 files changed, 1684 insertions(+), 1 deletion(-) create mode 100644 doc/doxygen/fluidsettings.txt diff --git a/doc/Doxyfile b/doc/Doxyfile index 10b8c1f5..b71b742b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -92,7 +92,8 @@ INPUT = \ ../doc/usage \ ../include \ ../include/fluidsynth \ -../src +../src \ +../doc/doxygen/fluidsettings.txt INPUT_ENCODING = UTF-8 FILE_PATTERNS = *.c *.h *.txt diff --git a/doc/doxygen/fluidsettings.txt b/doc/doxygen/fluidsettings.txt new file mode 100644 index 00000000..bdd2c240 --- /dev/null +++ b/doc/doxygen/fluidsettings.txt @@ -0,0 +1,1677 @@ +/*! +\page fluidsettings Settings Reference + + + + + + + + + + + +- \subpage settings_audio Audio driver settings +- \subpage settings_midi MIDI driver settings +- \subpage settings_player MIDI player settings +- \subpage settings_shell Shell (command line) settings +- \subpage settings_synth Synthesizer settings + + + + + + + + + + +\page settings_audio Audio driver settings + +\section settings_audio_driver audio.driver + + +\par Type +Selection (str)\par Options + alsa, coreaudio, dart, dsound, file, jack, oboe, opensles, oss, portaudio, pulseaudio, sdl2, sndman, waveout + +\par Default +\htmlonly +jack (Linux),
+ dsound (Windows),
+ sndman (MacOS9),
+ coreaudio (Mac OS X),
+ dart (OS/2) +
+\endhtmlonly + + + +\htmlonly + + The audio system to be used. In order to use sdl2 as audio driver, the application is responsible for initializing SDL's audio subsystem.

Note: sdl2 and waveout are available since fluidsynth 2.1. +
+\endhtmlonly + + +\section settings_audio_periods audio.periods + + +\par Type +Integer (int)\par Min - Max + 2 - 64 + +\par Default +\htmlonly +8 (Windows, MacOS9),
+ 16 (all other) +
+\endhtmlonly + + + +\htmlonly + + The number of the audio buffers used by the driver. This number of buffers, multiplied by the buffer size (see setting audio.period-size), determines the maximum latency of the audio driver. + +\endhtmlonly + + +\section settings_audio_period-size audio.period-size + + +\par Type +Integer (int)\par Min - Max + 64 - 8192 + +\par Default +\htmlonly +512 (Windows),
+ 64 (all other) +
+\endhtmlonly + + + +\htmlonly + + The size of the audio buffers (in frames). + +\endhtmlonly + + +\section settings_audio_realtime-prio audio.realtime-prio + + +\par Type +Integer (int)\par Min - Max + 0 - 99 + +\par Default +\htmlonly +60 +\endhtmlonly + + + +\htmlonly + + Sets the realtime scheduling priority of the audio synthesis thread. This includes the synthesis threads created by the synth (in case synth.cpu-cores was greater 1). A value of 0 disables high priority scheduling. Linux is the only platform which currently makes use of different priority levels as specified by this setting. On other operating systems the thread priority is set to maximum. Drivers which use this option: alsa, oss and pulseaudio + +\endhtmlonly + + +\section settings_audio_sample-format audio.sample-format + + +\par Type +Selection (str)\par Options + 16bits, float + +\par Default +\htmlonly +16bits +\endhtmlonly + + + +\htmlonly + + The format of the audio samples. This is currently only an indication; the audio driver may ignore this setting if it can't handle the specified format. + +\endhtmlonly + + +\section settings_audio_alsa_device audio.alsa.device + + +\par Type +Selection (str)\par Options + ALSA device string, such as: "hw:0", "plughw:1", etc. + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Selects the ALSA audio device to use. + +\endhtmlonly + + +\section settings_audio_coreaudio_device audio.coreaudio.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Selects the CoreAudio device to use. + +\endhtmlonly + + +\section settings_audio_dart_device audio.dart.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Selects the Dart (OS/2 driver) device to use. + +\endhtmlonly + + +\section settings_audio_dsound_device audio.dsound.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Selects the DirectSound (Windows) device to use. + +\endhtmlonly + + +\section settings_audio_file_endian audio.file.endian + + +\par Type +Selection (str)\par Options + auto, big, cpu, little ('cpu' is all that is supported if libsndfile support is not built in) + +\par Default +\htmlonly +'auto' if libsndfile support is built in,
+ 'cpu' otherwise.
+\endhtmlonly + + + +\htmlonly + + Defines the byte order when using the 'file' driver or file renderer to store audio to a file. 'auto' uses the default for the given file type, 'cpu' uses the CPU byte order, 'big' uses big endian byte order and 'little' uses little endian byte order. + +\endhtmlonly + + +\section settings_audio_file_format audio.file.format + + +\par Type +Selection (str)\par Options + double, float, s16, s24, s32, s8, u8 + +\par Default +\htmlonly +s16 +\endhtmlonly + + + +\htmlonly + + Defines the audio format when rendering audio to a file. Limited to 's16' if no libsndfile support. +

    +
  • 'double' is 64 bit floating point,
  • +
  • 'float' is 32 bit floating point,
  • +
  • 's16' is 16 bit signed PCM,
  • +
  • 's24' is 24 bit signed PCM,
  • +
  • 's32' is 32 bit signed PCM,
  • +
  • 's8' is 8 bit signed PCM and
  • +
  • 'u8' is 8 bit unsigned PCM.
  • +
+ +\endhtmlonly + + +\section settings_audio_file_name audio.file.name + + +\par Type +String (str) + +\par Default +\htmlonly +'fluidsynth.wav' if libsndfile support is built in,
+ 'fluidsynth.raw' otherwise.
+\endhtmlonly + + + +\htmlonly + + Specifies the file name to store the audio to, when rendering audio to a file. + +\endhtmlonly + + +\section settings_audio_file_type audio.file.type + + +\par Type +Selection (str)\par Options + aiff, au, auto, avr, caf, flac, htk, iff, mat, oga, paf, pvf, raw, sd2, sds, sf, voc, w64, wav, xi + +\par Default +\htmlonly +'auto' if libsndfile support is built in,
+ 'raw' otherwise.
+\endhtmlonly + + + +\htmlonly + + Sets the file type of the file which the audio will be stored to. 'auto' attempts to determine the file type from the audio.file.name file extension and falls back to 'wav' if the extension doesn't match any types. Limited to 'raw' if compiled without libsndfile support. Actual options will vary depending on libsndfile library. + +\endhtmlonly + + +\section settings_audio_jack_autoconnect audio.jack.autoconnect + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + If 1 (TRUE), then FluidSynth output is automatically connected to jack system audio output. + +\endhtmlonly + + +\section settings_audio_jack_id audio.jack.id + + +\par Type +String (str) + +\par Default +\htmlonly +fluidsynth +\endhtmlonly + + + +\htmlonly + + Unique identifier used when creating Jack client connection. + +\endhtmlonly + + +\section settings_audio_jack_multi audio.jack.multi + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + If 1 (TRUE), then multi-channel Jack output will be enabled if synth.audio-channels is greater than 1. + +\endhtmlonly + + +\section settings_audio_jack_server audio.jack.server + + +\par Type +String (str) + +\par Default +\htmlonly +(empty string) +\endhtmlonly + + + +\htmlonly + + Jack server to connect to. Defaults to an empty string, which uses default Jack server. + +\endhtmlonly + + +\section settings_audio_oboe_id audio.oboe.id + + +\par Type +Integer (int)\par Min - Max + 0 - 2147483647 + +\par Default +\htmlonly +0 +\endhtmlonly + + + +\htmlonly + + Request an audio device identified device using an ID as pointed out by Oboe's documentation. + +\endhtmlonly + + +\section settings_audio_oboe_sharing-mode audio.oboe.sharing-mode + + +\par Type +Selection (str)\par Options + Shared, Exclusive + +\par Default +\htmlonly +Shared +\endhtmlonly + + + +\htmlonly + + Sets the sharing mode as pointed out by Oboe's documentation. + +\endhtmlonly + + +\section settings_audio_oboe_performance-mode audio.oboe.performance-mode + + +\par Type +Selection (str)\par Options + None, PowerSaving, LowLatency + +\par Default +\htmlonly +None +\endhtmlonly + + + +\htmlonly + + Sets the performance mode as pointed out by Oboe's documentation. + +\endhtmlonly + + +\section settings_audio_oss_device audio.oss.device + + +\par Type +String (str) + +\par Default +\htmlonly +/dev/dsp +\endhtmlonly + + + +\htmlonly + + Device to use for OSS audio output. + +\endhtmlonly + + +\section settings_audio_portaudio_device audio.portaudio.device + + +\par Type +String (str) + +\par Default +\htmlonly +PortAudio Default +\endhtmlonly + + + +\htmlonly + + Device to use for PortAudio driver output. Note that 'PortAudio Default' is a special value which outputs to the default PortAudio device. The format of the device name is: "<device_index>:<host_api_name>:<host_device_name>" e.g. "11:Windows DirectSound:SB PCI" + +\endhtmlonly + + +\section settings_audio_pulseaudio_adjust-latency audio.pulseaudio.adjust-latency + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + + +\htmlonly + + If TRUE initializes the maximum length of the audio buffer to the highest supported value and increases the latency dynamically if PulseAudio suggests so. Else uses a buffer with length of "audio.period-size". + +\endhtmlonly + + +\section settings_audio_pulseaudio_device audio.pulseaudio.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Device to use for PulseAudio driver output. + +\endhtmlonly + + +\section settings_audio_pulseaudio_media-role audio.pulseaudio.media-role + + +\par Type +String (str) + +\par Default +\htmlonly +music +\endhtmlonly + + + +\htmlonly + + PulseAudio media role information. + +\endhtmlonly + + +\section settings_audio_pulseaudio_server audio.pulseaudio.server + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly + + Server to use for PulseAudio driver output. + +\endhtmlonly + + +\page settings_midi MIDI driver settings + +\section settings_midi_autoconnect midi.autoconnect + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + If 1 (TRUE), automatically connects FluidSynth to available MIDI input ports. alsa_seq, coremidi and jack are currently the only drivers making use of this. + +\endhtmlonly + + +\section settings_midi_driver midi.driver + + +\par Type +Selection (str)\par Options + alsa_raw, alsa_seq, coremidi, jack, midishare, oss, winmidi + +\par Default +\htmlonly +alsa_seq (Linux),
+ winmidi (Windows),
+ jack (Mac OS X)
+\endhtmlonly + + + +\htmlonly +The MIDI system to be used. +\endhtmlonly + + +\section settings_midi_realtime-prio midi.realtime-prio + + +\par Type +Integer (int)\par Min - Max + 0 - 99 + +\par Default +\htmlonly +50 +\endhtmlonly + + + +\htmlonly +Sets the realtime scheduling priority of the MIDI thread (0 disables high priority scheduling). Linux is the only platform which currently makes use of different priority levels. Drivers which use this option: alsa_raw, alsa_seq, oss +\endhtmlonly + + +\section settings_midi_portname midi.portname + + +\par Type +String (str) + +\par Default +\htmlonly +(empty string) +\endhtmlonly + + + +\htmlonly +Used by coremidi and alsa_seq drivers for the portnames registered with the MIDI subsystem. +\endhtmlonly + + +\section settings_midi_alsa_device midi.alsa.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly +ALSA MIDI hardware device to use for RAW ALSA MIDI driver (not to be confused with the MIDI port). +\endhtmlonly + + +\section settings_midi_alsa_seq_device midi.alsa_seq.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly +ALSA sequencer hardware device to use for ALSA sequencer driver (not to be confused with the MIDI port). +\endhtmlonly + + +\section settings_midi_alsa_seq_id midi.alsa_seq.id + + +\par Type +String (str) + +\par Default +\htmlonly +pid +\endhtmlonly + + + +\htmlonly +ID to use when registering ports with the ALSA sequencer driver. If set to "pid" then the ID will be "FLUID Synth (PID)", where PID is the FluidSynth process ID of the audio thread otherwise the provided string will be used in place of PID. +\endhtmlonly + + +\section settings_midi_coremidi_id midi.coremidi.id + + +\par Type +String (str) + +\par Default +\htmlonly +pid +\endhtmlonly + + + +\htmlonly +Client ID to use for CoreMIDI driver. 'pid' will use process ID as port of the client name. +\endhtmlonly + + +\section settings_midi_jack_server midi.jack.server + + +\par Type +String (str) + +\par Default +\htmlonly +(empty string) +\endhtmlonly + + + +\htmlonly +Jack server to connect to for Jack MIDI driver. If an empty string then the default server will be used. +\endhtmlonly + + +\section settings_midi_jack_id midi.jack.id + + +\par Type +String (str) + +\par Default +\htmlonly +fluidsynth-midi +\endhtmlonly + + + +\htmlonly +Client ID to use with the Jack MIDI driver. If jack is also used as audio driver and "midi.jack.server" and "audio.jack.server" are equal, this setting will be overridden by "audio.jack.id", because a client cannot have multiple names. +\endhtmlonly + + +\section settings_midi_oss_device midi.oss.device + + +\par Type +String (str) + +\par Default +\htmlonly +/dev/midi +\endhtmlonly + + + +\htmlonly +The hardware device to use for OSS MIDI driver (not to be confused with the MIDI port). +\endhtmlonly + + +\section settings_midi_winmidi_device midi.winmidi.device + + +\par Type +String (str) + +\par Default +\htmlonly +default +\endhtmlonly + + + +\htmlonly +The hardware device to use for Windows MIDI driver (not to be confused with the MIDI port). Multiple devices can be specified by a list of devices index separated by a semicolon (e.g "2;0", which is equivalent to one device with 32 MIDI channels). +\endhtmlonly + + +\page settings_player MIDI player settings + +\section settings_player_reset-synth player.reset-synth + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly +If true, reset the synth before starting a new MIDI song, so the state of a previous song can't affect the new song. Turn it off for seamless looping of a song. +\endhtmlonly + + +\section settings_player_timing-source player.timing-source + + +\par Type +Selection (str)\par Options + sample, system + +\par Default +\htmlonly +sample +\endhtmlonly + + + +\htmlonly +Determines the timing source of the player sequencer. 'sample' uses the sample clock (how much audio has been output) to sequence events, in which case audio is synchronized with MIDI events. 'system' uses the system clock, audio and MIDI are not synchronized exactly. +\endhtmlonly + + +\page settings_shell Shell (command line) settings + +\section settings_shell_prompt shell.prompt + + +\par Type +String (str) + +\par Default +\htmlonly +(empty string) +\endhtmlonly + + + +\htmlonly +In dump mode we set the prompt to "" (empty string). The ui cannot easily handle lines, which don't end with cr. Changing the prompt cannot be done through a command, because the current shell does not handle empty arguments. +\endhtmlonly + + +\section settings_shell_port shell.port + + +\par Type +Integer (int)\par Min - Max + 1 - 65535 + +\par Default +\htmlonly +9800 +\endhtmlonly + + + +\htmlonly +The shell can be used in a client/server mode. This setting controls what TCP/IP port the server uses. +\endhtmlonly + + +\page settings_synth Synthesizer settings + +\section settings_synth_audio-channels synth.audio-channels + + +\par Type +Integer (int)\par Min - Max + 1 - 128 + +\par Default +\htmlonly +1 +\endhtmlonly + + + +\htmlonly + + By default, the synthesizer outputs a single stereo signal. Using this option, the synthesizer can output multi-channel audio. Sets the number of stereo channel pairs. So 1 is actually 2 channels (a stereo pair). +\endhtmlonly + + +\section settings_synth_audio-groups synth.audio-groups + + +\par Type +Integer (int)\par Min - Max + 1 - 128 + +\par Default +\htmlonly +1 +\endhtmlonly + + + +\htmlonly + + The output audio channel associated with a MIDI channel is wrapped around using the number of synth.audio-groups as modulo divider. This is typically the number of output channels on the sound card, as long as the LADSPA Fx unit is not used. In case of LADSPA unit, think of it as subgroups on a mixer. +\endhtmlonly + + +\section settings_synth_chorus_active synth.chorus.active + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + When set to 1 (TRUE) the chorus effects module is activated. Otherwise, no chorus will be added to the output signal. Note that the amount of signal sent to the chorus module depends on the "chorus send" generator defined in the SoundFont. +\endhtmlonly + + +\section settings_synth_chorus_depth synth.chorus.depth + + +\par Type +Float (num)\par Min - Max + 0.0 - 256.0 + +\par Default +\htmlonly +8.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Specifies the modulation depth of the chorus. +\endhtmlonly + + +\section settings_synth_chorus_level synth.chorus.level + + +\par Type +Float (num)\par Min - Max + 0.0 - 10.0 + +\par Default +\htmlonly +2.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Specifies the output amplitude of the chorus signal. +\endhtmlonly + + +\section settings_synth_chorus_nr synth.chorus.nr + + +\par Type +Integer (int)\par Min - Max + 0 - 99 + +\par Default +\htmlonly +3 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the voice count of the chorus. +\endhtmlonly + + +\section settings_synth_chorus_speed synth.chorus.speed + + +\par Type +Float (num)\par Min - Max + 0.1 - 5.0 + +\par Default +\htmlonly +0.3 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the modulation speed in Hz. +\endhtmlonly + + +\section settings_synth_cpu-cores synth.cpu-cores + + +\par Type +Integer (int)\par Min - Max + 1 - 256 + +\par Default +\htmlonly +1 +\endhtmlonly + + + +\htmlonly + + Sets the number of synthesis CPU cores. If set to a value greater than 1, additional synthesis threads will be created to do the actual rendering work that is then returned synchronously by the render function. This has the affect of utilizing more of the total CPU for voices or decreasing render times when synthesizing audio. + So for example, if you set cpu-cores to 4, fluidsynth will attempt to split the synthesis work it needs to do between the client's calling thread and three additional (internal) worker threads. As soon as all threads have done their work, their results are collected and the resulting buffer is returned to the caller. + +\endhtmlonly + + +\section settings_synth_default-soundfont synth.default-soundfont + + +\par Type +String (str) + +\par Default +\htmlonly +C:\soundfonts\default.sf2 (Windows),
+ ${CMAKE_INSTALL_PREFIX}/share/soundfonts/default.sf2 (all others)
+\endhtmlonly + + + +\htmlonly + + The default soundfont file to use by the fluidsynth executable. The default value can be overridden during compilation time by setting the DEFAULT_SOUNDFONT cmake variable. +\endhtmlonly + + +\section settings_synth_device-id synth.device-id + + +\par Type +Integer (int)\par Min - Max + 0 - 126 + +\par Default +\htmlonly +0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Device identifier used for SYSEX commands, such as MIDI Tuning Standard commands. Only those SYSEX commands destined for this ID or to all devices will be acted upon. +\endhtmlonly + + +\section settings_synth_dynamic-sample-loading synth.dynamic-sample-loading + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + When set to 1 (TRUE), samples are loaded to and unloaded from memory + on demand. + +\endhtmlonly + + +\section settings_synth_effects-channels synth.effects-channels + + +\par Type +Integer (int)\par Min - Max + 2 - 2 + +\par Default +\htmlonly +2 +\endhtmlonly + + + +\htmlonly +Specifies the number of effects per effects group. Currently this value can not be changed so there are always two effects per group available (reverb and chorus). +\endhtmlonly + + +\section settings_synth_effects-groups synth.effects-groups + + +\par Type +Integer (int)\par Min - Max + 1 - 128 + +\par Default +\htmlonly +1 +\endhtmlonly + + + +\htmlonly +Specifies the number of effects groups. By default, the sound of all voices is rendered by one reverb and one chorus effect respectively (even for multi-channel rendering). This setting gives the user control which effects of a voice to render to which independent audio channels. E.g. setting synth.effects-groups == synth.midi-channels allows to render the effects of each MIDI channel to separate audio buffers. If synth.effects-groups is smaller than the number of MIDI channels, it will wrap around. Note that any value >1 will significantly increase CPU usage. +\endhtmlonly + + +\section settings_synth_gain synth.gain + + +\par Type +Float (num)\par Min - Max + 0.0 - 10.0 + +\par Default +\htmlonly +0.2 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly +The gain is applied to the final or master output of the synthesizer. It is set to a low value by default to avoid the saturation of the output when many notes are played. +\endhtmlonly + + +\section settings_synth_ladspa_active synth.ladspa.active + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + When set to 1 (TRUE) the LADSPA subsystem will be enabled. This subsystem allows to load and interconnect LADSPA plug-ins. The output of the synthesizer is processed by the LADSPA subsystem. Note that the synthesizer has to be compiled with LADSPA support. More information about the LADSPA subsystem can be found in doc/ladspa.md or on the FluidSynth website. +\endhtmlonly + + +\section settings_synth_lock-memory synth.lock-memory + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + + +\htmlonly + + Page-lock memory that contains audio sample data, if true. +\endhtmlonly + + +\section settings_synth_midi-channels synth.midi-channels + + +\par Type +Integer (int)\par Min - Max + 16 - 256 + +\par Default +\htmlonly +16 +\endhtmlonly + + + +\htmlonly + + This setting defines the number of MIDI channels of the synthesizer. The MIDI standard defines 16 channels, so MIDI hardware is limited to this number. Internally FluidSynth can use more channels which can be mapped to different MIDI sources. +\endhtmlonly + + +\section settings_synth_midi-bank-select synth.midi-bank-select + + +\par Type +Selection (str)\par Options + gs, gm, xg, mma + +\par Default +\htmlonly +gs +\endhtmlonly + + + +\htmlonly + + This setting defines how the synthesizer interprets Bank Select messages. +
    +
  • gs: (default) CC0 becomes the bank number, CC32 is ignored.
  • +
  • gm: ignores CC0 and CC32 messages.
  • +
  • xg: CC32 becomes the bank number, CC0 toggles between melodic or drum channel.
  • +
  • mma: bank is calculated as CC0*128+CC32.
  • +
+
+\endhtmlonly + + +\section settings_synth_min-note-length synth.min-note-length + + +\par Type +Integer (int)\par Min - Max + 0 - 65535 + +\par Default +\htmlonly +10 +\endhtmlonly + + + +\htmlonly + + Sets the minimum note duration in milliseconds. This ensures that really short duration note events, such as percussion notes, have a better chance of sounding as intended. Set to 0 to disable this feature. +\endhtmlonly + + +\section settings_synth_overflow_age synth.overflow.age + + +\par Type +Float (num)\par Min - Max + -10000.0 - 10000.0 + +\par Default +\htmlonly +1000.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + This score is divided by the number of seconds this voice has been + active and is added to the overflow priority. It is usually a positive + value and gives voices which have just been started a higher priority, + making them less likely to be killed in an overflow situation. + +\endhtmlonly + + +\section settings_synth_overflow_important synth.overflow.important + + +\par Type +Float (num)\par Min - Max + -50000.0 - 50000.0 + +\par Default +\htmlonly +5000.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + This score is added to voices on channels marked with the + synth.overflow.important-channels setting. + +\endhtmlonly + + +\section settings_synth_overflow_important-channels synth.overflow.important-channels + + +\par Type +String (str) + +\par Default +\htmlonly +(empty string) +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + This setting is a comma-separated list of MIDI channel numbers that should + be treated as "important" by the overflow calculation, adding the score + set by synth.overflow.important to each voice on those channels. It can + be used to make voices on particular MIDI channels + less likely (synth.overflow.important > 0) or more likely + (synth.overflow.important < 0) to be killed in an overflow situation. Channel + numbers are 1-based, so the first MIDI channel is number 1. + +\endhtmlonly + + +\section settings_synth_overflow_percussion synth.overflow.percussion + + +\par Type +Float (num)\par Min - Max + -10000.0 - 10000.0 + +\par Default +\htmlonly +4000.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the overflow priority score added to voices on a percussion + channel. This is usually a positive score, to give percussion voices + a higher priority and less chance of being killed in an overflow + situation. + +\endhtmlonly + + +\section settings_synth_overflow_released synth.overflow.released + + +\par Type +Float (num)\par Min - Max + -10000.0 - 10000.0 + +\par Default +\htmlonly +-2000.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the overflow priority score added to voices that have already + received a note-off event. This is usually a negative score, to give released + voices a lower priority so that they are killed first in an overflow + situation. + +\endhtmlonly + + +\section settings_synth_overflow_sustained synth.overflow.sustained + + +\par Type +Float (num)\par Min - Max + -10000.0 - 10000.0 + +\par Default +\htmlonly +-1000.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the overflow priority score added to voices that are currently + sustained. With the default value, sustained voices are considered less + important and are more likely to be killed in an overflow situation. + +\endhtmlonly + + +\section settings_synth_overflow_volume synth.overflow.volume + + +\par Type +Float (num)\par Min - Max + -10000.0 - 10000.0 + +\par Default +\htmlonly +500.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the overflow priority score added to voices based on their current + volume. The voice volume is normalized to a value between 0 and 1 and + multiplied with this setting. So voices with maximum volume get added + the full score, voices with only half that volume get added half of this + score. + +\endhtmlonly + + +\section settings_synth_polyphony synth.polyphony + + +\par Type +Integer (int)\par Min - Max + 1 - 65535 + +\par Default +\htmlonly +256 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + The polyphony defines how many voices can be played in parallel. A note event produces one or more voices. Its good to set this to a value which the system can handle and will thus limit FluidSynth's CPU usage. When FluidSynth runs out of voices it will begin terminating lower priority voices for new note events. +\endhtmlonly + + +\section settings_synth_reverb_active synth.reverb.active + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + When set to 1 (TRUE) the reverb effects module is activated. Otherwise, no reverb will be added to the output signal. Note that the amount of signal sent to the reverb module depends on the "reverb send" generator defined in the SoundFont. + +\endhtmlonly + + +\section settings_synth_reverb_damp synth.reverb.damp + + +\par Type +Float (num)\par Min - Max + 0.0 - 1.0 + +\par Default +\htmlonly +0.0 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the amount of reverb damping. +\endhtmlonly + + +\section settings_synth_reverb_level synth.reverb.level + + +\par Type +Float (num)\par Min - Max + 0.0 - 1.0 + +\par Default +\htmlonly +0.9 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the reverb output amplitude. +\endhtmlonly + + +\section settings_synth_reverb_room-size synth.reverb.room-size + + +\par Type +Float (num)\par Min - Max + 0.0 - 1.0 + +\par Default +\htmlonly +0.2 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the room size (i.e. amount of wet) reverb. +\endhtmlonly + + +\section settings_synth_reverb_width synth.reverb.width + + +\par Type +Float (num)\par Min - Max + 0.0 - 100.0 + +\par Default +\htmlonly +0.5 +\endhtmlonly + + +\par Real-time +This setting can be changed during runtime of the synthesizer. + +\htmlonly + + Sets the stereo spread of the reverb signal. +\endhtmlonly + + +\section settings_synth_sample-rate synth.sample-rate + + +\par Type +Float (num)\par Min - Max + 8000.0 - 96000.0 + +\par Default +\htmlonly +44100.0 +\endhtmlonly + + + +\htmlonly + + The sample rate of the audio generated by the synthesizer. + +\endhtmlonly + + +\section settings_synth_threadsafe-api synth.threadsafe-api + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +1 (TRUE) +\endhtmlonly + + + +\htmlonly + + Controls whether the synth's public API is protected by a mutex or not. Default is on, turn it off for slightly better performance if you know you're only accessing the synth from one thread only, this could be the case in many embedded use cases for example. Note that libfluidsynth can use many threads by itself (shell is one, midi driver is one, midi player is one etc) so you should usually leave it on. + +\endhtmlonly + + +\section settings_synth_verbose synth.verbose + + +\par Type +Boolean (int)\par Values + 0, 1 + +\par Default +\htmlonly +0 (FALSE) +\endhtmlonly + + + +\htmlonly + + When set to 1 (TRUE) the synthesizer will print out information about the received MIDI events to the stdout. This can be helpful for debugging. This setting cannot be changed after the synthesizer has started. + +\endhtmlonly + + +*/ + + diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 7f66ab2a..4cca7702 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -14,6 +14,11 @@ https://stackoverflow.com/a/6251757 Developers: - Settings can be deprecated by adding: SOME TEXT - Real-time settings can be marked with SOME OPTIONAL TEXT. + +ATTENTION: if you change anything in this file, make sure you also refresh +the doxygen/fluidsettings.txt file (used by fluidsynth.org API doc build) +and commit the results. Refresh with the following command: + xsltproc -o doxygen/fluidsettings.txt doxygen/fluidsettings.xsl fluidsettings.xml --> From 5ccc7543aeb4d5bd246e1d2d46fc0de44942b2c4 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 16:12:17 +0100 Subject: [PATCH 42/55] Hide nav sync toggle button --- doc/doxygen/custom.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 214ee2c3..226716b9 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -75,6 +75,10 @@ span.permalink a { } +#nav-sync { + display: none; +} + #nav-tree, div.header, .ui-resizable-e, From a03362a155dea1ad6aac6c70e0eae0e69c8410c8 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 16:13:10 +0100 Subject: [PATCH 43/55] Style improvements for small screens - hide side nav - hide search box - make content full height --- doc/doxygen/custom.css | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 226716b9..71203457 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -61,7 +61,8 @@ span.permalink a { display: none; } -#nav-tree +#nav-tree, +#side-nav { background: #333; } @@ -74,11 +75,33 @@ span.permalink a { background: #666; } +#side-nav { + width: 350px; +} + +.ui-resizable-e { + background: #ddd; +} #nav-sync { display: none; } +@media (max-width: 767px) { + #side-nav { + display: none !important; + } + #doc-content { + margin-left: 0 !important; + height: auto !important; + width: auto !important; + } + + #MSearchBox { + display: none !important; + } +} + #nav-tree, div.header, .ui-resizable-e, From 6113c15f94c4a4f56cf9bf031963783938803baf Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 16:22:50 +0100 Subject: [PATCH 44/55] Improve styling of field tables (enum values) --- doc/doxygen/custom.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 71203457..ba5430f9 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -184,6 +184,16 @@ code { white-space: nowrap; } +table.fieldtable, +table.fieldtable td, +table.fieldtable th +{ + box-shadow: none; + text-shadow: none; + border-radius: 0; + border-color: #DFE5F1 !important; +} + div.fragment { padding: 5px; } From d0db0cc1192787674893eabcce31b6751ab899d0 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 16:27:43 +0100 Subject: [PATCH 45/55] Document how to revert the styling and layout changes --- doc/Doxyfile | 13 +++++++++---- doc/Doxyfile.cmake | 13 +++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index b71b742b..1324f728 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -72,7 +72,6 @@ SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = -LAYOUT_FILE = ../doc/doxygen/layout.xml #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -132,15 +131,21 @@ IGNORE_PREFIX = GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = ../doc/doxygen/footer.html -HTML_EXTRA_STYLESHEET = ../doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ ../doc/fluidsettings.xml \ ../doc/fluidsettings.xsl \ ../doc/FluidMixer.pdf +# FluidSynth specific layout and style customizations. +# Comment the following four lines to return to the +# default doxygen styling and layout +LAYOUT_FILE = ../doc/doxygen/layout.xml +HTML_HEADER = +HTML_FOOTER = ../doc/doxygen/footer.html +HTML_EXTRA_STYLESHEET = ../doc/doxygen/custom.css +# end FluidSynth styling and layout + GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index d8f925c0..0e8b9735 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -72,7 +72,6 @@ SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = -LAYOUT_FILE = @CMAKE_SOURCE_DIR@/doc/doxygen/layout.xml #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- @@ -133,15 +132,21 @@ IGNORE_PREFIX = GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html -HTML_HEADER = -HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/doxygen/footer.html -HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxygen/custom.css HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl \ @CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf +# FluidSynth specific layout and style customizations. +# Comment the following four lines to return to the +# default doxygen styling and layout +LAYOUT_FILE = @CMAKE_SOURCE_DIR@/doc/doxygen/layout.xml +HTML_HEADER = +HTML_FOOTER = @CMAKE_SOURCE_DIR@/doc/doxygen/footer.html +HTML_EXTRA_STYLESHEET = @CMAKE_SOURCE_DIR@/doc/doxygen/custom.css +# end FluidSynth styling and layout + GENERATE_HTMLHELP = NO GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" From 961579774dc1a4378d688d6918cb7d7b86c78510 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 16:54:01 +0100 Subject: [PATCH 46/55] Add documentation hints to style guide --- CONTRIBUTING.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe1ce407..30fef1fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,3 +38,60 @@ Find FluidSynth's style guide below. Syntax related issues, like missing braces, * Every block after an if, else, while or for should be enclosed in braces * **Allman-Style** braces everywhere +### Documentation Style Guide +We use Doxygen for public API functions, usage examples and other +information. + +#### Order of Elements +Please ensure that the order of elements in the documentation block +is consistent with the existing documentation. Most importantly, +each function starts with a single sentence brief description, +followed by any `@param` and `@return` tags. `@deprecated` and +`@since` should always come last. + +Example: +``` +/** + * Brief description of the function (only a single sentence). + * + * @param ... + * @param ... + * @return ... + * + * Detailed description of the function. This can be multiple paragraphs, + * include code examples etc. It can also include @note, @warning or + * other special tags not mentioned below. + * + * @deprecated This is deprecated because ... + * + * @since 1.2.3 + */ +``` + +#### Mark Lifecycle Functions +All functions are sorted alphabetically in the generated API documentation. +To ensure that the `new_*` and `delete_*` functions appear first, please make +sure to surround those lifecycle functions with `@startlifecycle{}` and +`@endlifecycle` tags. + +Example: +``` +/** @startlifecycle{Some Name} */ +new_fluid_some_name(...); +delete_fluid_some_name(...); +/** @endlifecycle */ +``` + +#### Referencing Setting Items +If you want to mention a setting item (for example `audio.periods`), +please use the custom `@setting{name}` tag. The argument `name` should be +the setting name with all `.` replaced by `_`. + +Example: +``` +/** + * This is a comment that references \setting{audio_periods}. You + * can also link to a group of settings with \setting{audio} or + * \setting{synth}. + */ +``` From a279b2b397c91738fb5a83f95f81ba993ee5e635 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 17:10:06 +0100 Subject: [PATCH 47/55] Make top links black on hover, not white --- doc/doxygen/custom.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index ba5430f9..8b2aca31 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -102,6 +102,10 @@ span.permalink a { } } +.sm-dox a:hover { + color: black !important; +} + #nav-tree, div.header, .ui-resizable-e, From 36280b9788973ed4605cbc384c8f7d3ae4633358 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 17:10:26 +0100 Subject: [PATCH 48/55] Add missing group brief descriptions --- include/fluidsynth/synth.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/fluidsynth/synth.h b/include/fluidsynth/synth.h index 48aaed37..e4794319 100644 --- a/include/fluidsynth/synth.h +++ b/include/fluidsynth/synth.h @@ -109,6 +109,8 @@ FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t *synth, int chan, int par * @defgroup voice_control Synthesis Voice Control * @ingroup synth * + * Low-level access to synthesis voices. + * * @{ */ FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t *synth, unsigned int id, @@ -129,6 +131,8 @@ FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t *synth, * @defgroup soundfont_management SoundFont Management * @ingroup synth * + * Functions to load and unload SoundFonts. + * * @{ */ FLUIDSYNTH_API @@ -209,6 +213,9 @@ FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t *synth); /* see flu * @defgroup synthesis_params Synthesis Parameters * @ingroup synth * + * Functions to control and query synthesis parameters like gain and + * polyphony count. + * * @{ */ FLUIDSYNTH_API int fluid_synth_count_midi_channels(fluid_synth_t *synth); From 3d930ffda44770c03dca3327d04f18e3b69b3141 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 17:12:39 +0100 Subject: [PATCH 49/55] Remove debug leftover --- doc/examples/fluidsynth_simple.c | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/examples/fluidsynth_simple.c b/doc/examples/fluidsynth_simple.c index 0e8180a4..23bfff3b 100644 --- a/doc/examples/fluidsynth_simple.c +++ b/doc/examples/fluidsynth_simple.c @@ -17,7 +17,6 @@ int main(int argc, char **argv) { - asdf fluid_settings_t *settings; fluid_synth_t *synth = NULL; fluid_audio_driver_t *adriver = NULL; From 0a23598126822cb5acae1a3c90efa7dac1a3e8b2 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 18:32:39 +0100 Subject: [PATCH 50/55] Remove obsolete doxygen config options --- doc/Doxyfile.cmake | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 0e8b9735..cb24ed85 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -68,7 +68,6 @@ GENERATE_DEPRECATEDLIST = YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES -SHOW_DIRECTORIES = NO SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = @@ -132,7 +131,6 @@ IGNORE_PREFIX = GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html -HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl \ @@ -199,8 +197,6 @@ MAN_LINKS = NO #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml -XML_SCHEMA = -XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output @@ -242,7 +238,7 @@ CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = NO -DOT_FONTNAME = FreeSans +DOT_FONTNAME = DOT_FONTPATH = CLASS_GRAPH = YES COLLABORATION_GRAPH = YES From 7d994ff9be98cf5448c5a855e571a1ba00c6b1f6 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 18:46:17 +0100 Subject: [PATCH 51/55] Add intro text to deprecated list --- doc/fluidsynth-v20-devdoc.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index 7cd2bf67..68b532d1 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -39,6 +39,13 @@ What is FluidSynth? - FluidSynth is open source, in active development. For more details, take a look at http://www.fluidsynth.org + +\page deprecated Deprecated Functions + +This page contains functions that have been marked obsolete. +Functions listed here will be removed in the next major release. +It is therefore not wise to use them in new code. + */ /*! From 42f91986cd12ceeee5a9807e31853c08ba4723b4 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sat, 14 Nov 2020 21:16:23 +0100 Subject: [PATCH 52/55] Use SVG for fluid mixer image --- doc/Doxyfile | 3 +- doc/Doxyfile.cmake | 3 +- doc/FluidMixer.pdf | Bin 10726 -> 0 bytes doc/doxygen/custom.css | 4 +- doc/images/fluid_mixer.png | Bin 67853 -> 0 bytes doc/images/fluid_mixer.svg | 2174 +++++++++++++++++++++++++++++++++++ doc/usage/multi_channel.txt | 2 +- 7 files changed, 2180 insertions(+), 6 deletions(-) delete mode 100644 doc/FluidMixer.pdf delete mode 100644 doc/images/fluid_mixer.png create mode 100644 doc/images/fluid_mixer.svg diff --git a/doc/Doxyfile b/doc/Doxyfile index 1324f728..ade2d9d0 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -134,8 +134,7 @@ HTML_FILE_EXTENSION = .html HTML_ALIGN_MEMBERS = YES HTML_EXTRA_FILES = \ ../doc/fluidsettings.xml \ -../doc/fluidsettings.xsl \ -../doc/FluidMixer.pdf +../doc/fluidsettings.xsl # FluidSynth specific layout and style customizations. # Comment the following four lines to return to the diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index cb24ed85..f01222a2 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -133,8 +133,7 @@ HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_EXTRA_FILES = \ @CMAKE_SOURCE_DIR@/doc/fluidsettings.xml \ -@CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl \ -@CMAKE_SOURCE_DIR@/doc/FluidMixer.pdf +@CMAKE_SOURCE_DIR@/doc/fluidsettings.xsl # FluidSynth specific layout and style customizations. # Comment the following four lines to return to the diff --git a/doc/FluidMixer.pdf b/doc/FluidMixer.pdf deleted file mode 100644 index bdc740f7f02c5d0f0ec096a916045696bdb54f73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10726 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 diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 8b2aca31..5e6b7d35 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -251,7 +251,9 @@ table.directory tr { border-bottom: 1px solid #eee; } -.image img { +.image img, +.image object +{ width: 100%; height: auto; } diff --git a/doc/images/fluid_mixer.png b/doc/images/fluid_mixer.png deleted file mode 100644 index 5387203e559f2e60dd0fd853a10fd6850b527e0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67853 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

;pi zn8%u7Vd06X+*QzfLSbO@`cpJ9|7q{vF7VYuFR!s!Rq|ocyK)^{%lWWYY#NZDY_MAv z#Sdi0pv4l@KZ%Z(Dmq7cZg&GD*7bW6qtO}m{Y1J6^~}Ky*;^2+cjV8r-flg5d;i7i ztajCF{RT7Yy5Y4!`{8kI^P2Gxr+VzK9p+^J%tWxWzX%;`iU{|ZUN%CaRZPz`GyfBR zCDAsLI2xI$)oNb)=(njPC$x#8+D^}kPGqx6%dpTtbJ8G&EO1^L*dslk48~LcJk~98 zkpKP?)8JsTef!$ztamKR7IjQ86CF^1M>jb%{#zUiF}t>OEBx<=%8)+|B8__{5rh$f zTxxKi@n4^cZoH+Am547O!{uZpaS<;23n#G}_&Ef`IGr*CrcqUMk@Sb|#A|-`3J(z^ z;f8U@Fn#w$uONzUD5iYEnJcqo7U`!+a+(B+$7s9>Q6s>(0@}X(^y5FXDh{oC8cqo& z+<8@ITMI!R=z1O}{TyVr?3jhc1@BwY4S0Qgm1G3cTgcH)M%mM>0|!v+iGy~G;ESef z7H{Agqa-wi`bSb~y@iUj_vSA2t5TlXN?zJc%}}7G_&`ERK1-t8*lpnVwFWi@@A=(B(WY?zop+!Z6c(WL`ZO}%?y}z2^jG$$ zPPrvbtrrqvl#2=Pf;sGvL7n5upOMH}s`Pv|V*NWMyf^?WvXX2T#)sL*;q58E3%|I= zsK!~6yFL{IOim~&FW!a>231hzz6hE+TYc66%9%e$Op-68GL&q4oVREFWzynH(*rmo z^ZV-;lm(t9I1+cWJrN<{7O%kQY$4d`#hf)pfIpik^ULk0bHNk;sUZOJn}qYufrHI! z9Q3Wvu^!4N51&8>Du|C-ES)4~h!|FeLxUk$zgyklpZdWF!Z}s-m|ytT_rI0kQ$9`h z;?!#i0h80mfXAQFCETz6_O)oQ^V_2#AeyNgS+}BV`YQzgJ>&CM@czHawbZUhxBx4~ z!cUww`8-@SUD1!J*j>bF>*F;~k`MWZri4SM#k7w?o)XVc{4*_BKzFwXmIYJRxc)0< z7hgby+y1qnDigEI*@C}A1LEsx`iODBHJ<7;e3UV&V2T3td}iN%J-^6qq%9aN>(mhL zub=*#0{CZn5W)XInI$~u_FHS`4d1O&Uoj>-$X4_z%;Sz9xYZ~6?pyB{(wooL9Z75G z{~gsU*P_A>48@=Gl}fBvXXp&f-JpzpbsU8DSL=zKci&$R4gdFVHxWA6O?q +image/svg+xmlVoice +Voice +Voice +Voice +Voice +Voice +send +pan +m +s +map MIDI chan-to-buf +map MIDI chan-to-fx +m +m +fx buf 1 +fx buf k +buf n ++ ++ +map fx1-to-bufmix / replace +m +s +s +s +s +Mixer +MIDInotes +Synthesis +map fxk-to-buf +Audio driver +Hostaudiodriver +Hostaudiodriver +synth.effects-groups = k +synth.audio-groups = n +synth.audio-channels = c +s +s +Fx units +fx1 +fx1 +in 1 +in c +buf 1 +fxk +fxk ++ ++ +s +s +in +in/out +s +m +s +s +s +s +LADSPA +fx plugin(s) +fx plugin(s) +fxin1 +fxink +Legend +: + +m/ +: mono +s/ +: stereo +buf[1..n] +: dry buffers +fx in[1..k] +: fx inputs +fx_buf[1..k] +: fx outputs +APIfluid_synth_process() +APIfluid_synth_process() +in/out +s +Application +settings ranges +Mappings +•MIDI chan x +to +dry +buf +i: +i = x % synth.audio-groups•MIDI chan x +to fx +unit j: +j = x % synth.effects-groups•fx +j +to +dry +buf +k: +k = j % synth.audio-groups +option +FluidSynth mixer + \ No newline at end of file diff --git a/doc/usage/multi_channel.txt b/doc/usage/multi_channel.txt index 9ef08004..0b750878 100644 --- a/doc/usage/multi_channel.txt +++ b/doc/usage/multi_channel.txt @@ -10,6 +10,6 @@ for information on how to do that: \ref fluidsynth_process.c The following chart illustrates how the voices (produced by MIDI NoteOns) are dispatched or mapped to their dry/effects audio buffers. -\image html fluid_mixer.png "FluidSynth Mixer Chart" +\image html fluid_mixer.svg "FluidSynth Mixer Chart" */ From 9971f9372c3374dbba63efd1ea03c12262e25c30 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Sun, 15 Nov 2020 00:31:32 +0100 Subject: [PATCH 53/55] Workaround for doxygen bug with linebreaks in ALIASES Using \_linebr is not ideal, as it's an internal command. But that seems to be the most compatible way to specify line breaks in ALIASES accross different doxygen versions at the moment. --- doc/Doxyfile.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index f01222a2..879f8547 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -24,8 +24,8 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 -ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" -ALIASES += startlifecycle="\name Lifecycle Functions\n@{" +ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\_linebr@{" +ALIASES += startlifecycle="\name Lifecycle Functions\_linebr@{" ALIASES += endlifecycle="@}" ALIASES += setting{1}="\ref settings_\1" OPTIMIZE_OUTPUT_FOR_C = YES From 602a2bcdc12b21393b1434043085a2d0c1ffe469 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Mon, 16 Nov 2020 22:09:06 +0100 Subject: [PATCH 54/55] Remove unused command alias and sync Doxyfile.cmake and Doxyfile --- doc/Doxyfile | 3 +-- doc/Doxyfile.cmake | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/Doxyfile b/doc/Doxyfile index ade2d9d0..cd418d0b 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -24,8 +24,7 @@ MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 -ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\n@{" -ALIASES += startlifecycle="\name Lifecycle Functions\n@{" +ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\_linebr@{" ALIASES += endlifecycle="@}" ALIASES += setting{1}="\ref settings_\1" OPTIMIZE_OUTPUT_FOR_C = YES diff --git a/doc/Doxyfile.cmake b/doc/Doxyfile.cmake index 879f8547..d3eaefe7 100644 --- a/doc/Doxyfile.cmake +++ b/doc/Doxyfile.cmake @@ -25,7 +25,6 @@ INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 8 ALIASES += startlifecycle{1}="\name Lifecycle Functions for \1\_linebr@{" -ALIASES += startlifecycle="\name Lifecycle Functions\_linebr@{" ALIASES += endlifecycle="@}" ALIASES += setting{1}="\ref settings_\1" OPTIMIZE_OUTPUT_FOR_C = YES From 4a7d7ad688fe4326e35364f0f67e47c9bf4a59b4 Mon Sep 17 00:00:00 2001 From: Marcus Weseloh Date: Mon, 16 Nov 2020 22:29:42 +0100 Subject: [PATCH 55/55] Settings reference style more consistent with rest of reference pages --- doc/doxygen/custom.css | 23 +++++++++++++++++++++++ doc/doxygen/fluidsettings.xsl | 7 ++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/doxygen/custom.css b/doc/doxygen/custom.css index 5e6b7d35..29ee8679 100644 --- a/doc/doxygen/custom.css +++ b/doc/doxygen/custom.css @@ -257,3 +257,26 @@ table.directory tr { width: 100%; height: auto; } + +.setting-item { + border-left: 1px solid #DFE5F1; + padding-bottom: 0.5em; + border-top-left-radius: 4px; +} + +.setting-item h1 { + width: 100%; + padding: 0.3em 0 0.3em 10px; + box-sizing: border-box; + background: #DFE5F1; + border: 0; + font-size: 1.5em; + font-weight: normal; + border-top-right-radius: 4px; + border-top-left-radius: 4px; +} + +.setting-item dl, +.setting-item p { + margin-left: 10px; +} diff --git a/doc/doxygen/fluidsettings.xsl b/doc/doxygen/fluidsettings.xsl index 106498a6..e0ee0496 100644 --- a/doc/doxygen/fluidsettings.xsl +++ b/doc/doxygen/fluidsettings.xsl @@ -34,9 +34,11 @@ +\htmlonly +

+\endhtmlonly \section - \par Type Boolean (int) @@ -84,6 +86,9 @@ \htmlonly \endhtmlonly +\htmlonly +
+\endhtmlonly