From: Rémi Bernon rbernon@codeweavers.com
--- configure.ac | 11 + dlls/dmsynth/Makefile.in | 3 +- dlls/dmsynth/synth.c | 11 + libs/fluidsynth/LICENSE | 504 + libs/fluidsynth/Makefile.in | 42 + libs/fluidsynth/config.h | 271 + libs/fluidsynth/fluid_conv_tables.inc.h | 3916 ++++++++ libs/fluidsynth/fluid_rvoice_dsp_tables.inc.h | 4110 ++++++++ libs/fluidsynth/glib.c | 106 + libs/fluidsynth/glib.h | 107 + libs/fluidsynth/glib/gstdio.h | 0 libs/fluidsynth/include/fluidsynth.h | 121 + libs/fluidsynth/include/fluidsynth/audio.h | 155 + libs/fluidsynth/include/fluidsynth/event.h | 143 + libs/fluidsynth/include/fluidsynth/gen.h | 134 + libs/fluidsynth/include/fluidsynth/ladspa.h | 69 + libs/fluidsynth/include/fluidsynth/log.h | 97 + libs/fluidsynth/include/fluidsynth/midi.h | 295 + libs/fluidsynth/include/fluidsynth/misc.h | 77 + libs/fluidsynth/include/fluidsynth/mod.h | 105 + libs/fluidsynth/include/fluidsynth/seq.h | 92 + libs/fluidsynth/include/fluidsynth/seqbind.h | 45 + libs/fluidsynth/include/fluidsynth/settings.h | 194 + libs/fluidsynth/include/fluidsynth/sfont.h | 362 + libs/fluidsynth/include/fluidsynth/shell.h | 150 + libs/fluidsynth/include/fluidsynth/synth.h | 552 ++ libs/fluidsynth/include/fluidsynth/types.h | 85 + libs/fluidsynth/include/fluidsynth/version.h | 47 + libs/fluidsynth/include/fluidsynth/voice.h | 77 + libs/fluidsynth/src/bindings/fluid_ladspa.h | 36 + libs/fluidsynth/src/midi/fluid_midi.c | 2851 ++++++ libs/fluidsynth/src/midi/fluid_midi.h | 384 + libs/fluidsynth/src/midi/fluid_midi_router.h | 32 + libs/fluidsynth/src/rvoice/fluid_adsr_env.c | 39 + libs/fluidsynth/src/rvoice/fluid_adsr_env.h | 167 + libs/fluidsynth/src/rvoice/fluid_chorus.c | 1071 +++ libs/fluidsynth/src/rvoice/fluid_chorus.h | 80 + libs/fluidsynth/src/rvoice/fluid_iir_filter.c | 419 + libs/fluidsynth/src/rvoice/fluid_iir_filter.h | 75 + libs/fluidsynth/src/rvoice/fluid_lfo.c | 17 + libs/fluidsynth/src/rvoice/fluid_lfo.h | 75 + libs/fluidsynth/src/rvoice/fluid_phase.h | 113 + libs/fluidsynth/src/rvoice/fluid_rev.c | 1523 +++ libs/fluidsynth/src/rvoice/fluid_rev.h | 91 + libs/fluidsynth/src/rvoice/fluid_rvoice.c | 937 ++ libs/fluidsynth/src/rvoice/fluid_rvoice.h | 231 + libs/fluidsynth/src/rvoice/fluid_rvoice_dsp.c | 636 ++ .../src/rvoice/fluid_rvoice_dsp_tables.h | 8 + .../src/rvoice/fluid_rvoice_event.c | 202 + .../src/rvoice/fluid_rvoice_event.h | 114 + .../src/rvoice/fluid_rvoice_mixer.c | 1727 ++++ .../src/rvoice/fluid_rvoice_mixer.h | 88 + libs/fluidsynth/src/sfloader/fluid_defsfont.c | 2372 +++++ libs/fluidsynth/src/sfloader/fluid_defsfont.h | 232 + .../fluidsynth/src/sfloader/fluid_instpatch.h | 14 + .../src/sfloader/fluid_samplecache.c | 313 + .../src/sfloader/fluid_samplecache.h | 37 + libs/fluidsynth/src/sfloader/fluid_sffile.c | 2527 +++++ libs/fluidsynth/src/sfloader/fluid_sffile.h | 194 + libs/fluidsynth/src/sfloader/fluid_sfont.c | 850 ++ libs/fluidsynth/src/sfloader/fluid_sfont.h | 189 + libs/fluidsynth/src/synth/fluid_chan.c | 732 ++ libs/fluidsynth/src/synth/fluid_chan.h | 276 + libs/fluidsynth/src/synth/fluid_event.c | 863 ++ libs/fluidsynth/src/synth/fluid_event.h | 65 + libs/fluidsynth/src/synth/fluid_gen.c | 133 + libs/fluidsynth/src/synth/fluid_gen.h | 67 + libs/fluidsynth/src/synth/fluid_mod.c | 881 ++ libs/fluidsynth/src/synth/fluid_mod.h | 54 + libs/fluidsynth/src/synth/fluid_synth.c | 8445 +++++++++++++++++ libs/fluidsynth/src/synth/fluid_synth.h | 263 + .../src/synth/fluid_synth_monopoly.c | 722 ++ libs/fluidsynth/src/synth/fluid_tuning.c | 190 + libs/fluidsynth/src/synth/fluid_tuning.h | 69 + libs/fluidsynth/src/synth/fluid_voice.c | 2052 ++++ libs/fluidsynth/src/synth/fluid_voice.h | 198 + libs/fluidsynth/src/utils/fluid_conv.c | 334 + libs/fluidsynth/src/utils/fluid_conv.h | 40 + libs/fluidsynth/src/utils/fluid_conv_tables.h | 41 + libs/fluidsynth/src/utils/fluid_hash.c | 1407 +++ libs/fluidsynth/src/utils/fluid_hash.h | 131 + libs/fluidsynth/src/utils/fluid_list.c | 337 + libs/fluidsynth/src/utils/fluid_list.h | 63 + libs/fluidsynth/src/utils/fluid_ringbuffer.c | 90 + libs/fluidsynth/src/utils/fluid_ringbuffer.h | 133 + libs/fluidsynth/src/utils/fluid_settings.c | 2004 ++++ libs/fluidsynth/src/utils/fluid_settings.h | 65 + libs/fluidsynth/src/utils/fluid_sys.c | 1803 ++++ libs/fluidsynth/src/utils/fluid_sys.h | 794 ++ libs/fluidsynth/src/utils/fluidsynth_priv.h | 331 + 90 files changed, 52107 insertions(+), 1 deletion(-) create mode 100644 libs/fluidsynth/LICENSE create mode 100644 libs/fluidsynth/Makefile.in create mode 100644 libs/fluidsynth/config.h create mode 100644 libs/fluidsynth/fluid_conv_tables.inc.h create mode 100644 libs/fluidsynth/fluid_rvoice_dsp_tables.inc.h create mode 100644 libs/fluidsynth/glib.c create mode 100644 libs/fluidsynth/glib.h create mode 100644 libs/fluidsynth/glib/gstdio.h create mode 100644 libs/fluidsynth/include/fluidsynth.h create mode 100644 libs/fluidsynth/include/fluidsynth/audio.h create mode 100644 libs/fluidsynth/include/fluidsynth/event.h create mode 100644 libs/fluidsynth/include/fluidsynth/gen.h create mode 100644 libs/fluidsynth/include/fluidsynth/ladspa.h create mode 100644 libs/fluidsynth/include/fluidsynth/log.h create mode 100644 libs/fluidsynth/include/fluidsynth/midi.h create mode 100644 libs/fluidsynth/include/fluidsynth/misc.h create mode 100644 libs/fluidsynth/include/fluidsynth/mod.h create mode 100644 libs/fluidsynth/include/fluidsynth/seq.h create mode 100644 libs/fluidsynth/include/fluidsynth/seqbind.h create mode 100644 libs/fluidsynth/include/fluidsynth/settings.h create mode 100644 libs/fluidsynth/include/fluidsynth/sfont.h create mode 100644 libs/fluidsynth/include/fluidsynth/shell.h create mode 100644 libs/fluidsynth/include/fluidsynth/synth.h create mode 100644 libs/fluidsynth/include/fluidsynth/types.h create mode 100644 libs/fluidsynth/include/fluidsynth/version.h create mode 100644 libs/fluidsynth/include/fluidsynth/voice.h create mode 100644 libs/fluidsynth/src/bindings/fluid_ladspa.h create mode 100644 libs/fluidsynth/src/midi/fluid_midi.c create mode 100644 libs/fluidsynth/src/midi/fluid_midi.h create mode 100644 libs/fluidsynth/src/midi/fluid_midi_router.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_adsr_env.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_adsr_env.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_chorus.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_chorus.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_iir_filter.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_iir_filter.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_lfo.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_lfo.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_phase.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_rev.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_rev.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_dsp.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_dsp_tables.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_event.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_event.h create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.c create mode 100644 libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.h create mode 100644 libs/fluidsynth/src/sfloader/fluid_defsfont.c create mode 100644 libs/fluidsynth/src/sfloader/fluid_defsfont.h create mode 100644 libs/fluidsynth/src/sfloader/fluid_instpatch.h create mode 100644 libs/fluidsynth/src/sfloader/fluid_samplecache.c create mode 100644 libs/fluidsynth/src/sfloader/fluid_samplecache.h create mode 100644 libs/fluidsynth/src/sfloader/fluid_sffile.c create mode 100644 libs/fluidsynth/src/sfloader/fluid_sffile.h create mode 100644 libs/fluidsynth/src/sfloader/fluid_sfont.c create mode 100644 libs/fluidsynth/src/sfloader/fluid_sfont.h create mode 100644 libs/fluidsynth/src/synth/fluid_chan.c create mode 100644 libs/fluidsynth/src/synth/fluid_chan.h create mode 100644 libs/fluidsynth/src/synth/fluid_event.c create mode 100644 libs/fluidsynth/src/synth/fluid_event.h create mode 100644 libs/fluidsynth/src/synth/fluid_gen.c create mode 100644 libs/fluidsynth/src/synth/fluid_gen.h create mode 100644 libs/fluidsynth/src/synth/fluid_mod.c create mode 100644 libs/fluidsynth/src/synth/fluid_mod.h create mode 100644 libs/fluidsynth/src/synth/fluid_synth.c create mode 100644 libs/fluidsynth/src/synth/fluid_synth.h create mode 100644 libs/fluidsynth/src/synth/fluid_synth_monopoly.c create mode 100644 libs/fluidsynth/src/synth/fluid_tuning.c create mode 100644 libs/fluidsynth/src/synth/fluid_tuning.h create mode 100644 libs/fluidsynth/src/synth/fluid_voice.c create mode 100644 libs/fluidsynth/src/synth/fluid_voice.h create mode 100644 libs/fluidsynth/src/utils/fluid_conv.c create mode 100644 libs/fluidsynth/src/utils/fluid_conv.h create mode 100644 libs/fluidsynth/src/utils/fluid_conv_tables.h create mode 100644 libs/fluidsynth/src/utils/fluid_hash.c create mode 100644 libs/fluidsynth/src/utils/fluid_hash.h create mode 100644 libs/fluidsynth/src/utils/fluid_list.c create mode 100644 libs/fluidsynth/src/utils/fluid_list.h create mode 100644 libs/fluidsynth/src/utils/fluid_ringbuffer.c create mode 100644 libs/fluidsynth/src/utils/fluid_ringbuffer.h create mode 100644 libs/fluidsynth/src/utils/fluid_settings.c create mode 100644 libs/fluidsynth/src/utils/fluid_settings.h create mode 100644 libs/fluidsynth/src/utils/fluid_sys.c create mode 100644 libs/fluidsynth/src/utils/fluid_sys.h create mode 100644 libs/fluidsynth/src/utils/fluidsynth_priv.h
diff --git a/configure.ac b/configure.ac index 7229e25dbed..85781ef124b 100644 --- a/configure.ac +++ b/configure.ac @@ -1028,6 +1028,15 @@ then WINE_NOTICE([FAudio ${notice_platform}MinGW development files not found (or too old); using bundled version.]) fi
+ WINE_MINGW_PACKAGE_FLAGS(FLUIDSYNTH,[fluidsynth],[-lfluidsynth], + [WINE_CHECK_MINGW_HEADER(fluidsynth.h, + [WINE_CHECK_MINGW_LIB(fluidsynth,new_fluid_synth,[:],[FLUIDSYNTH_PE_CFLAGS=""; FLUIDSYNTH_PE_LIBS=""],[$FLUIDSYNTH_PE_LIBS])], + [FLUIDSYNTH_PE_CFLAGS=""; FLUIDSYNTH_PE_LIBS=""])]) + if test "x$FLUIDSYNTH_PE_LIBS" = "x" + then + WINE_NOTICE([Fluidsynth ${notice_platform}MinGW development files not found (or too old); using bundled version.]) + fi + WINE_MINGW_PACKAGE_FLAGS(JPEG,[libjpeg],, [WINE_CHECK_MINGW_HEADER(jpeglib.h, [WINE_CHECK_MINGW_LIB(jpeg,jpeg_start_decompress,[:],[JPEG_PE_CFLAGS=""; JPEG_PE_LIBS=""],[$JPEG_PE_LIBS])], @@ -1138,6 +1147,7 @@ then fi
WINE_EXTLIB_FLAGS(FAUDIO, faudio, "faudio mfplat mfreadwrite mfuuid propsys", "-I$(top_srcdir)/libs/faudio/include") +WINE_EXTLIB_FLAGS(FLUIDSYNTH, fluidsynth, "fluidsynth", "-I$(top_srcdir)/libs/fluidsynth/include") WINE_EXTLIB_FLAGS(GSM, gsm, gsm, "-I$(top_srcdir)/libs/gsm/inc") WINE_EXTLIB_FLAGS(JPEG, jpeg, jpeg, "-I$(top_srcdir)/libs/jpeg") WINE_EXTLIB_FLAGS(JXR, jxr, jxr, "-I$(top_srcdir)/libs/jxr/jxrgluelib -I$(top_srcdir)/libs/jxr/image/sys") @@ -3312,6 +3322,7 @@ WINE_CONFIG_MAKEFILE(libs/dxerr8) WINE_CONFIG_MAKEFILE(libs/dxerr9) WINE_CONFIG_MAKEFILE(libs/dxguid) WINE_CONFIG_MAKEFILE(libs/faudio) +WINE_CONFIG_MAKEFILE(libs/fluidsynth) WINE_CONFIG_MAKEFILE(libs/gsm) WINE_CONFIG_MAKEFILE(libs/jpeg) WINE_CONFIG_MAKEFILE(libs/jxr) diff --git a/dlls/dmsynth/Makefile.in b/dlls/dmsynth/Makefile.in index 276597db5ad..7c6d2db721b 100644 --- a/dlls/dmsynth/Makefile.in +++ b/dlls/dmsynth/Makefile.in @@ -1,5 +1,6 @@ MODULE = dmsynth.dll -IMPORTS = dxguid uuid ole32 advapi32 +IMPORTS = $(FLUIDSYNTH_PE_LIBS) dxguid uuid ole32 advapi32 user32 +EXTRAINCL = $(FLUIDSYNTH_PE_CFLAGS)
C_SRCS = \ dmsynth_main.c \ diff --git a/dlls/dmsynth/synth.c b/dlls/dmsynth/synth.c index a3243d4b683..3727ac8bc79 100644 --- a/dlls/dmsynth/synth.c +++ b/dlls/dmsynth/synth.c @@ -26,6 +26,8 @@
#include "dmsynth_private.h"
+#include <fluidsynth.h> + WINE_DEFAULT_DEBUG_CHANNEL(dmsynth);
static void dump_dmus_instrument(DMUS_INSTRUMENT *instrument) @@ -228,6 +230,8 @@ struct synth
struct list instruments; struct list waves; + + fluid_settings_t *fluid_settings; };
static inline struct synth *impl_from_IDirectMusicSynth8(IDirectMusicSynth8 *iface) @@ -299,6 +303,7 @@ static ULONG WINAPI synth_Release(IDirectMusicSynth8 *iface) wave_release(wave); }
+ delete_fluid_settings(This->fluid_settings); free(This); }
@@ -1084,7 +1089,13 @@ HRESULT synth_create(IUnknown **ret_iface) list_init(&obj->instruments); list_init(&obj->waves);
+ if (!(obj->fluid_settings = new_fluid_settings())) goto failed; + TRACE("Created DirectMusicSynth %p\n", obj); *ret_iface = (IUnknown *)&obj->IDirectMusicSynth8_iface; return S_OK; + +failed: + free(obj); + return E_OUTOFMEMORY; } diff --git a/libs/fluidsynth/LICENSE b/libs/fluidsynth/LICENSE new file mode 100644 index 00000000000..19e307187ac --- /dev/null +++ b/libs/fluidsynth/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +(This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.) + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + 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 Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + {signature of Ty Coon}, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/libs/fluidsynth/Makefile.in b/libs/fluidsynth/Makefile.in new file mode 100644 index 00000000000..8173c6a41b1 --- /dev/null +++ b/libs/fluidsynth/Makefile.in @@ -0,0 +1,42 @@ +EXTLIB = libfluidsynth.a +EXTRADEFS = -DNDEBUG -DWITH_PROFILING -Wno-deprecated-declarations +EXTRAINCL = -I$(srcdir)/src \ + -I$(srcdir)/src/bindings \ + -I$(srcdir)/src/drivers \ + -I$(srcdir)/src/midi \ + -I$(srcdir)/src/rvoice \ + -I$(srcdir)/src/sfloader \ + -I$(srcdir)/src/synth \ + -I$(srcdir)/src/utils \ + $(FLUIDSYNTH_PE_CFLAGS) + +C_SRCS = \ + glib.c \ + src/midi/fluid_midi.c \ + src/rvoice/fluid_adsr_env.c \ + src/rvoice/fluid_chorus.c \ + src/rvoice/fluid_iir_filter.c \ + src/rvoice/fluid_lfo.c \ + src/rvoice/fluid_rev.c \ + src/rvoice/fluid_rvoice.c \ + src/rvoice/fluid_rvoice_dsp.c \ + src/rvoice/fluid_rvoice_event.c \ + src/rvoice/fluid_rvoice_mixer.c \ + src/sfloader/fluid_defsfont.c \ + src/sfloader/fluid_samplecache.c \ + src/sfloader/fluid_sffile.c \ + src/sfloader/fluid_sfont.c \ + src/synth/fluid_chan.c \ + src/synth/fluid_event.c \ + src/synth/fluid_gen.c \ + src/synth/fluid_mod.c \ + src/synth/fluid_synth.c \ + src/synth/fluid_synth_monopoly.c \ + src/synth/fluid_tuning.c \ + src/synth/fluid_voice.c \ + src/utils/fluid_conv.c \ + src/utils/fluid_hash.c \ + src/utils/fluid_list.c \ + src/utils/fluid_ringbuffer.c \ + src/utils/fluid_settings.c \ + src/utils/fluid_sys.c diff --git a/libs/fluidsynth/config.h b/libs/fluidsynth/config.h new file mode 100644 index 00000000000..ec31659c818 --- /dev/null +++ b/libs/fluidsynth/config.h @@ -0,0 +1,271 @@ +#ifndef CONFIG_H +#define CONFIG_H + +/* Define to enable ALSA driver */ +/* #undef ALSA_SUPPORT TRUE */ + +/* Define to activate sound output to files */ +/* #undef AUFILE_SUPPORT 1 */ + +/* whether or not we are supporting CoreAudio */ +/* #undef COREAUDIO_SUPPORT */ + +/* whether or not we are supporting CoreMIDI */ +/* #undef COREMIDI_SUPPORT */ + +/* whether or not we are supporting DART */ +/* #undef DART_SUPPORT */ + +/* Define if building for Mac OS X Darwin */ +/* #undef DARWIN */ + +/* Define if D-Bus support is enabled */ +/* #undef DBUS_SUPPORT 1 */ + +/* Soundfont to load automatically in some use cases */ +/* #undef DEFAULT_SOUNDFONT "/usr/local/share/soundfonts/default.sf2" */ + +/* Define to enable FPE checks */ +/* #undef FPE_CHECK */ + +/* Define to 1 if you have the <arpa/inet.h> header file. */ +/* #undef HAVE_ARPA_INET_H */ + +/* Define to 1 if you have the <errno.h> header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the <fcntl.h> header file. */ +/* #undef HAVE_FCNTL_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <io.h> header file. */ +#define HAVE_IO_H 1 + +/* whether or not we are supporting lash */ +/* #undef HAVE_LASH */ + +/* Define if systemd support is enabled */ +/* #undef SYSTEMD_SUPPORT */ + +/* Define to 1 if you have the <limits.h> header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the <linux/soundcard.h> header file. */ +/* #undef HAVE_LINUX_SOUNDCARD_H */ + +/* Define to 1 if you have the <machine/soundcard.h> header file. */ +/* #undef HAVE_MACHINE_SOUNDCARD_H */ + +/* Define to 1 if you have the <math.h> header file. */ +#define HAVE_MATH_H 1 + +/* Define to 1 if you have the <netinet/in.h> header file. */ +/* #undef HAVE_NETINET_IN_H */ + +/* Define to 1 if you have the <netinet/tcp.h> header file. */ +/* #undef HAVE_NETINET_TCP_H */ + +/* Define if compiling the mixer with multi-thread support */ +#define ENABLE_MIXER_THREADS 1 + +/* Define if compiling with openMP to enable parallel audio rendering */ +/* #undef HAVE_OPENMP */ + +/* Define to 1 if you have the <pthread.h> header file. */ +/* #undef HAVE_PTHREAD_H */ + +/* Define to 1 if you have the <signal.h> header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the <stdarg.h> header file. */ +#define HAVE_STDARG_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdio.h> header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +/* #undef HAVE_STRINGS_H */ + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/mman.h> header file. */ +/* #undef HAVE_SYS_MMAN_H */ + +/* Define to 1 if you have the <sys/socket.h> header file. */ +/* #undef HAVE_SYS_SOCKET_H */ + +/* Define to 1 if you have the <sys/soundcard.h> header file. */ +/* #undef HAVE_SYS_SOUNDCARD_H */ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/time.h> header file. */ +/* #undef HAVE_SYS_TIME_H */ + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +/* #undef HAVE_UNISTD_H */ + +/* Define to 1 if you have the <windows.h> header file. */ +/* #undef HAVE_WINDOWS_H */ + +/* Define to 1 if you have the <getopt.h> header file. */ +/* #undef HAVE_GETOPT_H */ + +/* Define to 1 if you have the inet_ntop() function. */ +/* #undef HAVE_INETNTOP */ + +/* Define to enable JACK driver */ +/* #undef JACK_SUPPORT */ + +/* Define to enable PipeWire driver */ +/* #undef PIPEWIRE_SUPPORT */ + +/* Include the LADSPA Fx unit */ +/* #undef LADSPA */ + +/* Define to enable IPV6 support */ +/* #undef IPV6_SUPPORT */ + +/* Define to enable network support */ +/* #undef NETWORK_SUPPORT */ + +/* Defined when fluidsynth is build in an automated environment, where no MSVC++ Runtime Debug Assertion dialogs should pop up */ +#define NO_GUI 1 + +/* libinstpatch for DLS and GIG */ +/* #undef LIBINSTPATCH_SUPPORT */ + +/* libsndfile has ogg vorbis support */ +/* #undef LIBSNDFILE_HASVORBIS */ + +/* Define to enable libsndfile support */ +/* #undef LIBSNDFILE_SUPPORT */ + +/* Define to enable MidiShare driver */ +/* #undef MIDISHARE_SUPPORT */ + +/* Define if using the MinGW32 environment */ +#define MINGW32 1 + +/* Define to enable OSS driver */ +/* #undef OSS_SUPPORT */ + +/* Define to enable OPENSLES driver */ +/* #undef OPENSLES_SUPPORT */ + +/* Define to enable Oboe driver */ +/* #undef OBOE_SUPPORT */ + +/* Name of package */ +#define PACKAGE "fluidsynth" + +/* Define to the address where bug reports for this package should be sent. */ +/* #undef PACKAGE_BUGREPORT */ + +/* Define to the full name of this package. */ +/* #undef PACKAGE_NAME */ + +/* Define to the full name and version of this package. */ +/* #undef PACKAGE_STRING */ + +/* Define to the one symbol short name of this package. */ +/* #undef PACKAGE_TARNAME */ + +/* Define to the version of this package. */ +/* #undef PACKAGE_VERSION */ + +/* Define to enable PortAudio driver */ +/* #undef PORTAUDIO_SUPPORT */ + +/* Define to enable PulseAudio driver */ +/* #undef PULSE_SUPPORT */ + +/* Define to enable DirectSound driver */ +/* #undef DSOUND_SUPPORT 1 */ + +/* Define to enable Windows WASAPI driver */ +/* #undef WASAPI_SUPPORT 1 */ + +/* Define to enable Windows WaveOut driver */ +/* #undef WAVEOUT_SUPPORT 1 */ + +/* Define to enable Windows MIDI driver */ +/* #undef WINMIDI_SUPPORT */ + +/* Define to enable SDL2 audio driver */ +/* #undef SDL2_SUPPORT */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Soundfont to load for unit testing */ +/* #undef TEST_SOUNDFONT */ + +/* Soundfont to load for UTF-8 unit testing */ +/* #undef TEST_SOUNDFONT_UTF8_1 */ +/* #undef TEST_SOUNDFONT_UTF8_2 */ +/* #undef TEST_MIDI_UTF8 */ + +/* SF3 Soundfont to load for unit testing */ +/* #undef TEST_SOUNDFONT_SF3 */ + +/* Define to enable SIGFPE assertions */ +/* #undef TRAP_ON_FPE */ + +/* Define to do all DSP in single floating point precision */ +/* #undef WITH_FLOAT */ + +/* Define to profile the DSP code */ +/* #undef WITH_PROFILING */ + +/* Define to use the readline library for line editing */ +/* #undef READLINE_SUPPORT */ + +/* Define if the compiler supports VLA */ +#define SUPPORTS_VLA 1 + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +/* #undef WORDS_BIGENDIAN */ + +/* Define to `__inline__' or `__inline' if that's what the C compiler + calls it, or to nothing if 'inline' is not supported under any name. */ +#ifndef __cplusplus +/* #undef inline */ +#endif + +/* Define to 1 if you have the sinf() function. */ +#define HAVE_SINF 1 + +/* Define to 1 if you have the cosf() function. */ +#define HAVE_COSF 1 + +/* Define to 1 if you have the fabsf() function. */ +#define HAVE_FABSF 1 + +/* Define to 1 if you have the powf() function. */ +#define HAVE_POWF 1 + +/* Define to 1 if you have the sqrtf() function. */ +#define HAVE_SQRTF 1 + +/* Define to 1 if you have the logf() function. */ +#define HAVE_LOGF 1 + +/* Define to 1 if you have the socklen_t type. */ +/* #undef HAVE_SOCKLEN_T */ + +#endif /* CONFIG_H */ diff --git a/libs/fluidsynth/fluid_conv_tables.inc.h b/libs/fluidsynth/fluid_conv_tables.inc.h new file mode 100644 index 00000000000..b47372861dc --- /dev/null +++ b/libs/fluidsynth/fluid_conv_tables.inc.h @@ -0,0 +1,3916 @@ +/* THIS FILE HAS BEEN AUTOMATICALLY GENERATED. DO NOT EDIT. */ + +static const fluid_real_t fluid_ct2hz_tab[1200] = { + 6.875000000000000e+00, /* 0 */ + 6.878972302857565e+00, /* 1 */ + 6.882946900870038e+00, /* 2 */ + 6.886923795363534e+00, /* 3 */ + 6.890902987664938e+00, /* 4 */ + 6.894884479101899e+00, /* 5 */ + 6.898868271002832e+00, /* 6 */ + 6.902854364696921e+00, /* 7 */ + 6.906842761514119e+00, /* 8 */ + 6.910833462785147e+00, /* 9 */ + 6.914826469841493e+00, /* 10 */ + 6.918821784015415e+00, /* 11 */ + 6.922819406639942e+00, /* 12 */ + 6.926819339048873e+00, /* 13 */ + 6.930821582576776e+00, /* 14 */ + 6.934826138558993e+00, /* 15 */ + 6.938833008331635e+00, /* 16 */ + 6.942842193231585e+00, /* 17 */ + 6.946853694596501e+00, /* 18 */ + 6.950867513764811e+00, /* 19 */ + 6.954883652075717e+00, /* 20 */ + 6.958902110869197e+00, /* 21 */ + 6.962922891485999e+00, /* 22 */ + 6.966945995267650e+00, /* 23 */ + 6.970971423556450e+00, /* 24 */ + 6.974999177695475e+00, /* 25 */ + 6.979029259028576e+00, /* 26 */ + 6.983061668900382e+00, /* 27 */ + 6.987096408656298e+00, /* 28 */ + 6.991133479642508e+00, /* 29 */ + 6.995172883205969e+00, /* 30 */ + 6.999214620694422e+00, /* 31 */ + 7.003258693456385e+00, /* 32 */ + 7.007305102841153e+00, /* 33 */ + 7.011353850198804e+00, /* 34 */ + 7.015404936880191e+00, /* 35 */ + 7.019458364236954e+00, /* 36 */ + 7.023514133621508e+00, /* 37 */ + 7.027572246387055e+00, /* 38 */ + 7.031632703887573e+00, /* 39 */ + 7.035695507477827e+00, /* 40 */ + 7.039760658513363e+00, /* 41 */ + 7.043828158350510e+00, /* 42 */ + 7.047898008346380e+00, /* 43 */ + 7.051970209858872e+00, /* 44 */ + 7.056044764246666e+00, /* 45 */ + 7.060121672869229e+00, /* 46 */ + 7.064200937086813e+00, /* 47 */ + 7.068282558260457e+00, /* 48 */ + 7.072366537751985e+00, /* 49 */ + 7.076452876924008e+00, /* 50 */ + 7.080541577139924e+00, /* 51 */ + 7.084632639763921e+00, /* 52 */ + 7.088726066160973e+00, /* 53 */ + 7.092821857696842e+00, /* 54 */ + 7.096920015738083e+00, /* 55 */ + 7.101020541652035e+00, /* 56 */ + 7.105123436806832e+00, /* 57 */ + 7.109228702571396e+00, /* 58 */ + 7.113336340315441e+00, /* 59 */ + 7.117446351409471e+00, /* 60 */ + 7.121558737224782e+00, /* 61 */ + 7.125673499133464e+00, /* 62 */ + 7.129790638508400e+00, /* 63 */ + 7.133910156723263e+00, /* 64 */ + 7.138032055152522e+00, /* 65 */ + 7.142156335171443e+00, /* 66 */ + 7.146282998156079e+00, /* 67 */ + 7.150412045483285e+00, /* 68 */ + 7.154543478530709e+00, /* 69 */ + 7.158677298676793e+00, /* 70 */ + 7.162813507300782e+00, /* 71 */ + 7.166952105782710e+00, /* 72 */ + 7.171093095503412e+00, /* 73 */ + 7.175236477844522e+00, /* 74 */ + 7.179382254188470e+00, /* 75 */ + 7.183530425918486e+00, /* 76 */ + 7.187680994418599e+00, /* 77 */ + 7.191833961073638e+00, /* 78 */ + 7.195989327269232e+00, /* 79 */ + 7.200147094391808e+00, /* 80 */ + 7.204307263828600e+00, /* 81 */ + 7.208469836967637e+00, /* 82 */ + 7.212634815197754e+00, /* 83 */ + 7.216802199908588e+00, /* 84 */ + 7.220971992490576e+00, /* 85 */ + 7.225144194334964e+00, /* 86 */ + 7.229318806833796e+00, /* 87 */ + 7.233495831379925e+00, /* 88 */ + 7.237675269367005e+00, /* 89 */ + 7.241857122189496e+00, /* 90 */ + 7.246041391242668e+00, /* 91 */ + 7.250228077922589e+00, /* 92 */ + 7.254417183626143e+00, /* 93 */ + 7.258608709751013e+00, /* 94 */ + 7.262802657695695e+00, /* 95 */ + 7.266999028859490e+00, /* 96 */ + 7.271197824642510e+00, /* 97 */ + 7.275399046445672e+00, /* 98 */ + 7.279602695670708e+00, /* 99 */ + 7.283808773720155e+00, /* 100 */ + 7.288017281997362e+00, /* 101 */ + 7.292228221906491e+00, /* 102 */ + 7.296441594852512e+00, /* 103 */ + 7.300657402241209e+00, /* 104 */ + 7.304875645479175e+00, /* 105 */ + 7.309096325973822e+00, /* 106 */ + 7.313319445133367e+00, /* 107 */ + 7.317545004366849e+00, /* 108 */ + 7.321773005084116e+00, /* 109 */ + 7.326003448695830e+00, /* 110 */ + 7.330236336613471e+00, /* 111 */ + 7.334471670249333e+00, /* 112 */ + 7.338709451016527e+00, /* 113 */ + 7.342949680328980e+00, /* 114 */ + 7.347192359601434e+00, /* 115 */ + 7.351437490249451e+00, /* 116 */ + 7.355685073689412e+00, /* 117 */ + 7.359935111338512e+00, /* 118 */ + 7.364187604614767e+00, /* 119 */ + 7.368442554937015e+00, /* 120 */ + 7.372699963724910e+00, /* 121 */ + 7.376959832398928e+00, /* 122 */ + 7.381222162380364e+00, /* 123 */ + 7.385486955091339e+00, /* 124 */ + 7.389754211954788e+00, /* 125 */ + 7.394023934394475e+00, /* 126 */ + 7.398296123834983e+00, /* 127 */ + 7.402570781701721e+00, /* 128 */ + 7.406847909420918e+00, /* 129 */ + 7.411127508419629e+00, /* 130 */ + 7.415409580125734e+00, /* 131 */ + 7.419694125967937e+00, /* 132 */ + 7.423981147375768e+00, /* 133 */ + 7.428270645779583e+00, /* 134 */ + 7.432562622610564e+00, /* 135 */ + 7.436857079300720e+00, /* 136 */ + 7.441154017282888e+00, /* 137 */ + 7.445453437990733e+00, /* 138 */ + 7.449755342858746e+00, /* 139 */ + 7.454059733322251e+00, /* 140 */ + 7.458366610817398e+00, /* 141 */ + 7.462675976781168e+00, /* 142 */ + 7.466987832651371e+00, /* 143 */ + 7.471302179866649e+00, /* 144 */ + 7.475619019866476e+00, /* 145 */ + 7.479938354091158e+00, /* 146 */ + 7.484260183981829e+00, /* 147 */ + 7.488584510980460e+00, /* 148 */ + 7.492911336529853e+00, /* 149 */ + 7.497240662073646e+00, /* 150 */ + 7.501572489056309e+00, /* 151 */ + 7.505906818923147e+00, /* 152 */ + 7.510243653120298e+00, /* 153 */ + 7.514582993094741e+00, /* 154 */ + 7.518924840294288e+00, /* 155 */ + 7.523269196167584e+00, /* 156 */ + 7.527616062164117e+00, /* 157 */ + 7.531965439734210e+00, /* 158 */ + 7.536317330329021e+00, /* 159 */ + 7.540671735400553e+00, /* 160 */ + 7.545028656401643e+00, /* 161 */ + 7.549388094785967e+00, /* 162 */ + 7.553750052008045e+00, /* 163 */ + 7.558114529523233e+00, /* 164 */ + 7.562481528787732e+00, /* 165 */ + 7.566851051258580e+00, /* 166 */ + 7.571223098393661e+00, /* 167 */ + 7.575597671651699e+00, /* 168 */ + 7.579974772492260e+00, /* 169 */ + 7.584354402375757e+00, /* 170 */ + 7.588736562763443e+00, /* 171 */ + 7.593121255117417e+00, /* 172 */ + 7.597508480900622e+00, /* 173 */ + 7.601898241576849e+00, /* 174 */ + 7.606290538610729e+00, /* 175 */ + 7.610685373467746e+00, /* 176 */ + 7.615082747614226e+00, /* 177 */ + 7.619482662517345e+00, /* 178 */ + 7.623885119645124e+00, /* 179 */ + 7.628290120466435e+00, /* 180 */ + 7.632697666450996e+00, /* 181 */ + 7.637107759069377e+00, /* 182 */ + 7.641520399792996e+00, /* 183 */ + 7.645935590094122e+00, /* 184 */ + 7.650353331445872e+00, /* 185 */ + 7.654773625322218e+00, /* 186 */ + 7.659196473197983e+00, /* 187 */ + 7.663621876548839e+00, /* 188 */ + 7.668049836851313e+00, /* 189 */ + 7.672480355582785e+00, /* 190 */ + 7.676913434221489e+00, /* 191 */ + 7.681349074246512e+00, /* 192 */ + 7.685787277137797e+00, /* 193 */ + 7.690228044376139e+00, /* 194 */ + 7.694671377443195e+00, /* 195 */ + 7.699117277821469e+00, /* 196 */ + 7.703565746994330e+00, /* 197 */ + 7.708016786445998e+00, /* 198 */ + 7.712470397661556e+00, /* 199 */ + 7.716926582126939e+00, /* 200 */ + 7.721385341328946e+00, /* 201 */ + 7.725846676755233e+00, /* 202 */ + 7.730310589894314e+00, /* 203 */ + 7.734777082235564e+00, /* 204 */ + 7.739246155269221e+00, /* 205 */ + 7.743717810486381e+00, /* 206 */ + 7.748192049379002e+00, /* 207 */ + 7.752668873439905e+00, /* 208 */ + 7.757148284162773e+00, /* 209 */ + 7.761630283042153e+00, /* 210 */ + 7.766114871573452e+00, /* 211 */ + 7.770602051252947e+00, /* 212 */ + 7.775091823577775e+00, /* 213 */ + 7.779584190045939e+00, /* 214 */ + 7.784079152156307e+00, /* 215 */ + 7.788576711408616e+00, /* 216 */ + 7.793076869303465e+00, /* 217 */ + 7.797579627342324e+00, /* 218 */ + 7.802084987027528e+00, /* 219 */ + 7.806592949862282e+00, /* 220 */ + 7.811103517350658e+00, /* 221 */ + 7.815616690997596e+00, /* 222 */ + 7.820132472308910e+00, /* 223 */ + 7.824650862791279e+00, /* 224 */ + 7.829171863952255e+00, /* 225 */ + 7.833695477300261e+00, /* 226 */ + 7.838221704344591e+00, /* 227 */ + 7.842750546595412e+00, /* 228 */ + 7.847282005563763e+00, /* 229 */ + 7.851816082761554e+00, /* 230 */ + 7.856352779701573e+00, /* 231 */ + 7.860892097897477e+00, /* 232 */ + 7.865434038863802e+00, /* 233 */ + 7.869978604115957e+00, /* 234 */ + 7.874525795170227e+00, /* 235 */ + 7.879075613543772e+00, /* 236 */ + 7.883628060754630e+00, /* 237 */ + 7.888183138321715e+00, /* 238 */ + 7.892740847764820e+00, /* 239 */ + 7.897301190604615e+00, /* 240 */ + 7.901864168362650e+00, /* 241 */ + 7.906429782561352e+00, /* 242 */ + 7.910998034724029e+00, /* 243 */ + 7.915568926374869e+00, /* 244 */ + 7.920142459038940e+00, /* 245 */ + 7.924718634242192e+00, /* 246 */ + 7.929297453511457e+00, /* 247 */ + 7.933878918374448e+00, /* 248 */ + 7.938463030359761e+00, /* 249 */ + 7.943049790996877e+00, /* 250 */ + 7.947639201816158e+00, /* 251 */ + 7.952231264348852e+00, /* 252 */ + 7.956825980127089e+00, /* 253 */ + 7.961423350683890e+00, /* 254 */ + 7.966023377553156e+00, /* 255 */ + 7.970626062269677e+00, /* 256 */ + 7.975231406369129e+00, /* 257 */ + 7.979839411388076e+00, /* 258 */ + 7.984450078863969e+00, /* 259 */ + 7.989063410335147e+00, /* 260 */ + 7.993679407340840e+00, /* 261 */ + 7.998298071421166e+00, /* 262 */ + 8.002919404117131e+00, /* 263 */ + 8.007543406970633e+00, /* 264 */ + 8.012170081524465e+00, /* 265 */ + 8.016799429322301e+00, /* 266 */ + 8.021431451908718e+00, /* 267 */ + 8.026066150829180e+00, /* 268 */ + 8.030703527630045e+00, /* 269 */ + 8.035343583858563e+00, /* 270 */ + 8.039986321062880e+00, /* 271 */ + 8.044631740792035e+00, /* 272 */ + 8.049279844595961e+00, /* 273 */ + 8.053930634025493e+00, /* 274 */ + 8.058584110632353e+00, /* 275 */ + 8.063240275969166e+00, /* 276 */ + 8.067899131589453e+00, /* 277 */ + 8.072560679047628e+00, /* 278 */ + 8.077224919899010e+00, /* 279 */ + 8.081891855699810e+00, /* 280 */ + 8.086561488007144e+00, /* 281 */ + 8.091233818379026e+00, /* 282 */ + 8.095908848374366e+00, /* 283 */ + 8.100586579552981e+00, /* 284 */ + 8.105267013475586e+00, /* 285 */ + 8.109950151703798e+00, /* 286 */ + 8.114635995800136e+00, /* 287 */ + 8.119324547328022e+00, /* 288 */ + 8.124015807851780e+00, /* 289 */ + 8.128709778936644e+00, /* 290 */ + 8.133406462148743e+00, /* 291 */ + 8.138105859055118e+00, /* 292 */ + 8.142807971223712e+00, /* 293 */ + 8.147512800223376e+00, /* 294 */ + 8.152220347623867e+00, /* 295 */ + 8.156930614995847e+00, /* 296 */ + 8.161643603910887e+00, /* 297 */ + 8.166359315941468e+00, /* 298 */ + 8.171077752660976e+00, /* 299 */ + 8.175798915643707e+00, /* 300 */ + 8.180522806464868e+00, /* 301 */ + 8.185249426700578e+00, /* 302 */ + 8.189978777927859e+00, /* 303 */ + 8.194710861724653e+00, /* 304 */ + 8.199445679669807e+00, /* 305 */ + 8.204183233343088e+00, /* 306 */ + 8.208923524325167e+00, /* 307 */ + 8.213666554197633e+00, /* 308 */ + 8.218412324542989e+00, /* 309 */ + 8.223160836944650e+00, /* 310 */ + 8.227912092986951e+00, /* 311 */ + 8.232666094255135e+00, /* 312 */ + 8.237422842335365e+00, /* 313 */ + 8.242182338814722e+00, /* 314 */ + 8.246944585281200e+00, /* 315 */ + 8.251709583323716e+00, /* 316 */ + 8.256477334532098e+00, /* 317 */ + 8.261247840497099e+00, /* 318 */ + 8.266021102810386e+00, /* 319 */ + 8.270797123064552e+00, /* 320 */ + 8.275575902853102e+00, /* 321 */ + 8.280357443770470e+00, /* 322 */ + 8.285141747412004e+00, /* 323 */ + 8.289928815373978e+00, /* 324 */ + 8.294718649253587e+00, /* 325 */ + 8.299511250648951e+00, /* 326 */ + 8.304306621159110e+00, /* 327 */ + 8.309104762384029e+00, /* 328 */ + 8.313905675924600e+00, /* 329 */ + 8.318709363382636e+00, /* 330 */ + 8.323515826360879e+00, /* 331 */ + 8.328325066462993e+00, /* 332 */ + 8.333137085293574e+00, /* 333 */ + 8.337951884458139e+00, /* 334 */ + 8.342769465563139e+00, /* 335 */ + 8.347589830215947e+00, /* 336 */ + 8.352412980024869e+00, /* 337 */ + 8.357238916599140e+00, /* 338 */ + 8.362067641548924e+00, /* 339 */ + 8.366899156485312e+00, /* 340 */ + 8.371733463020332e+00, /* 341 */ + 8.376570562766940e+00, /* 342 */ + 8.381410457339022e+00, /* 343 */ + 8.386253148351402e+00, /* 344 */ + 8.391098637419832e+00, /* 345 */ + 8.395946926161001e+00, /* 346 */ + 8.400798016192528e+00, /* 347 */ + 8.405651909132970e+00, /* 348 */ + 8.410508606601821e+00, /* 349 */ + 8.415368110219505e+00, /* 350 */ + 8.420230421607386e+00, /* 351 */ + 8.425095542387766e+00, /* 352 */ + 8.429963474183879e+00, /* 353 */ + 8.434834218619903e+00, /* 354 */ + 8.439707777320951e+00, /* 355 */ + 8.444584151913077e+00, /* 356 */ + 8.449463344023272e+00, /* 357 */ + 8.454345355279468e+00, /* 358 */ + 8.459230187310540e+00, /* 359 */ + 8.464117841746299e+00, /* 360 */ + 8.469008320217505e+00, /* 361 */ + 8.473901624355852e+00, /* 362 */ + 8.478797755793982e+00, /* 363 */ + 8.483696716165481e+00, /* 364 */ + 8.488598507104875e+00, /* 365 */ + 8.493503130247639e+00, /* 366 */ + 8.498410587230186e+00, /* 367 */ + 8.503320879689882e+00, /* 368 */ + 8.508234009265037e+00, /* 369 */ + 8.513149977594903e+00, /* 370 */ + 8.518068786319684e+00, /* 371 */ + 8.522990437080532e+00, /* 372 */ + 8.527914931519545e+00, /* 373 */ + 8.532842271279769e+00, /* 374 */ + 8.537772458005202e+00, /* 375 */ + 8.542705493340792e+00, /* 376 */ + 8.547641378932433e+00, /* 377 */ + 8.552580116426974e+00, /* 378 */ + 8.557521707472215e+00, /* 379 */ + 8.562466153716908e+00, /* 380 */ + 8.567413456810757e+00, /* 381 */ + 8.572363618404419e+00, /* 382 */ + 8.577316640149505e+00, /* 383 */ + 8.582272523698583e+00, /* 384 */ + 8.587231270705169e+00, /* 385 */ + 8.592192882823742e+00, /* 386 */ + 8.597157361709733e+00, /* 387 */ + 8.602124709019529e+00, /* 388 */ + 8.607094926410477e+00, /* 389 */ + 8.612068015540880e+00, /* 390 */ + 8.617043978069995e+00, /* 391 */ + 8.622022815658045e+00, /* 392 */ + 8.627004529966209e+00, /* 393 */ + 8.631989122656625e+00, /* 394 */ + 8.636976595392392e+00, /* 395 */ + 8.641966949837570e+00, /* 396 */ + 8.646960187657180e+00, /* 397 */ + 8.651956310517207e+00, /* 398 */ + 8.656955320084593e+00, /* 399 */ + 8.661957218027252e+00, /* 400 */ + 8.666962006014057e+00, /* 401 */ + 8.671969685714840e+00, /* 402 */ + 8.676980258800409e+00, /* 403 */ + 8.681993726942528e+00, /* 404 */ + 8.687010091813930e+00, /* 405 */ + 8.692029355088316e+00, /* 406 */ + 8.697051518440352e+00, /* 407 */ + 8.702076583545674e+00, /* 408 */ + 8.707104552080883e+00, /* 409 */ + 8.712135425723552e+00, /* 410 */ + 8.717169206152221e+00, /* 411 */ + 8.722205895046399e+00, /* 412 */ + 8.727245494086567e+00, /* 413 */ + 8.732288004954178e+00, /* 414 */ + 8.737333429331656e+00, /* 415 */ + 8.742381768902394e+00, /* 416 */ + 8.747433025350762e+00, /* 417 */ + 8.752487200362102e+00, /* 418 */ + 8.757544295622727e+00, /* 419 */ + 8.762604312819928e+00, /* 420 */ + 8.767667253641967e+00, /* 421 */ + 8.772733119778087e+00, /* 422 */ + 8.777801912918500e+00, /* 423 */ + 8.782873634754402e+00, /* 424 */ + 8.787948286977960e+00, /* 425 */ + 8.793025871282323e+00, /* 426 */ + 8.798106389361616e+00, /* 427 */ + 8.803189842910941e+00, /* 428 */ + 8.808276233626385e+00, /* 429 */ + 8.813365563205011e+00, /* 430 */ + 8.818457833344864e+00, /* 431 */ + 8.823553045744966e+00, /* 432 */ + 8.828651202105329e+00, /* 433 */ + 8.833752304126937e+00, /* 434 */ + 8.838856353511767e+00, /* 435 */ + 8.843963351962772e+00, /* 436 */ + 8.849073301183891e+00, /* 437 */ + 8.854186202880051e+00, /* 438 */ + 8.859302058757157e+00, /* 439 */ + 8.864420870522107e+00, /* 440 */ + 8.869542639882781e+00, /* 441 */ + 8.874667368548046e+00, /* 442 */ + 8.879795058227758e+00, /* 443 */ + 8.884925710632759e+00, /* 444 */ + 8.890059327474882e+00, /* 445 */ + 8.895195910466947e+00, /* 446 */ + 8.900335461322765e+00, /* 447 */ + 8.905477981757135e+00, /* 448 */ + 8.910623473485851e+00, /* 449 */ + 8.915771938225692e+00, /* 450 */ + 8.920923377694434e+00, /* 451 */ + 8.926077793610846e+00, /* 452 */ + 8.931235187694687e+00, /* 453 */ + 8.936395561666711e+00, /* 454 */ + 8.941558917248665e+00, /* 455 */ + 8.946725256163294e+00, /* 456 */ + 8.951894580134335e+00, /* 457 */ + 8.957066890886521e+00, /* 458 */ + 8.962242190145584e+00, /* 459 */ + 8.967420479638255e+00, /* 460 */ + 8.972601761092255e+00, /* 461 */ + 8.977786036236310e+00, /* 462 */ + 8.982973306800142e+00, /* 463 */ + 8.988163574514473e+00, /* 464 */ + 8.993356841111027e+00, /* 465 */ + 8.998553108322524e+00, /* 466 */ + 9.003752377882689e+00, /* 467 */ + 9.008954651526247e+00, /* 468 */ + 9.014159930988928e+00, /* 469 */ + 9.019368218007461e+00, /* 470 */ + 9.024579514319580e+00, /* 471 */ + 9.029793821664024e+00, /* 472 */ + 9.035011141780535e+00, /* 473 */ + 9.040231476409861e+00, /* 474 */ + 9.045454827293758e+00, /* 475 */ + 9.050681196174985e+00, /* 476 */ + 9.055910584797308e+00, /* 477 */ + 9.061142994905502e+00, /* 478 */ + 9.066378428245352e+00, /* 479 */ + 9.071616886563648e+00, /* 480 */ + 9.076858371608191e+00, /* 481 */ + 9.082102885127791e+00, /* 482 */ + 9.087350428872268e+00, /* 483 */ + 9.092601004592458e+00, /* 484 */ + 9.097854614040202e+00, /* 485 */ + 9.103111258968356e+00, /* 486 */ + 9.108370941130788e+00, /* 487 */ + 9.113633662282384e+00, /* 488 */ + 9.118899424179036e+00, /* 489 */ + 9.124168228577656e+00, /* 490 */ + 9.129440077236168e+00, /* 491 */ + 9.134714971913517e+00, /* 492 */ + 9.139992914369659e+00, /* 493 */ + 9.145273906365567e+00, /* 494 */ + 9.150557949663234e+00, /* 495 */ + 9.155845046025673e+00, /* 496 */ + 9.161135197216907e+00, /* 497 */ + 9.166428405001991e+00, /* 498 */ + 9.171724671146988e+00, /* 499 */ + 9.177023997418987e+00, /* 500 */ + 9.182326385586098e+00, /* 501 */ + 9.187631837417451e+00, /* 502 */ + 9.192940354683200e+00, /* 503 */ + 9.198251939154520e+00, /* 504 */ + 9.203566592603611e+00, /* 505 */ + 9.208884316803697e+00, /* 506 */ + 9.214205113529024e+00, /* 507 */ + 9.219528984554865e+00, /* 508 */ + 9.224855931657519e+00, /* 509 */ + 9.230185956614312e+00, /* 510 */ + 9.235519061203593e+00, /* 511 */ + 9.240855247204744e+00, /* 512 */ + 9.246194516398171e+00, /* 513 */ + 9.251536870565310e+00, /* 514 */ + 9.256882311488630e+00, /* 515 */ + 9.262230840951620e+00, /* 516 */ + 9.267582460738812e+00, /* 517 */ + 9.272937172635757e+00, /* 518 */ + 9.278294978429049e+00, /* 519 */ + 9.283655879906306e+00, /* 520 */ + 9.289019878856182e+00, /* 521 */ + 9.294386977068365e+00, /* 522 */ + 9.299757176333575e+00, /* 523 */ + 9.305130478443569e+00, /* 524 */ + 9.310506885191138e+00, /* 525 */ + 9.315886398370107e+00, /* 526 */ + 9.321269019775343e+00, /* 527 */ + 9.326654751202744e+00, /* 528 */ + 9.332043594449249e+00, /* 529 */ + 9.337435551312835e+00, /* 530 */ + 9.342830623592516e+00, /* 531 */ + 9.348228813088346e+00, /* 532 */ + 9.353630121601423e+00, /* 533 */ + 9.359034550933879e+00, /* 534 */ + 9.364442102888894e+00, /* 535 */ + 9.369852779270683e+00, /* 536 */ + 9.375266581884510e+00, /* 537 */ + 9.380683512536677e+00, /* 538 */ + 9.386103573034532e+00, /* 539 */ + 9.391526765186470e+00, /* 540 */ + 9.396953090801922e+00, /* 541 */ + 9.402382551691376e+00, /* 542 */ + 9.407815149666359e+00, /* 543 */ + 9.413250886539444e+00, /* 544 */ + 9.418689764124254e+00, /* 545 */ + 9.424131784235461e+00, /* 546 */ + 9.429576948688782e+00, /* 547 */ + 9.435025259300986e+00, /* 548 */ + 9.440476717889890e+00, /* 549 */ + 9.445931326274362e+00, /* 550 */ + 9.451389086274322e+00, /* 551 */ + 9.456849999710737e+00, /* 552 */ + 9.462314068405634e+00, /* 553 */ + 9.467781294182085e+00, /* 554 */ + 9.473251678864221e+00, /* 555 */ + 9.478725224277222e+00, /* 556 */ + 9.484201932247325e+00, /* 557 */ + 9.489681804601826e+00, /* 558 */ + 9.495164843169070e+00, /* 559 */ + 9.500651049778460e+00, /* 560 */ + 9.506140426260462e+00, /* 561 */ + 9.511632974446592e+00, /* 562 */ + 9.517128696169429e+00, /* 563 */ + 9.522627593262607e+00, /* 564 */ + 9.528129667560824e+00, /* 565 */ + 9.533634920899836e+00, /* 566 */ + 9.539143355116456e+00, /* 567 */ + 9.544654972048564e+00, /* 568 */ + 9.550169773535101e+00, /* 569 */ + 9.555687761416067e+00, /* 570 */ + 9.561208937532529e+00, /* 571 */ + 9.566733303726613e+00, /* 572 */ + 9.572260861841515e+00, /* 573 */ + 9.577791613721493e+00, /* 574 */ + 9.583325561211870e+00, /* 575 */ + 9.588862706159038e+00, /* 576 */ + 9.594403050410451e+00, /* 577 */ + 9.599946595814636e+00, /* 578 */ + 9.605493344221186e+00, /* 579 */ + 9.611043297480759e+00, /* 580 */ + 9.616596457445088e+00, /* 581 */ + 9.622152825966971e+00, /* 582 */ + 9.627712404900283e+00, /* 583 */ + 9.633275196099962e+00, /* 584 */ + 9.638841201422023e+00, /* 585 */ + 9.644410422723555e+00, /* 586 */ + 9.649982861862712e+00, /* 587 */ + 9.655558520698731e+00, /* 588 */ + 9.661137401091917e+00, /* 589 */ + 9.666719504903652e+00, /* 590 */ + 9.672304833996394e+00, /* 591 */ + 9.677893390233677e+00, /* 592 */ + 9.683485175480111e+00, /* 593 */ + 9.689080191601384e+00, /* 594 */ + 9.694678440464259e+00, /* 595 */ + 9.700279923936582e+00, /* 596 */ + 9.705884643887279e+00, /* 597 */ + 9.711492602186349e+00, /* 598 */ + 9.717103800704876e+00, /* 599 */ + 9.722718241315029e+00, /* 600 */ + 9.728335925890050e+00, /* 601 */ + 9.733956856304269e+00, /* 602 */ + 9.739581034433099e+00, /* 603 */ + 9.745208462153036e+00, /* 604 */ + 9.750839141341658e+00, /* 605 */ + 9.756473073877629e+00, /* 606 */ + 9.762110261640702e+00, /* 607 */ + 9.767750706511709e+00, /* 608 */ + 9.773394410372575e+00, /* 609 */ + 9.779041375106310e+00, /* 610 */ + 9.784691602597013e+00, /* 611 */ + 9.790345094729869e+00, /* 612 */ + 9.796001853391154e+00, /* 613 */ + 9.801661880468235e+00, /* 614 */ + 9.807325177849568e+00, /* 615 */ + 9.812991747424702e+00, /* 616 */ + 9.818661591084274e+00, /* 617 */ + 9.824334710720015e+00, /* 618 */ + 9.830011108224751e+00, /* 619 */ + 9.835690785492401e+00, /* 620 */ + 9.841373744417977e+00, /* 621 */ + 9.847059986897586e+00, /* 622 */ + 9.852749514828432e+00, /* 623 */ + 9.858442330108813e+00, /* 624 */ + 9.864138434638127e+00, /* 625 */ + 9.869837830316865e+00, /* 626 */ + 9.875540519046620e+00, /* 627 */ + 9.881246502730082e+00, /* 628 */ + 9.886955783271041e+00, /* 629 */ + 9.892668362574387e+00, /* 630 */ + 9.898384242546111e+00, /* 631 */ + 9.904103425093302e+00, /* 632 */ + 9.909825912124154e+00, /* 633 */ + 9.915551705547966e+00, /* 634 */ + 9.921280807275133e+00, /* 635 */ + 9.927013219217161e+00, /* 636 */ + 9.932748943286656e+00, /* 637 */ + 9.938487981397330e+00, /* 638 */ + 9.944230335464004e+00, /* 639 */ + 9.949976007402599e+00, /* 640 */ + 9.955724999130149e+00, /* 641 */ + 9.961477312564792e+00, /* 642 */ + 9.967232949625776e+00, /* 643 */ + 9.972991912233459e+00, /* 644 */ + 9.978754202309304e+00, /* 645 */ + 9.984519821775887e+00, /* 646 */ + 9.990288772556898e+00, /* 647 */ + 9.996061056577135e+00, /* 648 */ + 1.000183667576251e+01, /* 649 */ + 1.000761563204004e+01, /* 650 */ + 1.001339792733786e+01, /* 651 */ + 1.001918356358524e+01, /* 652 */ + 1.002497254271253e+01, /* 653 */ + 1.003076486665121e+01, /* 654 */ + 1.003656053733387e+01, /* 655 */ + 1.004235955669425e+01, /* 656 */ + 1.004816192666716e+01, /* 657 */ + 1.005396764918855e+01, /* 658 */ + 1.005977672619549e+01, /* 659 */ + 1.006558915962617e+01, /* 660 */ + 1.007140495141990e+01, /* 661 */ + 1.007722410351709e+01, /* 662 */ + 1.008304661785931e+01, /* 663 */ + 1.008887249638921e+01, /* 664 */ + 1.009470174105059e+01, /* 665 */ + 1.010053435378837e+01, /* 666 */ + 1.010637033654859e+01, /* 667 */ + 1.011220969127841e+01, /* 668 */ + 1.011805241992611e+01, /* 669 */ + 1.012389852444111e+01, /* 670 */ + 1.012974800677396e+01, /* 671 */ + 1.013560086887632e+01, /* 672 */ + 1.014145711270099e+01, /* 673 */ + 1.014731674020188e+01, /* 674 */ + 1.015317975333406e+01, /* 675 */ + 1.015904615405370e+01, /* 676 */ + 1.016491594431812e+01, /* 677 */ + 1.017078912608576e+01, /* 678 */ + 1.017666570131619e+01, /* 679 */ + 1.018254567197013e+01, /* 680 */ + 1.018842904000941e+01, /* 681 */ + 1.019431580739701e+01, /* 682 */ + 1.020020597609703e+01, /* 683 */ + 1.020609954807471e+01, /* 684 */ + 1.021199652529644e+01, /* 685 */ + 1.021789690972973e+01, /* 686 */ + 1.022380070334324e+01, /* 687 */ + 1.022970790810674e+01, /* 688 */ + 1.023561852599116e+01, /* 689 */ + 1.024153255896858e+01, /* 690 */ + 1.024745000901219e+01, /* 691 */ + 1.025337087809634e+01, /* 692 */ + 1.025929516819652e+01, /* 693 */ + 1.026522288128936e+01, /* 694 */ + 1.027115401935261e+01, /* 695 */ + 1.027708858436520e+01, /* 696 */ + 1.028302657830718e+01, /* 697 */ + 1.028896800315975e+01, /* 698 */ + 1.029491286090526e+01, /* 699 */ + 1.030086115352719e+01, /* 700 */ + 1.030681288301017e+01, /* 701 */ + 1.031276805134000e+01, /* 702 */ + 1.031872666050360e+01, /* 703 */ + 1.032468871248905e+01, /* 704 */ + 1.033065420928557e+01, /* 705 */ + 1.033662315288354e+01, /* 706 */ + 1.034259554527449e+01, /* 707 */ + 1.034857138845109e+01, /* 708 */ + 1.035455068440717e+01, /* 709 */ + 1.036053343513771e+01, /* 710 */ + 1.036651964263884e+01, /* 711 */ + 1.037250930890785e+01, /* 712 */ + 1.037850243594318e+01, /* 713 */ + 1.038449902574443e+01, /* 714 */ + 1.039049908031233e+01, /* 715 */ + 1.039650260164880e+01, /* 716 */ + 1.040250959175691e+01, /* 717 */ + 1.040852005264086e+01, /* 718 */ + 1.041453398630604e+01, /* 719 */ + 1.042055139475899e+01, /* 720 */ + 1.042657228000739e+01, /* 721 */ + 1.043259664406012e+01, /* 722 */ + 1.043862448892718e+01, /* 723 */ + 1.044465581661974e+01, /* 724 */ + 1.045069062915016e+01, /* 725 */ + 1.045672892853194e+01, /* 726 */ + 1.046277071677973e+01, /* 727 */ + 1.046881599590938e+01, /* 728 */ + 1.047486476793787e+01, /* 729 */ + 1.048091703488336e+01, /* 730 */ + 1.048697279876519e+01, /* 731 */ + 1.049303206160384e+01, /* 732 */ + 1.049909482542098e+01, /* 733 */ + 1.050516109223943e+01, /* 734 */ + 1.051123086408320e+01, /* 735 */ + 1.051730414297744e+01, /* 736 */ + 1.052338093094850e+01, /* 737 */ + 1.052946123002388e+01, /* 738 */ + 1.053554504223227e+01, /* 739 */ + 1.054163236960350e+01, /* 740 */ + 1.054772321416862e+01, /* 741 */ + 1.055381757795981e+01, /* 742 */ + 1.055991546301045e+01, /* 743 */ + 1.056601687135509e+01, /* 744 */ + 1.057212180502944e+01, /* 745 */ + 1.057823026607040e+01, /* 746 */ + 1.058434225651606e+01, /* 747 */ + 1.059045777840566e+01, /* 748 */ + 1.059657683377963e+01, /* 749 */ + 1.060269942467959e+01, /* 750 */ + 1.060882555314833e+01, /* 751 */ + 1.061495522122981e+01, /* 752 */ + 1.062108843096918e+01, /* 753 */ + 1.062722518441279e+01, /* 754 */ + 1.063336548360814e+01, /* 755 */ + 1.063950933060393e+01, /* 756 */ + 1.064565672745005e+01, /* 757 */ + 1.065180767619755e+01, /* 758 */ + 1.065796217889870e+01, /* 759 */ + 1.066412023760692e+01, /* 760 */ + 1.067028185437685e+01, /* 761 */ + 1.067644703126430e+01, /* 762 */ + 1.068261577032625e+01, /* 763 */ + 1.068878807362090e+01, /* 764 */ + 1.069496394320763e+01, /* 765 */ + 1.070114338114700e+01, /* 766 */ + 1.070732638950076e+01, /* 767 */ + 1.071351297033187e+01, /* 768 */ + 1.071970312570447e+01, /* 769 */ + 1.072589685768389e+01, /* 770 */ + 1.073209416833664e+01, /* 771 */ + 1.073829505973047e+01, /* 772 */ + 1.074449953393427e+01, /* 773 */ + 1.075070759301816e+01, /* 774 */ + 1.075691923905345e+01, /* 775 */ + 1.076313447411263e+01, /* 776 */ + 1.076935330026941e+01, /* 777 */ + 1.077557571959869e+01, /* 778 */ + 1.078180173417656e+01, /* 779 */ + 1.078803134608032e+01, /* 780 */ + 1.079426455738847e+01, /* 781 */ + 1.080050137018071e+01, /* 782 */ + 1.080674178653793e+01, /* 783 */ + 1.081298580854224e+01, /* 784 */ + 1.081923343827694e+01, /* 785 */ + 1.082548467782655e+01, /* 786 */ + 1.083173952927677e+01, /* 787 */ + 1.083799799471452e+01, /* 788 */ + 1.084426007622793e+01, /* 789 */ + 1.085052577590632e+01, /* 790 */ + 1.085679509584024e+01, /* 791 */ + 1.086306803812144e+01, /* 792 */ + 1.086934460484285e+01, /* 793 */ + 1.087562479809866e+01, /* 794 */ + 1.088190861998423e+01, /* 795 */ + 1.088819607259615e+01, /* 796 */ + 1.089448715803220e+01, /* 797 */ + 1.090078187839141e+01, /* 798 */ + 1.090708023577399e+01, /* 799 */ + 1.091338223228137e+01, /* 800 */ + 1.091968787001621e+01, /* 801 */ + 1.092599715108236e+01, /* 802 */ + 1.093231007758490e+01, /* 803 */ + 1.093862665163013e+01, /* 804 */ + 1.094494687532556e+01, /* 805 */ + 1.095127075077993e+01, /* 806 */ + 1.095759828010317e+01, /* 807 */ + 1.096392946540646e+01, /* 808 */ + 1.097026430880218e+01, /* 809 */ + 1.097660281240394e+01, /* 810 */ + 1.098294497832656e+01, /* 811 */ + 1.098929080868611e+01, /* 812 */ + 1.099564030559985e+01, /* 813 */ + 1.100199347118628e+01, /* 814 */ + 1.100835030756511e+01, /* 815 */ + 1.101471081685730e+01, /* 816 */ + 1.102107500118502e+01, /* 817 */ + 1.102744286267166e+01, /* 818 */ + 1.103381440344184e+01, /* 819 */ + 1.104018962562143e+01, /* 820 */ + 1.104656853133749e+01, /* 821 */ + 1.105295112271833e+01, /* 822 */ + 1.105933740189350e+01, /* 823 */ + 1.106572737099377e+01, /* 824 */ + 1.107212103215112e+01, /* 825 */ + 1.107851838749881e+01, /* 826 */ + 1.108491943917128e+01, /* 827 */ + 1.109132418930424e+01, /* 828 */ + 1.109773264003461e+01, /* 829 */ + 1.110414479350058e+01, /* 830 */ + 1.111056065184153e+01, /* 831 */ + 1.111698021719810e+01, /* 832 */ + 1.112340349171218e+01, /* 833 */ + 1.112983047752687e+01, /* 834 */ + 1.113626117678652e+01, /* 835 */ + 1.114269559163672e+01, /* 836 */ + 1.114913372422430e+01, /* 837 */ + 1.115557557669733e+01, /* 838 */ + 1.116202115120513e+01, /* 839 */ + 1.116847044989824e+01, /* 840 */ + 1.117492347492846e+01, /* 841 */ + 1.118138022844883e+01, /* 842 */ + 1.118784071261362e+01, /* 843 */ + 1.119430492957838e+01, /* 844 */ + 1.120077288149986e+01, /* 845 */ + 1.120724457053610e+01, /* 846 */ + 1.121371999884635e+01, /* 847 */ + 1.122019916859113e+01, /* 848 */ + 1.122668208193219e+01, /* 849 */ + 1.123316874103256e+01, /* 850 */ + 1.123965914805649e+01, /* 851 */ + 1.124615330516949e+01, /* 852 */ + 1.125265121453833e+01, /* 853 */ + 1.125915287833101e+01, /* 854 */ + 1.126565829871680e+01, /* 855 */ + 1.127216747786624e+01, /* 856 */ + 1.127868041795107e+01, /* 857 */ + 1.128519712114435e+01, /* 858 */ + 1.129171758962035e+01, /* 859 */ + 1.129824182555462e+01, /* 860 */ + 1.130476983112394e+01, /* 861 */ + 1.131130160850638e+01, /* 862 */ + 1.131783715988125e+01, /* 863 */ + 1.132437648742913e+01, /* 864 */ + 1.133091959333184e+01, /* 865 */ + 1.133746647977249e+01, /* 866 */ + 1.134401714893542e+01, /* 867 */ + 1.135057160300625e+01, /* 868 */ + 1.135712984417187e+01, /* 869 */ + 1.136369187462041e+01, /* 870 */ + 1.137025769654129e+01, /* 871 */ + 1.137682731212518e+01, /* 872 */ + 1.138340072356401e+01, /* 873 */ + 1.138997793305099e+01, /* 874 */ + 1.139655894278060e+01, /* 875 */ + 1.140314375494857e+01, /* 876 */ + 1.140973237175192e+01, /* 877 */ + 1.141632479538892e+01, /* 878 */ + 1.142292102805911e+01, /* 879 */ + 1.142952107196333e+01, /* 880 */ + 1.143612492930366e+01, /* 881 */ + 1.144273260228346e+01, /* 882 */ + 1.144934409310737e+01, /* 883 */ + 1.145595940398131e+01, /* 884 */ + 1.146257853711245e+01, /* 885 */ + 1.146920149470925e+01, /* 886 */ + 1.147582827898146e+01, /* 887 */ + 1.148245889214008e+01, /* 888 */ + 1.148909333639740e+01, /* 889 */ + 1.149573161396700e+01, /* 890 */ + 1.150237372706373e+01, /* 891 */ + 1.150901967790370e+01, /* 892 */ + 1.151566946870432e+01, /* 893 */ + 1.152232310168429e+01, /* 894 */ + 1.152898057906358e+01, /* 895 */ + 1.153564190306344e+01, /* 896 */ + 1.154230707590640e+01, /* 897 */ + 1.154897609981630e+01, /* 898 */ + 1.155564897701822e+01, /* 899 */ + 1.156232570973857e+01, /* 900 */ + 1.156900630020503e+01, /* 901 */ + 1.157569075064656e+01, /* 902 */ + 1.158237906329340e+01, /* 903 */ + 1.158907124037712e+01, /* 904 */ + 1.159576728413052e+01, /* 905 */ + 1.160246719678775e+01, /* 906 */ + 1.160917098058420e+01, /* 907 */ + 1.161587863775658e+01, /* 908 */ + 1.162259017054289e+01, /* 909 */ + 1.162930558118242e+01, /* 910 */ + 1.163602487191574e+01, /* 911 */ + 1.164274804498475e+01, /* 912 */ + 1.164947510263260e+01, /* 913 */ + 1.165620604710378e+01, /* 914 */ + 1.166294088064403e+01, /* 915 */ + 1.166967960550044e+01, /* 916 */ + 1.167642222392135e+01, /* 917 */ + 1.168316873815644e+01, /* 918 */ + 1.168991915045666e+01, /* 919 */ + 1.169667346307427e+01, /* 920 */ + 1.170343167826283e+01, /* 921 */ + 1.171019379827721e+01, /* 922 */ + 1.171695982537358e+01, /* 923 */ + 1.172372976180941e+01, /* 924 */ + 1.173050360984346e+01, /* 925 */ + 1.173728137173583e+01, /* 926 */ + 1.174406304974791e+01, /* 927 */ + 1.175084864614237e+01, /* 928 */ + 1.175763816318322e+01, /* 929 */ + 1.176443160313578e+01, /* 930 */ + 1.177122896826666e+01, /* 931 */ + 1.177803026084377e+01, /* 932 */ + 1.178483548313637e+01, /* 933 */ + 1.179164463741501e+01, /* 934 */ + 1.179845772595153e+01, /* 935 */ + 1.180527475101911e+01, /* 936 */ + 1.181209571489225e+01, /* 937 */ + 1.181892061984674e+01, /* 938 */ + 1.182574946815969e+01, /* 939 */ + 1.183258226210954e+01, /* 940 */ + 1.183941900397603e+01, /* 941 */ + 1.184625969604024e+01, /* 942 */ + 1.185310434058453e+01, /* 943 */ + 1.185995293989262e+01, /* 944 */ + 1.186680549624953e+01, /* 945 */ + 1.187366201194159e+01, /* 946 */ + 1.188052248925647e+01, /* 947 */ + 1.188738693048315e+01, /* 948 */ + 1.189425533791194e+01, /* 949 */ + 1.190112771383447e+01, /* 950 */ + 1.190800406054369e+01, /* 951 */ + 1.191488438033388e+01, /* 952 */ + 1.192176867550065e+01, /* 953 */ + 1.192865694834093e+01, /* 954 */ + 1.193554920115298e+01, /* 955 */ + 1.194244543623637e+01, /* 956 */ + 1.194934565589204e+01, /* 957 */ + 1.195624986242221e+01, /* 958 */ + 1.196315805813046e+01, /* 959 */ + 1.197007024532171e+01, /* 960 */ + 1.197698642630218e+01, /* 961 */ + 1.198390660337945e+01, /* 962 */ + 1.199083077886241e+01, /* 963 */ + 1.199775895506131e+01, /* 964 */ + 1.200469113428772e+01, /* 965 */ + 1.201162731885455e+01, /* 966 */ + 1.201856751107603e+01, /* 967 */ + 1.202551171326775e+01, /* 968 */ + 1.203245992774663e+01, /* 969 */ + 1.203941215683092e+01, /* 970 */ + 1.204636840284023e+01, /* 971 */ + 1.205332866809548e+01, /* 972 */ + 1.206029295491897e+01, /* 973 */ + 1.206726126563430e+01, /* 974 */ + 1.207423360256643e+01, /* 975 */ + 1.208120996804169e+01, /* 976 */ + 1.208819036438771e+01, /* 977 */ + 1.209517479393349e+01, /* 978 */ + 1.210216325900937e+01, /* 979 */ + 1.210915576194704e+01, /* 980 */ + 1.211615230507953e+01, /* 981 */ + 1.212315289074123e+01, /* 982 */ + 1.213015752126786e+01, /* 983 */ + 1.213716619899651e+01, /* 984 */ + 1.214417892626560e+01, /* 985 */ + 1.215119570541492e+01, /* 986 */ + 1.215821653878560e+01, /* 987 */ + 1.216524142872013e+01, /* 988 */ + 1.217227037756235e+01, /* 989 */ + 1.217930338765746e+01, /* 990 */ + 1.218634046135199e+01, /* 991 */ + 1.219338160099387e+01, /* 992 */ + 1.220042680893234e+01, /* 993 */ + 1.220747608751803e+01, /* 994 */ + 1.221452943910292e+01, /* 995 */ + 1.222158686604034e+01, /* 996 */ + 1.222864837068499e+01, /* 997 */ + 1.223571395539292e+01, /* 998 */ + 1.224278362252155e+01, /* 999 */ + 1.224985737442966e+01, /* 1000 */ + 1.225693521347741e+01, /* 1001 */ + 1.226401714202627e+01, /* 1002 */ + 1.227110316243915e+01, /* 1003 */ + 1.227819327708026e+01, /* 1004 */ + 1.228528748831521e+01, /* 1005 */ + 1.229238579851096e+01, /* 1006 */ + 1.229948821003587e+01, /* 1007 */ + 1.230659472525962e+01, /* 1008 */ + 1.231370534655330e+01, /* 1009 */ + 1.232082007628935e+01, /* 1010 */ + 1.232793891684158e+01, /* 1011 */ + 1.233506187058518e+01, /* 1012 */ + 1.234218893989671e+01, /* 1013 */ + 1.234932012715410e+01, /* 1014 */ + 1.235645543473665e+01, /* 1015 */ + 1.236359486502506e+01, /* 1016 */ + 1.237073842040136e+01, /* 1017 */ + 1.237788610324901e+01, /* 1018 */ + 1.238503791595280e+01, /* 1019 */ + 1.239219386089892e+01, /* 1020 */ + 1.239935394047494e+01, /* 1021 */ + 1.240651815706980e+01, /* 1022 */ + 1.241368651307384e+01, /* 1023 */ + 1.242085901087876e+01, /* 1024 */ + 1.242803565287764e+01, /* 1025 */ + 1.243521644146496e+01, /* 1026 */ + 1.244240137903658e+01, /* 1027 */ + 1.244959046798973e+01, /* 1028 */ + 1.245678371072304e+01, /* 1029 */ + 1.246398110963652e+01, /* 1030 */ + 1.247118266713156e+01, /* 1031 */ + 1.247838838561096e+01, /* 1032 */ + 1.248559826747888e+01, /* 1033 */ + 1.249281231514089e+01, /* 1034 */ + 1.250003053100394e+01, /* 1035 */ + 1.250725291747637e+01, /* 1036 */ + 1.251447947696792e+01, /* 1037 */ + 1.252171021188970e+01, /* 1038 */ + 1.252894512465425e+01, /* 1039 */ + 1.253618421767548e+01, /* 1040 */ + 1.254342749336869e+01, /* 1041 */ + 1.255067495415059e+01, /* 1042 */ + 1.255792660243928e+01, /* 1043 */ + 1.256518244065426e+01, /* 1044 */ + 1.257244247121641e+01, /* 1045 */ + 1.257970669654805e+01, /* 1046 */ + 1.258697511907285e+01, /* 1047 */ + 1.259424774121592e+01, /* 1048 */ + 1.260152456540375e+01, /* 1049 */ + 1.260880559406423e+01, /* 1050 */ + 1.261609082962667e+01, /* 1051 */ + 1.262338027452177e+01, /* 1052 */ + 1.263067393118164e+01, /* 1053 */ + 1.263797180203979e+01, /* 1054 */ + 1.264527388953115e+01, /* 1055 */ + 1.265258019609203e+01, /* 1056 */ + 1.265989072416018e+01, /* 1057 */ + 1.266720547617473e+01, /* 1058 */ + 1.267452445457624e+01, /* 1059 */ + 1.268184766180666e+01, /* 1060 */ + 1.268917510030938e+01, /* 1061 */ + 1.269650677252918e+01, /* 1062 */ + 1.270384268091225e+01, /* 1063 */ + 1.271118282790620e+01, /* 1064 */ + 1.271852721596007e+01, /* 1065 */ + 1.272587584752428e+01, /* 1066 */ + 1.273322872505070e+01, /* 1067 */ + 1.274058585099260e+01, /* 1068 */ + 1.274794722780466e+01, /* 1069 */ + 1.275531285794301e+01, /* 1070 */ + 1.276268274386515e+01, /* 1071 */ + 1.277005688803004e+01, /* 1072 */ + 1.277743529289805e+01, /* 1073 */ + 1.278481796093098e+01, /* 1074 */ + 1.279220489459201e+01, /* 1075 */ + 1.279959609634581e+01, /* 1076 */ + 1.280699156865842e+01, /* 1077 */ + 1.281439131399733e+01, /* 1078 */ + 1.282179533483144e+01, /* 1079 */ + 1.282920363363110e+01, /* 1080 */ + 1.283661621286807e+01, /* 1081 */ + 1.284403307501554e+01, /* 1082 */ + 1.285145422254812e+01, /* 1083 */ + 1.285887965794188e+01, /* 1084 */ + 1.286630938367429e+01, /* 1085 */ + 1.287374340222427e+01, /* 1086 */ + 1.288118171607215e+01, /* 1087 */ + 1.288862432769973e+01, /* 1088 */ + 1.289607123959020e+01, /* 1089 */ + 1.290352245422822e+01, /* 1090 */ + 1.291097797409987e+01, /* 1091 */ + 1.291843780169266e+01, /* 1092 */ + 1.292590193949556e+01, /* 1093 */ + 1.293337038999896e+01, /* 1094 */ + 1.294084315569469e+01, /* 1095 */ + 1.294832023907602e+01, /* 1096 */ + 1.295580164263767e+01, /* 1097 */ + 1.296328736887579e+01, /* 1098 */ + 1.297077742028798e+01, /* 1099 */ + 1.297827179937329e+01, /* 1100 */ + 1.298577050863218e+01, /* 1101 */ + 1.299327355056660e+01, /* 1102 */ + 1.300078092767991e+01, /* 1103 */ + 1.300829264247694e+01, /* 1104 */ + 1.301580869746396e+01, /* 1105 */ + 1.302332909514868e+01, /* 1106 */ + 1.303085383804027e+01, /* 1107 */ + 1.303838292864934e+01, /* 1108 */ + 1.304591636948796e+01, /* 1109 */ + 1.305345416306964e+01, /* 1110 */ + 1.306099631190936e+01, /* 1111 */ + 1.306854281852353e+01, /* 1112 */ + 1.307609368543003e+01, /* 1113 */ + 1.308364891514820e+01, /* 1114 */ + 1.309120851019883e+01, /* 1115 */ + 1.309877247310414e+01, /* 1116 */ + 1.310634080638785e+01, /* 1117 */ + 1.311391351257511e+01, /* 1118 */ + 1.312149059419255e+01, /* 1119 */ + 1.312907205376823e+01, /* 1120 */ + 1.313665789383170e+01, /* 1121 */ + 1.314424811691396e+01, /* 1122 */ + 1.315184272554746e+01, /* 1123 */ + 1.315944172226614e+01, /* 1124 */ + 1.316704510960539e+01, /* 1125 */ + 1.317465289010205e+01, /* 1126 */ + 1.318226506629446e+01, /* 1127 */ + 1.318988164072239e+01, /* 1128 */ + 1.319750261592710e+01, /* 1129 */ + 1.320512799445131e+01, /* 1130 */ + 1.321275777883922e+01, /* 1131 */ + 1.322039197163648e+01, /* 1132 */ + 1.322803057539023e+01, /* 1133 */ + 1.323567359264908e+01, /* 1134 */ + 1.324332102596310e+01, /* 1135 */ + 1.325097287788384e+01, /* 1136 */ + 1.325862915096432e+01, /* 1137 */ + 1.326628984775905e+01, /* 1138 */ + 1.327395497082400e+01, /* 1139 */ + 1.328162452271663e+01, /* 1140 */ + 1.328929850599585e+01, /* 1141 */ + 1.329697692322209e+01, /* 1142 */ + 1.330465977695723e+01, /* 1143 */ + 1.331234706976464e+01, /* 1144 */ + 1.332003880420917e+01, /* 1145 */ + 1.332773498285714e+01, /* 1146 */ + 1.333543560827638e+01, /* 1147 */ + 1.334314068303618e+01, /* 1148 */ + 1.335085020970733e+01, /* 1149 */ + 1.335856419086208e+01, /* 1150 */ + 1.336628262907420e+01, /* 1151 */ + 1.337400552691893e+01, /* 1152 */ + 1.338173288697299e+01, /* 1153 */ + 1.338946471181460e+01, /* 1154 */ + 1.339720100402347e+01, /* 1155 */ + 1.340494176618080e+01, /* 1156 */ + 1.341268700086928e+01, /* 1157 */ + 1.342043671067309e+01, /* 1158 */ + 1.342819089817790e+01, /* 1159 */ + 1.343594956597088e+01, /* 1160 */ + 1.344371271664070e+01, /* 1161 */ + 1.345148035277751e+01, /* 1162 */ + 1.345925247697298e+01, /* 1163 */ + 1.346702909182024e+01, /* 1164 */ + 1.347481019991397e+01, /* 1165 */ + 1.348259580385030e+01, /* 1166 */ + 1.349038590622688e+01, /* 1167 */ + 1.349818050964288e+01, /* 1168 */ + 1.350597961669893e+01, /* 1169 */ + 1.351378322999720e+01, /* 1170 */ + 1.352159135214135e+01, /* 1171 */ + 1.352940398573654e+01, /* 1172 */ + 1.353722113338944e+01, /* 1173 */ + 1.354504279770823e+01, /* 1174 */ + 1.355286898130258e+01, /* 1175 */ + 1.356069968678369e+01, /* 1176 */ + 1.356853491676425e+01, /* 1177 */ + 1.357637467385848e+01, /* 1178 */ + 1.358421896068210e+01, /* 1179 */ + 1.359206777985232e+01, /* 1180 */ + 1.359992113398790e+01, /* 1181 */ + 1.360777902570909e+01, /* 1182 */ + 1.361564145763767e+01, /* 1183 */ + 1.362350843239690e+01, /* 1184 */ + 1.363137995261160e+01, /* 1185 */ + 1.363925602090809e+01, /* 1186 */ + 1.364713663991418e+01, /* 1187 */ + 1.365502181225924e+01, /* 1188 */ + 1.366291154057414e+01, /* 1189 */ + 1.367080582749128e+01, /* 1190 */ + 1.367870467564455e+01, /* 1191 */ + 1.368660808766940e+01, /* 1192 */ + 1.369451606620278e+01, /* 1193 */ + 1.370242861388318e+01, /* 1194 */ + 1.371034573335060e+01, /* 1195 */ + 1.371826742724657e+01, /* 1196 */ + 1.372619369821415e+01, /* 1197 */ + 1.373412454889792e+01, /* 1198 */ + 1.374205998194399e+01, /* 1199 */ +}; + +static const fluid_real_t fluid_cb2amp_tab[1441] = { + 1.000000000000000e+00, /* 0 */ + 9.885530946569389e-01, /* 1 */ + 9.772372209558107e-01, /* 2 */ + 9.660508789898133e-01, /* 3 */ + 9.549925860214360e-01, /* 4 */ + 9.440608762859234e-01, /* 5 */ + 9.332543007969910e-01, /* 6 */ + 9.225714271547631e-01, /* 7 */ + 9.120108393559098e-01, /* 8 */ + 9.015711376059569e-01, /* 9 */ + 8.912509381337456e-01, /* 10 */ + 8.810488730080140e-01, /* 11 */ + 8.709635899560806e-01, /* 12 */ + 8.609937521846006e-01, /* 13 */ + 8.511380382023764e-01, /* 14 */ + 8.413951416451951e-01, /* 15 */ + 8.317637711026710e-01, /* 16 */ + 8.222426499470712e-01, /* 17 */ + 8.128305161640993e-01, /* 18 */ + 8.035261221856173e-01, /* 19 */ + 7.943282347242815e-01, /* 20 */ + 7.852356346100717e-01, /* 21 */ + 7.762471166286917e-01, /* 22 */ + 7.673614893618189e-01, /* 23 */ + 7.585775750291838e-01, /* 24 */ + 7.498942093324559e-01, /* 25 */ + 7.413102413009175e-01, /* 26 */ + 7.328245331389041e-01, /* 27 */ + 7.244359600749901e-01, /* 28 */ + 7.161434102129021e-01, /* 29 */ + 7.079457843841379e-01, /* 30 */ + 6.998419960022735e-01, /* 31 */ + 6.918309709189365e-01, /* 32 */ + 6.839116472814293e-01, /* 33 */ + 6.760829753919818e-01, /* 34 */ + 6.683439175686146e-01, /* 35 */ + 6.606934480075960e-01, /* 36 */ + 6.531305526474723e-01, /* 37 */ + 6.456542290346555e-01, /* 38 */ + 6.382634861905487e-01, /* 39 */ + 6.309573444801932e-01, /* 40 */ + 6.237348354824193e-01, /* 41 */ + 6.165950018614822e-01, /* 42 */ + 6.095368972401691e-01, /* 43 */ + 6.025595860743578e-01, /* 44 */ + 5.956621435290105e-01, /* 45 */ + 5.888436553555889e-01, /* 46 */ + 5.821032177708714e-01, /* 47 */ + 5.754399373371569e-01, /* 48 */ + 5.688529308438415e-01, /* 49 */ + 5.623413251903491e-01, /* 50 */ + 5.559042572704036e-01, /* 51 */ + 5.495408738576245e-01, /* 52 */ + 5.432503314924332e-01, /* 53 */ + 5.370317963702528e-01, /* 54 */ + 5.308844442309884e-01, /* 55 */ + 5.248074602497727e-01, /* 56 */ + 5.188000389289611e-01, /* 57 */ + 5.128613839913648e-01, /* 58 */ + 5.069907082747044e-01, /* 59 */ + 5.011872336272722e-01, /* 60 */ + 4.954501908047902e-01, /* 61 */ + 4.897788193684462e-01, /* 62 */ + 4.841723675840993e-01, /* 63 */ + 4.786300923226384e-01, /* 64 */ + 4.731512589614805e-01, /* 65 */ + 4.677351412871982e-01, /* 66 */ + 4.623810213992603e-01, /* 67 */ + 4.570881896148750e-01, /* 68 */ + 4.518559443749224e-01, /* 69 */ + 4.466835921509631e-01, /* 70 */ + 4.415704473533125e-01, /* 71 */ + 4.365158322401660e-01, /* 72 */ + 4.315190768277652e-01, /* 73 */ + 4.265795188015927e-01, /* 74 */ + 4.216965034285822e-01, /* 75 */ + 4.168693834703354e-01, /* 76 */ + 4.120975190973302e-01, /* 77 */ + 4.073802778041127e-01, /* 78 */ + 4.027170343254591e-01, /* 79 */ + 3.981071705534973e-01, /* 80 */ + 3.935500754557775e-01, /* 81 */ + 3.890451449942806e-01, /* 82 */ + 3.845917820453535e-01, /* 83 */ + 3.801893963205612e-01, /* 84 */ + 3.758374042884441e-01, /* 85 */ + 3.715352290971725e-01, /* 86 */ + 3.672823004980846e-01, /* 87 */ + 3.630780547701014e-01, /* 88 */ + 3.589219346450052e-01, /* 89 */ + 3.548133892335755e-01, /* 90 */ + 3.507518739525680e-01, /* 91 */ + 3.467368504525317e-01, /* 92 */ + 3.427677865464503e-01, /* 93 */ + 3.388441561392025e-01, /* 94 */ + 3.349654391578277e-01, /* 95 */ + 3.311311214825911e-01, /* 96 */ + 3.273406948788382e-01, /* 97 */ + 3.235936569296283e-01, /* 98 */ + 3.198895109691398e-01, /* 99 */ + 3.162277660168379e-01, /* 100 */ + 3.126079367123955e-01, /* 101 */ + 3.090295432513591e-01, /* 102 */ + 3.054921113215513e-01, /* 103 */ + 3.019951720402016e-01, /* 104 */ + 2.985382618917959e-01, /* 105 */ + 2.951209226666386e-01, /* 106 */ + 2.917427014001167e-01, /* 107 */ + 2.884031503126606e-01, /* 108 */ + 2.851018267503909e-01, /* 109 */ + 2.818382931264454e-01, /* 110 */ + 2.786121168629770e-01, /* 111 */ + 2.754228703338166e-01, /* 112 */ + 2.722701308077912e-01, /* 113 */ + 2.691534803926915e-01, /* 114 */ + 2.660725059798810e-01, /* 115 */ + 2.630267991895382e-01, /* 116 */ + 2.600159563165272e-01, /* 117 */ + 2.570395782768864e-01, /* 118 */ + 2.540972705549305e-01, /* 119 */ + 2.511886431509580e-01, /* 120 */ + 2.483133105295570e-01, /* 121 */ + 2.454708915685030e-01, /* 122 */ + 2.426610095082415e-01, /* 123 */ + 2.398832919019490e-01, /* 124 */ + 2.371373705661655e-01, /* 125 */ + 2.344228815319922e-01, /* 126 */ + 2.317394649968479e-01, /* 127 */ + 2.290867652767773e-01, /* 128 */ + 2.264644307593060e-01, /* 129 */ + 2.238721138568339e-01, /* 130 */ + 2.213094709605638e-01, /* 131 */ + 2.187761623949553e-01, /* 132 */ + 2.162718523727020e-01, /* 133 */ + 2.137962089502232e-01, /* 134 */ + 2.113489039836647e-01, /* 135 */ + 2.089296130854039e-01, /* 136 */ + 2.065380155810529e-01, /* 137 */ + 2.041737944669529e-01, /* 138 */ + 2.018366363681561e-01, /* 139 */ + 1.995262314968880e-01, /* 140 */ + 1.972422736114854e-01, /* 141 */ + 1.949844599758045e-01, /* 142 */ + 1.927524913190936e-01, /* 143 */ + 1.905460717963247e-01, /* 144 */ + 1.883649089489801e-01, /* 145 */ + 1.862087136662867e-01, /* 146 */ + 1.840772001468956e-01, /* 147 */ + 1.819700858609983e-01, /* 148 */ + 1.798870915128788e-01, /* 149 */ + 1.778279410038923e-01, /* 150 */ + 1.757923613958693e-01, /* 151 */ + 1.737800828749375e-01, /* 152 */ + 1.717908387157588e-01, /* 153 */ + 1.698243652461744e-01, /* 154 */ + 1.678804018122560e-01, /* 155 */ + 1.659586907437561e-01, /* 156 */ + 1.640589773199539e-01, /* 157 */ + 1.621810097358930e-01, /* 158 */ + 1.603245390690042e-01, /* 159 */ + 1.584893192461113e-01, /* 160 */ + 1.566751070108149e-01, /* 161 */ + 1.548816618912481e-01, /* 162 */ + 1.531087461682030e-01, /* 163 */ + 1.513561248436208e-01, /* 164 */ + 1.496235656094433e-01, /* 165 */ + 1.479108388168207e-01, /* 166 */ + 1.462177174456718e-01, /* 167 */ + 1.445439770745927e-01, /* 168 */ + 1.428893958511103e-01, /* 169 */ + 1.412537544622754e-01, /* 170 */ + 1.396368361055938e-01, /* 171 */ + 1.380384264602885e-01, /* 172 */ + 1.364583136588925e-01, /* 173 */ + 1.348962882591654e-01, /* 174 */ + 1.333521432163324e-01, /* 175 */ + 1.318256738556407e-01, /* 176 */ + 1.303166778452299e-01, /* 177 */ + 1.288249551693134e-01, /* 178 */ + 1.273503081016662e-01, /* 179 */ + 1.258925411794167e-01, /* 180 */ + 1.244514611771385e-01, /* 181 */ + 1.230268770812381e-01, /* 182 */ + 1.216186000646368e-01, /* 183 */ + 1.202264434617413e-01, /* 184 */ + 1.188502227437018e-01, /* 185 */ + 1.174897554939529e-01, /* 186 */ + 1.161448613840343e-01, /* 187 */ + 1.148153621496883e-01, /* 188 */ + 1.135010815672315e-01, /* 189 */ + 1.122018454301963e-01, /* 190 */ + 1.109174815262401e-01, /* 191 */ + 1.096478196143185e-01, /* 192 */ + 1.083926914021204e-01, /* 193 */ + 1.071519305237606e-01, /* 194 */ + 1.059253725177289e-01, /* 195 */ + 1.047128548050899e-01, /* 196 */ + 1.035142166679344e-01, /* 197 */ + 1.023292992280754e-01, /* 198 */ + 1.011579454259899e-01, /* 199 */ + 1.000000000000000e-01, /* 200 */ + 9.885530946569389e-02, /* 201 */ + 9.772372209558107e-02, /* 202 */ + 9.660508789898134e-02, /* 203 */ + 9.549925860214359e-02, /* 204 */ + 9.440608762859234e-02, /* 205 */ + 9.332543007969911e-02, /* 206 */ + 9.225714271547632e-02, /* 207 */ + 9.120108393559097e-02, /* 208 */ + 9.015711376059569e-02, /* 209 */ + 8.912509381337455e-02, /* 210 */ + 8.810488730080140e-02, /* 211 */ + 8.709635899560807e-02, /* 212 */ + 8.609937521846006e-02, /* 213 */ + 8.511380382023764e-02, /* 214 */ + 8.413951416451951e-02, /* 215 */ + 8.317637711026710e-02, /* 216 */ + 8.222426499470711e-02, /* 217 */ + 8.128305161640992e-02, /* 218 */ + 8.035261221856173e-02, /* 219 */ + 7.943282347242815e-02, /* 220 */ + 7.852356346100718e-02, /* 221 */ + 7.762471166286918e-02, /* 222 */ + 7.673614893618190e-02, /* 223 */ + 7.585775750291837e-02, /* 224 */ + 7.498942093324558e-02, /* 225 */ + 7.413102413009175e-02, /* 226 */ + 7.328245331389041e-02, /* 227 */ + 7.244359600749900e-02, /* 228 */ + 7.161434102129020e-02, /* 229 */ + 7.079457843841379e-02, /* 230 */ + 6.998419960022735e-02, /* 231 */ + 6.918309709189364e-02, /* 232 */ + 6.839116472814294e-02, /* 233 */ + 6.760829753919818e-02, /* 234 */ + 6.683439175686146e-02, /* 235 */ + 6.606934480075960e-02, /* 236 */ + 6.531305526474723e-02, /* 237 */ + 6.456542290346555e-02, /* 238 */ + 6.382634861905487e-02, /* 239 */ + 6.309573444801933e-02, /* 240 */ + 6.237348354824192e-02, /* 241 */ + 6.165950018614821e-02, /* 242 */ + 6.095368972401691e-02, /* 243 */ + 6.025595860743577e-02, /* 244 */ + 5.956621435290105e-02, /* 245 */ + 5.888436553555890e-02, /* 246 */ + 5.821032177708714e-02, /* 247 */ + 5.754399373371569e-02, /* 248 */ + 5.688529308438414e-02, /* 249 */ + 5.623413251903491e-02, /* 250 */ + 5.559042572704036e-02, /* 251 */ + 5.495408738576246e-02, /* 252 */ + 5.432503314924332e-02, /* 253 */ + 5.370317963702528e-02, /* 254 */ + 5.308844442309883e-02, /* 255 */ + 5.248074602497726e-02, /* 256 */ + 5.188000389289611e-02, /* 257 */ + 5.128613839913648e-02, /* 258 */ + 5.069907082747044e-02, /* 259 */ + 5.011872336272723e-02, /* 260 */ + 4.954501908047902e-02, /* 261 */ + 4.897788193684462e-02, /* 262 */ + 4.841723675840993e-02, /* 263 */ + 4.786300923226383e-02, /* 264 */ + 4.731512589614805e-02, /* 265 */ + 4.677351412871982e-02, /* 266 */ + 4.623810213992603e-02, /* 267 */ + 4.570881896148751e-02, /* 268 */ + 4.518559443749224e-02, /* 269 */ + 4.466835921509631e-02, /* 270 */ + 4.415704473533125e-02, /* 271 */ + 4.365158322401660e-02, /* 272 */ + 4.315190768277652e-02, /* 273 */ + 4.265795188015926e-02, /* 274 */ + 4.216965034285822e-02, /* 275 */ + 4.168693834703354e-02, /* 276 */ + 4.120975190973302e-02, /* 277 */ + 4.073802778041127e-02, /* 278 */ + 4.027170343254591e-02, /* 279 */ + 3.981071705534973e-02, /* 280 */ + 3.935500754557775e-02, /* 281 */ + 3.890451449942806e-02, /* 282 */ + 3.845917820453536e-02, /* 283 */ + 3.801893963205612e-02, /* 284 */ + 3.758374042884442e-02, /* 285 */ + 3.715352290971725e-02, /* 286 */ + 3.672823004980846e-02, /* 287 */ + 3.630780547701013e-02, /* 288 */ + 3.589219346450052e-02, /* 289 */ + 3.548133892335754e-02, /* 290 */ + 3.507518739525680e-02, /* 291 */ + 3.467368504525317e-02, /* 292 */ + 3.427677865464503e-02, /* 293 */ + 3.388441561392026e-02, /* 294 */ + 3.349654391578277e-02, /* 295 */ + 3.311311214825911e-02, /* 296 */ + 3.273406948788382e-02, /* 297 */ + 3.235936569296283e-02, /* 298 */ + 3.198895109691398e-02, /* 299 */ + 3.162277660168379e-02, /* 300 */ + 3.126079367123955e-02, /* 301 */ + 3.090295432513590e-02, /* 302 */ + 3.054921113215513e-02, /* 303 */ + 3.019951720402016e-02, /* 304 */ + 2.985382618917960e-02, /* 305 */ + 2.951209226666386e-02, /* 306 */ + 2.917427014001167e-02, /* 307 */ + 2.884031503126606e-02, /* 308 */ + 2.851018267503909e-02, /* 309 */ + 2.818382931264454e-02, /* 310 */ + 2.786121168629770e-02, /* 311 */ + 2.754228703338166e-02, /* 312 */ + 2.722701308077912e-02, /* 313 */ + 2.691534803926916e-02, /* 314 */ + 2.660725059798810e-02, /* 315 */ + 2.630267991895382e-02, /* 316 */ + 2.600159563165272e-02, /* 317 */ + 2.570395782768864e-02, /* 318 */ + 2.540972705549305e-02, /* 319 */ + 2.511886431509580e-02, /* 320 */ + 2.483133105295570e-02, /* 321 */ + 2.454708915685030e-02, /* 322 */ + 2.426610095082415e-02, /* 323 */ + 2.398832919019490e-02, /* 324 */ + 2.371373705661655e-02, /* 325 */ + 2.344228815319922e-02, /* 326 */ + 2.317394649968479e-02, /* 327 */ + 2.290867652767773e-02, /* 328 */ + 2.264644307593060e-02, /* 329 */ + 2.238721138568340e-02, /* 330 */ + 2.213094709605638e-02, /* 331 */ + 2.187761623949553e-02, /* 332 */ + 2.162718523727020e-02, /* 333 */ + 2.137962089502232e-02, /* 334 */ + 2.113489039836647e-02, /* 335 */ + 2.089296130854040e-02, /* 336 */ + 2.065380155810529e-02, /* 337 */ + 2.041737944669529e-02, /* 338 */ + 2.018366363681561e-02, /* 339 */ + 1.995262314968880e-02, /* 340 */ + 1.972422736114854e-02, /* 341 */ + 1.949844599758045e-02, /* 342 */ + 1.927524913190936e-02, /* 343 */ + 1.905460717963247e-02, /* 344 */ + 1.883649089489800e-02, /* 345 */ + 1.862087136662868e-02, /* 346 */ + 1.840772001468956e-02, /* 347 */ + 1.819700858609984e-02, /* 348 */ + 1.798870915128788e-02, /* 349 */ + 1.778279410038923e-02, /* 350 */ + 1.757923613958693e-02, /* 351 */ + 1.737800828749375e-02, /* 352 */ + 1.717908387157588e-02, /* 353 */ + 1.698243652461744e-02, /* 354 */ + 1.678804018122560e-02, /* 355 */ + 1.659586907437561e-02, /* 356 */ + 1.640589773199539e-02, /* 357 */ + 1.621810097358930e-02, /* 358 */ + 1.603245390690041e-02, /* 359 */ + 1.584893192461113e-02, /* 360 */ + 1.566751070108149e-02, /* 361 */ + 1.548816618912481e-02, /* 362 */ + 1.531087461682030e-02, /* 363 */ + 1.513561248436208e-02, /* 364 */ + 1.496235656094433e-02, /* 365 */ + 1.479108388168207e-02, /* 366 */ + 1.462177174456718e-02, /* 367 */ + 1.445439770745928e-02, /* 368 */ + 1.428893958511103e-02, /* 369 */ + 1.412537544622754e-02, /* 370 */ + 1.396368361055938e-02, /* 371 */ + 1.380384264602885e-02, /* 372 */ + 1.364583136588924e-02, /* 373 */ + 1.348962882591654e-02, /* 374 */ + 1.333521432163324e-02, /* 375 */ + 1.318256738556407e-02, /* 376 */ + 1.303166778452299e-02, /* 377 */ + 1.288249551693134e-02, /* 378 */ + 1.273503081016662e-02, /* 379 */ + 1.258925411794167e-02, /* 380 */ + 1.244514611771385e-02, /* 381 */ + 1.230268770812382e-02, /* 382 */ + 1.216186000646368e-02, /* 383 */ + 1.202264434617413e-02, /* 384 */ + 1.188502227437018e-02, /* 385 */ + 1.174897554939529e-02, /* 386 */ + 1.161448613840343e-02, /* 387 */ + 1.148153621496883e-02, /* 388 */ + 1.135010815672315e-02, /* 389 */ + 1.122018454301963e-02, /* 390 */ + 1.109174815262401e-02, /* 391 */ + 1.096478196143185e-02, /* 392 */ + 1.083926914021204e-02, /* 393 */ + 1.071519305237606e-02, /* 394 */ + 1.059253725177289e-02, /* 395 */ + 1.047128548050900e-02, /* 396 */ + 1.035142166679344e-02, /* 397 */ + 1.023292992280754e-02, /* 398 */ + 1.011579454259899e-02, /* 399 */ + 1.000000000000000e-02, /* 400 */ + 9.885530946569389e-03, /* 401 */ + 9.772372209558107e-03, /* 402 */ + 9.660508789898133e-03, /* 403 */ + 9.549925860214359e-03, /* 404 */ + 9.440608762859234e-03, /* 405 */ + 9.332543007969910e-03, /* 406 */ + 9.225714271547631e-03, /* 407 */ + 9.120108393559097e-03, /* 408 */ + 9.015711376059568e-03, /* 409 */ + 8.912509381337455e-03, /* 410 */ + 8.810488730080140e-03, /* 411 */ + 8.709635899560806e-03, /* 412 */ + 8.609937521846007e-03, /* 413 */ + 8.511380382023764e-03, /* 414 */ + 8.413951416451950e-03, /* 415 */ + 8.317637711026711e-03, /* 416 */ + 8.222426499470711e-03, /* 417 */ + 8.128305161640993e-03, /* 418 */ + 8.035261221856172e-03, /* 419 */ + 7.943282347242816e-03, /* 420 */ + 7.852356346100717e-03, /* 421 */ + 7.762471166286917e-03, /* 422 */ + 7.673614893618189e-03, /* 423 */ + 7.585775750291838e-03, /* 424 */ + 7.498942093324558e-03, /* 425 */ + 7.413102413009175e-03, /* 426 */ + 7.328245331389041e-03, /* 427 */ + 7.244359600749901e-03, /* 428 */ + 7.161434102129020e-03, /* 429 */ + 7.079457843841380e-03, /* 430 */ + 6.998419960022735e-03, /* 431 */ + 6.918309709189364e-03, /* 432 */ + 6.839116472814293e-03, /* 433 */ + 6.760829753919818e-03, /* 434 */ + 6.683439175686146e-03, /* 435 */ + 6.606934480075960e-03, /* 436 */ + 6.531305526474723e-03, /* 437 */ + 6.456542290346555e-03, /* 438 */ + 6.382634861905487e-03, /* 439 */ + 6.309573444801933e-03, /* 440 */ + 6.237348354824193e-03, /* 441 */ + 6.165950018614821e-03, /* 442 */ + 6.095368972401692e-03, /* 443 */ + 6.025595860743578e-03, /* 444 */ + 5.956621435290105e-03, /* 445 */ + 5.888436553555890e-03, /* 446 */ + 5.821032177708714e-03, /* 447 */ + 5.754399373371569e-03, /* 448 */ + 5.688529308438415e-03, /* 449 */ + 5.623413251903491e-03, /* 450 */ + 5.559042572704035e-03, /* 451 */ + 5.495408738576246e-03, /* 452 */ + 5.432503314924332e-03, /* 453 */ + 5.370317963702527e-03, /* 454 */ + 5.308844442309883e-03, /* 455 */ + 5.248074602497726e-03, /* 456 */ + 5.188000389289611e-03, /* 457 */ + 5.128613839913649e-03, /* 458 */ + 5.069907082747044e-03, /* 459 */ + 5.011872336272723e-03, /* 460 */ + 4.954501908047903e-03, /* 461 */ + 4.897788193684462e-03, /* 462 */ + 4.841723675840993e-03, /* 463 */ + 4.786300923226384e-03, /* 464 */ + 4.731512589614805e-03, /* 465 */ + 4.677351412871982e-03, /* 466 */ + 4.623810213992603e-03, /* 467 */ + 4.570881896148750e-03, /* 468 */ + 4.518559443749223e-03, /* 469 */ + 4.466835921509631e-03, /* 470 */ + 4.415704473533125e-03, /* 471 */ + 4.365158322401659e-03, /* 472 */ + 4.315190768277652e-03, /* 473 */ + 4.265795188015927e-03, /* 474 */ + 4.216965034285823e-03, /* 475 */ + 4.168693834703354e-03, /* 476 */ + 4.120975190973302e-03, /* 477 */ + 4.073802778041128e-03, /* 478 */ + 4.027170343254591e-03, /* 479 */ + 3.981071705534973e-03, /* 480 */ + 3.935500754557775e-03, /* 481 */ + 3.890451449942806e-03, /* 482 */ + 3.845917820453536e-03, /* 483 */ + 3.801893963205612e-03, /* 484 */ + 3.758374042884442e-03, /* 485 */ + 3.715352290971725e-03, /* 486 */ + 3.672823004980846e-03, /* 487 */ + 3.630780547701014e-03, /* 488 */ + 3.589219346450052e-03, /* 489 */ + 3.548133892335755e-03, /* 490 */ + 3.507518739525680e-03, /* 491 */ + 3.467368504525316e-03, /* 492 */ + 3.427677865464504e-03, /* 493 */ + 3.388441561392026e-03, /* 494 */ + 3.349654391578276e-03, /* 495 */ + 3.311311214825911e-03, /* 496 */ + 3.273406948788382e-03, /* 497 */ + 3.235936569296282e-03, /* 498 */ + 3.198895109691398e-03, /* 499 */ + 3.162277660168379e-03, /* 500 */ + 3.126079367123955e-03, /* 501 */ + 3.090295432513590e-03, /* 502 */ + 3.054921113215513e-03, /* 503 */ + 3.019951720402016e-03, /* 504 */ + 2.985382618917960e-03, /* 505 */ + 2.951209226666386e-03, /* 506 */ + 2.917427014001167e-03, /* 507 */ + 2.884031503126606e-03, /* 508 */ + 2.851018267503909e-03, /* 509 */ + 2.818382931264454e-03, /* 510 */ + 2.786121168629770e-03, /* 511 */ + 2.754228703338166e-03, /* 512 */ + 2.722701308077912e-03, /* 513 */ + 2.691534803926916e-03, /* 514 */ + 2.660725059798810e-03, /* 515 */ + 2.630267991895382e-03, /* 516 */ + 2.600159563165272e-03, /* 517 */ + 2.570395782768864e-03, /* 518 */ + 2.540972705549305e-03, /* 519 */ + 2.511886431509580e-03, /* 520 */ + 2.483133105295570e-03, /* 521 */ + 2.454708915685030e-03, /* 522 */ + 2.426610095082416e-03, /* 523 */ + 2.398832919019490e-03, /* 524 */ + 2.371373705661655e-03, /* 525 */ + 2.344228815319922e-03, /* 526 */ + 2.317394649968479e-03, /* 527 */ + 2.290867652767773e-03, /* 528 */ + 2.264644307593060e-03, /* 529 */ + 2.238721138568339e-03, /* 530 */ + 2.213094709605638e-03, /* 531 */ + 2.187761623949552e-03, /* 532 */ + 2.162718523727020e-03, /* 533 */ + 2.137962089502232e-03, /* 534 */ + 2.113489039836647e-03, /* 535 */ + 2.089296130854039e-03, /* 536 */ + 2.065380155810529e-03, /* 537 */ + 2.041737944669529e-03, /* 538 */ + 2.018366363681561e-03, /* 539 */ + 1.995262314968880e-03, /* 540 */ + 1.972422736114854e-03, /* 541 */ + 1.949844599758045e-03, /* 542 */ + 1.927524913190936e-03, /* 543 */ + 1.905460717963247e-03, /* 544 */ + 1.883649089489801e-03, /* 545 */ + 1.862087136662868e-03, /* 546 */ + 1.840772001468956e-03, /* 547 */ + 1.819700858609984e-03, /* 548 */ + 1.798870915128788e-03, /* 549 */ + 1.778279410038923e-03, /* 550 */ + 1.757923613958693e-03, /* 551 */ + 1.737800828749375e-03, /* 552 */ + 1.717908387157588e-03, /* 553 */ + 1.698243652461744e-03, /* 554 */ + 1.678804018122560e-03, /* 555 */ + 1.659586907437561e-03, /* 556 */ + 1.640589773199539e-03, /* 557 */ + 1.621810097358930e-03, /* 558 */ + 1.603245390690041e-03, /* 559 */ + 1.584893192461113e-03, /* 560 */ + 1.566751070108149e-03, /* 561 */ + 1.548816618912481e-03, /* 562 */ + 1.531087461682030e-03, /* 563 */ + 1.513561248436208e-03, /* 564 */ + 1.496235656094433e-03, /* 565 */ + 1.479108388168207e-03, /* 566 */ + 1.462177174456718e-03, /* 567 */ + 1.445439770745928e-03, /* 568 */ + 1.428893958511103e-03, /* 569 */ + 1.412537544622754e-03, /* 570 */ + 1.396368361055938e-03, /* 571 */ + 1.380384264602885e-03, /* 572 */ + 1.364583136588925e-03, /* 573 */ + 1.348962882591654e-03, /* 574 */ + 1.333521432163324e-03, /* 575 */ + 1.318256738556407e-03, /* 576 */ + 1.303166778452299e-03, /* 577 */ + 1.288249551693134e-03, /* 578 */ + 1.273503081016662e-03, /* 579 */ + 1.258925411794167e-03, /* 580 */ + 1.244514611771385e-03, /* 581 */ + 1.230268770812381e-03, /* 582 */ + 1.216186000646368e-03, /* 583 */ + 1.202264434617413e-03, /* 584 */ + 1.188502227437018e-03, /* 585 */ + 1.174897554939529e-03, /* 586 */ + 1.161448613840343e-03, /* 587 */ + 1.148153621496883e-03, /* 588 */ + 1.135010815672315e-03, /* 589 */ + 1.122018454301963e-03, /* 590 */ + 1.109174815262401e-03, /* 591 */ + 1.096478196143185e-03, /* 592 */ + 1.083926914021204e-03, /* 593 */ + 1.071519305237606e-03, /* 594 */ + 1.059253725177289e-03, /* 595 */ + 1.047128548050900e-03, /* 596 */ + 1.035142166679344e-03, /* 597 */ + 1.023292992280754e-03, /* 598 */ + 1.011579454259898e-03, /* 599 */ + 1.000000000000000e-03, /* 600 */ + 9.885530946569388e-04, /* 601 */ + 9.772372209558107e-04, /* 602 */ + 9.660508789898134e-04, /* 603 */ + 9.549925860214359e-04, /* 604 */ + 9.440608762859234e-04, /* 605 */ + 9.332543007969911e-04, /* 606 */ + 9.225714271547631e-04, /* 607 */ + 9.120108393559097e-04, /* 608 */ + 9.015711376059569e-04, /* 609 */ + 8.912509381337455e-04, /* 610 */ + 8.810488730080141e-04, /* 611 */ + 8.709635899560806e-04, /* 612 */ + 8.609937521846006e-04, /* 613 */ + 8.511380382023765e-04, /* 614 */ + 8.413951416451951e-04, /* 615 */ + 8.317637711026710e-04, /* 616 */ + 8.222426499470711e-04, /* 617 */ + 8.128305161640993e-04, /* 618 */ + 8.035261221856172e-04, /* 619 */ + 7.943282347242815e-04, /* 620 */ + 7.852356346100718e-04, /* 621 */ + 7.762471166286917e-04, /* 622 */ + 7.673614893618189e-04, /* 623 */ + 7.585775750291837e-04, /* 624 */ + 7.498942093324559e-04, /* 625 */ + 7.413102413009175e-04, /* 626 */ + 7.328245331389041e-04, /* 627 */ + 7.244359600749900e-04, /* 628 */ + 7.161434102129020e-04, /* 629 */ + 7.079457843841379e-04, /* 630 */ + 6.998419960022735e-04, /* 631 */ + 6.918309709189364e-04, /* 632 */ + 6.839116472814293e-04, /* 633 */ + 6.760829753919818e-04, /* 634 */ + 6.683439175686146e-04, /* 635 */ + 6.606934480075960e-04, /* 636 */ + 6.531305526474723e-04, /* 637 */ + 6.456542290346556e-04, /* 638 */ + 6.382634861905487e-04, /* 639 */ + 6.309573444801932e-04, /* 640 */ + 6.237348354824193e-04, /* 641 */ + 6.165950018614822e-04, /* 642 */ + 6.095368972401691e-04, /* 643 */ + 6.025595860743578e-04, /* 644 */ + 5.956621435290105e-04, /* 645 */ + 5.888436553555889e-04, /* 646 */ + 5.821032177708714e-04, /* 647 */ + 5.754399373371570e-04, /* 648 */ + 5.688529308438415e-04, /* 649 */ + 5.623413251903491e-04, /* 650 */ + 5.559042572704036e-04, /* 651 */ + 5.495408738576246e-04, /* 652 */ + 5.432503314924332e-04, /* 653 */ + 5.370317963702527e-04, /* 654 */ + 5.308844442309884e-04, /* 655 */ + 5.248074602497726e-04, /* 656 */ + 5.188000389289611e-04, /* 657 */ + 5.128613839913648e-04, /* 658 */ + 5.069907082747043e-04, /* 659 */ + 5.011872336272723e-04, /* 660 */ + 4.954501908047902e-04, /* 661 */ + 4.897788193684462e-04, /* 662 */ + 4.841723675840993e-04, /* 663 */ + 4.786300923226383e-04, /* 664 */ + 4.731512589614805e-04, /* 665 */ + 4.677351412871982e-04, /* 666 */ + 4.623810213992603e-04, /* 667 */ + 4.570881896148750e-04, /* 668 */ + 4.518559443749224e-04, /* 669 */ + 4.466835921509631e-04, /* 670 */ + 4.415704473533125e-04, /* 671 */ + 4.365158322401659e-04, /* 672 */ + 4.315190768277652e-04, /* 673 */ + 4.265795188015927e-04, /* 674 */ + 4.216965034285822e-04, /* 675 */ + 4.168693834703354e-04, /* 676 */ + 4.120975190973302e-04, /* 677 */ + 4.073802778041127e-04, /* 678 */ + 4.027170343254591e-04, /* 679 */ + 3.981071705534972e-04, /* 680 */ + 3.935500754557775e-04, /* 681 */ + 3.890451449942806e-04, /* 682 */ + 3.845917820453535e-04, /* 683 */ + 3.801893963205612e-04, /* 684 */ + 3.758374042884442e-04, /* 685 */ + 3.715352290971725e-04, /* 686 */ + 3.672823004980847e-04, /* 687 */ + 3.630780547701013e-04, /* 688 */ + 3.589219346450052e-04, /* 689 */ + 3.548133892335755e-04, /* 690 */ + 3.507518739525680e-04, /* 691 */ + 3.467368504525316e-04, /* 692 */ + 3.427677865464504e-04, /* 693 */ + 3.388441561392026e-04, /* 694 */ + 3.349654391578277e-04, /* 695 */ + 3.311311214825911e-04, /* 696 */ + 3.273406948788382e-04, /* 697 */ + 3.235936569296283e-04, /* 698 */ + 3.198895109691398e-04, /* 699 */ + 3.162277660168379e-04, /* 700 */ + 3.126079367123955e-04, /* 701 */ + 3.090295432513590e-04, /* 702 */ + 3.054921113215513e-04, /* 703 */ + 3.019951720402016e-04, /* 704 */ + 2.985382618917959e-04, /* 705 */ + 2.951209226666386e-04, /* 706 */ + 2.917427014001167e-04, /* 707 */ + 2.884031503126606e-04, /* 708 */ + 2.851018267503909e-04, /* 709 */ + 2.818382931264454e-04, /* 710 */ + 2.786121168629771e-04, /* 711 */ + 2.754228703338166e-04, /* 712 */ + 2.722701308077912e-04, /* 713 */ + 2.691534803926916e-04, /* 714 */ + 2.660725059798809e-04, /* 715 */ + 2.630267991895382e-04, /* 716 */ + 2.600159563165272e-04, /* 717 */ + 2.570395782768864e-04, /* 718 */ + 2.540972705549305e-04, /* 719 */ + 2.511886431509580e-04, /* 720 */ + 2.483133105295570e-04, /* 721 */ + 2.454708915685030e-04, /* 722 */ + 2.426610095082416e-04, /* 723 */ + 2.398832919019490e-04, /* 724 */ + 2.371373705661655e-04, /* 725 */ + 2.344228815319922e-04, /* 726 */ + 2.317394649968479e-04, /* 727 */ + 2.290867652767773e-04, /* 728 */ + 2.264644307593060e-04, /* 729 */ + 2.238721138568340e-04, /* 730 */ + 2.213094709605638e-04, /* 731 */ + 2.187761623949553e-04, /* 732 */ + 2.162718523727020e-04, /* 733 */ + 2.137962089502232e-04, /* 734 */ + 2.113489039836647e-04, /* 735 */ + 2.089296130854040e-04, /* 736 */ + 2.065380155810529e-04, /* 737 */ + 2.041737944669529e-04, /* 738 */ + 2.018366363681561e-04, /* 739 */ + 1.995262314968880e-04, /* 740 */ + 1.972422736114854e-04, /* 741 */ + 1.949844599758045e-04, /* 742 */ + 1.927524913190936e-04, /* 743 */ + 1.905460717963247e-04, /* 744 */ + 1.883649089489800e-04, /* 745 */ + 1.862087136662868e-04, /* 746 */ + 1.840772001468956e-04, /* 747 */ + 1.819700858609983e-04, /* 748 */ + 1.798870915128788e-04, /* 749 */ + 1.778279410038923e-04, /* 750 */ + 1.757923613958693e-04, /* 751 */ + 1.737800828749376e-04, /* 752 */ + 1.717908387157588e-04, /* 753 */ + 1.698243652461744e-04, /* 754 */ + 1.678804018122560e-04, /* 755 */ + 1.659586907437561e-04, /* 756 */ + 1.640589773199539e-04, /* 757 */ + 1.621810097358930e-04, /* 758 */ + 1.603245390690042e-04, /* 759 */ + 1.584893192461113e-04, /* 760 */ + 1.566751070108149e-04, /* 761 */ + 1.548816618912481e-04, /* 762 */ + 1.531087461682030e-04, /* 763 */ + 1.513561248436208e-04, /* 764 */ + 1.496235656094433e-04, /* 765 */ + 1.479108388168207e-04, /* 766 */ + 1.462177174456718e-04, /* 767 */ + 1.445439770745927e-04, /* 768 */ + 1.428893958511103e-04, /* 769 */ + 1.412537544622754e-04, /* 770 */ + 1.396368361055938e-04, /* 771 */ + 1.380384264602885e-04, /* 772 */ + 1.364583136588925e-04, /* 773 */ + 1.348962882591654e-04, /* 774 */ + 1.333521432163324e-04, /* 775 */ + 1.318256738556407e-04, /* 776 */ + 1.303166778452299e-04, /* 777 */ + 1.288249551693134e-04, /* 778 */ + 1.273503081016662e-04, /* 779 */ + 1.258925411794167e-04, /* 780 */ + 1.244514611771385e-04, /* 781 */ + 1.230268770812382e-04, /* 782 */ + 1.216186000646368e-04, /* 783 */ + 1.202264434617413e-04, /* 784 */ + 1.188502227437018e-04, /* 785 */ + 1.174897554939530e-04, /* 786 */ + 1.161448613840343e-04, /* 787 */ + 1.148153621496883e-04, /* 788 */ + 1.135010815672315e-04, /* 789 */ + 1.122018454301963e-04, /* 790 */ + 1.109174815262401e-04, /* 791 */ + 1.096478196143185e-04, /* 792 */ + 1.083926914021204e-04, /* 793 */ + 1.071519305237606e-04, /* 794 */ + 1.059253725177289e-04, /* 795 */ + 1.047128548050899e-04, /* 796 */ + 1.035142166679344e-04, /* 797 */ + 1.023292992280754e-04, /* 798 */ + 1.011579454259898e-04, /* 799 */ + 1.000000000000000e-04, /* 800 */ + 9.885530946569389e-05, /* 801 */ + 9.772372209558107e-05, /* 802 */ + 9.660508789898133e-05, /* 803 */ + 9.549925860214359e-05, /* 804 */ + 9.440608762859233e-05, /* 805 */ + 9.332543007969910e-05, /* 806 */ + 9.225714271547631e-05, /* 807 */ + 9.120108393559098e-05, /* 808 */ + 9.015711376059569e-05, /* 809 */ + 8.912509381337455e-05, /* 810 */ + 8.810488730080140e-05, /* 811 */ + 8.709635899560807e-05, /* 812 */ + 8.609937521846007e-05, /* 813 */ + 8.511380382023765e-05, /* 814 */ + 8.413951416451952e-05, /* 815 */ + 8.317637711026710e-05, /* 816 */ + 8.222426499470712e-05, /* 817 */ + 8.128305161640992e-05, /* 818 */ + 8.035261221856173e-05, /* 819 */ + 7.943282347242815e-05, /* 820 */ + 7.852356346100718e-05, /* 821 */ + 7.762471166286918e-05, /* 822 */ + 7.673614893618189e-05, /* 823 */ + 7.585775750291837e-05, /* 824 */ + 7.498942093324559e-05, /* 825 */ + 7.413102413009175e-05, /* 826 */ + 7.328245331389041e-05, /* 827 */ + 7.244359600749901e-05, /* 828 */ + 7.161434102129020e-05, /* 829 */ + 7.079457843841379e-05, /* 830 */ + 6.998419960022735e-05, /* 831 */ + 6.918309709189365e-05, /* 832 */ + 6.839116472814293e-05, /* 833 */ + 6.760829753919818e-05, /* 834 */ + 6.683439175686147e-05, /* 835 */ + 6.606934480075961e-05, /* 836 */ + 6.531305526474724e-05, /* 837 */ + 6.456542290346555e-05, /* 838 */ + 6.382634861905487e-05, /* 839 */ + 6.309573444801932e-05, /* 840 */ + 6.237348354824193e-05, /* 841 */ + 6.165950018614821e-05, /* 842 */ + 6.095368972401692e-05, /* 843 */ + 6.025595860743577e-05, /* 844 */ + 5.956621435290105e-05, /* 845 */ + 5.888436553555889e-05, /* 846 */ + 5.821032177708714e-05, /* 847 */ + 5.754399373371569e-05, /* 848 */ + 5.688529308438414e-05, /* 849 */ + 5.623413251903491e-05, /* 850 */ + 5.559042572704036e-05, /* 851 */ + 5.495408738576245e-05, /* 852 */ + 5.432503314924332e-05, /* 853 */ + 5.370317963702527e-05, /* 854 */ + 5.308844442309884e-05, /* 855 */ + 5.248074602497726e-05, /* 856 */ + 5.188000389289611e-05, /* 857 */ + 5.128613839913649e-05, /* 858 */ + 5.069907082747044e-05, /* 859 */ + 5.011872336272723e-05, /* 860 */ + 4.954501908047902e-05, /* 861 */ + 4.897788193684462e-05, /* 862 */ + 4.841723675840994e-05, /* 863 */ + 4.786300923226384e-05, /* 864 */ + 4.731512589614805e-05, /* 865 */ + 4.677351412871982e-05, /* 866 */ + 4.623810213992603e-05, /* 867 */ + 4.570881896148750e-05, /* 868 */ + 4.518559443749224e-05, /* 869 */ + 4.466835921509631e-05, /* 870 */ + 4.415704473533125e-05, /* 871 */ + 4.365158322401660e-05, /* 872 */ + 4.315190768277652e-05, /* 873 */ + 4.265795188015926e-05, /* 874 */ + 4.216965034285822e-05, /* 875 */ + 4.168693834703354e-05, /* 876 */ + 4.120975190973302e-05, /* 877 */ + 4.073802778041127e-05, /* 878 */ + 4.027170343254591e-05, /* 879 */ + 3.981071705534972e-05, /* 880 */ + 3.935500754557775e-05, /* 881 */ + 3.890451449942806e-05, /* 882 */ + 3.845917820453536e-05, /* 883 */ + 3.801893963205612e-05, /* 884 */ + 3.758374042884442e-05, /* 885 */ + 3.715352290971725e-05, /* 886 */ + 3.672823004980847e-05, /* 887 */ + 3.630780547701013e-05, /* 888 */ + 3.589219346450052e-05, /* 889 */ + 3.548133892335755e-05, /* 890 */ + 3.507518739525680e-05, /* 891 */ + 3.467368504525316e-05, /* 892 */ + 3.427677865464504e-05, /* 893 */ + 3.388441561392026e-05, /* 894 */ + 3.349654391578277e-05, /* 895 */ + 3.311311214825911e-05, /* 896 */ + 3.273406948788382e-05, /* 897 */ + 3.235936569296283e-05, /* 898 */ + 3.198895109691398e-05, /* 899 */ + 3.162277660168380e-05, /* 900 */ + 3.126079367123955e-05, /* 901 */ + 3.090295432513591e-05, /* 902 */ + 3.054921113215513e-05, /* 903 */ + 3.019951720402016e-05, /* 904 */ + 2.985382618917960e-05, /* 905 */ + 2.951209226666386e-05, /* 906 */ + 2.917427014001167e-05, /* 907 */ + 2.884031503126606e-05, /* 908 */ + 2.851018267503909e-05, /* 909 */ + 2.818382931264454e-05, /* 910 */ + 2.786121168629771e-05, /* 911 */ + 2.754228703338166e-05, /* 912 */ + 2.722701308077912e-05, /* 913 */ + 2.691534803926916e-05, /* 914 */ + 2.660725059798809e-05, /* 915 */ + 2.630267991895382e-05, /* 916 */ + 2.600159563165272e-05, /* 917 */ + 2.570395782768864e-05, /* 918 */ + 2.540972705549305e-05, /* 919 */ + 2.511886431509580e-05, /* 920 */ + 2.483133105295570e-05, /* 921 */ + 2.454708915685030e-05, /* 922 */ + 2.426610095082415e-05, /* 923 */ + 2.398832919019490e-05, /* 924 */ + 2.371373705661655e-05, /* 925 */ + 2.344228815319922e-05, /* 926 */ + 2.317394649968478e-05, /* 927 */ + 2.290867652767773e-05, /* 928 */ + 2.264644307593060e-05, /* 929 */ + 2.238721138568340e-05, /* 930 */ + 2.213094709605638e-05, /* 931 */ + 2.187761623949553e-05, /* 932 */ + 2.162718523727020e-05, /* 933 */ + 2.137962089502232e-05, /* 934 */ + 2.113489039836647e-05, /* 935 */ + 2.089296130854040e-05, /* 936 */ + 2.065380155810529e-05, /* 937 */ + 2.041737944669529e-05, /* 938 */ + 2.018366363681561e-05, /* 939 */ + 1.995262314968880e-05, /* 940 */ + 1.972422736114854e-05, /* 941 */ + 1.949844599758045e-05, /* 942 */ + 1.927524913190936e-05, /* 943 */ + 1.905460717963247e-05, /* 944 */ + 1.883649089489800e-05, /* 945 */ + 1.862087136662867e-05, /* 946 */ + 1.840772001468956e-05, /* 947 */ + 1.819700858609983e-05, /* 948 */ + 1.798870915128788e-05, /* 949 */ + 1.778279410038923e-05, /* 950 */ + 1.757923613958692e-05, /* 951 */ + 1.737800828749375e-05, /* 952 */ + 1.717908387157588e-05, /* 953 */ + 1.698243652461744e-05, /* 954 */ + 1.678804018122560e-05, /* 955 */ + 1.659586907437560e-05, /* 956 */ + 1.640589773199539e-05, /* 957 */ + 1.621810097358930e-05, /* 958 */ + 1.603245390690041e-05, /* 959 */ + 1.584893192461113e-05, /* 960 */ + 1.566751070108149e-05, /* 961 */ + 1.548816618912481e-05, /* 962 */ + 1.531087461682030e-05, /* 963 */ + 1.513561248436208e-05, /* 964 */ + 1.496235656094433e-05, /* 965 */ + 1.479108388168207e-05, /* 966 */ + 1.462177174456718e-05, /* 967 */ + 1.445439770745928e-05, /* 968 */ + 1.428893958511103e-05, /* 969 */ + 1.412537544622754e-05, /* 970 */ + 1.396368361055938e-05, /* 971 */ + 1.380384264602885e-05, /* 972 */ + 1.364583136588924e-05, /* 973 */ + 1.348962882591654e-05, /* 974 */ + 1.333521432163324e-05, /* 975 */ + 1.318256738556407e-05, /* 976 */ + 1.303166778452299e-05, /* 977 */ + 1.288249551693134e-05, /* 978 */ + 1.273503081016662e-05, /* 979 */ + 1.258925411794167e-05, /* 980 */ + 1.244514611771385e-05, /* 981 */ + 1.230268770812382e-05, /* 982 */ + 1.216186000646368e-05, /* 983 */ + 1.202264434617413e-05, /* 984 */ + 1.188502227437018e-05, /* 985 */ + 1.174897554939530e-05, /* 986 */ + 1.161448613840343e-05, /* 987 */ + 1.148153621496883e-05, /* 988 */ + 1.135010815672315e-05, /* 989 */ + 1.122018454301964e-05, /* 990 */ + 1.109174815262401e-05, /* 991 */ + 1.096478196143185e-05, /* 992 */ + 1.083926914021204e-05, /* 993 */ + 1.071519305237606e-05, /* 994 */ + 1.059253725177289e-05, /* 995 */ + 1.047128548050900e-05, /* 996 */ + 1.035142166679344e-05, /* 997 */ + 1.023292992280754e-05, /* 998 */ + 1.011579454259898e-05, /* 999 */ + 1.000000000000000e-05, /* 1000 */ + 9.885530946569389e-06, /* 1001 */ + 9.772372209558108e-06, /* 1002 */ + 9.660508789898134e-06, /* 1003 */ + 9.549925860214359e-06, /* 1004 */ + 9.440608762859234e-06, /* 1005 */ + 9.332543007969911e-06, /* 1006 */ + 9.225714271547632e-06, /* 1007 */ + 9.120108393559098e-06, /* 1008 */ + 9.015711376059568e-06, /* 1009 */ + 8.912509381337456e-06, /* 1010 */ + 8.810488730080140e-06, /* 1011 */ + 8.709635899560806e-06, /* 1012 */ + 8.609937521846006e-06, /* 1013 */ + 8.511380382023765e-06, /* 1014 */ + 8.413951416451952e-06, /* 1015 */ + 8.317637711026709e-06, /* 1016 */ + 8.222426499470711e-06, /* 1017 */ + 8.128305161640993e-06, /* 1018 */ + 8.035261221856173e-06, /* 1019 */ + 7.943282347242815e-06, /* 1020 */ + 7.852356346100718e-06, /* 1021 */ + 7.762471166286918e-06, /* 1022 */ + 7.673614893618189e-06, /* 1023 */ + 7.585775750291837e-06, /* 1024 */ + 7.498942093324558e-06, /* 1025 */ + 7.413102413009175e-06, /* 1026 */ + 7.328245331389041e-06, /* 1027 */ + 7.244359600749901e-06, /* 1028 */ + 7.161434102129020e-06, /* 1029 */ + 7.079457843841379e-06, /* 1030 */ + 6.998419960022735e-06, /* 1031 */ + 6.918309709189365e-06, /* 1032 */ + 6.839116472814293e-06, /* 1033 */ + 6.760829753919818e-06, /* 1034 */ + 6.683439175686146e-06, /* 1035 */ + 6.606934480075960e-06, /* 1036 */ + 6.531305526474723e-06, /* 1037 */ + 6.456542290346555e-06, /* 1038 */ + 6.382634861905487e-06, /* 1039 */ + 6.309573444801932e-06, /* 1040 */ + 6.237348354824192e-06, /* 1041 */ + 6.165950018614822e-06, /* 1042 */ + 6.095368972401692e-06, /* 1043 */ + 6.025595860743577e-06, /* 1044 */ + 5.956621435290104e-06, /* 1045 */ + 5.888436553555889e-06, /* 1046 */ + 5.821032177708714e-06, /* 1047 */ + 5.754399373371569e-06, /* 1048 */ + 5.688529308438415e-06, /* 1049 */ + 5.623413251903491e-06, /* 1050 */ + 5.559042572704035e-06, /* 1051 */ + 5.495408738576246e-06, /* 1052 */ + 5.432503314924332e-06, /* 1053 */ + 5.370317963702528e-06, /* 1054 */ + 5.308844442309884e-06, /* 1055 */ + 5.248074602497726e-06, /* 1056 */ + 5.188000389289611e-06, /* 1057 */ + 5.128613839913649e-06, /* 1058 */ + 5.069907082747044e-06, /* 1059 */ + 5.011872336272722e-06, /* 1060 */ + 4.954501908047903e-06, /* 1061 */ + 4.897788193684462e-06, /* 1062 */ + 4.841723675840994e-06, /* 1063 */ + 4.786300923226383e-06, /* 1064 */ + 4.731512589614805e-06, /* 1065 */ + 4.677351412871982e-06, /* 1066 */ + 4.623810213992603e-06, /* 1067 */ + 4.570881896148750e-06, /* 1068 */ + 4.518559443749223e-06, /* 1069 */ + 4.466835921509631e-06, /* 1070 */ + 4.415704473533125e-06, /* 1071 */ + 4.365158322401660e-06, /* 1072 */ + 4.315190768277652e-06, /* 1073 */ + 4.265795188015927e-06, /* 1074 */ + 4.216965034285822e-06, /* 1075 */ + 4.168693834703354e-06, /* 1076 */ + 4.120975190973302e-06, /* 1077 */ + 4.073802778041127e-06, /* 1078 */ + 4.027170343254591e-06, /* 1079 */ + 3.981071705534973e-06, /* 1080 */ + 3.935500754557774e-06, /* 1081 */ + 3.890451449942806e-06, /* 1082 */ + 3.845917820453536e-06, /* 1083 */ + 3.801893963205612e-06, /* 1084 */ + 3.758374042884442e-06, /* 1085 */ + 3.715352290971725e-06, /* 1086 */ + 3.672823004980847e-06, /* 1087 */ + 3.630780547701013e-06, /* 1088 */ + 3.589219346450052e-06, /* 1089 */ + 3.548133892335755e-06, /* 1090 */ + 3.507518739525680e-06, /* 1091 */ + 3.467368504525316e-06, /* 1092 */ + 3.427677865464503e-06, /* 1093 */ + 3.388441561392025e-06, /* 1094 */ + 3.349654391578277e-06, /* 1095 */ + 3.311311214825911e-06, /* 1096 */ + 3.273406948788382e-06, /* 1097 */ + 3.235936569296283e-06, /* 1098 */ + 3.198895109691398e-06, /* 1099 */ + 3.162277660168379e-06, /* 1100 */ + 3.126079367123955e-06, /* 1101 */ + 3.090295432513591e-06, /* 1102 */ + 3.054921113215513e-06, /* 1103 */ + 3.019951720402016e-06, /* 1104 */ + 2.985382618917960e-06, /* 1105 */ + 2.951209226666386e-06, /* 1106 */ + 2.917427014001167e-06, /* 1107 */ + 2.884031503126606e-06, /* 1108 */ + 2.851018267503909e-06, /* 1109 */ + 2.818382931264454e-06, /* 1110 */ + 2.786121168629770e-06, /* 1111 */ + 2.754228703338166e-06, /* 1112 */ + 2.722701308077913e-06, /* 1113 */ + 2.691534803926916e-06, /* 1114 */ + 2.660725059798810e-06, /* 1115 */ + 2.630267991895382e-06, /* 1116 */ + 2.600159563165272e-06, /* 1117 */ + 2.570395782768864e-06, /* 1118 */ + 2.540972705549305e-06, /* 1119 */ + 2.511886431509580e-06, /* 1120 */ + 2.483133105295570e-06, /* 1121 */ + 2.454708915685031e-06, /* 1122 */ + 2.426610095082416e-06, /* 1123 */ + 2.398832919019491e-06, /* 1124 */ + 2.371373705661655e-06, /* 1125 */ + 2.344228815319922e-06, /* 1126 */ + 2.317394649968478e-06, /* 1127 */ + 2.290867652767773e-06, /* 1128 */ + 2.264644307593060e-06, /* 1129 */ + 2.238721138568340e-06, /* 1130 */ + 2.213094709605638e-06, /* 1131 */ + 2.187761623949553e-06, /* 1132 */ + 2.162718523727020e-06, /* 1133 */ + 2.137962089502232e-06, /* 1134 */ + 2.113489039836647e-06, /* 1135 */ + 2.089296130854039e-06, /* 1136 */ + 2.065380155810529e-06, /* 1137 */ + 2.041737944669529e-06, /* 1138 */ + 2.018366363681561e-06, /* 1139 */ + 1.995262314968880e-06, /* 1140 */ + 1.972422736114854e-06, /* 1141 */ + 1.949844599758045e-06, /* 1142 */ + 1.927524913190936e-06, /* 1143 */ + 1.905460717963247e-06, /* 1144 */ + 1.883649089489801e-06, /* 1145 */ + 1.862087136662868e-06, /* 1146 */ + 1.840772001468956e-06, /* 1147 */ + 1.819700858609983e-06, /* 1148 */ + 1.798870915128788e-06, /* 1149 */ + 1.778279410038923e-06, /* 1150 */ + 1.757923613958693e-06, /* 1151 */ + 1.737800828749375e-06, /* 1152 */ + 1.717908387157588e-06, /* 1153 */ + 1.698243652461744e-06, /* 1154 */ + 1.678804018122560e-06, /* 1155 */ + 1.659586907437561e-06, /* 1156 */ + 1.640589773199539e-06, /* 1157 */ + 1.621810097358930e-06, /* 1158 */ + 1.603245390690041e-06, /* 1159 */ + 1.584893192461113e-06, /* 1160 */ + 1.566751070108149e-06, /* 1161 */ + 1.548816618912481e-06, /* 1162 */ + 1.531087461682030e-06, /* 1163 */ + 1.513561248436208e-06, /* 1164 */ + 1.496235656094434e-06, /* 1165 */ + 1.479108388168207e-06, /* 1166 */ + 1.462177174456718e-06, /* 1167 */ + 1.445439770745928e-06, /* 1168 */ + 1.428893958511103e-06, /* 1169 */ + 1.412537544622754e-06, /* 1170 */ + 1.396368361055938e-06, /* 1171 */ + 1.380384264602885e-06, /* 1172 */ + 1.364583136588924e-06, /* 1173 */ + 1.348962882591654e-06, /* 1174 */ + 1.333521432163324e-06, /* 1175 */ + 1.318256738556407e-06, /* 1176 */ + 1.303166778452299e-06, /* 1177 */ + 1.288249551693134e-06, /* 1178 */ + 1.273503081016662e-06, /* 1179 */ + 1.258925411794167e-06, /* 1180 */ + 1.244514611771385e-06, /* 1181 */ + 1.230268770812382e-06, /* 1182 */ + 1.216186000646368e-06, /* 1183 */ + 1.202264434617413e-06, /* 1184 */ + 1.188502227437018e-06, /* 1185 */ + 1.174897554939530e-06, /* 1186 */ + 1.161448613840343e-06, /* 1187 */ + 1.148153621496883e-06, /* 1188 */ + 1.135010815672315e-06, /* 1189 */ + 1.122018454301963e-06, /* 1190 */ + 1.109174815262401e-06, /* 1191 */ + 1.096478196143185e-06, /* 1192 */ + 1.083926914021203e-06, /* 1193 */ + 1.071519305237606e-06, /* 1194 */ + 1.059253725177289e-06, /* 1195 */ + 1.047128548050900e-06, /* 1196 */ + 1.035142166679344e-06, /* 1197 */ + 1.023292992280754e-06, /* 1198 */ + 1.011579454259898e-06, /* 1199 */ + 1.000000000000000e-06, /* 1200 */ + 9.885530946569389e-07, /* 1201 */ + 9.772372209558107e-07, /* 1202 */ + 9.660508789898133e-07, /* 1203 */ + 9.549925860214360e-07, /* 1204 */ + 9.440608762859233e-07, /* 1205 */ + 9.332543007969910e-07, /* 1206 */ + 9.225714271547632e-07, /* 1207 */ + 9.120108393559097e-07, /* 1208 */ + 9.015711376059569e-07, /* 1209 */ + 8.912509381337455e-07, /* 1210 */ + 8.810488730080140e-07, /* 1211 */ + 8.709635899560807e-07, /* 1212 */ + 8.609937521846007e-07, /* 1213 */ + 8.511380382023765e-07, /* 1214 */ + 8.413951416451951e-07, /* 1215 */ + 8.317637711026710e-07, /* 1216 */ + 8.222426499470711e-07, /* 1217 */ + 8.128305161640992e-07, /* 1218 */ + 8.035261221856172e-07, /* 1219 */ + 7.943282347242815e-07, /* 1220 */ + 7.852356346100718e-07, /* 1221 */ + 7.762471166286918e-07, /* 1222 */ + 7.673614893618189e-07, /* 1223 */ + 7.585775750291838e-07, /* 1224 */ + 7.498942093324558e-07, /* 1225 */ + 7.413102413009175e-07, /* 1226 */ + 7.328245331389040e-07, /* 1227 */ + 7.244359600749901e-07, /* 1228 */ + 7.161434102129020e-07, /* 1229 */ + 7.079457843841379e-07, /* 1230 */ + 6.998419960022735e-07, /* 1231 */ + 6.918309709189365e-07, /* 1232 */ + 6.839116472814294e-07, /* 1233 */ + 6.760829753919818e-07, /* 1234 */ + 6.683439175686146e-07, /* 1235 */ + 6.606934480075960e-07, /* 1236 */ + 6.531305526474723e-07, /* 1237 */ + 6.456542290346555e-07, /* 1238 */ + 6.382634861905487e-07, /* 1239 */ + 6.309573444801933e-07, /* 1240 */ + 6.237348354824193e-07, /* 1241 */ + 6.165950018614822e-07, /* 1242 */ + 6.095368972401692e-07, /* 1243 */ + 6.025595860743577e-07, /* 1244 */ + 5.956621435290105e-07, /* 1245 */ + 5.888436553555889e-07, /* 1246 */ + 5.821032177708714e-07, /* 1247 */ + 5.754399373371570e-07, /* 1248 */ + 5.688529308438414e-07, /* 1249 */ + 5.623413251903490e-07, /* 1250 */ + 5.559042572704035e-07, /* 1251 */ + 5.495408738576246e-07, /* 1252 */ + 5.432503314924332e-07, /* 1253 */ + 5.370317963702528e-07, /* 1254 */ + 5.308844442309884e-07, /* 1255 */ + 5.248074602497726e-07, /* 1256 */ + 5.188000389289611e-07, /* 1257 */ + 5.128613839913648e-07, /* 1258 */ + 5.069907082747044e-07, /* 1259 */ + 5.011872336272723e-07, /* 1260 */ + 4.954501908047903e-07, /* 1261 */ + 4.897788193684462e-07, /* 1262 */ + 4.841723675840993e-07, /* 1263 */ + 4.786300923226383e-07, /* 1264 */ + 4.731512589614805e-07, /* 1265 */ + 4.677351412871982e-07, /* 1266 */ + 4.623810213992603e-07, /* 1267 */ + 4.570881896148751e-07, /* 1268 */ + 4.518559443749224e-07, /* 1269 */ + 4.466835921509631e-07, /* 1270 */ + 4.415704473533125e-07, /* 1271 */ + 4.365158322401660e-07, /* 1272 */ + 4.315190768277652e-07, /* 1273 */ + 4.265795188015926e-07, /* 1274 */ + 4.216965034285823e-07, /* 1275 */ + 4.168693834703354e-07, /* 1276 */ + 4.120975190973302e-07, /* 1277 */ + 4.073802778041127e-07, /* 1278 */ + 4.027170343254591e-07, /* 1279 */ + 3.981071705534972e-07, /* 1280 */ + 3.935500754557775e-07, /* 1281 */ + 3.890451449942806e-07, /* 1282 */ + 3.845917820453536e-07, /* 1283 */ + 3.801893963205612e-07, /* 1284 */ + 3.758374042884442e-07, /* 1285 */ + 3.715352290971725e-07, /* 1286 */ + 3.672823004980847e-07, /* 1287 */ + 3.630780547701014e-07, /* 1288 */ + 3.589219346450052e-07, /* 1289 */ + 3.548133892335755e-07, /* 1290 */ + 3.507518739525680e-07, /* 1291 */ + 3.467368504525316e-07, /* 1292 */ + 3.427677865464503e-07, /* 1293 */ + 3.388441561392025e-07, /* 1294 */ + 3.349654391578277e-07, /* 1295 */ + 3.311311214825911e-07, /* 1296 */ + 3.273406948788382e-07, /* 1297 */ + 3.235936569296283e-07, /* 1298 */ + 3.198895109691398e-07, /* 1299 */ + 3.162277660168379e-07, /* 1300 */ + 3.126079367123955e-07, /* 1301 */ + 3.090295432513590e-07, /* 1302 */ + 3.054921113215513e-07, /* 1303 */ + 3.019951720402016e-07, /* 1304 */ + 2.985382618917960e-07, /* 1305 */ + 2.951209226666386e-07, /* 1306 */ + 2.917427014001167e-07, /* 1307 */ + 2.884031503126606e-07, /* 1308 */ + 2.851018267503909e-07, /* 1309 */ + 2.818382931264454e-07, /* 1310 */ + 2.786121168629770e-07, /* 1311 */ + 2.754228703338166e-07, /* 1312 */ + 2.722701308077912e-07, /* 1313 */ + 2.691534803926916e-07, /* 1314 */ + 2.660725059798809e-07, /* 1315 */ + 2.630267991895382e-07, /* 1316 */ + 2.600159563165272e-07, /* 1317 */ + 2.570395782768864e-07, /* 1318 */ + 2.540972705549305e-07, /* 1319 */ + 2.511886431509580e-07, /* 1320 */ + 2.483133105295570e-07, /* 1321 */ + 2.454708915685030e-07, /* 1322 */ + 2.426610095082416e-07, /* 1323 */ + 2.398832919019490e-07, /* 1324 */ + 2.371373705661655e-07, /* 1325 */ + 2.344228815319922e-07, /* 1326 */ + 2.317394649968478e-07, /* 1327 */ + 2.290867652767773e-07, /* 1328 */ + 2.264644307593060e-07, /* 1329 */ + 2.238721138568340e-07, /* 1330 */ + 2.213094709605638e-07, /* 1331 */ + 2.187761623949553e-07, /* 1332 */ + 2.162718523727020e-07, /* 1333 */ + 2.137962089502232e-07, /* 1334 */ + 2.113489039836647e-07, /* 1335 */ + 2.089296130854040e-07, /* 1336 */ + 2.065380155810529e-07, /* 1337 */ + 2.041737944669529e-07, /* 1338 */ + 2.018366363681561e-07, /* 1339 */ + 1.995262314968880e-07, /* 1340 */ + 1.972422736114854e-07, /* 1341 */ + 1.949844599758045e-07, /* 1342 */ + 1.927524913190936e-07, /* 1343 */ + 1.905460717963247e-07, /* 1344 */ + 1.883649089489800e-07, /* 1345 */ + 1.862087136662867e-07, /* 1346 */ + 1.840772001468956e-07, /* 1347 */ + 1.819700858609983e-07, /* 1348 */ + 1.798870915128788e-07, /* 1349 */ + 1.778279410038923e-07, /* 1350 */ + 1.757923613958693e-07, /* 1351 */ + 1.737800828749375e-07, /* 1352 */ + 1.717908387157588e-07, /* 1353 */ + 1.698243652461744e-07, /* 1354 */ + 1.678804018122560e-07, /* 1355 */ + 1.659586907437561e-07, /* 1356 */ + 1.640589773199539e-07, /* 1357 */ + 1.621810097358930e-07, /* 1358 */ + 1.603245390690041e-07, /* 1359 */ + 1.584893192461114e-07, /* 1360 */ + 1.566751070108149e-07, /* 1361 */ + 1.548816618912481e-07, /* 1362 */ + 1.531087461682030e-07, /* 1363 */ + 1.513561248436208e-07, /* 1364 */ + 1.496235656094433e-07, /* 1365 */ + 1.479108388168207e-07, /* 1366 */ + 1.462177174456718e-07, /* 1367 */ + 1.445439770745928e-07, /* 1368 */ + 1.428893958511103e-07, /* 1369 */ + 1.412537544622754e-07, /* 1370 */ + 1.396368361055938e-07, /* 1371 */ + 1.380384264602885e-07, /* 1372 */ + 1.364583136588925e-07, /* 1373 */ + 1.348962882591654e-07, /* 1374 */ + 1.333521432163324e-07, /* 1375 */ + 1.318256738556407e-07, /* 1376 */ + 1.303166778452299e-07, /* 1377 */ + 1.288249551693134e-07, /* 1378 */ + 1.273503081016662e-07, /* 1379 */ + 1.258925411794167e-07, /* 1380 */ + 1.244514611771385e-07, /* 1381 */ + 1.230268770812381e-07, /* 1382 */ + 1.216186000646368e-07, /* 1383 */ + 1.202264434617413e-07, /* 1384 */ + 1.188502227437018e-07, /* 1385 */ + 1.174897554939530e-07, /* 1386 */ + 1.161448613840343e-07, /* 1387 */ + 1.148153621496883e-07, /* 1388 */ + 1.135010815672315e-07, /* 1389 */ + 1.122018454301963e-07, /* 1390 */ + 1.109174815262401e-07, /* 1391 */ + 1.096478196143185e-07, /* 1392 */ + 1.083926914021204e-07, /* 1393 */ + 1.071519305237606e-07, /* 1394 */ + 1.059253725177289e-07, /* 1395 */ + 1.047128548050900e-07, /* 1396 */ + 1.035142166679344e-07, /* 1397 */ + 1.023292992280754e-07, /* 1398 */ + 1.011579454259898e-07, /* 1399 */ + 1.000000000000000e-07, /* 1400 */ + 9.885530946569389e-08, /* 1401 */ + 9.772372209558107e-08, /* 1402 */ + 9.660508789898134e-08, /* 1403 */ + 9.549925860214360e-08, /* 1404 */ + 9.440608762859234e-08, /* 1405 */ + 9.332543007969910e-08, /* 1406 */ + 9.225714271547632e-08, /* 1407 */ + 9.120108393559098e-08, /* 1408 */ + 9.015711376059569e-08, /* 1409 */ + 8.912509381337455e-08, /* 1410 */ + 8.810488730080140e-08, /* 1411 */ + 8.709635899560806e-08, /* 1412 */ + 8.609937521846006e-08, /* 1413 */ + 8.511380382023765e-08, /* 1414 */ + 8.413951416451951e-08, /* 1415 */ + 8.317637711026710e-08, /* 1416 */ + 8.222426499470712e-08, /* 1417 */ + 8.128305161640992e-08, /* 1418 */ + 8.035261221856172e-08, /* 1419 */ + 7.943282347242815e-08, /* 1420 */ + 7.852356346100718e-08, /* 1421 */ + 7.762471166286917e-08, /* 1422 */ + 7.673614893618189e-08, /* 1423 */ + 7.585775750291838e-08, /* 1424 */ + 7.498942093324559e-08, /* 1425 */ + 7.413102413009175e-08, /* 1426 */ + 7.328245331389041e-08, /* 1427 */ + 7.244359600749901e-08, /* 1428 */ + 7.161434102129020e-08, /* 1429 */ + 7.079457843841380e-08, /* 1430 */ + 6.998419960022735e-08, /* 1431 */ + 6.918309709189365e-08, /* 1432 */ + 6.839116472814293e-08, /* 1433 */ + 6.760829753919818e-08, /* 1434 */ + 6.683439175686146e-08, /* 1435 */ + 6.606934480075960e-08, /* 1436 */ + 6.531305526474724e-08, /* 1437 */ + 6.456542290346556e-08, /* 1438 */ + 6.382634861905487e-08, /* 1439 */ + 6.309573444801932e-08, /* 1440 */ +}; + +static const fluid_real_t fluid_concave_tab[128] = { + 0.000000000000000e+00, /* 0 */ + 1.430489932664151e-03, /* 1 */ + 2.872378311625187e-03, /* 2 */ + 4.325848247384080e-03, /* 3 */ + 5.791087298566222e-03, /* 4 */ + 7.268287617170264e-03, /* 5 */ + 8.757646099794491e-03, /* 6 */ + 1.025936454513835e-02, /* 7 */ + 1.177364981809421e-02, /* 8 */ + 1.330071402076312e-02, /* 9 */ + 1.484077467074801e-02, /* 10 */ + 1.639405488709933e-02, /* 11 */ + 1.796078358431049e-02, /* 12 */ + 1.954119567478511e-02, /* 13 */ + 2.113553228022381e-02, /* 14 */ + 2.274404095240635e-02, /* 15 */ + 2.436697590387476e-02, /* 16 */ + 2.600459824905492e-02, /* 17 */ + 2.765717625638884e-02, /* 18 */ + 2.932498561208631e-02, /* 19 */ + 3.100830969614467e-02, /* 20 */ + 3.270743987132776e-02, /* 21 */ + 3.442267578584116e-02, /* 22 */ + 3.615432569049021e-02, /* 23 */ + 3.790270677116027e-02, /* 24 */ + 3.966814549751637e-02, /* 25 */ + 4.145097798888095e-02, /* 26 */ + 4.325155039831535e-02, /* 27 */ + 4.507021931600289e-02, /* 28 */ + 4.690735219310917e-02, /* 29 */ + 4.876332778738000e-02, /* 30 */ + 5.063853663182852e-02, /* 31 */ + 5.253338152796212e-02, /* 32 */ + 5.444827806510758e-02, /* 33 */ + 5.638365516750905e-02, /* 34 */ + 5.833995567100066e-02, /* 35 */ + 6.031763693119302e-02, /* 36 */ + 6.231717146526333e-02, /* 37 */ + 6.433904762960170e-02, /* 38 */ + 6.638377033574509e-02, /* 39 */ + 6.845186180722430e-02, /* 40 */ + 7.054386238016214e-02, /* 41 */ + 7.266033135069339e-02, /* 42 */ + 7.480184787253133e-02, /* 43 */ + 7.696901190828456e-02, /* 44 */ + 7.916244523843340e-02, /* 45 */ + 8.138279253221128e-02, /* 46 */ + 8.363072248500553e-02, /* 47 */ + 8.590692902729809e-02, /* 48 */ + 8.821213261061518e-02, /* 49 */ + 9.054708157644790e-02, /* 50 */ + 9.291255361465228e-02, /* 51 */ + 9.530935731844033e-02, /* 52 */ + 9.773833384374193e-02, /* 53 */ + 1.002003586814587e-01, /* 54 */ + 1.026963435519535e-01, /* 55 */ + 1.052272384320340e-01, /* 56 */ + 1.077940337257083e-01, /* 57 */ + 1.103977625911256e-01, /* 58 */ + 1.130395034373835e-01, /* 59 */ + 1.157203826063043e-01, /* 60 */ + 1.184415772558701e-01, /* 61 */ + 1.212043184637922e-01, /* 62 */ + 1.240098945716957e-01, /* 63 */ + 1.268596547926563e-01, /* 64 */ + 1.297550131073762e-01, /* 65 */ + 1.326974524771624e-01, /* 66 */ + 1.356885294051305e-01, /* 67 */ + 1.387298788807553e-01, /* 68 */ + 1.418232197470915e-01, /* 69 */ + 1.449703605347773e-01, /* 70 */ + 1.481732058123985e-01, /* 71 */ + 1.514337631090471e-01, /* 72 */ + 1.547541504720785e-01, /* 73 */ + 1.581366047313199e-01, /* 74 */ + 1.615834905504824e-01, /* 75 */ + 1.650973103575085e-01, /* 76 */ + 1.686807152583075e-01, /* 77 */ + 1.723365170531013e-01, /* 78 */ + 1.760677014918207e-01, /* 79 */ + 1.798774429250997e-01, /* 80 */ + 1.837691205309928e-01, /* 81 */ + 1.877463363252555e-01, /* 82 */ + 1.918129351957372e-01, /* 83 */ + 1.959730272401543e-01, /* 84 */ + 2.002310127325235e-01, /* 85 */ + 2.045916100984256e-01, /* 86 */ + 2.090598873449977e-01, /* 87 */ + 2.136412974706073e-01, /* 88 */ + 2.183417184746445e-01, /* 89 */ + 2.231674987037341e-01, /* 90 */ + 2.281255084119456e-01, /* 91 */ + 2.332231985857005e-01, /* 92 */ + 2.384686682973757e-01, /* 93 */ + 2.438707421158622e-01, /* 94 */ + 2.494390594316878e-01, /* 95 */ + 2.551841779673684e-01, /* 96 */ + 2.611176942651227e-01, /* 97 */ + 2.672523846070836e-01, /* 98 */ + 2.736023706723907e-01, /* 99 */ + 2.801833153320706e-01, /* 100 */ + 2.870126554104745e-01, /* 101 */ + 2.941098801182996e-01, /* 102 */ + 3.014968663518128e-01, /* 103 */ + 3.091982853909850e-01, /* 104 */ + 3.172421000557294e-01, /* 105 */ + 3.256601775925156e-01, /* 106 */ + 3.344890522049898e-01, /* 107 */ + 3.437708833346366e-01, /* 108 */ + 3.535546732719378e-01, /* 109 */ + 3.638978331573678e-01, /* 110 */ + 3.748682242916800e-01, /* 111 */ + 3.865468591251148e-01, /* 112 */ + 3.990315355323828e-01, /* 113 */ + 4.124418202704667e-01, /* 114 */ + 4.269260312118050e-01, /* 115 */ + 4.426712649157216e-01, /* 116 */ + 4.599182170649820e-01, /* 117 */ + 4.789838381319300e-01, /* 118 */ + 5.002973891516721e-01, /* 119 */ + 5.244607003923749e-01, /* 120 */ + 5.523551960717972e-01, /* 121 */ + 5.853473819249742e-01, /* 122 */ + 6.257265540116643e-01, /* 123 */ + 6.777843609317893e-01, /* 124 */ + 7.511557188716564e-01, /* 125 */ + 8.765848837316486e-01, /* 126 */ + 1.000000000000000e+00, /* 127 */ +}; + +static const fluid_real_t fluid_convex_tab[128] = { + 0.000000000000000e+00, /* 0 */ + 1.234151162683514e-01, /* 1 */ + 2.488442811283435e-01, /* 2 */ + 3.222156390682107e-01, /* 3 */ + 3.742734459883357e-01, /* 4 */ + 4.146526180750258e-01, /* 5 */ + 4.476448039282029e-01, /* 6 */ + 4.755392996076250e-01, /* 7 */ + 4.997026108483278e-01, /* 8 */ + 5.210161618680701e-01, /* 9 */ + 5.400817829350180e-01, /* 10 */ + 5.573287350842785e-01, /* 11 */ + 5.730739687881951e-01, /* 12 */ + 5.875581797295333e-01, /* 13 */ + 6.009684644676172e-01, /* 14 */ + 6.134531408748852e-01, /* 15 */ + 6.251317757083200e-01, /* 16 */ + 6.361021668426321e-01, /* 17 */ + 6.464453267280622e-01, /* 18 */ + 6.562291166653634e-01, /* 19 */ + 6.655109477950102e-01, /* 20 */ + 6.743398224074844e-01, /* 21 */ + 6.827578999442706e-01, /* 22 */ + 6.908017146090151e-01, /* 23 */ + 6.985031336481872e-01, /* 24 */ + 7.058901198817004e-01, /* 25 */ + 7.129873445895255e-01, /* 26 */ + 7.198166846679294e-01, /* 27 */ + 7.263976293276093e-01, /* 28 */ + 7.327476153929163e-01, /* 29 */ + 7.388823057348773e-01, /* 30 */ + 7.448158220326316e-01, /* 31 */ + 7.505609405683121e-01, /* 32 */ + 7.561292578841378e-01, /* 33 */ + 7.615313317026243e-01, /* 34 */ + 7.667768014142995e-01, /* 35 */ + 7.718744915880543e-01, /* 36 */ + 7.768325012962659e-01, /* 37 */ + 7.816582815253555e-01, /* 38 */ + 7.863587025293927e-01, /* 39 */ + 7.909401126550023e-01, /* 40 */ + 7.954083899015745e-01, /* 41 */ + 7.997689872674765e-01, /* 42 */ + 8.040269727598457e-01, /* 43 */ + 8.081870648042627e-01, /* 44 */ + 8.122536636747445e-01, /* 45 */ + 8.162308794690072e-01, /* 46 */ + 8.201225570749002e-01, /* 47 */ + 8.239322985081793e-01, /* 48 */ + 8.276634829468987e-01, /* 49 */ + 8.313192847416925e-01, /* 50 */ + 8.349026896424915e-01, /* 51 */ + 8.384165094495176e-01, /* 52 */ + 8.418633952686800e-01, /* 53 */ + 8.452458495279215e-01, /* 54 */ + 8.485662368909529e-01, /* 55 */ + 8.518267941876015e-01, /* 56 */ + 8.550296394652227e-01, /* 57 */ + 8.581767802529086e-01, /* 58 */ + 8.612701211192447e-01, /* 59 */ + 8.643114705948695e-01, /* 60 */ + 8.673025475228375e-01, /* 61 */ + 8.702449868926238e-01, /* 62 */ + 8.731403452073437e-01, /* 63 */ + 8.759901054283044e-01, /* 64 */ + 8.787956815362078e-01, /* 65 */ + 8.815584227441299e-01, /* 66 */ + 8.842796173936956e-01, /* 67 */ + 8.869604965626164e-01, /* 68 */ + 8.896022374088743e-01, /* 69 */ + 8.922059662742917e-01, /* 70 */ + 8.947727615679660e-01, /* 71 */ + 8.973036564480465e-01, /* 72 */ + 8.997996413185413e-01, /* 73 */ + 9.022616661562580e-01, /* 74 */ + 9.046906426815596e-01, /* 75 */ + 9.070874463853477e-01, /* 76 */ + 9.094529184235521e-01, /* 77 */ + 9.117878673893848e-01, /* 78 */ + 9.140930709727019e-01, /* 79 */ + 9.163692775149945e-01, /* 80 */ + 9.186172074677887e-01, /* 81 */ + 9.208375547615666e-01, /* 82 */ + 9.230309880917155e-01, /* 83 */ + 9.251981521274687e-01, /* 84 */ + 9.273396686493066e-01, /* 85 */ + 9.294561376198379e-01, /* 86 */ + 9.315481381927757e-01, /* 87 */ + 9.336162296642549e-01, /* 88 */ + 9.356609523703983e-01, /* 89 */ + 9.376828285347367e-01, /* 90 */ + 9.396823630688069e-01, /* 91 */ + 9.416600443289993e-01, /* 92 */ + 9.436163448324909e-01, /* 93 */ + 9.455517219348925e-01, /* 94 */ + 9.474666184720378e-01, /* 95 */ + 9.493614633681715e-01, /* 96 */ + 9.512366722126200e-01, /* 97 */ + 9.530926478068908e-01, /* 98 */ + 9.549297806839971e-01, /* 99 */ + 9.567484496016846e-01, /* 100 */ + 9.585490220111190e-01, /* 101 */ + 9.603318545024836e-01, /* 102 */ + 9.620972932288397e-01, /* 103 */ + 9.638456743095097e-01, /* 104 */ + 9.655773242141589e-01, /* 105 */ + 9.672925601286723e-01, /* 106 */ + 9.689916903038553e-01, /* 107 */ + 9.706750143879137e-01, /* 108 */ + 9.723428237436111e-01, /* 109 */ + 9.739954017509451e-01, /* 110 */ + 9.756330240961253e-01, /* 111 */ + 9.772559590475937e-01, /* 112 */ + 9.788644677197762e-01, /* 113 */ + 9.804588043252149e-01, /* 114 */ + 9.820392164156895e-01, /* 115 */ + 9.836059451129007e-01, /* 116 */ + 9.851592253292520e-01, /* 117 */ + 9.866992859792368e-01, /* 118 */ + 9.882263501819057e-01, /* 119 */ + 9.897406354548617e-01, /* 120 */ + 9.912423539002055e-01, /* 121 */ + 9.927317123828298e-01, /* 122 */ + 9.942089127014337e-01, /* 123 */ + 9.956741517526159e-01, /* 124 */ + 9.971276216883748e-01, /* 125 */ + 9.985695100673359e-01, /* 126 */ + 1.000000000000000e+00, /* 127 */ +}; + +static const fluid_real_t fluid_pan_tab[1002] = { + 0.000000000000000e+00, /* 0 */ + 1.569226455665206e-03, /* 1 */ + 3.138449047152344e-03, /* 2 */ + 4.707663910292860e-03, /* 3 */ + 6.276867180937232e-03, /* 4 */ + 7.846054994964486e-03, /* 5 */ + 9.415223488291704e-03, /* 6 */ + 1.098436879688355e-02, /* 7 */ + 1.255348705676178e-02, /* 8 */ + 1.412257440401476e-02, /* 9 */ + 1.569162697480695e-02, /* 10 */ + 1.726064090538850e-02, /* 11 */ + 1.882961233210465e-02, /* 12 */ + 2.039853739140535e-02, /* 13 */ + 2.196741221985471e-02, /* 14 */ + 2.353623295414053e-02, /* 15 */ + 2.510499573108383e-02, /* 16 */ + 2.667369668764832e-02, /* 17 */ + 2.824233196094998e-02, /* 18 */ + 2.981089768826650e-02, /* 19 */ + 3.137939000704683e-02, /* 20 */ + 3.294780505492070e-02, /* 21 */ + 3.451613896970813e-02, /* 22 */ + 3.608438788942888e-02, /* 23 */ + 3.765254795231206e-02, /* 24 */ + 3.922061529680555e-02, /* 25 */ + 4.078858606158557e-02, /* 26 */ + 4.235645638556616e-02, /* 27 */ + 4.392422240790868e-02, /* 28 */ + 4.549188026803134e-02, /* 29 */ + 4.705942610561871e-02, /* 30 */ + 4.862685606063118e-02, /* 31 */ + 5.019416627331453e-02, /* 32 */ + 5.176135288420938e-02, /* 33 */ + 5.332841203416073e-02, /* 34 */ + 5.489533986432744e-02, /* 35 */ + 5.646213251619175e-02, /* 36 */ + 5.802878613156876e-02, /* 37 */ + 5.959529685261596e-02, /* 38 */ + 6.116166082184270e-02, /* 39 */ + 6.272787418211971e-02, /* 40 */ + 6.429393307668860e-02, /* 41 */ + 6.585983364917131e-02, /* 42 */ + 6.742557204357968e-02, /* 43 */ + 6.899114440432493e-02, /* 44 */ + 7.055654687622705e-02, /* 45 */ + 7.212177560452446e-02, /* 46 */ + 7.368682673488337e-02, /* 47 */ + 7.525169641340737e-02, /* 48 */ + 7.681638078664681e-02, /* 49 */ + 7.838087600160838e-02, /* 50 */ + 7.994517820576458e-02, /* 51 */ + 8.150928354706316e-02, /* 52 */ + 8.307318817393668e-02, /* 53 */ + 8.463688823531192e-02, /* 54 */ + 8.620037988061939e-02, /* 55 */ + 8.776365925980288e-02, /* 56 */ + 8.932672252332881e-02, /* 57 */ + 9.088956582219582e-02, /* 58 */ + 9.245218530794418e-02, /* 59 */ + 9.401457713266531e-02, /* 60 */ + 9.557673744901124e-02, /* 61 */ + 9.713866241020409e-02, /* 62 */ + 9.870034817004553e-02, /* 63 */ + 1.002617908829262e-01, /* 64 */ + 1.018229867038354e-01, /* 65 */ + 1.033839317883702e-01, /* 66 */ + 1.049446222927451e-01, /* 67 */ + 1.065050543738018e-01, /* 68 */ + 1.080652241890180e-01, /* 69 */ + 1.096251278965173e-01, /* 70 */ + 1.111847616550789e-01, /* 71 */ + 1.127441216241462e-01, /* 72 */ + 1.143032039638373e-01, /* 73 */ + 1.158620048349536e-01, /* 74 */ + 1.174205203989899e-01, /* 75 */ + 1.189787468181433e-01, /* 76 */ + 1.205366802553230e-01, /* 77 */ + 1.220943168741599e-01, /* 78 */ + 1.236516528390153e-01, /* 79 */ + 1.252086843149914e-01, /* 80 */ + 1.267654074679397e-01, /* 81 */ + 1.283218184644714e-01, /* 82 */ + 1.298779134719661e-01, /* 83 */ + 1.314336886585815e-01, /* 84 */ + 1.329891401932629e-01, /* 85 */ + 1.345442642457527e-01, /* 86 */ + 1.360990569865997e-01, /* 87 */ + 1.376535145871682e-01, /* 88 */ + 1.392076332196483e-01, /* 89 */ + 1.407614090570644e-01, /* 90 */ + 1.423148382732851e-01, /* 91 */ + 1.438679170430328e-01, /* 92 */ + 1.454206415418926e-01, /* 93 */ + 1.469730079463220e-01, /* 94 */ + 1.485250124336605e-01, /* 95 */ + 1.500766511821384e-01, /* 96 */ + 1.516279203708872e-01, /* 97 */ + 1.531788161799479e-01, /* 98 */ + 1.547293347902812e-01, /* 99 */ + 1.562794723837767e-01, /* 100 */ + 1.578292251432621e-01, /* 101 */ + 1.593785892525127e-01, /* 102 */ + 1.609275608962610e-01, /* 103 */ + 1.624761362602058e-01, /* 104 */ + 1.640243115310219e-01, /* 105 */ + 1.655720828963691e-01, /* 106 */ + 1.671194465449020e-01, /* 107 */ + 1.686663986662791e-01, /* 108 */ + 1.702129354511722e-01, /* 109 */ + 1.717590530912760e-01, /* 110 */ + 1.733047477793173e-01, /* 111 */ + 1.748500157090643e-01, /* 112 */ + 1.763948530753363e-01, /* 113 */ + 1.779392560740125e-01, /* 114 */ + 1.794832209020421e-01, /* 115 */ + 1.810267437574530e-01, /* 116 */ + 1.825698208393617e-01, /* 117 */ + 1.841124483479821e-01, /* 118 */ + 1.856546224846354e-01, /* 119 */ + 1.871963394517592e-01, /* 120 */ + 1.887375954529167e-01, /* 121 */ + 1.902783866928064e-01, /* 122 */ + 1.918187093772711e-01, /* 123 */ + 1.933585597133076e-01, /* 124 */ + 1.948979339090757e-01, /* 125 */ + 1.964368281739078e-01, /* 126 */ + 1.979752387183178e-01, /* 127 */ + 1.995131617540112e-01, /* 128 */ + 2.010505934938938e-01, /* 129 */ + 2.025875301520809e-01, /* 130 */ + 2.041239679439075e-01, /* 131 */ + 2.056599030859366e-01, /* 132 */ + 2.071953317959691e-01, /* 133 */ + 2.087302502930529e-01, /* 134 */ + 2.102646547974925e-01, /* 135 */ + 2.117985415308578e-01, /* 136 */ + 2.133319067159940e-01, /* 137 */ + 2.148647465770304e-01, /* 138 */ + 2.163970573393899e-01, /* 139 */ + 2.179288352297983e-01, /* 140 */ + 2.194600764762938e-01, /* 141 */ + 2.209907773082357e-01, /* 142 */ + 2.225209339563144e-01, /* 143 */ + 2.240505426525601e-01, /* 144 */ + 2.255795996303523e-01, /* 145 */ + 2.271081011244294e-01, /* 146 */ + 2.286360433708974e-01, /* 147 */ + 2.301634226072393e-01, /* 148 */ + 2.316902350723250e-01, /* 149 */ + 2.332164770064195e-01, /* 150 */ + 2.347421446511931e-01, /* 151 */ + 2.362672342497300e-01, /* 152 */ + 2.377917420465381e-01, /* 153 */ + 2.393156642875578e-01, /* 154 */ + 2.408389972201713e-01, /* 155 */ + 2.423617370932123e-01, /* 156 */ + 2.438838801569746e-01, /* 157 */ + 2.454054226632218e-01, /* 158 */ + 2.469263608651961e-01, /* 159 */ + 2.484466910176281e-01, /* 160 */ + 2.499664093767456e-01, /* 161 */ + 2.514855122002828e-01, /* 162 */ + 2.530039957474898e-01, /* 163 */ + 2.545218562791415e-01, /* 164 */ + 2.560390900575471e-01, /* 165 */ + 2.575556933465591e-01, /* 166 */ + 2.590716624115826e-01, /* 167 */ + 2.605869935195844e-01, /* 168 */ + 2.621016829391022e-01, /* 169 */ + 2.636157269402540e-01, /* 170 */ + 2.651291217947471e-01, /* 171 */ + 2.666418637758871e-01, /* 172 */ + 2.681539491585876e-01, /* 173 */ + 2.696653742193788e-01, /* 174 */ + 2.711761352364170e-01, /* 175 */ + 2.726862284894938e-01, /* 176 */ + 2.741956502600449e-01, /* 177 */ + 2.757043968311598e-01, /* 178 */ + 2.772124644875906e-01, /* 179 */ + 2.787198495157609e-01, /* 180 */ + 2.802265482037756e-01, /* 181 */ + 2.817325568414297e-01, /* 182 */ + 2.832378717202171e-01, /* 183 */ + 2.847424891333405e-01, /* 184 */ + 2.862464053757197e-01, /* 185 */ + 2.877496167440013e-01, /* 186 */ + 2.892521195365677e-01, /* 187 */ + 2.907539100535459e-01, /* 188 */ + 2.922549845968172e-01, /* 189 */ + 2.937553394700257e-01, /* 190 */ + 2.952549709785878e-01, /* 191 */ + 2.967538754297011e-01, /* 192 */ + 2.982520491323535e-01, /* 193 */ + 2.997494883973326e-01, /* 194 */ + 3.012461895372343e-01, /* 195 */ + 3.027421488664720e-01, /* 196 */ + 3.042373627012863e-01, /* 197 */ + 3.057318273597529e-01, /* 198 */ + 3.072255391617928e-01, /* 199 */ + 3.087184944291808e-01, /* 200 */ + 3.102106894855545e-01, /* 201 */ + 3.117021206564237e-01, /* 202 */ + 3.131927842691789e-01, /* 203 */ + 3.146826766531011e-01, /* 204 */ + 3.161717941393703e-01, /* 205 */ + 3.176601330610745e-01, /* 206 */ + 3.191476897532191e-01, /* 207 */ + 3.206344605527355e-01, /* 208 */ + 3.221204417984906e-01, /* 209 */ + 3.236056298312954e-01, /* 210 */ + 3.250900209939143e-01, /* 211 */ + 3.265736116310736e-01, /* 212 */ + 3.280563980894714e-01, /* 213 */ + 3.295383767177856e-01, /* 214 */ + 3.310195438666838e-01, /* 215 */ + 3.324998958888314e-01, /* 216 */ + 3.339794291389013e-01, /* 217 */ + 3.354581399735826e-01, /* 218 */ + 3.369360247515896e-01, /* 219 */ + 3.384130798336704e-01, /* 220 */ + 3.398893015826167e-01, /* 221 */ + 3.413646863632719e-01, /* 222 */ + 3.428392305425407e-01, /* 223 */ + 3.443129304893974e-01, /* 224 */ + 3.457857825748955e-01, /* 225 */ + 3.472577831721762e-01, /* 226 */ + 3.487289286564775e-01, /* 227 */ + 3.501992154051431e-01, /* 228 */ + 3.516686397976314e-01, /* 229 */ + 3.531371982155241e-01, /* 230 */ + 3.546048870425356e-01, /* 231 */ + 3.560717026645214e-01, /* 232 */ + 3.575376414694875e-01, /* 233 */ + 3.590026998475987e-01, /* 234 */ + 3.604668741911882e-01, /* 235 */ + 3.619301608947658e-01, /* 236 */ + 3.633925563550274e-01, /* 237 */ + 3.648540569708633e-01, /* 238 */ + 3.663146591433675e-01, /* 239 */ + 3.677743592758461e-01, /* 240 */ + 3.692331537738269e-01, /* 241 */ + 3.706910390450675e-01, /* 242 */ + 3.721480114995644e-01, /* 243 */ + 3.736040675495622e-01, /* 244 */ + 3.750592036095618e-01, /* 245 */ + 3.765134160963297e-01, /* 246 */ + 3.779667014289065e-01, /* 247 */ + 3.794190560286163e-01, /* 248 */ + 3.808704763190747e-01, /* 249 */ + 3.823209587261981e-01, /* 250 */ + 3.837704996782126e-01, /* 251 */ + 3.852190956056624e-01, /* 252 */ + 3.866667429414188e-01, /* 253 */ + 3.881134381206892e-01, /* 254 */ + 3.895591775810255e-01, /* 255 */ + 3.910039577623329e-01, /* 256 */ + 3.924477751068791e-01, /* 257 */ + 3.938906260593025e-01, /* 258 */ + 3.953325070666214e-01, /* 259 */ + 3.967734145782425e-01, /* 260 */ + 3.982133450459696e-01, /* 261 */ + 3.996522949240126e-01, /* 262 */ + 4.010902606689959e-01, /* 263 */ + 4.025272387399675e-01, /* 264 */ + 4.039632255984075e-01, /* 265 */ + 4.053982177082366e-01, /* 266 */ + 4.068322115358254e-01, /* 267 */ + 4.082652035500025e-01, /* 268 */ + 4.096971902220634e-01, /* 269 */ + 4.111281680257793e-01, /* 270 */ + 4.125581334374058e-01, /* 271 */ + 4.139870829356915e-01, /* 272 */ + 4.154150130018864e-01, /* 273 */ + 4.168419201197511e-01, /* 274 */ + 4.182678007755651e-01, /* 275 */ + 4.196926514581356e-01, /* 276 */ + 4.211164686588058e-01, /* 277 */ + 4.225392488714641e-01, /* 278 */ + 4.239609885925524e-01, /* 279 */ + 4.253816843210749e-01, /* 280 */ + 4.268013325586062e-01, /* 281 */ + 4.282199298093007e-01, /* 282 */ + 4.296374725799008e-01, /* 283 */ + 4.310539573797453e-01, /* 284 */ + 4.324693807207784e-01, /* 285 */ + 4.338837391175581e-01, /* 286 */ + 4.352970290872648e-01, /* 287 */ + 4.367092471497098e-01, /* 288 */ + 4.381203898273440e-01, /* 289 */ + 4.395304536452664e-01, /* 290 */ + 4.409394351312327e-01, /* 291 */ + 4.423473308156637e-01, /* 292 */ + 4.437541372316541e-01, /* 293 */ + 4.451598509149808e-01, /* 294 */ + 4.465644684041115e-01, /* 295 */ + 4.479679862402133e-01, /* 296 */ + 4.493704009671613e-01, /* 297 */ + 4.507717091315467e-01, /* 298 */ + 4.521719072826857e-01, /* 299 */ + 4.535709919726280e-01, /* 300 */ + 4.549689597561650e-01, /* 301 */ + 4.563658071908386e-01, /* 302 */ + 4.577615308369494e-01, /* 303 */ + 4.591561272575653e-01, /* 304 */ + 4.605495930185300e-01, /* 305 */ + 4.619419246884716e-01, /* 306 */ + 4.633331188388105e-01, /* 307 */ + 4.647231720437685e-01, /* 308 */ + 4.661120808803769e-01, /* 309 */ + 4.674998419284848e-01, /* 310 */ + 4.688864517707680e-01, /* 311 */ + 4.702719069927368e-01, /* 312 */ + 4.716562041827449e-01, /* 313 */ + 4.730393399319976e-01, /* 314 */ + 4.744213108345603e-01, /* 315 */ + 4.758021134873666e-01, /* 316 */ + 4.771817444902270e-01, /* 317 */ + 4.785602004458372e-01, /* 318 */ + 4.799374779597863e-01, /* 319 */ + 4.813135736405654e-01, /* 320 */ + 4.826884840995759e-01, /* 321 */ + 4.840622059511374e-01, /* 322 */ + 4.854347358124969e-01, /* 323 */ + 4.868060703038363e-01, /* 324 */ + 4.881762060482813e-01, /* 325 */ + 4.895451396719092e-01, /* 326 */ + 4.909128678037579e-01, /* 327 */ + 4.922793870758333e-01, /* 328 */ + 4.936446941231185e-01, /* 329 */ + 4.950087855835814e-01, /* 330 */ + 4.963716580981834e-01, /* 331 */ + 4.977333083108875e-01, /* 332 */ + 4.990937328686666e-01, /* 333 */ + 5.004529284215117e-01, /* 334 */ + 5.018108916224401e-01, /* 335 */ + 5.031676191275039e-01, /* 336 */ + 5.045231075957979e-01, /* 337 */ + 5.058773536894682e-01, /* 338 */ + 5.072303540737202e-01, /* 339 */ + 5.085821054168265e-01, /* 340 */ + 5.099326043901359e-01, /* 341 */ + 5.112818476680807e-01, /* 342 */ + 5.126298319281856e-01, /* 343 */ + 5.139765538510755e-01, /* 344 */ + 5.153220101204837e-01, /* 345 */ + 5.166661974232605e-01, /* 346 */ + 5.180091124493803e-01, /* 347 */ + 5.193507518919511e-01, /* 348 */ + 5.206911124472217e-01, /* 349 */ + 5.220301908145902e-01, /* 350 */ + 5.233679836966120e-01, /* 351 */ + 5.247044877990080e-01, /* 352 */ + 5.260396998306727e-01, /* 353 */ + 5.273736165036822e-01, /* 354 */ + 5.287062345333027e-01, /* 355 */ + 5.300375506379977e-01, /* 356 */ + 5.313675615394372e-01, /* 357 */ + 5.326962639625050e-01, /* 358 */ + 5.340236546353070e-01, /* 359 */ + 5.353497302891792e-01, /* 360 */ + 5.366744876586959e-01, /* 361 */ + 5.379979234816776e-01, /* 362 */ + 5.393200344991992e-01, /* 363 */ + 5.406408174555976e-01, /* 364 */ + 5.419602690984802e-01, /* 365 */ + 5.432783861787328e-01, /* 366 */ + 5.445951654505273e-01, /* 367 */ + 5.459106036713303e-01, /* 368 */ + 5.472246976019102e-01, /* 369 */ + 5.485374440063460e-01, /* 370 */ + 5.498488396520349e-01, /* 371 */ + 5.511588813097004e-01, /* 372 */ + 5.524675657533998e-01, /* 373 */ + 5.537748897605330e-01, /* 374 */ + 5.550808501118496e-01, /* 375 */ + 5.563854435914573e-01, /* 376 */ + 5.576886669868294e-01, /* 377 */ + 5.589905170888135e-01, /* 378 */ + 5.602909906916385e-01, /* 379 */ + 5.615900845929231e-01, /* 380 */ + 5.628877955936834e-01, /* 381 */ + 5.641841204983408e-01, /* 382 */ + 5.654790561147299e-01, /* 383 */ + 5.667725992541067e-01, /* 384 */ + 5.680647467311558e-01, /* 385 */ + 5.693554953639987e-01, /* 386 */ + 5.706448419742013e-01, /* 387 */ + 5.719327833867824e-01, /* 388 */ + 5.732193164302208e-01, /* 389 */ + 5.745044379364633e-01, /* 390 */ + 5.757881447409327e-01, /* 391 */ + 5.770704336825352e-01, /* 392 */ + 5.783513016036690e-01, /* 393 */ + 5.796307453502310e-01, /* 394 */ + 5.809087617716252e-01, /* 395 */ + 5.821853477207707e-01, /* 396 */ + 5.834605000541085e-01, /* 397 */ + 5.847342156316105e-01, /* 398 */ + 5.860064913167862e-01, /* 399 */ + 5.872773239766905e-01, /* 400 */ + 5.885467104819324e-01, /* 401 */ + 5.898146477066816e-01, /* 402 */ + 5.910811325286766e-01, /* 403 */ + 5.923461618292324e-01, /* 404 */ + 5.936097324932486e-01, /* 405 */ + 5.948718414092160e-01, /* 406 */ + 5.961324854692254e-01, /* 407 */ + 5.973916615689745e-01, /* 408 */ + 5.986493666077760e-01, /* 409 */ + 5.999055974885650e-01, /* 410 */ + 6.011603511179066e-01, /* 411 */ + 6.024136244060035e-01, /* 412 */ + 6.036654142667041e-01, /* 413 */ + 6.049157176175093e-01, /* 414 */ + 6.061645313795805e-01, /* 415 */ + 6.074118524777475e-01, /* 416 */ + 6.086576778405154e-01, /* 417 */ + 6.099020044000728e-01, /* 418 */ + 6.111448290922987e-01, /* 419 */ + 6.123861488567709e-01, /* 420 */ + 6.136259606367725e-01, /* 421 */ + 6.148642613793004e-01, /* 422 */ + 6.161010480350722e-01, /* 423 */ + 6.173363175585338e-01, /* 424 */ + 6.185700669078673e-01, /* 425 */ + 6.198022930449979e-01, /* 426 */ + 6.210329929356019e-01, /* 427 */ + 6.222621635491136e-01, /* 428 */ + 6.234898018587335e-01, /* 429 */ + 6.247159048414351e-01, /* 430 */ + 6.259404694779729e-01, /* 431 */ + 6.271634927528890e-01, /* 432 */ + 6.283849716545215e-01, /* 433 */ + 6.296049031750114e-01, /* 434 */ + 6.308232843103100e-01, /* 435 */ + 6.320401120601865e-01, /* 436 */ + 6.332553834282351e-01, /* 437 */ + 6.344690954218827e-01, /* 438 */ + 6.356812450523961e-01, /* 439 */ + 6.368918293348892e-01, /* 440 */ + 6.381008452883308e-01, /* 441 */ + 6.393082899355514e-01, /* 442 */ + 6.405141603032511e-01, /* 443 */ + 6.417184534220064e-01, /* 444 */ + 6.429211663262777e-01, /* 445 */ + 6.441222960544168e-01, /* 446 */ + 6.453218396486741e-01, /* 447 */ + 6.465197941552053e-01, /* 448 */ + 6.477161566240798e-01, /* 449 */ + 6.489109241092871e-01, /* 450 */ + 6.501040936687442e-01, /* 451 */ + 6.512956623643031e-01, /* 452 */ + 6.524856272617580e-01, /* 453 */ + 6.536739854308520e-01, /* 454 */ + 6.548607339452850e-01, /* 455 */ + 6.560458698827208e-01, /* 456 */ + 6.572293903247938e-01, /* 457 */ + 6.584112923571166e-01, /* 458 */ + 6.595915730692873e-01, /* 459 */ + 6.607702295548961e-01, /* 460 */ + 6.619472589115332e-01, /* 461 */ + 6.631226582407952e-01, /* 462 */ + 6.642964246482929e-01, /* 463 */ + 6.654685552436579e-01, /* 464 */ + 6.666390471405501e-01, /* 465 */ + 6.678078974566646e-01, /* 466 */ + 6.689751033137388e-01, /* 467 */ + 6.701406618375595e-01, /* 468 */ + 6.713045701579703e-01, /* 469 */ + 6.724668254088779e-01, /* 470 */ + 6.736274247282602e-01, /* 471 */ + 6.747863652581724e-01, /* 472 */ + 6.759436441447543e-01, /* 473 */ + 6.770992585382379e-01, /* 474 */ + 6.782532055929538e-01, /* 475 */ + 6.794054824673382e-01, /* 476 */ + 6.805560863239402e-01, /* 477 */ + 6.817050143294286e-01, /* 478 */ + 6.828522636545991e-01, /* 479 */ + 6.839978314743810e-01, /* 480 */ + 6.851417149678442e-01, /* 481 */ + 6.862839113182062e-01, /* 482 */ + 6.874244177128394e-01, /* 483 */ + 6.885632313432770e-01, /* 484 */ + 6.897003494052213e-01, /* 485 */ + 6.908357690985494e-01, /* 486 */ + 6.919694876273208e-01, /* 487 */ + 6.931015021997841e-01, /* 488 */ + 6.942318100283835e-01, /* 489 */ + 6.953604083297665e-01, /* 490 */ + 6.964872943247901e-01, /* 491 */ + 6.976124652385276e-01, /* 492 */ + 6.987359183002758e-01, /* 493 */ + 6.998576507435618e-01, /* 494 */ + 7.009776598061495e-01, /* 495 */ + 7.020959427300464e-01, /* 496 */ + 7.032124967615111e-01, /* 497 */ + 7.043273191510590e-01, /* 498 */ + 7.054404071534700e-01, /* 499 */ + 7.065517580277947e-01, /* 500 */ + 7.076613690373614e-01, /* 501 */ + 7.087692374497827e-01, /* 502 */ + 7.098753605369623e-01, /* 503 */ + 7.109797355751019e-01, /* 504 */ + 7.120823598447074e-01, /* 505 */ + 7.131832306305962e-01, /* 506 */ + 7.142823452219036e-01, /* 507 */ + 7.153797009120892e-01, /* 508 */ + 7.164752949989442e-01, /* 509 */ + 7.175691247845974e-01, /* 510 */ + 7.186611875755224e-01, /* 511 */ + 7.197514806825439e-01, /* 512 */ + 7.208400014208443e-01, /* 513 */ + 7.219267471099703e-01, /* 514 */ + 7.230117150738400e-01, /* 515 */ + 7.240949026407488e-01, /* 516 */ + 7.251763071433764e-01, /* 517 */ + 7.262559259187933e-01, /* 518 */ + 7.273337563084670e-01, /* 519 */ + 7.284097956582691e-01, /* 520 */ + 7.294840413184817e-01, /* 521 */ + 7.305564906438036e-01, /* 522 */ + 7.316271409933570e-01, /* 523 */ + 7.326959897306943e-01, /* 524 */ + 7.337630342238040e-01, /* 525 */ + 7.348282718451178e-01, /* 526 */ + 7.358916999715164e-01, /* 527 */ + 7.369533159843368e-01, /* 528 */ + 7.380131172693778e-01, /* 529 */ + 7.390711012169073e-01, /* 530 */ + 7.401272652216682e-01, /* 531 */ + 7.411816066828849e-01, /* 532 */ + 7.422341230042699e-01, /* 533 */ + 7.432848115940299e-01, /* 534 */ + 7.443336698648725e-01, /* 535 */ + 7.453806952340122e-01, /* 536 */ + 7.464258851231773e-01, /* 537 */ + 7.474692369586156e-01, /* 538 */ + 7.485107481711011e-01, /* 539 */ + 7.495504161959405e-01, /* 540 */ + 7.505882384729792e-01, /* 541 */ + 7.516242124466075e-01, /* 542 */ + 7.526583355657674e-01, /* 543 */ + 7.536906052839586e-01, /* 544 */ + 7.547210190592443e-01, /* 545 */ + 7.557495743542583e-01, /* 546 */ + 7.567762686362108e-01, /* 547 */ + 7.578010993768947e-01, /* 548 */ + 7.588240640526916e-01, /* 549 */ + 7.598451601445788e-01, /* 550 */ + 7.608643851381341e-01, /* 551 */ + 7.618817365235437e-01, /* 552 */ + 7.628972117956068e-01, /* 553 */ + 7.639108084537428e-01, /* 554 */ + 7.649225240019972e-01, /* 555 */ + 7.659323559490476e-01, /* 556 */ + 7.669403018082099e-01, /* 557 */ + 7.679463590974444e-01, /* 558 */ + 7.689505253393620e-01, /* 559 */ + 7.699527980612303e-01, /* 560 */ + 7.709531747949796e-01, /* 561 */ + 7.719516530772089e-01, /* 562 */ + 7.729482304491924e-01, /* 563 */ + 7.739429044568850e-01, /* 564 */ + 7.749356726509284e-01, /* 565 */ + 7.759265325866578e-01, /* 566 */ + 7.769154818241071e-01, /* 567 */ + 7.779025179280153e-01, /* 568 */ + 7.788876384678325e-01, /* 569 */ + 7.798708410177257e-01, /* 570 */ + 7.808521231565851e-01, /* 571 */ + 7.818314824680298e-01, /* 572 */ + 7.828089165404135e-01, /* 573 */ + 7.837844229668313e-01, /* 574 */ + 7.847579993451246e-01, /* 575 */ + 7.857296432778876e-01, /* 576 */ + 7.866993523724733e-01, /* 577 */ + 7.876671242409992e-01, /* 578 */ + 7.886329565003528e-01, /* 579 */ + 7.895968467721981e-01, /* 580 */ + 7.905587926829811e-01, /* 581 */ + 7.915187918639360e-01, /* 582 */ + 7.924768419510904e-01, /* 583 */ + 7.934329405852717e-01, /* 584 */ + 7.943870854121126e-01, /* 585 */ + 7.953392740820571e-01, /* 586 */ + 7.962895042503660e-01, /* 587 */ + 7.972377735771232e-01, /* 588 */ + 7.981840797272409e-01, /* 589 */ + 7.991284203704654e-01, /* 590 */ + 8.000707931813833e-01, /* 591 */ + 8.010111958394268e-01, /* 592 */ + 8.019496260288795e-01, /* 593 */ + 8.028860814388824e-01, /* 594 */ + 8.038205597634391e-01, /* 595 */ + 8.047530587014217e-01, /* 596 */ + 8.056835759565766e-01, /* 597 */ + 8.066121092375300e-01, /* 598 */ + 8.075386562577938e-01, /* 599 */ + 8.084632147357704e-01, /* 600 */ + 8.093857823947597e-01, /* 601 */ + 8.103063569629634e-01, /* 602 */ + 8.112249361734913e-01, /* 603 */ + 8.121415177643669e-01, /* 604 */ + 8.130560994785324e-01, /* 605 */ + 8.139686790638551e-01, /* 606 */ + 8.148792542731320e-01, /* 607 */ + 8.157878228640961e-01, /* 608 */ + 8.166943825994217e-01, /* 609 */ + 8.175989312467298e-01, /* 610 */ + 8.185014665785935e-01, /* 611 */ + 8.194019863725437e-01, /* 612 */ + 8.203004884110747e-01, /* 613 */ + 8.211969704816493e-01, /* 614 */ + 8.220914303767043e-01, /* 615 */ + 8.229838658936564e-01, /* 616 */ + 8.238742748349069e-01, /* 617 */ + 8.247626550078477e-01, /* 618 */ + 8.256490042248665e-01, /* 619 */ + 8.265333203033521e-01, /* 620 */ + 8.274156010656999e-01, /* 621 */ + 8.282958443393171e-01, /* 622 */ + 8.291740479566283e-01, /* 623 */ + 8.300502097550806e-01, /* 624 */ + 8.309243275771491e-01, /* 625 */ + 8.317963992703420e-01, /* 626 */ + 8.326664226872064e-01, /* 627 */ + 8.335343956853326e-01, /* 628 */ + 8.344003161273608e-01, /* 629 */ + 8.352641818809847e-01, /* 630 */ + 8.361259908189583e-01, /* 631 */ + 8.369857408191002e-01, /* 632 */ + 8.378434297642989e-01, /* 633 */ + 8.386990555425186e-01, /* 634 */ + 8.395526160468036e-01, /* 635 */ + 8.404041091752841e-01, /* 636 */ + 8.412535328311811e-01, /* 637 */ + 8.421008849228117e-01, /* 638 */ + 8.429461633635940e-01, /* 639 */ + 8.437893660720525e-01, /* 640 */ + 8.446304909718232e-01, /* 641 */ + 8.454695359916585e-01, /* 642 */ + 8.463064990654326e-01, /* 643 */ + 8.471413781321465e-01, /* 644 */ + 8.479741711359327e-01, /* 645 */ + 8.488048760260607e-01, /* 646 */ + 8.496334907569423e-01, /* 647 */ + 8.504600132881356e-01, /* 648 */ + 8.512844415843511e-01, /* 649 */ + 8.521067736154565e-01, /* 650 */ + 8.529270073564810e-01, /* 651 */ + 8.537451407876209e-01, /* 652 */ + 8.545611718942447e-01, /* 653 */ + 8.553750986668979e-01, /* 654 */ + 8.561869191013073e-01, /* 655 */ + 8.569966311983869e-01, /* 656 */ + 8.578042329642425e-01, /* 657 */ + 8.586097224101764e-01, /* 658 */ + 8.594130975526924e-01, /* 659 */ + 8.602143564135006e-01, /* 660 */ + 8.610134970195229e-01, /* 661 */ + 8.618105174028966e-01, /* 662 */ + 8.626054156009807e-01, /* 663 */ + 8.633981896563595e-01, /* 664 */ + 8.641888376168482e-01, /* 665 */ + 8.649773575354974e-01, /* 666 */ + 8.657637474705979e-01, /* 667 */ + 8.665480054856857e-01, /* 668 */ + 8.673301296495464e-01, /* 669 */ + 8.681101180362202e-01, /* 670 */ + 8.688879687250065e-01, /* 671 */ + 8.696636798004691e-01, /* 672 */ + 8.704372493524400e-01, /* 673 */ + 8.712086754760252e-01, /* 674 */ + 8.719779562716083e-01, /* 675 */ + 8.727450898448561e-01, /* 676 */ + 8.735100743067228e-01, /* 677 */ + 8.742729077734545e-01, /* 678 */ + 8.750335883665944e-01, /* 679 */ + 8.757921142129869e-01, /* 680 */ + 8.765484834447824e-01, /* 681 */ + 8.773026941994420e-01, /* 682 */ + 8.780547446197419e-01, /* 683 */ + 8.788046328537781e-01, /* 684 */ + 8.795523570549709e-01, /* 685 */ + 8.802979153820697e-01, /* 686 */ + 8.810413059991569e-01, /* 687 */ + 8.817825270756531e-01, /* 688 */ + 8.825215767863213e-01, /* 689 */ + 8.832584533112713e-01, /* 690 */ + 8.839931548359646e-01, /* 691 */ + 8.847256795512183e-01, /* 692 */ + 8.854560256532099e-01, /* 693 */ + 8.861841913434817e-01, /* 694 */ + 8.869101748289453e-01, /* 695 */ + 8.876339743218858e-01, /* 696 */ + 8.883555880399664e-01, /* 697 */ + 8.890750142062326e-01, /* 698 */ + 8.897922510491166e-01, /* 699 */ + 8.905072968024423e-01, /* 700 */ + 8.912201497054284e-01, /* 701 */ + 8.919308080026938e-01, /* 702 */ + 8.926392699442616e-01, /* 703 */ + 8.933455337855631e-01, /* 704 */ + 8.940495977874426e-01, /* 705 */ + 8.947514602161615e-01, /* 706 */ + 8.954511193434023e-01, /* 707 */ + 8.961485734462731e-01, /* 708 */ + 8.968438208073118e-01, /* 709 */ + 8.975368597144907e-01, /* 710 */ + 8.982276884612198e-01, /* 711 */ + 8.989163053463520e-01, /* 712 */ + 8.996027086741867e-01, /* 713 */ + 9.002868967544739e-01, /* 714 */ + 9.009688679024191e-01, /* 715 */ + 9.016486204386864e-01, /* 716 */ + 9.023261526894035e-01, /* 717 */ + 9.030014629861653e-01, /* 718 */ + 9.036745496660386e-01, /* 719 */ + 9.043454110715651e-01, /* 720 */ + 9.050140455507668e-01, /* 721 */ + 9.056804514571491e-01, /* 722 */ + 9.063446271497057e-01, /* 723 */ + 9.070065709929211e-01, /* 724 */ + 9.076662813567770e-01, /* 725 */ + 9.083237566167539e-01, /* 726 */ + 9.089789951538368e-01, /* 727 */ + 9.096319953545183e-01, /* 728 */ + 9.102827556108030e-01, /* 729 */ + 9.109312743202110e-01, /* 730 */ + 9.115775498857827e-01, /* 731 */ + 9.122215807160815e-01, /* 732 */ + 9.128633652251990e-01, /* 733 */ + 9.135029018327580e-01, /* 734 */ + 9.141401889639166e-01, /* 735 */ + 9.147752250493725e-01, /* 736 */ + 9.154080085253663e-01, /* 737 */ + 9.160385378336857e-01, /* 738 */ + 9.166668114216692e-01, /* 739 */ + 9.172928277422099e-01, /* 740 */ + 9.179165852537593e-01, /* 741 */ + 9.185380824203315e-01, /* 742 */ + 9.191573177115061e-01, /* 743 */ + 9.197742896024330e-01, /* 744 */ + 9.203889965738354e-01, /* 745 */ + 9.210014371120140e-01, /* 746 */ + 9.216116097088501e-01, /* 747 */ + 9.222195128618103e-01, /* 748 */ + 9.228251450739493e-01, /* 749 */ + 9.234285048539139e-01, /* 750 */ + 9.240295907159470e-01, /* 751 */ + 9.246284011798909e-01, /* 752 */ + 9.252249347711905e-01, /* 753 */ + 9.258191900208981e-01, /* 754 */ + 9.264111654656760e-01, /* 755 */ + 9.270008596478005e-01, /* 756 */ + 9.275882711151657e-01, /* 757 */ + 9.281733984212863e-01, /* 758 */ + 9.287562401253023e-01, /* 759 */ + 9.293367947919815e-01, /* 760 */ + 9.299150609917235e-01, /* 761 */ + 9.304910373005634e-01, /* 762 */ + 9.310647223001750e-01, /* 763 */ + 9.316361145778743e-01, /* 764 */ + 9.322052127266233e-01, /* 765 */ + 9.327720153450327e-01, /* 766 */ + 9.333365210373668e-01, /* 767 */ + 9.338987284135449e-01, /* 768 */ + 9.344586360891468e-01, /* 769 */ + 9.350162426854148e-01, /* 770 */ + 9.355715468292575e-01, /* 771 */ + 9.361245471532534e-01, /* 772 */ + 9.366752422956540e-01, /* 773 */ + 9.372236309003873e-01, /* 774 */ + 9.377697116170610e-01, /* 775 */ + 9.383134831009662e-01, /* 776 */ + 9.388549440130799e-01, /* 777 */ + 9.393940930200693e-01, /* 778 */ + 9.399309287942944e-01, /* 779 */ + 9.404654500138115e-01, /* 780 */ + 9.409976553623765e-01, /* 781 */ + 9.415275435294478e-01, /* 782 */ + 9.420551132101902e-01, /* 783 */ + 9.425803631054773e-01, /* 784 */ + 9.431032919218956e-01, /* 785 */ + 9.436238983717468e-01, /* 786 */ + 9.441421811730513e-01, /* 787 */ + 9.446581390495518e-01, /* 788 */ + 9.451717707307158e-01, /* 789 */ + 9.456830749517390e-01, /* 790 */ + 9.461920504535486e-01, /* 791 */ + 9.466986959828059e-01, /* 792 */ + 9.472030102919100e-01, /* 793 */ + 9.477049921390005e-01, /* 794 */ + 9.482046402879605e-01, /* 795 */ + 9.487019535084197e-01, /* 796 */ + 9.491969305757577e-01, /* 797 */ + 9.496895702711068e-01, /* 798 */ + 9.501798713813550e-01, /* 799 */ + 9.506678326991488e-01, /* 800 */ + 9.511534530228968e-01, /* 801 */ + 9.516367311567716e-01, /* 802 */ + 9.521176659107141e-01, /* 803 */ + 9.525962561004352e-01, /* 804 */ + 9.530725005474194e-01, /* 805 */ + 9.535463980789276e-01, /* 806 */ + 9.540179475279997e-01, /* 807 */ + 9.544871477334580e-01, /* 808 */ + 9.549539975399095e-01, /* 809 */ + 9.554184957977490e-01, /* 810 */ + 9.558806413631620e-01, /* 811 */ + 9.563404330981276e-01, /* 812 */ + 9.567978698704207e-01, /* 813 */ + 9.572529505536158e-01, /* 814 */ + 9.577056740270887e-01, /* 815 */ + 9.581560391760202e-01, /* 816 */ + 9.586040448913981e-01, /* 817 */ + 9.590496900700202e-01, /* 818 */ + 9.594929736144974e-01, /* 819 */ + 9.599338944332557e-01, /* 820 */ + 9.603724514405396e-01, /* 821 */ + 9.608086435564140e-01, /* 822 */ + 9.612424697067677e-01, /* 823 */ + 9.616739288233154e-01, /* 824 */ + 9.621030198436005e-01, /* 825 */ + 9.625297417109979e-01, /* 826 */ + 9.629540933747166e-01, /* 827 */ + 9.633760737898017e-01, /* 828 */ + 9.637956819171380e-01, /* 829 */ + 9.642129167234518e-01, /* 830 */ + 9.646277771813133e-01, /* 831 */ + 9.650402622691399e-01, /* 832 */ + 9.654503709711981e-01, /* 833 */ + 9.658581022776063e-01, /* 834 */ + 9.662634551843370e-01, /* 835 */ + 9.666664286932195e-01, /* 836 */ + 9.670670218119425e-01, /* 837 */ + 9.674652335540560e-01, /* 838 */ + 9.678610629389744e-01, /* 839 */ + 9.682545089919784e-01, /* 840 */ + 9.686455707442176e-01, /* 841 */ + 9.690342472327130e-01, /* 842 */ + 9.694205375003593e-01, /* 843 */ + 9.698044405959267e-01, /* 844 */ + 9.701859555740645e-01, /* 845 */ + 9.705650814953021e-01, /* 846 */ + 9.709418174260520e-01, /* 847 */ + 9.713161624386123e-01, /* 848 */ + 9.716881156111683e-01, /* 849 */ + 9.720576760277954e-01, /* 850 */ + 9.724248427784608e-01, /* 851 */ + 9.727896149590264e-01, /* 852 */ + 9.731519916712503e-01, /* 853 */ + 9.735119720227898e-01, /* 854 */ + 9.738695551272029e-01, /* 855 */ + 9.742247401039505e-01, /* 856 */ + 9.745775260783994e-01, /* 857 */ + 9.749279121818236e-01, /* 858 */ + 9.752758975514066e-01, /* 859 */ + 9.756214813302438e-01, /* 860 */ + 9.759646626673444e-01, /* 861 */ + 9.763054407176336e-01, /* 862 */ + 9.766438146419546e-01, /* 863 */ + 9.769797836070706e-01, /* 864 */ + 9.773133467856671e-01, /* 865 */ + 9.776445033563537e-01, /* 866 */ + 9.779732525036661e-01, /* 867 */ + 9.782995934180686e-01, /* 868 */ + 9.786235252959552e-01, /* 869 */ + 9.789450473396526e-01, /* 870 */ + 9.792641587574211e-01, /* 871 */ + 9.795808587634576e-01, /* 872 */ + 9.798951465778968e-01, /* 873 */ + 9.802070214268133e-01, /* 874 */ + 9.805164825422236e-01, /* 875 */ + 9.808235291620881e-01, /* 876 */ + 9.811281605303128e-01, /* 877 */ + 9.814303758967510e-01, /* 878 */ + 9.817301745172056e-01, /* 879 */ + 9.820275556534303e-01, /* 880 */ + 9.823225185731322e-01, /* 881 */ + 9.826150625499731e-01, /* 882 */ + 9.829051868635711e-01, /* 883 */ + 9.831928907995030e-01, /* 884 */ + 9.834781736493055e-01, /* 885 */ + 9.837610347104772e-01, /* 886 */ + 9.840414732864804e-01, /* 887 */ + 9.843194886867427e-01, /* 888 */ + 9.845950802266584e-01, /* 889 */ + 9.848682472275909e-01, /* 890 */ + 9.851389890168738e-01, /* 891 */ + 9.854073049278126e-01, /* 892 */ + 9.856731942996866e-01, /* 893 */ + 9.859366564777504e-01, /* 894 */ + 9.861976908132354e-01, /* 895 */ + 9.864562966633516e-01, /* 896 */ + 9.867124733912889e-01, /* 897 */ + 9.869662203662192e-01, /* 898 */ + 9.872175369632971e-01, /* 899 */ + 9.874664225636623e-01, /* 900 */ + 9.877128765544410e-01, /* 901 */ + 9.879568983287464e-01, /* 902 */ + 9.881984872856817e-01, /* 903 */ + 9.884376428303405e-01, /* 904 */ + 9.886743643738087e-01, /* 905 */ + 9.889086513331659e-01, /* 906 */ + 9.891405031314865e-01, /* 907 */ + 9.893699191978420e-01, /* 908 */ + 9.895968989673012e-01, /* 909 */ + 9.898214418809327e-01, /* 910 */ + 9.900435473858056e-01, /* 911 */ + 9.902632149349908e-01, /* 912 */ + 9.904804439875632e-01, /* 913 */ + 9.906952340086018e-01, /* 914 */ + 9.909075844691920e-01, /* 915 */ + 9.911174948464266e-01, /* 916 */ + 9.913249646234069e-01, /* 917 */ + 9.915299932892441e-01, /* 918 */ + 9.917325803390605e-01, /* 919 */ + 9.919327252739911e-01, /* 920 */ + 9.921304276011843e-01, /* 921 */ + 9.923256868338034e-01, /* 922 */ + 9.925185024910277e-01, /* 923 */ + 9.927088740980540e-01, /* 924 */ + 9.928968011860971e-01, /* 925 */ + 9.930822832923918e-01, /* 926 */ + 9.932653199601933e-01, /* 927 */ + 9.934459107387786e-01, /* 928 */ + 9.936240551834480e-01, /* 929 */ + 9.937997528555254e-01, /* 930 */ + 9.939730033223599e-01, /* 931 */ + 9.941438061573271e-01, /* 932 */ + 9.943121609398295e-01, /* 933 */ + 9.944780672552980e-01, /* 934 */ + 9.946415246951927e-01, /* 935 */ + 9.948025328570040e-01, /* 936 */ + 9.949610913442537e-01, /* 937 */ + 9.951171997664957e-01, /* 938 */ + 9.952708577393173e-01, /* 939 */ + 9.954220648843398e-01, /* 940 */ + 9.955708208292197e-01, /* 941 */ + 9.957171252076493e-01, /* 942 */ + 9.958609776593582e-01, /* 943 */ + 9.960023778301135e-01, /* 944 */ + 9.961413253717212e-01, /* 945 */ + 9.962778199420265e-01, /* 946 */ + 9.964118612049152e-01, /* 947 */ + 9.965434488303145e-01, /* 948 */ + 9.966725824941932e-01, /* 949 */ + 9.967992618785633e-01, /* 950 */ + 9.969234866714800e-01, /* 951 */ + 9.970452565670431e-01, /* 952 */ + 9.971645712653977e-01, /* 953 */ + 9.972814304727343e-01, /* 954 */ + 9.973958339012905e-01, /* 955 */ + 9.975077812693507e-01, /* 956 */ + 9.976172723012476e-01, /* 957 */ + 9.977243067273625e-01, /* 958 */ + 9.978288842841259e-01, /* 959 */ + 9.979310047140184e-01, /* 960 */ + 9.980306677655713e-01, /* 961 */ + 9.981278731933668e-01, /* 962 */ + 9.982226207580394e-01, /* 963 */ + 9.983149102262756e-01, /* 964 */ + 9.984047413708150e-01, /* 965 */ + 9.984921139704509e-01, /* 966 */ + 9.985770278100307e-01, /* 967 */ + 9.986594826804561e-01, /* 968 */ + 9.987394783786845e-01, /* 969 */ + 9.988170147077284e-01, /* 970 */ + 9.988920914766568e-01, /* 971 */ + 9.989647085005952e-01, /* 972 */ + 9.990348656007260e-01, /* 973 */ + 9.991025626042892e-01, /* 974 */ + 9.991677993445829e-01, /* 975 */ + 9.992305756609632e-01, /* 976 */ + 9.992908913988453e-01, /* 977 */ + 9.993487464097032e-01, /* 978 */ + 9.994041405510704e-01, /* 979 */ + 9.994570736865406e-01, /* 980 */ + 9.995075456857671e-01, /* 981 */ + 9.995555564244639e-01, /* 982 */ + 9.996011057844061e-01, /* 983 */ + 9.996441936534295e-01, /* 984 */ + 9.996848199254315e-01, /* 985 */ + 9.997229845003707e-01, /* 986 */ + 9.997586872842681e-01, /* 987 */ + 9.997919281892065e-01, /* 988 */ + 9.998227071333310e-01, /* 989 */ + 9.998510240408494e-01, /* 990 */ + 9.998768788420320e-01, /* 991 */ + 9.999002714732120e-01, /* 992 */ + 9.999212018767858e-01, /* 993 */ + 9.999396700012126e-01, /* 994 */ + 9.999556758010154e-01, /* 995 */ + 9.999692192367803e-01, /* 996 */ + 9.999803002751568e-01, /* 997 */ + 9.999889188888583e-01, /* 998 */ + 9.999950750566616e-01, /* 999 */ + 9.999987687634074e-01, /* 1000 */ + 1.000000000000000e+00, /* 1001 */ +}; + diff --git a/libs/fluidsynth/fluid_rvoice_dsp_tables.inc.h b/libs/fluidsynth/fluid_rvoice_dsp_tables.inc.h new file mode 100644 index 00000000000..b031f421d34 --- /dev/null +++ b/libs/fluidsynth/fluid_rvoice_dsp_tables.inc.h @@ -0,0 +1,4110 @@ +/* THIS FILE HAS BEEN AUTOMATICALLY GENERATED. DO NOT EDIT. */ + +static const fluid_real_t interp_coeff_linear[256][2] = { + { + 1.000000000000000e+00, /* 0 */ + 0.000000000000000e+00, /* 1 */ + }, { + 9.960937500000000e-01, /* 2 */ + 3.906250000000000e-03, /* 3 */ + }, { + 9.921875000000000e-01, /* 4 */ + 7.812500000000000e-03, /* 5 */ + }, { + 9.882812500000000e-01, /* 6 */ + 1.171875000000000e-02, /* 7 */ + }, { + 9.843750000000000e-01, /* 8 */ + 1.562500000000000e-02, /* 9 */ + }, { + 9.804687500000000e-01, /* 10 */ + 1.953125000000000e-02, /* 11 */ + }, { + 9.765625000000000e-01, /* 12 */ + 2.343750000000000e-02, /* 13 */ + }, { + 9.726562500000000e-01, /* 14 */ + 2.734375000000000e-02, /* 15 */ + }, { + 9.687500000000000e-01, /* 16 */ + 3.125000000000000e-02, /* 17 */ + }, { + 9.648437500000000e-01, /* 18 */ + 3.515625000000000e-02, /* 19 */ + }, { + 9.609375000000000e-01, /* 20 */ + 3.906250000000000e-02, /* 21 */ + }, { + 9.570312500000000e-01, /* 22 */ + 4.296875000000000e-02, /* 23 */ + }, { + 9.531250000000000e-01, /* 24 */ + 4.687500000000000e-02, /* 25 */ + }, { + 9.492187500000000e-01, /* 26 */ + 5.078125000000000e-02, /* 27 */ + }, { + 9.453125000000000e-01, /* 28 */ + 5.468750000000000e-02, /* 29 */ + }, { + 9.414062500000000e-01, /* 30 */ + 5.859375000000000e-02, /* 31 */ + }, { + 9.375000000000000e-01, /* 32 */ + 6.250000000000000e-02, /* 33 */ + }, { + 9.335937500000000e-01, /* 34 */ + 6.640625000000000e-02, /* 35 */ + }, { + 9.296875000000000e-01, /* 36 */ + 7.031250000000000e-02, /* 37 */ + }, { + 9.257812500000000e-01, /* 38 */ + 7.421875000000000e-02, /* 39 */ + }, { + 9.218750000000000e-01, /* 40 */ + 7.812500000000000e-02, /* 41 */ + }, { + 9.179687500000000e-01, /* 42 */ + 8.203125000000000e-02, /* 43 */ + }, { + 9.140625000000000e-01, /* 44 */ + 8.593750000000000e-02, /* 45 */ + }, { + 9.101562500000000e-01, /* 46 */ + 8.984375000000000e-02, /* 47 */ + }, { + 9.062500000000000e-01, /* 48 */ + 9.375000000000000e-02, /* 49 */ + }, { + 9.023437500000000e-01, /* 50 */ + 9.765625000000000e-02, /* 51 */ + }, { + 8.984375000000000e-01, /* 52 */ + 1.015625000000000e-01, /* 53 */ + }, { + 8.945312500000000e-01, /* 54 */ + 1.054687500000000e-01, /* 55 */ + }, { + 8.906250000000000e-01, /* 56 */ + 1.093750000000000e-01, /* 57 */ + }, { + 8.867187500000000e-01, /* 58 */ + 1.132812500000000e-01, /* 59 */ + }, { + 8.828125000000000e-01, /* 60 */ + 1.171875000000000e-01, /* 61 */ + }, { + 8.789062500000000e-01, /* 62 */ + 1.210937500000000e-01, /* 63 */ + }, { + 8.750000000000000e-01, /* 64 */ + 1.250000000000000e-01, /* 65 */ + }, { + 8.710937500000000e-01, /* 66 */ + 1.289062500000000e-01, /* 67 */ + }, { + 8.671875000000000e-01, /* 68 */ + 1.328125000000000e-01, /* 69 */ + }, { + 8.632812500000000e-01, /* 70 */ + 1.367187500000000e-01, /* 71 */ + }, { + 8.593750000000000e-01, /* 72 */ + 1.406250000000000e-01, /* 73 */ + }, { + 8.554687500000000e-01, /* 74 */ + 1.445312500000000e-01, /* 75 */ + }, { + 8.515625000000000e-01, /* 76 */ + 1.484375000000000e-01, /* 77 */ + }, { + 8.476562500000000e-01, /* 78 */ + 1.523437500000000e-01, /* 79 */ + }, { + 8.437500000000000e-01, /* 80 */ + 1.562500000000000e-01, /* 81 */ + }, { + 8.398437500000000e-01, /* 82 */ + 1.601562500000000e-01, /* 83 */ + }, { + 8.359375000000000e-01, /* 84 */ + 1.640625000000000e-01, /* 85 */ + }, { + 8.320312500000000e-01, /* 86 */ + 1.679687500000000e-01, /* 87 */ + }, { + 8.281250000000000e-01, /* 88 */ + 1.718750000000000e-01, /* 89 */ + }, { + 8.242187500000000e-01, /* 90 */ + 1.757812500000000e-01, /* 91 */ + }, { + 8.203125000000000e-01, /* 92 */ + 1.796875000000000e-01, /* 93 */ + }, { + 8.164062500000000e-01, /* 94 */ + 1.835937500000000e-01, /* 95 */ + }, { + 8.125000000000000e-01, /* 96 */ + 1.875000000000000e-01, /* 97 */ + }, { + 8.085937500000000e-01, /* 98 */ + 1.914062500000000e-01, /* 99 */ + }, { + 8.046875000000000e-01, /* 100 */ + 1.953125000000000e-01, /* 101 */ + }, { + 8.007812500000000e-01, /* 102 */ + 1.992187500000000e-01, /* 103 */ + }, { + 7.968750000000000e-01, /* 104 */ + 2.031250000000000e-01, /* 105 */ + }, { + 7.929687500000000e-01, /* 106 */ + 2.070312500000000e-01, /* 107 */ + }, { + 7.890625000000000e-01, /* 108 */ + 2.109375000000000e-01, /* 109 */ + }, { + 7.851562500000000e-01, /* 110 */ + 2.148437500000000e-01, /* 111 */ + }, { + 7.812500000000000e-01, /* 112 */ + 2.187500000000000e-01, /* 113 */ + }, { + 7.773437500000000e-01, /* 114 */ + 2.226562500000000e-01, /* 115 */ + }, { + 7.734375000000000e-01, /* 116 */ + 2.265625000000000e-01, /* 117 */ + }, { + 7.695312500000000e-01, /* 118 */ + 2.304687500000000e-01, /* 119 */ + }, { + 7.656250000000000e-01, /* 120 */ + 2.343750000000000e-01, /* 121 */ + }, { + 7.617187500000000e-01, /* 122 */ + 2.382812500000000e-01, /* 123 */ + }, { + 7.578125000000000e-01, /* 124 */ + 2.421875000000000e-01, /* 125 */ + }, { + 7.539062500000000e-01, /* 126 */ + 2.460937500000000e-01, /* 127 */ + }, { + 7.500000000000000e-01, /* 128 */ + 2.500000000000000e-01, /* 129 */ + }, { + 7.460937500000000e-01, /* 130 */ + 2.539062500000000e-01, /* 131 */ + }, { + 7.421875000000000e-01, /* 132 */ + 2.578125000000000e-01, /* 133 */ + }, { + 7.382812500000000e-01, /* 134 */ + 2.617187500000000e-01, /* 135 */ + }, { + 7.343750000000000e-01, /* 136 */ + 2.656250000000000e-01, /* 137 */ + }, { + 7.304687500000000e-01, /* 138 */ + 2.695312500000000e-01, /* 139 */ + }, { + 7.265625000000000e-01, /* 140 */ + 2.734375000000000e-01, /* 141 */ + }, { + 7.226562500000000e-01, /* 142 */ + 2.773437500000000e-01, /* 143 */ + }, { + 7.187500000000000e-01, /* 144 */ + 2.812500000000000e-01, /* 145 */ + }, { + 7.148437500000000e-01, /* 146 */ + 2.851562500000000e-01, /* 147 */ + }, { + 7.109375000000000e-01, /* 148 */ + 2.890625000000000e-01, /* 149 */ + }, { + 7.070312500000000e-01, /* 150 */ + 2.929687500000000e-01, /* 151 */ + }, { + 7.031250000000000e-01, /* 152 */ + 2.968750000000000e-01, /* 153 */ + }, { + 6.992187500000000e-01, /* 154 */ + 3.007812500000000e-01, /* 155 */ + }, { + 6.953125000000000e-01, /* 156 */ + 3.046875000000000e-01, /* 157 */ + }, { + 6.914062500000000e-01, /* 158 */ + 3.085937500000000e-01, /* 159 */ + }, { + 6.875000000000000e-01, /* 160 */ + 3.125000000000000e-01, /* 161 */ + }, { + 6.835937500000000e-01, /* 162 */ + 3.164062500000000e-01, /* 163 */ + }, { + 6.796875000000000e-01, /* 164 */ + 3.203125000000000e-01, /* 165 */ + }, { + 6.757812500000000e-01, /* 166 */ + 3.242187500000000e-01, /* 167 */ + }, { + 6.718750000000000e-01, /* 168 */ + 3.281250000000000e-01, /* 169 */ + }, { + 6.679687500000000e-01, /* 170 */ + 3.320312500000000e-01, /* 171 */ + }, { + 6.640625000000000e-01, /* 172 */ + 3.359375000000000e-01, /* 173 */ + }, { + 6.601562500000000e-01, /* 174 */ + 3.398437500000000e-01, /* 175 */ + }, { + 6.562500000000000e-01, /* 176 */ + 3.437500000000000e-01, /* 177 */ + }, { + 6.523437500000000e-01, /* 178 */ + 3.476562500000000e-01, /* 179 */ + }, { + 6.484375000000000e-01, /* 180 */ + 3.515625000000000e-01, /* 181 */ + }, { + 6.445312500000000e-01, /* 182 */ + 3.554687500000000e-01, /* 183 */ + }, { + 6.406250000000000e-01, /* 184 */ + 3.593750000000000e-01, /* 185 */ + }, { + 6.367187500000000e-01, /* 186 */ + 3.632812500000000e-01, /* 187 */ + }, { + 6.328125000000000e-01, /* 188 */ + 3.671875000000000e-01, /* 189 */ + }, { + 6.289062500000000e-01, /* 190 */ + 3.710937500000000e-01, /* 191 */ + }, { + 6.250000000000000e-01, /* 192 */ + 3.750000000000000e-01, /* 193 */ + }, { + 6.210937500000000e-01, /* 194 */ + 3.789062500000000e-01, /* 195 */ + }, { + 6.171875000000000e-01, /* 196 */ + 3.828125000000000e-01, /* 197 */ + }, { + 6.132812500000000e-01, /* 198 */ + 3.867187500000000e-01, /* 199 */ + }, { + 6.093750000000000e-01, /* 200 */ + 3.906250000000000e-01, /* 201 */ + }, { + 6.054687500000000e-01, /* 202 */ + 3.945312500000000e-01, /* 203 */ + }, { + 6.015625000000000e-01, /* 204 */ + 3.984375000000000e-01, /* 205 */ + }, { + 5.976562500000000e-01, /* 206 */ + 4.023437500000000e-01, /* 207 */ + }, { + 5.937500000000000e-01, /* 208 */ + 4.062500000000000e-01, /* 209 */ + }, { + 5.898437500000000e-01, /* 210 */ + 4.101562500000000e-01, /* 211 */ + }, { + 5.859375000000000e-01, /* 212 */ + 4.140625000000000e-01, /* 213 */ + }, { + 5.820312500000000e-01, /* 214 */ + 4.179687500000000e-01, /* 215 */ + }, { + 5.781250000000000e-01, /* 216 */ + 4.218750000000000e-01, /* 217 */ + }, { + 5.742187500000000e-01, /* 218 */ + 4.257812500000000e-01, /* 219 */ + }, { + 5.703125000000000e-01, /* 220 */ + 4.296875000000000e-01, /* 221 */ + }, { + 5.664062500000000e-01, /* 222 */ + 4.335937500000000e-01, /* 223 */ + }, { + 5.625000000000000e-01, /* 224 */ + 4.375000000000000e-01, /* 225 */ + }, { + 5.585937500000000e-01, /* 226 */ + 4.414062500000000e-01, /* 227 */ + }, { + 5.546875000000000e-01, /* 228 */ + 4.453125000000000e-01, /* 229 */ + }, { + 5.507812500000000e-01, /* 230 */ + 4.492187500000000e-01, /* 231 */ + }, { + 5.468750000000000e-01, /* 232 */ + 4.531250000000000e-01, /* 233 */ + }, { + 5.429687500000000e-01, /* 234 */ + 4.570312500000000e-01, /* 235 */ + }, { + 5.390625000000000e-01, /* 236 */ + 4.609375000000000e-01, /* 237 */ + }, { + 5.351562500000000e-01, /* 238 */ + 4.648437500000000e-01, /* 239 */ + }, { + 5.312500000000000e-01, /* 240 */ + 4.687500000000000e-01, /* 241 */ + }, { + 5.273437500000000e-01, /* 242 */ + 4.726562500000000e-01, /* 243 */ + }, { + 5.234375000000000e-01, /* 244 */ + 4.765625000000000e-01, /* 245 */ + }, { + 5.195312500000000e-01, /* 246 */ + 4.804687500000000e-01, /* 247 */ + }, { + 5.156250000000000e-01, /* 248 */ + 4.843750000000000e-01, /* 249 */ + }, { + 5.117187500000000e-01, /* 250 */ + 4.882812500000000e-01, /* 251 */ + }, { + 5.078125000000000e-01, /* 252 */ + 4.921875000000000e-01, /* 253 */ + }, { + 5.039062500000000e-01, /* 254 */ + 4.960937500000000e-01, /* 255 */ + }, { + 5.000000000000000e-01, /* 256 */ + 5.000000000000000e-01, /* 257 */ + }, { + 4.960937500000000e-01, /* 258 */ + 5.039062500000000e-01, /* 259 */ + }, { + 4.921875000000000e-01, /* 260 */ + 5.078125000000000e-01, /* 261 */ + }, { + 4.882812500000000e-01, /* 262 */ + 5.117187500000000e-01, /* 263 */ + }, { + 4.843750000000000e-01, /* 264 */ + 5.156250000000000e-01, /* 265 */ + }, { + 4.804687500000000e-01, /* 266 */ + 5.195312500000000e-01, /* 267 */ + }, { + 4.765625000000000e-01, /* 268 */ + 5.234375000000000e-01, /* 269 */ + }, { + 4.726562500000000e-01, /* 270 */ + 5.273437500000000e-01, /* 271 */ + }, { + 4.687500000000000e-01, /* 272 */ + 5.312500000000000e-01, /* 273 */ + }, { + 4.648437500000000e-01, /* 274 */ + 5.351562500000000e-01, /* 275 */ + }, { + 4.609375000000000e-01, /* 276 */ + 5.390625000000000e-01, /* 277 */ + }, { + 4.570312500000000e-01, /* 278 */ + 5.429687500000000e-01, /* 279 */ + }, { + 4.531250000000000e-01, /* 280 */ + 5.468750000000000e-01, /* 281 */ + }, { + 4.492187500000000e-01, /* 282 */ + 5.507812500000000e-01, /* 283 */ + }, { + 4.453125000000000e-01, /* 284 */ + 5.546875000000000e-01, /* 285 */ + }, { + 4.414062500000000e-01, /* 286 */ + 5.585937500000000e-01, /* 287 */ + }, { + 4.375000000000000e-01, /* 288 */ + 5.625000000000000e-01, /* 289 */ + }, { + 4.335937500000000e-01, /* 290 */ + 5.664062500000000e-01, /* 291 */ + }, { + 4.296875000000000e-01, /* 292 */ + 5.703125000000000e-01, /* 293 */ + }, { + 4.257812500000000e-01, /* 294 */ + 5.742187500000000e-01, /* 295 */ + }, { + 4.218750000000000e-01, /* 296 */ + 5.781250000000000e-01, /* 297 */ + }, { + 4.179687500000000e-01, /* 298 */ + 5.820312500000000e-01, /* 299 */ + }, { + 4.140625000000000e-01, /* 300 */ + 5.859375000000000e-01, /* 301 */ + }, { + 4.101562500000000e-01, /* 302 */ + 5.898437500000000e-01, /* 303 */ + }, { + 4.062500000000000e-01, /* 304 */ + 5.937500000000000e-01, /* 305 */ + }, { + 4.023437500000000e-01, /* 306 */ + 5.976562500000000e-01, /* 307 */ + }, { + 3.984375000000000e-01, /* 308 */ + 6.015625000000000e-01, /* 309 */ + }, { + 3.945312500000000e-01, /* 310 */ + 6.054687500000000e-01, /* 311 */ + }, { + 3.906250000000000e-01, /* 312 */ + 6.093750000000000e-01, /* 313 */ + }, { + 3.867187500000000e-01, /* 314 */ + 6.132812500000000e-01, /* 315 */ + }, { + 3.828125000000000e-01, /* 316 */ + 6.171875000000000e-01, /* 317 */ + }, { + 3.789062500000000e-01, /* 318 */ + 6.210937500000000e-01, /* 319 */ + }, { + 3.750000000000000e-01, /* 320 */ + 6.250000000000000e-01, /* 321 */ + }, { + 3.710937500000000e-01, /* 322 */ + 6.289062500000000e-01, /* 323 */ + }, { + 3.671875000000000e-01, /* 324 */ + 6.328125000000000e-01, /* 325 */ + }, { + 3.632812500000000e-01, /* 326 */ + 6.367187500000000e-01, /* 327 */ + }, { + 3.593750000000000e-01, /* 328 */ + 6.406250000000000e-01, /* 329 */ + }, { + 3.554687500000000e-01, /* 330 */ + 6.445312500000000e-01, /* 331 */ + }, { + 3.515625000000000e-01, /* 332 */ + 6.484375000000000e-01, /* 333 */ + }, { + 3.476562500000000e-01, /* 334 */ + 6.523437500000000e-01, /* 335 */ + }, { + 3.437500000000000e-01, /* 336 */ + 6.562500000000000e-01, /* 337 */ + }, { + 3.398437500000000e-01, /* 338 */ + 6.601562500000000e-01, /* 339 */ + }, { + 3.359375000000000e-01, /* 340 */ + 6.640625000000000e-01, /* 341 */ + }, { + 3.320312500000000e-01, /* 342 */ + 6.679687500000000e-01, /* 343 */ + }, { + 3.281250000000000e-01, /* 344 */ + 6.718750000000000e-01, /* 345 */ + }, { + 3.242187500000000e-01, /* 346 */ + 6.757812500000000e-01, /* 347 */ + }, { + 3.203125000000000e-01, /* 348 */ + 6.796875000000000e-01, /* 349 */ + }, { + 3.164062500000000e-01, /* 350 */ + 6.835937500000000e-01, /* 351 */ + }, { + 3.125000000000000e-01, /* 352 */ + 6.875000000000000e-01, /* 353 */ + }, { + 3.085937500000000e-01, /* 354 */ + 6.914062500000000e-01, /* 355 */ + }, { + 3.046875000000000e-01, /* 356 */ + 6.953125000000000e-01, /* 357 */ + }, { + 3.007812500000000e-01, /* 358 */ + 6.992187500000000e-01, /* 359 */ + }, { + 2.968750000000000e-01, /* 360 */ + 7.031250000000000e-01, /* 361 */ + }, { + 2.929687500000000e-01, /* 362 */ + 7.070312500000000e-01, /* 363 */ + }, { + 2.890625000000000e-01, /* 364 */ + 7.109375000000000e-01, /* 365 */ + }, { + 2.851562500000000e-01, /* 366 */ + 7.148437500000000e-01, /* 367 */ + }, { + 2.812500000000000e-01, /* 368 */ + 7.187500000000000e-01, /* 369 */ + }, { + 2.773437500000000e-01, /* 370 */ + 7.226562500000000e-01, /* 371 */ + }, { + 2.734375000000000e-01, /* 372 */ + 7.265625000000000e-01, /* 373 */ + }, { + 2.695312500000000e-01, /* 374 */ + 7.304687500000000e-01, /* 375 */ + }, { + 2.656250000000000e-01, /* 376 */ + 7.343750000000000e-01, /* 377 */ + }, { + 2.617187500000000e-01, /* 378 */ + 7.382812500000000e-01, /* 379 */ + }, { + 2.578125000000000e-01, /* 380 */ + 7.421875000000000e-01, /* 381 */ + }, { + 2.539062500000000e-01, /* 382 */ + 7.460937500000000e-01, /* 383 */ + }, { + 2.500000000000000e-01, /* 384 */ + 7.500000000000000e-01, /* 385 */ + }, { + 2.460937500000000e-01, /* 386 */ + 7.539062500000000e-01, /* 387 */ + }, { + 2.421875000000000e-01, /* 388 */ + 7.578125000000000e-01, /* 389 */ + }, { + 2.382812500000000e-01, /* 390 */ + 7.617187500000000e-01, /* 391 */ + }, { + 2.343750000000000e-01, /* 392 */ + 7.656250000000000e-01, /* 393 */ + }, { + 2.304687500000000e-01, /* 394 */ + 7.695312500000000e-01, /* 395 */ + }, { + 2.265625000000000e-01, /* 396 */ + 7.734375000000000e-01, /* 397 */ + }, { + 2.226562500000000e-01, /* 398 */ + 7.773437500000000e-01, /* 399 */ + }, { + 2.187500000000000e-01, /* 400 */ + 7.812500000000000e-01, /* 401 */ + }, { + 2.148437500000000e-01, /* 402 */ + 7.851562500000000e-01, /* 403 */ + }, { + 2.109375000000000e-01, /* 404 */ + 7.890625000000000e-01, /* 405 */ + }, { + 2.070312500000000e-01, /* 406 */ + 7.929687500000000e-01, /* 407 */ + }, { + 2.031250000000000e-01, /* 408 */ + 7.968750000000000e-01, /* 409 */ + }, { + 1.992187500000000e-01, /* 410 */ + 8.007812500000000e-01, /* 411 */ + }, { + 1.953125000000000e-01, /* 412 */ + 8.046875000000000e-01, /* 413 */ + }, { + 1.914062500000000e-01, /* 414 */ + 8.085937500000000e-01, /* 415 */ + }, { + 1.875000000000000e-01, /* 416 */ + 8.125000000000000e-01, /* 417 */ + }, { + 1.835937500000000e-01, /* 418 */ + 8.164062500000000e-01, /* 419 */ + }, { + 1.796875000000000e-01, /* 420 */ + 8.203125000000000e-01, /* 421 */ + }, { + 1.757812500000000e-01, /* 422 */ + 8.242187500000000e-01, /* 423 */ + }, { + 1.718750000000000e-01, /* 424 */ + 8.281250000000000e-01, /* 425 */ + }, { + 1.679687500000000e-01, /* 426 */ + 8.320312500000000e-01, /* 427 */ + }, { + 1.640625000000000e-01, /* 428 */ + 8.359375000000000e-01, /* 429 */ + }, { + 1.601562500000000e-01, /* 430 */ + 8.398437500000000e-01, /* 431 */ + }, { + 1.562500000000000e-01, /* 432 */ + 8.437500000000000e-01, /* 433 */ + }, { + 1.523437500000000e-01, /* 434 */ + 8.476562500000000e-01, /* 435 */ + }, { + 1.484375000000000e-01, /* 436 */ + 8.515625000000000e-01, /* 437 */ + }, { + 1.445312500000000e-01, /* 438 */ + 8.554687500000000e-01, /* 439 */ + }, { + 1.406250000000000e-01, /* 440 */ + 8.593750000000000e-01, /* 441 */ + }, { + 1.367187500000000e-01, /* 442 */ + 8.632812500000000e-01, /* 443 */ + }, { + 1.328125000000000e-01, /* 444 */ + 8.671875000000000e-01, /* 445 */ + }, { + 1.289062500000000e-01, /* 446 */ + 8.710937500000000e-01, /* 447 */ + }, { + 1.250000000000000e-01, /* 448 */ + 8.750000000000000e-01, /* 449 */ + }, { + 1.210937500000000e-01, /* 450 */ + 8.789062500000000e-01, /* 451 */ + }, { + 1.171875000000000e-01, /* 452 */ + 8.828125000000000e-01, /* 453 */ + }, { + 1.132812500000000e-01, /* 454 */ + 8.867187500000000e-01, /* 455 */ + }, { + 1.093750000000000e-01, /* 456 */ + 8.906250000000000e-01, /* 457 */ + }, { + 1.054687500000000e-01, /* 458 */ + 8.945312500000000e-01, /* 459 */ + }, { + 1.015625000000000e-01, /* 460 */ + 8.984375000000000e-01, /* 461 */ + }, { + 9.765625000000000e-02, /* 462 */ + 9.023437500000000e-01, /* 463 */ + }, { + 9.375000000000000e-02, /* 464 */ + 9.062500000000000e-01, /* 465 */ + }, { + 8.984375000000000e-02, /* 466 */ + 9.101562500000000e-01, /* 467 */ + }, { + 8.593750000000000e-02, /* 468 */ + 9.140625000000000e-01, /* 469 */ + }, { + 8.203125000000000e-02, /* 470 */ + 9.179687500000000e-01, /* 471 */ + }, { + 7.812500000000000e-02, /* 472 */ + 9.218750000000000e-01, /* 473 */ + }, { + 7.421875000000000e-02, /* 474 */ + 9.257812500000000e-01, /* 475 */ + }, { + 7.031250000000000e-02, /* 476 */ + 9.296875000000000e-01, /* 477 */ + }, { + 6.640625000000000e-02, /* 478 */ + 9.335937500000000e-01, /* 479 */ + }, { + 6.250000000000000e-02, /* 480 */ + 9.375000000000000e-01, /* 481 */ + }, { + 5.859375000000000e-02, /* 482 */ + 9.414062500000000e-01, /* 483 */ + }, { + 5.468750000000000e-02, /* 484 */ + 9.453125000000000e-01, /* 485 */ + }, { + 5.078125000000000e-02, /* 486 */ + 9.492187500000000e-01, /* 487 */ + }, { + 4.687500000000000e-02, /* 488 */ + 9.531250000000000e-01, /* 489 */ + }, { + 4.296875000000000e-02, /* 490 */ + 9.570312500000000e-01, /* 491 */ + }, { + 3.906250000000000e-02, /* 492 */ + 9.609375000000000e-01, /* 493 */ + }, { + 3.515625000000000e-02, /* 494 */ + 9.648437500000000e-01, /* 495 */ + }, { + 3.125000000000000e-02, /* 496 */ + 9.687500000000000e-01, /* 497 */ + }, { + 2.734375000000000e-02, /* 498 */ + 9.726562500000000e-01, /* 499 */ + }, { + 2.343750000000000e-02, /* 500 */ + 9.765625000000000e-01, /* 501 */ + }, { + 1.953125000000000e-02, /* 502 */ + 9.804687500000000e-01, /* 503 */ + }, { + 1.562500000000000e-02, /* 504 */ + 9.843750000000000e-01, /* 505 */ + }, { + 1.171875000000000e-02, /* 506 */ + 9.882812500000000e-01, /* 507 */ + }, { + 7.812500000000000e-03, /* 508 */ + 9.921875000000000e-01, /* 509 */ + }, { + 3.906250000000000e-03, /* 510 */ + 9.960937500000000e-01, /* 511 */ + } +}; + +static const fluid_real_t interp_coeff[256][4] = { + { + -0.000000000000000e+00, /* 0 */ + 1.000000000000000e+00, /* 1 */ + 0.000000000000000e+00, /* 2 */ + -0.000000000000000e+00, /* 3 */ + }, { + -1.937896013259888e-03, /* 4 */ + 9.999619424343109e-01, /* 5 */ + 1.983553171157837e-03, /* 6 */ + -7.599592208862305e-06, /* 7 */ + }, { + -3.845453262329102e-03, /* 8 */ + 9.998481273651123e-01, /* 9 */ + 4.027605056762695e-03, /* 10 */ + -3.027915954589844e-05, /* 11 */ + }, { + -5.722850561141968e-03, /* 12 */ + 9.996590912342072e-01, /* 13 */ + 6.131619215011597e-03, /* 14 */ + -6.785988807678223e-05, /* 15 */ + }, { + -7.570266723632812e-03, /* 16 */ + 9.993953704833984e-01, /* 17 */ + 8.295059204101562e-03, /* 18 */ + -1.201629638671875e-04, /* 19 */ + }, { + -9.387880563735962e-03, /* 20 */ + 9.990575015544891e-01, /* 21 */ + 1.051738858222961e-02, /* 22 */ + -1.870095729827881e-04, /* 23 */ + }, { + -1.117587089538574e-02, /* 24 */ + 9.986460208892822e-01, /* 25 */ + 1.279807090759277e-02, /* 26 */ + -2.682209014892578e-04, /* 27 */ + }, { + -1.293441653251648e-02, /* 28 */ + 9.981614649295807e-01, /* 29 */ + 1.513656973838806e-02, /* 30 */ + -3.636181354522705e-04, /* 31 */ + }, { + -1.466369628906250e-02, /* 32 */ + 9.976043701171875e-01, /* 33 */ + 1.753234863281250e-02, /* 34 */ + -4.730224609375000e-04, /* 35 */ + }, { + -1.636388897895813e-02, /* 36 */ + 9.969752728939056e-01, /* 37 */ + 1.998487114906311e-02, /* 38 */ + -5.962550640106201e-04, /* 39 */ + }, { + -1.803517341613770e-02, /* 40 */ + 9.962747097015381e-01, /* 41 */ + 2.249360084533691e-02, /* 42 */ + -7.331371307373047e-04, /* 43 */ + }, { + -1.967772841453552e-02, /* 44 */ + 9.955032169818878e-01, /* 45 */ + 2.505800127983093e-02, /* 46 */ + -8.834898471832275e-04, /* 47 */ + }, { + -2.129173278808594e-02, /* 48 */ + 9.946613311767578e-01, /* 49 */ + 2.767753601074219e-02, /* 50 */ + -1.047134399414062e-03, /* 51 */ + }, { + -2.287736535072327e-02, /* 52 */ + 9.937495887279510e-01, /* 53 */ + 3.035166859626770e-02, /* 54 */ + -1.223891973495483e-03, /* 55 */ + }, { + -2.443480491638184e-02, /* 56 */ + 9.927685260772705e-01, /* 57 */ + 3.307986259460449e-02, /* 58 */ + -1.413583755493164e-03, /* 59 */ + }, { + -2.596423029899597e-02, /* 60 */ + 9.917186796665192e-01, /* 61 */ + 3.586158156394958e-02, /* 62 */ + -1.616030931472778e-03, /* 63 */ + }, { + -2.746582031250000e-02, /* 64 */ + 9.906005859375000e-01, /* 65 */ + 3.869628906250000e-02, /* 66 */ + -1.831054687500000e-03, /* 67 */ + }, { + -2.893975377082825e-02, /* 68 */ + 9.894147813320160e-01, /* 69 */ + 4.158344864845276e-02, /* 70 */ + -2.058476209640503e-03, /* 71 */ + }, { + -3.038620948791504e-02, /* 72 */ + 9.881618022918701e-01, /* 73 */ + 4.452252388000488e-02, /* 74 */ + -2.298116683959961e-03, /* 75 */ + }, { + -3.180536627769470e-02, /* 76 */ + 9.868421852588654e-01, /* 77 */ + 4.751297831535339e-02, /* 78 */ + -2.549797296524048e-03, /* 79 */ + }, { + -3.319740295410156e-02, /* 80 */ + 9.854564666748047e-01, /* 81 */ + 5.055427551269531e-02, /* 82 */ + -2.813339233398438e-03, /* 83 */ + }, { + -3.456249833106995e-02, /* 84 */ + 9.840051829814911e-01, /* 85 */ + 5.364587903022766e-02, /* 86 */ + -3.088563680648804e-03, /* 87 */ + }, { + -3.590083122253418e-02, /* 88 */ + 9.824888706207275e-01, /* 89 */ + 5.678725242614746e-02, /* 90 */ + -3.375291824340820e-03, /* 91 */ + }, { + -3.721258044242859e-02, /* 92 */ + 9.809080660343170e-01, /* 93 */ + 5.997785925865173e-02, /* 94 */ + -3.673344850540161e-03, /* 95 */ + }, { + -3.849792480468750e-02, /* 96 */ + 9.792633056640625e-01, /* 97 */ + 6.321716308593750e-02, /* 98 */ + -3.982543945312500e-03, /* 99 */ + }, { + -3.975704312324524e-02, /* 100 */ + 9.775551259517670e-01, /* 101 */ + 6.650462746620178e-02, /* 102 */ + -4.302710294723511e-03, /* 103 */ + }, { + -4.099011421203613e-02, /* 104 */ + 9.757840633392334e-01, /* 105 */ + 6.983971595764160e-02, /* 106 */ + -4.633665084838867e-03, /* 107 */ + }, { + -4.219731688499451e-02, /* 108 */ + 9.739506542682648e-01, /* 109 */ + 7.322189211845398e-02, /* 110 */ + -4.975229501724243e-03, /* 111 */ + }, { + -4.337882995605469e-02, /* 112 */ + 9.720554351806641e-01, /* 113 */ + 7.665061950683594e-02, /* 114 */ + -5.327224731445312e-03, /* 115 */ + }, { + -4.453483223915100e-02, /* 116 */ + 9.700989425182343e-01, /* 117 */ + 8.012536168098450e-02, /* 118 */ + -5.689471960067749e-03, /* 119 */ + }, { + -4.566550254821777e-02, /* 120 */ + 9.680817127227783e-01, /* 121 */ + 8.364558219909668e-02, /* 122 */ + -6.061792373657227e-03, /* 123 */ + }, { + -4.677101969718933e-02, /* 124 */ + 9.660042822360992e-01, /* 125 */ + 8.721074461936951e-02, /* 126 */ + -6.444007158279419e-03, /* 127 */ + }, { + -4.785156250000000e-02, /* 128 */ + 9.638671875000000e-01, /* 129 */ + 9.082031250000000e-02, /* 130 */ + -6.835937500000000e-03, /* 131 */ + }, { + -4.890730977058411e-02, /* 132 */ + 9.616709649562836e-01, /* 133 */ + 9.447374939918518e-02, /* 134 */ + -7.237404584884644e-03, /* 135 */ + }, { + -4.993844032287598e-02, /* 136 */ + 9.594161510467529e-01, /* 137 */ + 9.817051887512207e-02, /* 138 */ + -7.648229598999023e-03, /* 139 */ + }, { + -5.094513297080994e-02, /* 140 */ + 9.571032822132111e-01, /* 141 */ + 1.019100844860077e-01, /* 142 */ + -8.068233728408813e-03, /* 143 */ + }, { + -5.192756652832031e-02, /* 144 */ + 9.547328948974609e-01, /* 145 */ + 1.056919097900391e-01, /* 146 */ + -8.497238159179688e-03, /* 147 */ + }, { + -5.288591980934143e-02, /* 148 */ + 9.523055255413055e-01, /* 149 */ + 1.095154583454132e-01, /* 150 */ + -8.935064077377319e-03, /* 151 */ + }, { + -5.382037162780762e-02, /* 152 */ + 9.498217105865479e-01, /* 153 */ + 1.133801937103271e-01, /* 154 */ + -9.381532669067383e-03, /* 155 */ + }, { + -5.473110079765320e-02, /* 156 */ + 9.472819864749908e-01, /* 157 */ + 1.172855794429779e-01, /* 158 */ + -9.836465120315552e-03, /* 159 */ + }, { + -5.561828613281250e-02, /* 160 */ + 9.446868896484375e-01, /* 161 */ + 1.212310791015625e-01, /* 162 */ + -1.029968261718750e-02, /* 163 */ + }, { + -5.648210644721985e-02, /* 164 */ + 9.420369565486908e-01, /* 165 */ + 1.252161562442780e-01, /* 166 */ + -1.077100634574890e-02, /* 167 */ + }, { + -5.732274055480957e-02, /* 168 */ + 9.393327236175537e-01, /* 169 */ + 1.292402744293213e-01, /* 170 */ + -1.125025749206543e-02, /* 171 */ + }, { + -5.814036726951599e-02, /* 172 */ + 9.365747272968292e-01, /* 173 */ + 1.333028972148895e-01, /* 174 */ + -1.173725724220276e-02, /* 175 */ + }, { + -5.893516540527344e-02, /* 176 */ + 9.337635040283203e-01, /* 177 */ + 1.374034881591797e-01, /* 178 */ + -1.223182678222656e-02, /* 179 */ + }, { + -5.970731377601624e-02, /* 180 */ + 9.308995902538300e-01, /* 181 */ + 1.415415108203888e-01, /* 182 */ + -1.273378729820251e-02, /* 183 */ + }, { + -6.045699119567871e-02, /* 184 */ + 9.279835224151611e-01, /* 185 */ + 1.457164287567139e-01, /* 186 */ + -1.324295997619629e-02, /* 187 */ + }, { + -6.118437647819519e-02, /* 188 */ + 9.250158369541168e-01, /* 189 */ + 1.499277055263519e-01, /* 190 */ + -1.375916600227356e-02, /* 191 */ + }, { + -6.188964843750000e-02, /* 192 */ + 9.219970703125000e-01, /* 193 */ + 1.541748046875000e-01, /* 194 */ + -1.428222656250000e-02, /* 195 */ + }, { + -6.257298588752747e-02, /* 196 */ + 9.189277589321136e-01, /* 197 */ + 1.584571897983551e-01, /* 198 */ + -1.481196284294128e-02, /* 199 */ + }, { + -6.323456764221191e-02, /* 200 */ + 9.158084392547607e-01, /* 201 */ + 1.627743244171143e-01, /* 202 */ + -1.534819602966309e-02, /* 203 */ + }, { + -6.387457251548767e-02, /* 204 */ + 9.126396477222443e-01, /* 205 */ + 1.671256721019745e-01, /* 206 */ + -1.589074730873108e-02, /* 207 */ + }, { + -6.449317932128906e-02, /* 208 */ + 9.094219207763672e-01, /* 209 */ + 1.715106964111328e-01, /* 210 */ + -1.643943786621094e-02, /* 211 */ + }, { + -6.509056687355042e-02, /* 212 */ + 9.061557948589325e-01, /* 213 */ + 1.759288609027863e-01, /* 214 */ + -1.699408888816833e-02, /* 215 */ + }, { + -6.566691398620605e-02, /* 216 */ + 9.028418064117432e-01, /* 217 */ + 1.803796291351318e-01, /* 218 */ + -1.755452156066895e-02, /* 219 */ + }, { + -6.622239947319031e-02, /* 220 */ + 8.994804918766022e-01, /* 221 */ + 1.848624646663666e-01, /* 222 */ + -1.812055706977844e-02, /* 223 */ + }, { + -6.675720214843750e-02, /* 224 */ + 8.960723876953125e-01, /* 225 */ + 1.893768310546875e-01, /* 226 */ + -1.869201660156250e-02, /* 227 */ + }, { + -6.727150082588196e-02, /* 228 */ + 8.926180303096771e-01, /* 229 */ + 1.939221918582916e-01, /* 230 */ + -1.926872134208679e-02, /* 231 */ + }, { + -6.776547431945801e-02, /* 232 */ + 8.891179561614990e-01, /* 233 */ + 1.984980106353760e-01, /* 234 */ + -1.985049247741699e-02, /* 235 */ + }, { + -6.823930144309998e-02, /* 236 */ + 8.855727016925812e-01, /* 237 */ + 2.031037509441376e-01, /* 238 */ + -2.043715119361877e-02, /* 239 */ + }, { + -6.869316101074219e-02, /* 240 */ + 8.819828033447266e-01, /* 241 */ + 2.077388763427734e-01, /* 242 */ + -2.102851867675781e-02, /* 243 */ + }, { + -6.912723183631897e-02, /* 244 */ + 8.783487975597382e-01, /* 245 */ + 2.124028503894806e-01, /* 246 */ + -2.162441611289978e-02, /* 247 */ + }, { + -6.954169273376465e-02, /* 248 */ + 8.746712207794189e-01, /* 249 */ + 2.170951366424561e-01, /* 250 */ + -2.222466468811035e-02, /* 251 */ + }, { + -6.993672251701355e-02, /* 252 */ + 8.709506094455719e-01, /* 253 */ + 2.218151986598969e-01, /* 254 */ + -2.282908558845520e-02, /* 255 */ + }, { + -7.031250000000000e-02, /* 256 */ + 8.671875000000000e-01, /* 257 */ + 2.265625000000000e-01, /* 258 */ + -2.343750000000000e-02, /* 259 */ + }, { + -7.066920399665833e-02, /* 260 */ + 8.633824288845062e-01, /* 261 */ + 2.313365042209625e-01, /* 262 */ + -2.404972910881042e-02, /* 263 */ + }, { + -7.100701332092285e-02, /* 264 */ + 8.595359325408936e-01, /* 265 */ + 2.361366748809814e-01, /* 266 */ + -2.466559410095215e-02, /* 267 */ + }, { + -7.132610678672791e-02, /* 268 */ + 8.556485474109650e-01, /* 269 */ + 2.409624755382538e-01, /* 270 */ + -2.528491616249084e-02, /* 271 */ + }, { + -7.162666320800781e-02, /* 272 */ + 8.517208099365234e-01, /* 273 */ + 2.458133697509766e-01, /* 274 */ + -2.590751647949219e-02, /* 275 */ + }, { + -7.190886139869690e-02, /* 276 */ + 8.477532565593719e-01, /* 277 */ + 2.506888210773468e-01, /* 278 */ + -2.653321623802185e-02, /* 279 */ + }, { + -7.217288017272949e-02, /* 280 */ + 8.437464237213135e-01, /* 281 */ + 2.555882930755615e-01, /* 282 */ + -2.716183662414551e-02, /* 283 */ + }, { + -7.241889834403992e-02, /* 284 */ + 8.397008478641510e-01, /* 285 */ + 2.605112493038177e-01, /* 286 */ + -2.779319882392883e-02, /* 287 */ + }, { + -7.264709472656250e-02, /* 288 */ + 8.356170654296875e-01, /* 289 */ + 2.654571533203125e-01, /* 290 */ + -2.842712402343750e-02, /* 291 */ + }, { + -7.285764813423157e-02, /* 292 */ + 8.314956128597260e-01, /* 293 */ + 2.704254686832428e-01, /* 294 */ + -2.906343340873718e-02, /* 295 */ + }, { + -7.305073738098145e-02, /* 296 */ + 8.273370265960693e-01, /* 297 */ + 2.754156589508057e-01, /* 298 */ + -2.970194816589355e-02, /* 299 */ + }, { + -7.322654128074646e-02, /* 300 */ + 8.231418430805206e-01, /* 301 */ + 2.804271876811981e-01, /* 302 */ + -3.034248948097229e-02, /* 303 */ + }, { + -7.338523864746094e-02, /* 304 */ + 8.189105987548828e-01, /* 305 */ + 2.854595184326172e-01, /* 306 */ + -3.098487854003906e-02, /* 307 */ + }, { + -7.352700829505920e-02, /* 308 */ + 8.146438300609589e-01, /* 309 */ + 2.905121147632599e-01, /* 310 */ + -3.162893652915955e-02, /* 311 */ + }, { + -7.365202903747559e-02, /* 312 */ + 8.103420734405518e-01, /* 313 */ + 2.955844402313232e-01, /* 314 */ + -3.227448463439941e-02, /* 315 */ + }, { + -7.376047968864441e-02, /* 316 */ + 8.060058653354645e-01, /* 317 */ + 3.006759583950043e-01, /* 318 */ + -3.292134404182434e-02, /* 319 */ + }, { + -7.385253906250000e-02, /* 320 */ + 8.016357421875000e-01, /* 321 */ + 3.057861328125000e-01, /* 322 */ + -3.356933593750000e-02, /* 323 */ + }, { + -7.392838597297668e-02, /* 324 */ + 7.972322404384613e-01, /* 325 */ + 3.109144270420074e-01, /* 326 */ + -3.421828150749207e-02, /* 327 */ + }, { + -7.398819923400879e-02, /* 328 */ + 7.927958965301514e-01, /* 329 */ + 3.160603046417236e-01, /* 330 */ + -3.486800193786621e-02, /* 331 */ + }, { + -7.403215765953064e-02, /* 332 */ + 7.883272469043732e-01, /* 333 */ + 3.212232291698456e-01, /* 334 */ + -3.551831841468811e-02, /* 335 */ + }, { + -7.406044006347656e-02, /* 336 */ + 7.838268280029297e-01, /* 337 */ + 3.264026641845703e-01, /* 338 */ + -3.616905212402344e-02, /* 339 */ + }, { + -7.407322525978088e-02, /* 340 */ + 7.792951762676239e-01, /* 341 */ + 3.315980732440948e-01, /* 342 */ + -3.682002425193787e-02, /* 343 */ + }, { + -7.407069206237793e-02, /* 344 */ + 7.747328281402588e-01, /* 345 */ + 3.368089199066162e-01, /* 346 */ + -3.747105598449707e-02, /* 347 */ + }, { + -7.405301928520203e-02, /* 348 */ + 7.701403200626373e-01, /* 349 */ + 3.420346677303314e-01, /* 350 */ + -3.812196850776672e-02, /* 351 */ + }, { + -7.402038574218750e-02, /* 352 */ + 7.655181884765625e-01, /* 353 */ + 3.472747802734375e-01, /* 354 */ + -3.877258300781250e-02, /* 355 */ + }, { + -7.397297024726868e-02, /* 356 */ + 7.608669698238373e-01, /* 357 */ + 3.525287210941315e-01, /* 358 */ + -3.942272067070007e-02, /* 359 */ + }, { + -7.391095161437988e-02, /* 360 */ + 7.561872005462646e-01, /* 361 */ + 3.577959537506104e-01, /* 362 */ + -4.007220268249512e-02, /* 363 */ + }, { + -7.383450865745544e-02, /* 364 */ + 7.514794170856476e-01, /* 365 */ + 3.630759418010712e-01, /* 366 */ + -4.072085022926331e-02, /* 367 */ + }, { + -7.374382019042969e-02, /* 368 */ + 7.467441558837891e-01, /* 369 */ + 3.683681488037109e-01, /* 370 */ + -4.136848449707031e-02, /* 371 */ + }, { + -7.363906502723694e-02, /* 372 */ + 7.419819533824921e-01, /* 373 */ + 3.736720383167267e-01, /* 374 */ + -4.201492667198181e-02, /* 375 */ + }, { + -7.352042198181152e-02, /* 376 */ + 7.371933460235596e-01, /* 377 */ + 3.789870738983154e-01, /* 378 */ + -4.265999794006348e-02, /* 379 */ + }, { + -7.338806986808777e-02, /* 380 */ + 7.323788702487946e-01, /* 381 */ + 3.843127191066742e-01, /* 382 */ + -4.330351948738098e-02, /* 383 */ + }, { + -7.324218750000000e-02, /* 384 */ + 7.275390625000000e-01, /* 385 */ + 3.896484375000000e-01, /* 386 */ + -4.394531250000000e-02, /* 387 */ + }, { + -7.308295369148254e-02, /* 388 */ + 7.226744592189789e-01, /* 389 */ + 3.949936926364899e-01, /* 390 */ + -4.458519816398621e-02, /* 391 */ + }, { + -7.291054725646973e-02, /* 392 */ + 7.177855968475342e-01, /* 393 */ + 4.003479480743408e-01, /* 394 */ + -4.522299766540527e-02, /* 395 */ + }, { + -7.272514700889587e-02, /* 396 */ + 7.128730118274689e-01, /* 397 */ + 4.057106673717499e-01, /* 398 */ + -4.585853219032288e-02, /* 399 */ + }, { + -7.252693176269531e-02, /* 400 */ + 7.079372406005859e-01, /* 401 */ + 4.110813140869141e-01, /* 402 */ + -4.649162292480469e-02, /* 403 */ + }, { + -7.231608033180237e-02, /* 404 */ + 7.029788196086884e-01, /* 405 */ + 4.164593517780304e-01, /* 406 */ + -4.712209105491638e-02, /* 407 */ + }, { + -7.209277153015137e-02, /* 408 */ + 6.979982852935791e-01, /* 409 */ + 4.218442440032959e-01, /* 410 */ + -4.774975776672363e-02, /* 411 */ + }, { + -7.185718417167664e-02, /* 412 */ + 6.929961740970612e-01, /* 413 */ + 4.272354543209076e-01, /* 414 */ + -4.837444424629211e-02, /* 415 */ + }, { + -7.160949707031250e-02, /* 416 */ + 6.879730224609375e-01, /* 417 */ + 4.326324462890625e-01, /* 418 */ + -4.899597167968750e-02, /* 419 */ + }, { + -7.134988903999329e-02, /* 420 */ + 6.829293668270111e-01, /* 421 */ + 4.380346834659576e-01, /* 422 */ + -4.961416125297546e-02, /* 423 */ + }, { + -7.107853889465332e-02, /* 424 */ + 6.778657436370850e-01, /* 425 */ + 4.434416294097900e-01, /* 426 */ + -5.022883415222168e-02, /* 427 */ + }, { + -7.079562544822693e-02, /* 428 */ + 6.727826893329620e-01, /* 429 */ + 4.488527476787567e-01, /* 430 */ + -5.083981156349182e-02, /* 431 */ + }, { + -7.050132751464844e-02, /* 432 */ + 6.676807403564453e-01, /* 433 */ + 4.542675018310547e-01, /* 434 */ + -5.144691467285156e-02, /* 435 */ + }, { + -7.019582390785217e-02, /* 436 */ + 6.625604331493378e-01, /* 437 */ + 4.596853554248810e-01, /* 438 */ + -5.204996466636658e-02, /* 439 */ + }, { + -6.987929344177246e-02, /* 440 */ + 6.574223041534424e-01, /* 441 */ + 4.651057720184326e-01, /* 442 */ + -5.264878273010254e-02, /* 443 */ + }, { + -6.955191493034363e-02, /* 444 */ + 6.522668898105621e-01, /* 445 */ + 4.705282151699066e-01, /* 446 */ + -5.324319005012512e-02, /* 447 */ + }, { + -6.921386718750000e-02, /* 448 */ + 6.470947265625000e-01, /* 449 */ + 4.759521484375000e-01, /* 450 */ + -5.383300781250000e-02, /* 451 */ + }, { + -6.886532902717590e-02, /* 452 */ + 6.419063508510590e-01, /* 453 */ + 4.813770353794098e-01, /* 454 */ + -5.441805720329285e-02, /* 455 */ + }, { + -6.850647926330566e-02, /* 456 */ + 6.367022991180420e-01, /* 457 */ + 4.868023395538330e-01, /* 458 */ + -5.499815940856934e-02, /* 459 */ + }, { + -6.813749670982361e-02, /* 460 */ + 6.314831078052521e-01, /* 461 */ + 4.922275245189667e-01, /* 462 */ + -5.557313561439514e-02, /* 463 */ + }, { + -6.775856018066406e-02, /* 464 */ + 6.262493133544922e-01, /* 465 */ + 4.976520538330078e-01, /* 466 */ + -5.614280700683594e-02, /* 467 */ + }, { + -6.736984848976135e-02, /* 468 */ + 6.210014522075653e-01, /* 469 */ + 5.030753910541534e-01, /* 470 */ + -5.670699477195740e-02, /* 471 */ + }, { + -6.697154045104980e-02, /* 472 */ + 6.157400608062744e-01, /* 473 */ + 5.084969997406006e-01, /* 474 */ + -5.726552009582520e-02, /* 475 */ + }, { + -6.656381487846375e-02, /* 476 */ + 6.104656755924225e-01, /* 477 */ + 5.139163434505463e-01, /* 478 */ + -5.781820416450500e-02, /* 479 */ + }, { + -6.614685058593750e-02, /* 480 */ + 6.051788330078125e-01, /* 481 */ + 5.193328857421875e-01, /* 482 */ + -5.836486816406250e-02, /* 483 */ + }, { + -6.572082638740540e-02, /* 484 */ + 5.998800694942474e-01, /* 485 */ + 5.247460901737213e-01, /* 486 */ + -5.890533328056335e-02, /* 487 */ + }, { + -6.528592109680176e-02, /* 488 */ + 5.945699214935303e-01, /* 489 */ + 5.301554203033447e-01, /* 490 */ + -5.943942070007324e-02, /* 491 */ + }, { + -6.484231352806091e-02, /* 492 */ + 5.892489254474640e-01, /* 493 */ + 5.355603396892548e-01, /* 494 */ + -5.996695160865784e-02, /* 495 */ + }, { + -6.439018249511719e-02, /* 496 */ + 5.839176177978516e-01, /* 497 */ + 5.409603118896484e-01, /* 498 */ + -6.048774719238281e-02, /* 499 */ + }, { + -6.392970681190491e-02, /* 500 */ + 5.785765349864960e-01, /* 501 */ + 5.463548004627228e-01, /* 502 */ + -6.100162863731384e-02, /* 503 */ + }, { + -6.346106529235840e-02, /* 504 */ + 5.732262134552002e-01, /* 505 */ + 5.517432689666748e-01, /* 506 */ + -6.150841712951660e-02, /* 507 */ + }, { + -6.298443675041199e-02, /* 508 */ + 5.678671896457672e-01, /* 509 */ + 5.571251809597015e-01, /* 510 */ + -6.200793385505676e-02, /* 511 */ + }, { + -6.250000000000000e-02, /* 512 */ + 5.625000000000000e-01, /* 513 */ + 5.625000000000000e-01, /* 514 */ + -6.250000000000000e-02, /* 515 */ + }, { + -6.200793385505676e-02, /* 516 */ + 5.571251809597015e-01, /* 517 */ + 5.678671896457672e-01, /* 518 */ + -6.298443675041199e-02, /* 519 */ + }, { + -6.150841712951660e-02, /* 520 */ + 5.517432689666748e-01, /* 521 */ + 5.732262134552002e-01, /* 522 */ + -6.346106529235840e-02, /* 523 */ + }, { + -6.100162863731384e-02, /* 524 */ + 5.463548004627228e-01, /* 525 */ + 5.785765349864960e-01, /* 526 */ + -6.392970681190491e-02, /* 527 */ + }, { + -6.048774719238281e-02, /* 528 */ + 5.409603118896484e-01, /* 529 */ + 5.839176177978516e-01, /* 530 */ + -6.439018249511719e-02, /* 531 */ + }, { + -5.996695160865784e-02, /* 532 */ + 5.355603396892548e-01, /* 533 */ + 5.892489254474640e-01, /* 534 */ + -6.484231352806091e-02, /* 535 */ + }, { + -5.943942070007324e-02, /* 536 */ + 5.301554203033447e-01, /* 537 */ + 5.945699214935303e-01, /* 538 */ + -6.528592109680176e-02, /* 539 */ + }, { + -5.890533328056335e-02, /* 540 */ + 5.247460901737213e-01, /* 541 */ + 5.998800694942474e-01, /* 542 */ + -6.572082638740540e-02, /* 543 */ + }, { + -5.836486816406250e-02, /* 544 */ + 5.193328857421875e-01, /* 545 */ + 6.051788330078125e-01, /* 546 */ + -6.614685058593750e-02, /* 547 */ + }, { + -5.781820416450500e-02, /* 548 */ + 5.139163434505463e-01, /* 549 */ + 6.104656755924225e-01, /* 550 */ + -6.656381487846375e-02, /* 551 */ + }, { + -5.726552009582520e-02, /* 552 */ + 5.084969997406006e-01, /* 553 */ + 6.157400608062744e-01, /* 554 */ + -6.697154045104980e-02, /* 555 */ + }, { + -5.670699477195740e-02, /* 556 */ + 5.030753910541534e-01, /* 557 */ + 6.210014522075653e-01, /* 558 */ + -6.736984848976135e-02, /* 559 */ + }, { + -5.614280700683594e-02, /* 560 */ + 4.976520538330078e-01, /* 561 */ + 6.262493133544922e-01, /* 562 */ + -6.775856018066406e-02, /* 563 */ + }, { + -5.557313561439514e-02, /* 564 */ + 4.922275245189667e-01, /* 565 */ + 6.314831078052521e-01, /* 566 */ + -6.813749670982361e-02, /* 567 */ + }, { + -5.499815940856934e-02, /* 568 */ + 4.868023395538330e-01, /* 569 */ + 6.367022991180420e-01, /* 570 */ + -6.850647926330566e-02, /* 571 */ + }, { + -5.441805720329285e-02, /* 572 */ + 4.813770353794098e-01, /* 573 */ + 6.419063508510590e-01, /* 574 */ + -6.886532902717590e-02, /* 575 */ + }, { + -5.383300781250000e-02, /* 576 */ + 4.759521484375000e-01, /* 577 */ + 6.470947265625000e-01, /* 578 */ + -6.921386718750000e-02, /* 579 */ + }, { + -5.324319005012512e-02, /* 580 */ + 4.705282151699066e-01, /* 581 */ + 6.522668898105621e-01, /* 582 */ + -6.955191493034363e-02, /* 583 */ + }, { + -5.264878273010254e-02, /* 584 */ + 4.651057720184326e-01, /* 585 */ + 6.574223041534424e-01, /* 586 */ + -6.987929344177246e-02, /* 587 */ + }, { + -5.204996466636658e-02, /* 588 */ + 4.596853554248810e-01, /* 589 */ + 6.625604331493378e-01, /* 590 */ + -7.019582390785217e-02, /* 591 */ + }, { + -5.144691467285156e-02, /* 592 */ + 4.542675018310547e-01, /* 593 */ + 6.676807403564453e-01, /* 594 */ + -7.050132751464844e-02, /* 595 */ + }, { + -5.083981156349182e-02, /* 596 */ + 4.488527476787567e-01, /* 597 */ + 6.727826893329620e-01, /* 598 */ + -7.079562544822693e-02, /* 599 */ + }, { + -5.022883415222168e-02, /* 600 */ + 4.434416294097900e-01, /* 601 */ + 6.778657436370850e-01, /* 602 */ + -7.107853889465332e-02, /* 603 */ + }, { + -4.961416125297546e-02, /* 604 */ + 4.380346834659576e-01, /* 605 */ + 6.829293668270111e-01, /* 606 */ + -7.134988903999329e-02, /* 607 */ + }, { + -4.899597167968750e-02, /* 608 */ + 4.326324462890625e-01, /* 609 */ + 6.879730224609375e-01, /* 610 */ + -7.160949707031250e-02, /* 611 */ + }, { + -4.837444424629211e-02, /* 612 */ + 4.272354543209076e-01, /* 613 */ + 6.929961740970612e-01, /* 614 */ + -7.185718417167664e-02, /* 615 */ + }, { + -4.774975776672363e-02, /* 616 */ + 4.218442440032959e-01, /* 617 */ + 6.979982852935791e-01, /* 618 */ + -7.209277153015137e-02, /* 619 */ + }, { + -4.712209105491638e-02, /* 620 */ + 4.164593517780304e-01, /* 621 */ + 7.029788196086884e-01, /* 622 */ + -7.231608033180237e-02, /* 623 */ + }, { + -4.649162292480469e-02, /* 624 */ + 4.110813140869141e-01, /* 625 */ + 7.079372406005859e-01, /* 626 */ + -7.252693176269531e-02, /* 627 */ + }, { + -4.585853219032288e-02, /* 628 */ + 4.057106673717499e-01, /* 629 */ + 7.128730118274689e-01, /* 630 */ + -7.272514700889587e-02, /* 631 */ + }, { + -4.522299766540527e-02, /* 632 */ + 4.003479480743408e-01, /* 633 */ + 7.177855968475342e-01, /* 634 */ + -7.291054725646973e-02, /* 635 */ + }, { + -4.458519816398621e-02, /* 636 */ + 3.949936926364899e-01, /* 637 */ + 7.226744592189789e-01, /* 638 */ + -7.308295369148254e-02, /* 639 */ + }, { + -4.394531250000000e-02, /* 640 */ + 3.896484375000000e-01, /* 641 */ + 7.275390625000000e-01, /* 642 */ + -7.324218750000000e-02, /* 643 */ + }, { + -4.330351948738098e-02, /* 644 */ + 3.843127191066742e-01, /* 645 */ + 7.323788702487946e-01, /* 646 */ + -7.338806986808777e-02, /* 647 */ + }, { + -4.265999794006348e-02, /* 648 */ + 3.789870738983154e-01, /* 649 */ + 7.371933460235596e-01, /* 650 */ + -7.352042198181152e-02, /* 651 */ + }, { + -4.201492667198181e-02, /* 652 */ + 3.736720383167267e-01, /* 653 */ + 7.419819533824921e-01, /* 654 */ + -7.363906502723694e-02, /* 655 */ + }, { + -4.136848449707031e-02, /* 656 */ + 3.683681488037109e-01, /* 657 */ + 7.467441558837891e-01, /* 658 */ + -7.374382019042969e-02, /* 659 */ + }, { + -4.072085022926331e-02, /* 660 */ + 3.630759418010712e-01, /* 661 */ + 7.514794170856476e-01, /* 662 */ + -7.383450865745544e-02, /* 663 */ + }, { + -4.007220268249512e-02, /* 664 */ + 3.577959537506104e-01, /* 665 */ + 7.561872005462646e-01, /* 666 */ + -7.391095161437988e-02, /* 667 */ + }, { + -3.942272067070007e-02, /* 668 */ + 3.525287210941315e-01, /* 669 */ + 7.608669698238373e-01, /* 670 */ + -7.397297024726868e-02, /* 671 */ + }, { + -3.877258300781250e-02, /* 672 */ + 3.472747802734375e-01, /* 673 */ + 7.655181884765625e-01, /* 674 */ + -7.402038574218750e-02, /* 675 */ + }, { + -3.812196850776672e-02, /* 676 */ + 3.420346677303314e-01, /* 677 */ + 7.701403200626373e-01, /* 678 */ + -7.405301928520203e-02, /* 679 */ + }, { + -3.747105598449707e-02, /* 680 */ + 3.368089199066162e-01, /* 681 */ + 7.747328281402588e-01, /* 682 */ + -7.407069206237793e-02, /* 683 */ + }, { + -3.682002425193787e-02, /* 684 */ + 3.315980732440948e-01, /* 685 */ + 7.792951762676239e-01, /* 686 */ + -7.407322525978088e-02, /* 687 */ + }, { + -3.616905212402344e-02, /* 688 */ + 3.264026641845703e-01, /* 689 */ + 7.838268280029297e-01, /* 690 */ + -7.406044006347656e-02, /* 691 */ + }, { + -3.551831841468811e-02, /* 692 */ + 3.212232291698456e-01, /* 693 */ + 7.883272469043732e-01, /* 694 */ + -7.403215765953064e-02, /* 695 */ + }, { + -3.486800193786621e-02, /* 696 */ + 3.160603046417236e-01, /* 697 */ + 7.927958965301514e-01, /* 698 */ + -7.398819923400879e-02, /* 699 */ + }, { + -3.421828150749207e-02, /* 700 */ + 3.109144270420074e-01, /* 701 */ + 7.972322404384613e-01, /* 702 */ + -7.392838597297668e-02, /* 703 */ + }, { + -3.356933593750000e-02, /* 704 */ + 3.057861328125000e-01, /* 705 */ + 8.016357421875000e-01, /* 706 */ + -7.385253906250000e-02, /* 707 */ + }, { + -3.292134404182434e-02, /* 708 */ + 3.006759583950043e-01, /* 709 */ + 8.060058653354645e-01, /* 710 */ + -7.376047968864441e-02, /* 711 */ + }, { + -3.227448463439941e-02, /* 712 */ + 2.955844402313232e-01, /* 713 */ + 8.103420734405518e-01, /* 714 */ + -7.365202903747559e-02, /* 715 */ + }, { + -3.162893652915955e-02, /* 716 */ + 2.905121147632599e-01, /* 717 */ + 8.146438300609589e-01, /* 718 */ + -7.352700829505920e-02, /* 719 */ + }, { + -3.098487854003906e-02, /* 720 */ + 2.854595184326172e-01, /* 721 */ + 8.189105987548828e-01, /* 722 */ + -7.338523864746094e-02, /* 723 */ + }, { + -3.034248948097229e-02, /* 724 */ + 2.804271876811981e-01, /* 725 */ + 8.231418430805206e-01, /* 726 */ + -7.322654128074646e-02, /* 727 */ + }, { + -2.970194816589355e-02, /* 728 */ + 2.754156589508057e-01, /* 729 */ + 8.273370265960693e-01, /* 730 */ + -7.305073738098145e-02, /* 731 */ + }, { + -2.906343340873718e-02, /* 732 */ + 2.704254686832428e-01, /* 733 */ + 8.314956128597260e-01, /* 734 */ + -7.285764813423157e-02, /* 735 */ + }, { + -2.842712402343750e-02, /* 736 */ + 2.654571533203125e-01, /* 737 */ + 8.356170654296875e-01, /* 738 */ + -7.264709472656250e-02, /* 739 */ + }, { + -2.779319882392883e-02, /* 740 */ + 2.605112493038177e-01, /* 741 */ + 8.397008478641510e-01, /* 742 */ + -7.241889834403992e-02, /* 743 */ + }, { + -2.716183662414551e-02, /* 744 */ + 2.555882930755615e-01, /* 745 */ + 8.437464237213135e-01, /* 746 */ + -7.217288017272949e-02, /* 747 */ + }, { + -2.653321623802185e-02, /* 748 */ + 2.506888210773468e-01, /* 749 */ + 8.477532565593719e-01, /* 750 */ + -7.190886139869690e-02, /* 751 */ + }, { + -2.590751647949219e-02, /* 752 */ + 2.458133697509766e-01, /* 753 */ + 8.517208099365234e-01, /* 754 */ + -7.162666320800781e-02, /* 755 */ + }, { + -2.528491616249084e-02, /* 756 */ + 2.409624755382538e-01, /* 757 */ + 8.556485474109650e-01, /* 758 */ + -7.132610678672791e-02, /* 759 */ + }, { + -2.466559410095215e-02, /* 760 */ + 2.361366748809814e-01, /* 761 */ + 8.595359325408936e-01, /* 762 */ + -7.100701332092285e-02, /* 763 */ + }, { + -2.404972910881042e-02, /* 764 */ + 2.313365042209625e-01, /* 765 */ + 8.633824288845062e-01, /* 766 */ + -7.066920399665833e-02, /* 767 */ + }, { + -2.343750000000000e-02, /* 768 */ + 2.265625000000000e-01, /* 769 */ + 8.671875000000000e-01, /* 770 */ + -7.031250000000000e-02, /* 771 */ + }, { + -2.282908558845520e-02, /* 772 */ + 2.218151986598969e-01, /* 773 */ + 8.709506094455719e-01, /* 774 */ + -6.993672251701355e-02, /* 775 */ + }, { + -2.222466468811035e-02, /* 776 */ + 2.170951366424561e-01, /* 777 */ + 8.746712207794189e-01, /* 778 */ + -6.954169273376465e-02, /* 779 */ + }, { + -2.162441611289978e-02, /* 780 */ + 2.124028503894806e-01, /* 781 */ + 8.783487975597382e-01, /* 782 */ + -6.912723183631897e-02, /* 783 */ + }, { + -2.102851867675781e-02, /* 784 */ + 2.077388763427734e-01, /* 785 */ + 8.819828033447266e-01, /* 786 */ + -6.869316101074219e-02, /* 787 */ + }, { + -2.043715119361877e-02, /* 788 */ + 2.031037509441376e-01, /* 789 */ + 8.855727016925812e-01, /* 790 */ + -6.823930144309998e-02, /* 791 */ + }, { + -1.985049247741699e-02, /* 792 */ + 1.984980106353760e-01, /* 793 */ + 8.891179561614990e-01, /* 794 */ + -6.776547431945801e-02, /* 795 */ + }, { + -1.926872134208679e-02, /* 796 */ + 1.939221918582916e-01, /* 797 */ + 8.926180303096771e-01, /* 798 */ + -6.727150082588196e-02, /* 799 */ + }, { + -1.869201660156250e-02, /* 800 */ + 1.893768310546875e-01, /* 801 */ + 8.960723876953125e-01, /* 802 */ + -6.675720214843750e-02, /* 803 */ + }, { + -1.812055706977844e-02, /* 804 */ + 1.848624646663666e-01, /* 805 */ + 8.994804918766022e-01, /* 806 */ + -6.622239947319031e-02, /* 807 */ + }, { + -1.755452156066895e-02, /* 808 */ + 1.803796291351318e-01, /* 809 */ + 9.028418064117432e-01, /* 810 */ + -6.566691398620605e-02, /* 811 */ + }, { + -1.699408888816833e-02, /* 812 */ + 1.759288609027863e-01, /* 813 */ + 9.061557948589325e-01, /* 814 */ + -6.509056687355042e-02, /* 815 */ + }, { + -1.643943786621094e-02, /* 816 */ + 1.715106964111328e-01, /* 817 */ + 9.094219207763672e-01, /* 818 */ + -6.449317932128906e-02, /* 819 */ + }, { + -1.589074730873108e-02, /* 820 */ + 1.671256721019745e-01, /* 821 */ + 9.126396477222443e-01, /* 822 */ + -6.387457251548767e-02, /* 823 */ + }, { + -1.534819602966309e-02, /* 824 */ + 1.627743244171143e-01, /* 825 */ + 9.158084392547607e-01, /* 826 */ + -6.323456764221191e-02, /* 827 */ + }, { + -1.481196284294128e-02, /* 828 */ + 1.584571897983551e-01, /* 829 */ + 9.189277589321136e-01, /* 830 */ + -6.257298588752747e-02, /* 831 */ + }, { + -1.428222656250000e-02, /* 832 */ + 1.541748046875000e-01, /* 833 */ + 9.219970703125000e-01, /* 834 */ + -6.188964843750000e-02, /* 835 */ + }, { + -1.375916600227356e-02, /* 836 */ + 1.499277055263519e-01, /* 837 */ + 9.250158369541168e-01, /* 838 */ + -6.118437647819519e-02, /* 839 */ + }, { + -1.324295997619629e-02, /* 840 */ + 1.457164287567139e-01, /* 841 */ + 9.279835224151611e-01, /* 842 */ + -6.045699119567871e-02, /* 843 */ + }, { + -1.273378729820251e-02, /* 844 */ + 1.415415108203888e-01, /* 845 */ + 9.308995902538300e-01, /* 846 */ + -5.970731377601624e-02, /* 847 */ + }, { + -1.223182678222656e-02, /* 848 */ + 1.374034881591797e-01, /* 849 */ + 9.337635040283203e-01, /* 850 */ + -5.893516540527344e-02, /* 851 */ + }, { + -1.173725724220276e-02, /* 852 */ + 1.333028972148895e-01, /* 853 */ + 9.365747272968292e-01, /* 854 */ + -5.814036726951599e-02, /* 855 */ + }, { + -1.125025749206543e-02, /* 856 */ + 1.292402744293213e-01, /* 857 */ + 9.393327236175537e-01, /* 858 */ + -5.732274055480957e-02, /* 859 */ + }, { + -1.077100634574890e-02, /* 860 */ + 1.252161562442780e-01, /* 861 */ + 9.420369565486908e-01, /* 862 */ + -5.648210644721985e-02, /* 863 */ + }, { + -1.029968261718750e-02, /* 864 */ + 1.212310791015625e-01, /* 865 */ + 9.446868896484375e-01, /* 866 */ + -5.561828613281250e-02, /* 867 */ + }, { + -9.836465120315552e-03, /* 868 */ + 1.172855794429779e-01, /* 869 */ + 9.472819864749908e-01, /* 870 */ + -5.473110079765320e-02, /* 871 */ + }, { + -9.381532669067383e-03, /* 872 */ + 1.133801937103271e-01, /* 873 */ + 9.498217105865479e-01, /* 874 */ + -5.382037162780762e-02, /* 875 */ + }, { + -8.935064077377319e-03, /* 876 */ + 1.095154583454132e-01, /* 877 */ + 9.523055255413055e-01, /* 878 */ + -5.288591980934143e-02, /* 879 */ + }, { + -8.497238159179688e-03, /* 880 */ + 1.056919097900391e-01, /* 881 */ + 9.547328948974609e-01, /* 882 */ + -5.192756652832031e-02, /* 883 */ + }, { + -8.068233728408813e-03, /* 884 */ + 1.019100844860077e-01, /* 885 */ + 9.571032822132111e-01, /* 886 */ + -5.094513297080994e-02, /* 887 */ + }, { + -7.648229598999023e-03, /* 888 */ + 9.817051887512207e-02, /* 889 */ + 9.594161510467529e-01, /* 890 */ + -4.993844032287598e-02, /* 891 */ + }, { + -7.237404584884644e-03, /* 892 */ + 9.447374939918518e-02, /* 893 */ + 9.616709649562836e-01, /* 894 */ + -4.890730977058411e-02, /* 895 */ + }, { + -6.835937500000000e-03, /* 896 */ + 9.082031250000000e-02, /* 897 */ + 9.638671875000000e-01, /* 898 */ + -4.785156250000000e-02, /* 899 */ + }, { + -6.444007158279419e-03, /* 900 */ + 8.721074461936951e-02, /* 901 */ + 9.660042822360992e-01, /* 902 */ + -4.677101969718933e-02, /* 903 */ + }, { + -6.061792373657227e-03, /* 904 */ + 8.364558219909668e-02, /* 905 */ + 9.680817127227783e-01, /* 906 */ + -4.566550254821777e-02, /* 907 */ + }, { + -5.689471960067749e-03, /* 908 */ + 8.012536168098450e-02, /* 909 */ + 9.700989425182343e-01, /* 910 */ + -4.453483223915100e-02, /* 911 */ + }, { + -5.327224731445312e-03, /* 912 */ + 7.665061950683594e-02, /* 913 */ + 9.720554351806641e-01, /* 914 */ + -4.337882995605469e-02, /* 915 */ + }, { + -4.975229501724243e-03, /* 916 */ + 7.322189211845398e-02, /* 917 */ + 9.739506542682648e-01, /* 918 */ + -4.219731688499451e-02, /* 919 */ + }, { + -4.633665084838867e-03, /* 920 */ + 6.983971595764160e-02, /* 921 */ + 9.757840633392334e-01, /* 922 */ + -4.099011421203613e-02, /* 923 */ + }, { + -4.302710294723511e-03, /* 924 */ + 6.650462746620178e-02, /* 925 */ + 9.775551259517670e-01, /* 926 */ + -3.975704312324524e-02, /* 927 */ + }, { + -3.982543945312500e-03, /* 928 */ + 6.321716308593750e-02, /* 929 */ + 9.792633056640625e-01, /* 930 */ + -3.849792480468750e-02, /* 931 */ + }, { + -3.673344850540161e-03, /* 932 */ + 5.997785925865173e-02, /* 933 */ + 9.809080660343170e-01, /* 934 */ + -3.721258044242859e-02, /* 935 */ + }, { + -3.375291824340820e-03, /* 936 */ + 5.678725242614746e-02, /* 937 */ + 9.824888706207275e-01, /* 938 */ + -3.590083122253418e-02, /* 939 */ + }, { + -3.088563680648804e-03, /* 940 */ + 5.364587903022766e-02, /* 941 */ + 9.840051829814911e-01, /* 942 */ + -3.456249833106995e-02, /* 943 */ + }, { + -2.813339233398438e-03, /* 944 */ + 5.055427551269531e-02, /* 945 */ + 9.854564666748047e-01, /* 946 */ + -3.319740295410156e-02, /* 947 */ + }, { + -2.549797296524048e-03, /* 948 */ + 4.751297831535339e-02, /* 949 */ + 9.868421852588654e-01, /* 950 */ + -3.180536627769470e-02, /* 951 */ + }, { + -2.298116683959961e-03, /* 952 */ + 4.452252388000488e-02, /* 953 */ + 9.881618022918701e-01, /* 954 */ + -3.038620948791504e-02, /* 955 */ + }, { + -2.058476209640503e-03, /* 956 */ + 4.158344864845276e-02, /* 957 */ + 9.894147813320160e-01, /* 958 */ + -2.893975377082825e-02, /* 959 */ + }, { + -1.831054687500000e-03, /* 960 */ + 3.869628906250000e-02, /* 961 */ + 9.906005859375000e-01, /* 962 */ + -2.746582031250000e-02, /* 963 */ + }, { + -1.616030931472778e-03, /* 964 */ + 3.586158156394958e-02, /* 965 */ + 9.917186796665192e-01, /* 966 */ + -2.596423029899597e-02, /* 967 */ + }, { + -1.413583755493164e-03, /* 968 */ + 3.307986259460449e-02, /* 969 */ + 9.927685260772705e-01, /* 970 */ + -2.443480491638184e-02, /* 971 */ + }, { + -1.223891973495483e-03, /* 972 */ + 3.035166859626770e-02, /* 973 */ + 9.937495887279510e-01, /* 974 */ + -2.287736535072327e-02, /* 975 */ + }, { + -1.047134399414062e-03, /* 976 */ + 2.767753601074219e-02, /* 977 */ + 9.946613311767578e-01, /* 978 */ + -2.129173278808594e-02, /* 979 */ + }, { + -8.834898471832275e-04, /* 980 */ + 2.505800127983093e-02, /* 981 */ + 9.955032169818878e-01, /* 982 */ + -1.967772841453552e-02, /* 983 */ + }, { + -7.331371307373047e-04, /* 984 */ + 2.249360084533691e-02, /* 985 */ + 9.962747097015381e-01, /* 986 */ + -1.803517341613770e-02, /* 987 */ + }, { + -5.962550640106201e-04, /* 988 */ + 1.998487114906311e-02, /* 989 */ + 9.969752728939056e-01, /* 990 */ + -1.636388897895813e-02, /* 991 */ + }, { + -4.730224609375000e-04, /* 992 */ + 1.753234863281250e-02, /* 993 */ + 9.976043701171875e-01, /* 994 */ + -1.466369628906250e-02, /* 995 */ + }, { + -3.636181354522705e-04, /* 996 */ + 1.513656973838806e-02, /* 997 */ + 9.981614649295807e-01, /* 998 */ + -1.293441653251648e-02, /* 999 */ + }, { + -2.682209014892578e-04, /* 1000 */ + 1.279807090759277e-02, /* 1001 */ + 9.986460208892822e-01, /* 1002 */ + -1.117587089538574e-02, /* 1003 */ + }, { + -1.870095729827881e-04, /* 1004 */ + 1.051738858222961e-02, /* 1005 */ + 9.990575015544891e-01, /* 1006 */ + -9.387880563735962e-03, /* 1007 */ + }, { + -1.201629638671875e-04, /* 1008 */ + 8.295059204101562e-03, /* 1009 */ + 9.993953704833984e-01, /* 1010 */ + -7.570266723632812e-03, /* 1011 */ + }, { + -6.785988807678223e-05, /* 1012 */ + 6.131619215011597e-03, /* 1013 */ + 9.996590912342072e-01, /* 1014 */ + -5.722850561141968e-03, /* 1015 */ + }, { + -3.027915954589844e-05, /* 1016 */ + 4.027605056762695e-03, /* 1017 */ + 9.998481273651123e-01, /* 1018 */ + -3.845453262329102e-03, /* 1019 */ + }, { + -7.599592208862305e-06, /* 1020 */ + 1.983553171157837e-03, /* 1021 */ + 9.999619424343109e-01, /* 1022 */ + -1.937896013259888e-03, /* 1023 */ + } +}; + +static const fluid_real_t sinc_table7[256][7] = { + { + 2.375620125729980e-02, /* 0 */ + -1.290049686942476e-01, /* 1 */ + 5.998790953453343e-01, /* 2 */ + 6.103020511314905e-01, /* 3 */ + -1.304058543975903e-01, /* 4 */ + 2.418010665366927e-02, /* 5 */ + -2.798064002790454e-07, /* 6 */ + }, { + 2.354065170871666e-02, /* 7 */ + -1.282805558938982e-01, /* 8 */ + 5.946483199408858e-01, /* 9 */ + 6.154931623267351e-01, /* 10 */ + -1.310817379215285e-01, /* 11 */ + 2.438827747279956e-02, /* 12 */ + -1.120220972351802e-06, /* 13 */ + }, { + 2.332282679065955e-02, /* 14 */ + -1.275405562274653e-01, /* 15 */ + 5.894053926563386e-01, /* 16 */ + 6.206699834160688e-01, /* 17 */ + -1.317408559206581e-01, /* 18 */ + 2.459380290371239e-02, /* 19 */ + -2.522356622734990e-06, /* 20 */ + }, { + 2.310281775655174e-02, /* 21 */ + -1.267852645808413e-01, /* 22 */ + 5.841508484795060e-01, /* 23 */ + 6.258319806421593e-01, /* 24 */ + -1.323829141999531e-01, /* 25 */ + 2.479658928687493e-02, /* 26 */ + -4.486817393493708e-06, /* 27 */ + }, { + 2.288071532172811e-02, /* 28 */ + -1.260149758298408e-01, /* 29 */ + 5.788852224239674e-01, /* 30 */ + 6.309786207146856e-01, /* 31 */ + -1.330076188523975e-01, /* 32 */ + 2.499654254034837e-02, /* 33 */ + -7.013696123785544e-06, /* 34 */ + }, { + 2.265660964514263e-02, /* 35 */ + -1.252299847913509e-01, /* 36 */ + 5.736090494558752e-01, /* 37 */ + 6.361093708841161e-01, /* 38 */ + -1.336146763094198e-01, /* 39 */ + 2.519356818002896e-02, /* 40 */ + -1.010257230258042e-05, /* 41 */ + }, { + 2.243059031136537e-02, /* 42 */ + -1.244305861747393e-01, /* 43 */ + 5.683228644208926e-01, /* 44 */ + 6.412236990155202e-01, /* 45 */ + -1.342037933915221e-01, /* 46 */ + 2.538757134015553e-02, /* 47 */ + -1.375251011368080e-05, /* 48 */ + }, { + 2.220274631287172e-02, /* 49 */ + -1.236170745335310e-01, /* 50 */ + 5.630272019712755e-01, /* 51 */ + 6.463210736624057e-01, /* 52 */ + -1.347746773590917e-01, /* 53 */ + 2.557845679407980e-02, /* 54 */ + -1.796205667443242e-05, /* 55 */ + }, { + 2.197316603262602e-02, /* 56 */ + -1.227897442173580e-01, /* 57 */ + 5.577225964931086e-01, /* 58 */ + 6.514009641405650e-01, /* 59 */ + -1.353270359633906e-01, /* 60 */ + 2.576612897529643e-02, /* 61 */ + -2.272924046911682e-05, /* 62 */ + }, { + 2.174193722696236e-02, /* 63 */ + -1.219488893241928e-01, /* 64 */ + 5.524095820337069e-01, /* 65 */ + 6.564628406019232e-01, /* 66 */ + -1.358605774977103e-01, /* 67 */ + 2.595049199872917e-02, /* 68 */ + -2.805156997831240e-05, /* 69 */ + }, { + 2.150914700876424e-02, /* 70 */ + -1.210948036528713e-01, /* 71 */ + 5.470886922291980e-01, /* 72 */ + 6.615061741083712e-01, /* 73 */ + -1.363750108486864e-01, /* 74 */ + 2.613144968226988e-02, /* 75 */ + -3.392603250514114e-05, /* 76 */ + }, { + 2.127488183094605e-02, /* 77 */ + -1.202277806559141e-01, /* 78 */ + 5.417604602322907e-01, /* 79 */ + 6.665304367055752e-01, /* 80 */ + -1.368700455477614e-01, /* 81 */ + 2.630890556856650e-02, /* 82 */ + -4.034909319948082e-05, /* 83 */ + }, { + 2.103922747023789e-02, /* 84 */ + -1.193481133926526e-01, /* 85 */ + 5.364254186402488e-01, /* 86 */ + 6.715351014967476e-01, /* 87 */ + -1.373453918227895e-01, /* 88 */ + 2.648276294705649e-02, /* 89 */ + -4.731669428110093e-05, /* 90 */ + }, { + 2.080226901127631e-02, /* 91 */ + -1.184560944826678e-01, /* 92 */ + 5.310840994230748e-01, /* 93 */ + 6.765196427163698e-01, /* 94 */ + -1.378007606497726e-01, /* 95 */ + 2.665292487624222e-02, /* 96 */ + -5.482425446254296e-05, /* 97 */ + }, { + 2.056409083100217e-02, /* 98 */ + -1.175520160595501e-01, /* 99 */ + 5.257370338519197e-01, /* 100 */ + 6.814835358038533e-01, /* 101 */ + -1.382358638047184e-01, /* 102 */ + 2.681929420620386e-02, /* 103 */ + -6.286666857267136e-05, /* 104 */ + }, { + 2.032477658336845e-02, /* 105 */ + -1.166361697249849e-01, /* 106 */ + 5.203847524277286e-01, /* 107 */ + 6.864262574771270e-01, /* 108 */ + -1.386504139156142e-01, /* 109 */ + 2.698177360134680e-02, /* 110 */ + -7.143830738156712e-05, /* 111 */ + }, { + 2.008440918435909e-02, /* 112 */ + -1.157088465031742e-01, /* 113 */ + 5.150277848101327e-01, /* 114 */ + 6.913472858061384e-01, /* 115 */ + -1.390441245145027e-01, /* 116 */ + 2.714026556337870e-02, /* 117 */ + -8.053301762762107e-05, /* 118 */ + }, { + 1.984307079732106e-02, /* 119 */ + -1.147703367955984e-01, /* 120 */ + 5.096666597466010e-01, /* 121 */ + 6.962461002862578e-01, /* 122 */ + -1.394167100896560e-01, /* 123 */ + 2.729467245451285e-02, /* 124 */ + -9.014412224732896e-05, /* 125 */ + }, { + 1.960084281861067e-02, /* 126 */ + -1.138209303361282e-01, /* 127 */ + 5.043019050018616e-01, /* 128 */ + 7.011221819115717e-01, /* 129 */ + -1.397678861378340e-01, /* 130 */ + 2.744489652089330e-02, /* 131 */ + -1.002644208085391e-04, /* 132 */ + }, { + 1.935780586355643e-02, /* 133 */ + -1.128609161464899e-01, /* 134 */ + 4.989340472876037e-01, /* 135 */ + 7.059750132480542e-01, /* 136 */ + -1.400973692166212e-01, /* 137 */ + 2.759083991623796e-02, /* 138 */ + -1.108861901476344e-04, /* 139 */ + }, { + 1.911403975273935e-02, /* 140 */ + -1.118905824920952e-01, /* 141 */ + 4.935636121924722e-01, /* 142 */ + 7.108040785066047e-01, /* 143 */ + -1.404048769968304e-01, /* 144 */ + 2.773240472569498e-02, /* 145 */ + -1.220011852110915e-04, /* 146 */ + }, { + 1.886962349859249e-02, /* 147 */ + -1.109102168382377e-01, /* 148 */ + 4.881911241123659e-01, /* 149 */ + 7.156088636159380e-01, /* 150 */ + -1.406901283149657e-01, /* 151 */ + 2.786949298990845e-02, /* 152 */ + -1.336006401019428e-04, /* 153 */ + }, { + 1.862463529232039e-02, /* 154 */ + -1.099201058066671e-01, /* 155 */ + 4.828171061810496e-01, /* 156 */ + 7.203888562953171e-01, /* 157 */ + -1.409528432257348e-01, /* 158 */ + 2.800200672928881e-02, /* 159 */ + -1.456752693314445e-04, /* 160 */ + }, { + 1.837915249114045e-02, /* 161 */ + -1.089205351325436e-01, /* 162 */ + 4.774420802010910e-01, /* 163 */ + 7.251435461271148e-01, /* 164 */ + -1.411927430545998e-01, /* 165 */ + 2.812984796848375e-02, /* 166 */ + -1.582152692763085e-04, /* 167 */ + }, { + 1.813325160584695e-02, /* 168 */ + -1.079117896217818e-01, /* 169 */ + 4.720665665751356e-01, /* 170 */ + 7.298724246291924e-01, /* 171 */ + -1.414095504503600e-01, /* 172 */ + 2.825291876104482e-02, /* 173 */ + -1.712103198416544e-04, /* 174 */ + }, { + 1.788700828869835e-02, /* 175 */ + -1.068941531087891e-01, /* 176 */ + 4.666910842375266e-01, /* 177 */ + 7.345749853270843e-01, /* 178 */ + -1.416029894377547e-01, /* 179 */ + 2.837112121428517e-02, /* 180 */ + -1.846495863300355e-04, /* 181 */ + }, { + 1.764049732162978e-02, /* 182 */ + -1.058679084146052e-01, /* 183 */ + 4.613161505862837e-01, /* 184 */ + 7.392507238259753e-01, /* 185 */ + -1.417727854700776e-01, /* 186 */ + 2.848435751432410e-02, /* 187 */ + -1.985217215164836e-04, /* 188 */ + }, { + 1.739379260479069e-02, /* 189 */ + -1.048333373054486e-01, /* 190 */ + 4.559422814154492e-01, /* 191 */ + 7.438991378824603e-01, /* 192 */ + -1.419186654817930e-01, /* 193 */ + 2.859252995131316e-02, /* 194 */ + -2.128148679298167e-04, /* 195 */ + }, { + 1.714696714540915e-02, /* 196 */ + -1.037907204516760e-01, /* 197 */ + 4.505699908478140e-01, /* 198 */ + 7.485197274760710e-01, /* 199 */ + -1.420403579411441e-01, /* 200 */ + 2.869554094483946e-02, /* 201 */ + -2.275166603400509e-04, /* 202 */ + }, { + 1.690009304698279e-02, /* 203 */ + -1.027403373871614e-01, /* 204 */ + 4.451997912680325e-01, /* 205 */ + 7.531119948805626e-01, /* 206 */ + -1.421375929027446e-01, /* 207 */ + 2.879329306950119e-02, /* 208 */ + -2.426142284520608e-04, /* 209 */ + }, { + 1.665324149879781e-02, /* 210 */ + -1.016824664690990e-01, /* 211 */ + 4.398321932561375e-01, /* 212 */ + 7.576754447349440e-01, /* 213 */ + -1.422101020601427e-01, /* 214 */ + 2.888568908065016e-02, /* 215 */ + -2.580941998051696e-04, /* 216 */ + }, { + 1.640648276577594e-02, /* 217 */ + -1.006173848382378e-01, /* 218 */ + 4.344677055214644e-01, /* 219 */ + 7.622095841142430e-01, /* 220 */ + -1.422576187983489e-01, /* 221 */ + 2.897263194029685e-02, /* 222 */ + -2.739427028786713e-04, /* 223 */ + }, { + 1.615988617865028e-02, /* 224 */ + -9.954536837955191e-02, /* 225 */ + 4.291068348369969e-01, /* 226 */ + 7.667139225999916e-01, /* 227 */ + -1.422798782463173e-01, /* 228 */ + 2.905402484317260e-02, /* 229 */ + -2.901453704029424e-04, /* 230 */ + }, { + 1.591352012446994e-02, /* 231 */ + -9.846669168335127e-02, /* 232 */ + 4.237500859741424e-01, /* 233 */ + 7.711879723504229e-01, /* 234 */ + -1.422766173293711e-01, /* 235 */ + 2.912977124294393e-02, /* 236 */ + -3.066873428758958e-04, /* 237 */ + }, { + 1.566745203743416e-02, /* 238 */ + -9.738162800684201e-02, /* 239 */ + 4.183979616379481e-01, /* 240 */ + 7.756312481703652e-01, /* 241 */ + -1.422475748215626e-01, /* 242 */ + 2.919977487857369e-02, /* 243 */ + -3.235532722844501e-04, /* 244 */ + }, { + 1.542174839005620e-02, /* 245 */ + -9.629044923613632e-02, /* 246 */ + 4.130509624027673e-01, /* 247 */ + 7.800432675808223e-01, /* 248 */ + -1.421924913979572e-01, /* 249 */ + 2.926393980082415e-02, /* 250 */ + -3.407273260304884e-04, /* 251 */ + }, { + 1.517647468465644e-02, /* 252 */ + -9.519342584872197e-02, /* 253 */ + 4.077095866483865e-01, /* 254 */ + 7.844235508882289e-01, /* 255 */ + -1.421111096868331e-01, /* 256 */ + 2.932217039889637e-02, /* 257 */ + -3.581931910609877e-04, /* 258 */ + }, { + 1.493169544518567e-02, /* 259 */ + -9.409082687639277e-02, /* 260 */ + 4.023743304966226e-01, /* 261 */ + 7.887716212533704e-01, /* 262 */ + -1.420031743217853e-01, /* 263 */ + 2.937437142720069e-02, /* 264 */ + -3.759340782016456e-04, /* 265 */ + }, { + 1.468747420937781e-02, /* 266 */ + -9.298291986864780e-02, /* 267 */ + 3.970456877483993e-01, /* 268 */ + 7.930870047599514e-01, /* 269 */ + -1.418684319937252e-01, /* 270 */ + 2.942044803225294e-02, /* 271 */ + -3.939327266934505e-04, /* 272 */ + }, { + 1.444387352123256e-02, /* 273 */ + -9.186997085656183e-02, /* 274 */ + 3.917241498213130e-01, /* 275 */ + 7.973692304828067e-01, /* 276 */ + -1.417066315027663e-01, /* 277 */ + 2.946030577969092e-02, /* 278 */ + -4.121714089315939e-04, /* 279 */ + }, { + 1.420095492382687e-02, /* 280 */ + -9.075224431713386e-02, /* 281 */ + 3.864102056876984e-01, /* 282 */ + 8.016178305557399e-01, /* 283 */ + -1.415175238099844e-01, /* 284 */ + 2.949385068140553e-02, /* 285 */ + -4.306319354058826e-04, /* 286 */ + }, { + 1.395877895245621e-02, /* 287 */ + -8.963000313811628e-02, /* 288 */ + 3.811043418132007e-01, /* 289 */ + 8.058323402389781e-01, /* 290 */ + -1.413008620890449e-01, /* 291 */ + 2.952098922278122e-02, /* 292 */ + -4.492956598419907e-04, /* 293 */ + }, { + 1.371740512810407e-02, /* 294 */ + -8.850350858333141e-02, /* 295 */ + 3.758070420958670e-01, /* 296 */ + 8.100122979862356e-01, /* 297 */ + -1.410564017776856e-01, /* 298 */ + 2.954162839003991e-02, /* 299 */ + -4.681434845426153e-04, /* 300 */ + }, { + 1.347689195124034e-02, /* 301 */ + -8.737302025847754e-02, /* 302 */ + 3.705187878057628e-01, /* 303 */ + 8.141572455113678e-01, /* 304 */ + -1.407839006290460e-01, /* 305 */ + 2.955567569768274e-02, /* 306 */ + -4.871558659276469e-04, /* 307 */ + }, { + 1.323729689594689e-02, /* 308 */ + -8.623879607743025e-02, /* 309 */ + 3.652400575251253e-01, /* 310 */ + 8.182667278546104e-01, /* 311 */ + -1.404831187628331e-01, /* 312 */ + 2.956303921602418e-02, /* 313 */ + -5.063128202724816e-04, /* 314 */ + }, { + 1.299867640437090e-02, /* 315 */ + -8.510109222904338e-02, /* 316 */ + 3.599713270890607e-01, /* 317 */ + 8.223402934483920e-01, /* 318 */ + -1.401538187163136e-01, /* 319 */ + 2.956362759881255e-02, /* 320 */ + -5.255939296432825e-04, /* 321 */ + }, { + 1.276108588150466e-02, /* 322 */ + -8.396016314445182e-02, /* 323 */ + 3.547130695267950e-01, /* 324 */ + 8.263774941827043e-01, /* 325 */ + -1.397957654951237e-01, /* 326 */ + 2.955735011093100e-02, /* 327 */ + -5.449783480282249e-04, /* 328 */ + }, { + 1.252457969029095e-02, /* 329 */ + -8.281626146488272e-02, /* 330 */ + 3.494657550034874e-01, /* 331 */ + 8.303778854700258e-01, /* 332 */ + -1.394087266238854e-01, /* 333 */ + 2.954411665617338e-02, /* 334 */ + -5.644448076635753e-04, /* 335 */ + }, { + 1.228921114705370e-02, /* 336 */ + -8.166963800997633e-02, /* 337 */ + 3.442298507626138e-01, /* 338 */ + 8.343410263097801e-01, /* 339 */ + -1.389924721966199e-01, /* 340 */ + 2.952383780508903e-02, /* 341 */ + -5.839716255532901e-04, /* 342 */ + }, { + 1.205503251725260e-02, /* 343 */ + -8.052054174662254e-02, /* 344 */ + 3.390058210689310e-01, /* 345 */ + 8.382664793523271e-01, /* 346 */ + -1.385467749269494e-01, /* 347 */ + 2.949642482289040e-02, /* 348 */ + -6.035367101809801e-04, /* 349 */ + }, { + 1.182209501156100e-02, /* 350 */ + -7.936921975831460e-02, /* 351 */ + 3.337941271520264e-01, /* 352 */ + 8.421538109624669e-01, /* 353 */ + -1.380714101980755e-01, /* 354 */ + 2.946178969741759e-02, /* 355 */ + -6.231175684128511e-04, /* 356 */ + }, { + 1.159044878226562e-02, /* 357 */ + -7.821591721502538e-02, /* 358 */ + 3.285952271504655e-01, /* 359 */ + 8.460025912824529e-01, /* 360 */ + -1.375661561125266e-01, /* 361 */ + 2.941984516715388e-02, /* 362 */ + -6.426913125902382e-04, /* 363 */ + }, { + 1.136014291998739e-02, /* 364 */ + -7.706087734360774e-02, /* 365 */ + 3.234095760565429e-01, /* 366 */ + 8.498123942944995e-01, /* 367 */ + -1.370307935416629e-01, /* 368 */ + 2.937050474928606e-02, /* 369 */ + -6.622346678102776e-04, /* 370 */ + }, { + 1.113122545072202e-02, /* 371 */ + -7.590434139872407e-02, /* 372 */ + 3.182376256616457e-01, /* 373 */ + 8.535827978827749e-01, /* 374 */ + -1.364651061749298e-01, /* 375 */ + 2.931368276780341e-02, /* 376 */ + -6.817239793932266e-04, /* 377 */ + }, { + 1.090374333319907e-02, /* 378 */ + -7.474654863430663e-02, /* 379 */ + 3.130798245022357e-01, /* 380 */ + 8.573133838948686e-01, /* 381 */ + -1.358688805688508e-01, /* 382 */ + 2.924929438162952e-02, /* 383 */ + -7.011352205348302e-04, /* 384 */ + }, { + 1.067774245655789e-02, /* 385 */ + -7.358773627555218e-02, /* 386 */ + 3.079366178064618e-01, /* 387 */ + 8.610037382027232e-01, /* 388 */ + -1.352419061957486e-01, /* 389 */ + 2.917725561278015e-02, /* 390 */ + -7.204440001421441e-04, /* 391 */ + }, { + 1.045326763833955e-02, /* 392 */ + -7.242813949145467e-02, /* 393 */ + 3.028084474414053e-01, /* 394 */ + 8.646534507630197e-01, /* 395 */ + -1.345839754921861e-01, /* 396 */ + 2.909748337454149e-02, /* 397 */ + -7.396255708510786e-04, /* 398 */ + }, { + 1.023036262279292e-02, /* 399 */ + -7.126799136787691e-02, /* 400 */ + 2.976957518609703e-01, /* 401 */ + 8.682621156770067e-01, /* 402 */ + -1.338948839071171e-01, /* 403 */ + 2.900989549966232e-02, /* 404 */ + -7.586548372239626e-04, /* 405 */ + }, { + 1.000907007949311e-02, /* 406 */ + -7.010752288116626e-02, /* 407 */ + 2.925989660544231e-01, /* 408 */ + 8.718293312497631e-01, /* 409 */ + -1.331744299497369e-01, /* 410 */ + 2.891441076855387e-02, /* 411 */ + -7.775063641252938e-04, /* 412 */ + }, { + 9.789431602271271e-03, /* 413 */ + -6.894696287231404e-02, /* 414 */ + 2.875185214955909e-01, /* 415 */ + 8.753547000488832e-01, /* 416 */ + -1.324224152370240e-01, /* 417 */ + 2.881094893749082e-02, /* 418 */ + -7.961543852738185e-04, /* 419 */ + }, { + 9.571487708453395e-03, /* 420 */ + -6.778653802166430e-02, /* 421 */ + 2.824548460927230e-01, /* 422 */ + 8.788378289625756e-01, /* 423 */ + -1.316386445409620e-01, /* 424 */ + 2.869943076680737e-02, /* 425 */ + -8.145728119690237e-04, /* 426 */ + }, { + 9.355277838406877e-03, /* 427 */ + -6.662647282417096e-02, /* 428 */ + 2.774083641390264e-01, /* 429 */ + 8.822783292571661e-01, /* 430 */ + -1.308229258354336e-01, /* 431 */ + 2.857977804908198e-02, /* 432 */ + -8.327352419900550e-04, /* 433 */ + }, { + 9.140840355392437e-03, /* 434 */ + -6.546698956520861e-02, /* 435 */ + 2.723794962638790e-01, /* 436 */ + 8.856758166339943e-01, /* 437 */ + -1.299750703427760e-01, /* 438 */ + 2.845191363730427e-02, /* 439 */ + -8.506149686650558e-04, /* 440 */ + }, { + 8.928212545720045e-03, /* 441 */ + -6.430830829693616e-02, /* 442 */ + 2.673686593847286e-01, /* 443 */ + 8.890299112856920e-01, /* 444 */ + -1.290948925799897e-01, /* 445 */ + 2.831576147301751e-02, /* 446 */ + -8.681849901087664e-04, /* 447 */ + }, { + 8.717430619206452e-03, /* 448 */ + -6.315064681521791e-02, /* 449 */ + 2.623762666596838e-01, /* 450 */ + 8.923402379518393e-01, /* 451 */ + -1.281822104045887e-01, /* 452 */ + 2.817124661443064e-02, /* 453 */ + -8.854180186263388e-04, /* 454 */ + }, { + 8.508529709932666e-03, /* 455 */ + -6.199422063710185e-02, /* 456 */ + 2.574027274408040e-01, /* 457 */ + 8.956064259739822e-01, /* 458 */ + -1.272368450600864e-01, /* 459 */ + 2.801829526449300e-02, /* 460 */ + -9.022864902810273e-04, /* 461 */ + }, { + 8.301543877298635e-03, /* 462 */ + -6.083924297885757e-02, /* 463 */ + 2.524484472280946e-01, /* 464 */ + 8.988281093500092e-01, /* 465 */ + -1.262586212211042e-01, /* 466 */ + 2.785683479892532e-02, /* 467 */ + -9.187625746236699e-04, /* 468 */ + }, { + 8.096506107373698e-03, /* 469 */ + -5.968592473457642e-02, /* 470 */ + 2.475138276242130e-01, /* 471 */ + 9.020049267878701e-01, /* 472 */ + -1.252473670380959e-01, /* 473 */ + 2.768679379420070e-02, /* 474 */ + -9.348181845814541e-04, /* 475 */ + }, { + 7.893448314540197e-03, /* 476 */ + -5.853447445533320e-02, /* 477 */ + 2.425992662898926e-01, /* 478 */ + 9.051365217586359e-01, /* 479 */ + -1.242029141816779e-01, /* 480 */ + 2.750810205546858e-02, /* 481 */ + -9.504249865037702e-04, /* 482 */ + }, { + 7.692401343427691e-03, /* 483 */ + -5.738509832891316e-02, /* 484 */ + 2.377051569000891e-01, /* 485 */ + 9.082225425488828e-01, /* 486 */ + -1.231250978865554e-01, /* 487 */ + 2.732069064441559e-02, /* 488 */ + -9.655544103626029e-04, /* 489 */ + }, { + 7.493394971135955e-03, /* 490 */ + -5.623800016010321e-02, /* 491 */ + 2.328318891008579e-01, /* 492 */ + 9.112626423123993e-01, /* 493 */ + -1.220137569950366e-01, /* 494 */ + 2.712449190705641e-02, /* 495 */ + -9.801776601050557e-04, /* 496 */ + }, { + 7.296457909743918e-03, /* 497 */ + -5.509338135155091e-02, /* 498 */ + 2.279798484669641e-01, /* 499 */ + 9.142564791211991e-01, /* 500 */ + -1.208687340001249e-01, /* 501 */ + 2.691943950144822e-02, /* 502 */ + -9.942657241554641e-04, /* 503 */ + }, { + 7.101617809102288e-03, /* 504 */ + -5.395144088518943e-02, /* 505 */ + 2.231494164602341e-01, /* 506 */ + 9.172037160158394e-01, /* 507 */ + -1.196898750881803e-01, /* 508 */ + 2.670546842532187e-02, /* 509 */ + -1.007789386064493e-03, /* 510 */ + }, { + 6.908901259906972e-03, /* 511 */ + -5.281237530423245e-02, /* 512 */ + 2.183409703886530e-01, /* 513 */ + 9.201040210550299e-01, /* 514 */ + -1.184770301811412e-01, /* 515 */ + 2.648251504362370e-02, /* 516 */ + -1.020719235302619e-03, /* 517 */ + }, { + 6.718333797051150e-03, /* 518 */ + -5.167637869573667e-02, /* 519 */ + 2.135548833662130e-01, /* 520 */ + 9.229570673645258e-01, /* 521 */ + -1.172300529782974e-01, /* 522 */ + 2.625051711596052e-02, /* 523 */ + -1.033025678195301e-03, /* 524 */ + }, { + 6.529939903253073e-03, /* 525 */ + -5.054364267373540e-02, /* 526 */ + 2.087915242735169e-01, /* 527 */ + 9.257625331852983e-01, /* 528 */ + -1.159488009976052e-01, /* 529 */ + 2.600941382394202e-02, /* 530 */ + -1.044678948997064e-03, /* 531 */ + }, { + 6.343743012956645e-03, /* 532 */ + -4.941435636294156e-02, /* 533 */ + 2.040512577191446e-01, /* 534 */ + 9.285201019209733e-01, /* 535 */ + -1.146331356165368e-01, /* 536 */ + 2.575914579841332e-02, /* 537 */ + -1.055649121101780e-03, /* 538 */ + }, { + 6.159765516502505e-03, /* 539 */ + -4.828870638302148e-02, /* 540 */ + 1.993344440017834e-01, /* 541 */ + 9.312294621845263e-01, /* 542 */ + -1.132829221124536e-01, /* 543 */ + 2.549965514657118e-02, /* 544 */ + -1.065906118386268e-03, /* 545 */ + }, { + 5.978028764566336e-03, /* 546 */ + -4.716687683344033e-02, /* 547 */ + 1.946414390731309e-01, /* 548 */ + 9.338903078442332e-01, /* 549 */ + -1.118980297024964e-01, /* 550 */ + 2.523088547895752e-02, /* 551 */ + -1.075419726684316e-03, /* 552 */ + }, { + 5.798553072861818e-03, /* 553 */ + -4.604904927887769e-02, /* 554 */ + 1.899725945015709e-01, /* 555 */ + 9.365023380688623e-01, /* 556 */ + -1.104783315829824e-01, /* 557 */ + 2.495278193632289e-02, /* 558 */ + -1.084159605388224e-03, /* 559 */ + }, { + 5.621357727104864e-03, /* 560 */ + -4.493540273521546e-02, /* 561 */ + 1.853282574366316e-01, /* 562 */ + 9.390652573721028e-01, /* 563 */ + -1.090237049683018e-01, /* 564 */ + 2.466529121635406e-02, /* 565 */ + -1.092095299174878e-03, /* 566 */ + }, { + 5.446460988236670e-03, /* 567 */ + -4.382611365609584e-02, /* 568 */ + 1.807087705742239e-01, /* 569 */ + 9.415787756562223e-01, /* 570 */ + -1.075340311293039e-01, /* 571 */ + 2.436836160025825e-02, /* 572 */ + -1.099196249853307e-03, /* 573 */ + }, { + 5.273880097902076e-03, /* 574 */ + -4.272135592005138e-02, /* 575 */ + 1.761144721226713e-01, /* 576 */ + 9.440426082549445e-01, /* 577 */ + -1.060091954311663e-01, /* 578 */ + 2.406194297919816e-02, /* 579 */ + -1.105431808330667e-03, /* 580 */ + }, { + 5.103631284180478e-03, /* 581 */ + -4.162130081820462e-02, /* 582 */ + 1.715456957695286e-01, /* 583 */ + 9.464564759755407e-01, /* 584 */ + -1.044490873707362e-01, /* 585 */ + 2.374598688057016e-02, /* 586 */ + -1.110771246693530e-03, /* 587 */ + }, { + 4.935729767565705e-03, /* 588 */ + -4.052611704253924e-02, /* 589 */ + 1.670027706491982e-01, /* 590 */ + 9.488201051401287e-01, /* 591 */ + -1.028536006133390e-01, /* 592 */ + 2.342044649412001e-02, /* 593 */ + -1.115183770401296e-03, /* 594 */ + }, { + 4.770189767192131e-03, /* 595 */ + -3.943597067473980e-02, /* 596 */ + 1.624860213113444e-01, /* 597 */ + 9.511332276261677e-01, /* 598 */ + -1.012226330290420e-01, /* 599 */ + 2.308527669788855e-02, /* 600 */ + -1.118638530588553e-03, /* 601 */ + }, { + 4.607024507303565e-03, /* 602 */ + -3.835102517560170e-02, /* 603 */ + 1.579957676901108e-01, /* 604 */ + 9.533955809061508e-01, /* 605 */ + -9.955608672836883e-02, /* 606 */ + 2.274043408398138e-02, /* 607 */ + -1.121104636473070e-03, /* 608 */ + }, { + 4.446246223961389e-03, /* 609 */ + -3.727144137500895e-02, /* 610 */ + 1.535323250741442e-01, /* 611 */ + 9.556069080864779e-01, /* 612 */ + -9.785386809745342e-02, /* 613 */ + 2.238587698415551e-02, /* 614 */ + -1.122551167866203e-03, /* 615 */ + }, { + 4.287866171989037e-03, /* 616 */ + -3.619737746247935e-02, /* 617 */ + 1.490960040774270e-01, /* 618 */ + 9.577669579455091e-01, /* 619 */ + -9.611588783262809e-02, /* 620 */ + 2.202156549521620e-02, /* 621 */ + -1.122947187782295e-03, /* 622 */ + }, { + 4.131894632149082e-03, /* 623 */ + -3.512898897827717e-02, /* 624 */ + 1.446871106109222e-01, /* 625 */ + 9.598754849707929e-01, /* 626 */ + -9.434206097443590e-02, /* 627 */ + 2.164746150421816e-02, /* 628 */ + -1.122261755143747e-03, /* 629 */ + }, { + 3.978340918549750e-03, /* 630 */ + -3.406642880509015e-02, /* 631 */ + 1.403059458550339e-01, /* 632 */ + 9.619322493954562e-01, /* 633 */ + -9.253230694106100e-02, /* 634 */ + 2.126352871346326e-02, /* 635 */ + -1.120463937578314e-03, /* 636 */ + }, { + 3.827213386276986e-03, /* 637 */ + -3.300984716027202e-02, /* 638 */ + 1.359528062328858e-01, /* 639 */ + 9.639370172337561e-01, /* 640 */ + -9.068654956116820e-02, /* 641 */ + 2.086973266528943e-02, /* 642 */ + -1.117522824305172e-03, /* 643 */ + }, { + 3.678519439249078e-03, /* 644 */ + -3.195939158864695e-02, /* 645 */ + 1.316279833844193e-01, /* 646 */ + 9.658895603157857e-01, /* 647 */ + -8.880471710614442e-02, /* 648 */ + 2.046604076664311e-02, /* 649 */ + -1.113407539106230e-03, /* 650 */ + }, { + 3.532265538289867e-03, /* 651 */ + -3.091520695587671e-02, /* 652 */ + 1.273317641413156e-01, /* 653 */ + 9.677896563213259e-01, /* 654 */ + -8.688674232173496e-02, /* 655 */ + 2.005242231342951e-02, /* 656 */ + -1.108087253379183e-03, /* 657 */ + }, { + 3.388457209417186e-03, /* 658 */ + -2.987743544238687e-02, /* 659 */ + 1.230644305027427e-01, /* 660 */ + 9.696370888128401e-01, /* 661 */ + -8.493256245906648e-02, /* 662 */ + 1.962884851463336e-02, /* 663 */ + -1.101531199268716e-03, /* 664 */ + }, { + 3.247099052342608e-03, /* 665 */ + -2.884621653785256e-02, /* 666 */ + 1.188262596119302e-01, /* 667 */ + 9.714316472676046e-01, /* 668 */ + -8.294211930504999e-02, /* 669 */ + 1.919529251620448e-02, /* 670 */ + -1.093708682872210e-03, /* 671 */ + }, { + 3.108194749179169e-03, /* 672 */ + -2.782168703623990e-02, /* 673 */ + 1.146175237335723e-01, /* 674 */ + 9.731731271089683e-01, /* 675 */ + -8.091535921215573e-02, /* 676 */ + 1.875172942470069e-02, /* 677 */ + -1.084589097516370e-03, /* 678 */ + }, { + 2.971747073353185e-03, /* 679 */ + -2.680398103140338e-02, /* 680 */ + 1.104384902320638e-01, /* 681 */ + 9.748613297367402e-01, /* 682 */ + -7.885223312755388e-02, /* 683 */ + 1.829813633068257e-02, /* 684 */ + -1.074141937101013e-03, /* 685 */ + }, { + 2.837757898716223e-03, /* 686 */ + -2.579322991323553e-02, /* 687 */ + 1.062894215505674e-01, /* 688 */ + 9.764960625566944e-01, /* 689 */ + -7.675269662161305e-02, /* 690 */ + 1.783449233185286e-02, /* 691 */ + -1.062336809506350e-03, /* 692 */ + }, { + 2.706228208853840e-03, /* 693 */ + -2.478956236436741e-02, /* 694 */ + 1.021705751909168e-01, /* 695 */ + 9.780771390091908e-01, /* 696 */ + -7.461670991575037e-02, /* 697 */ + 1.736077855593420e-02, /* 698 */ + -1.049143450059986e-03, /* 699 */ + }, { + 2.577158106586916e-03, /* 700 */ + -2.379310435741876e-02, /* 701 */ + 9.808220369435408e-02, /* 702 */ + 9.796043785969055e-01, /* 703 */ + -7.244423790962552e-02, /* 704 */ + 1.687697818327941e-02, /* 705 */ + -1.034531735059858e-03, /* 706 */ + }, { + 2.450546823661987e-03, /* 707 */ + -2.280397915279337e-02, /* 708 */ + 9.402455462310588e-02, /* 709 */ + 9.810776069116667e-01, /* 710 */ + -7.023525020767289e-02, /* 711 */ + 1.638307646920682e-02, /* 712 */ + -1.018471695349289e-03, /* 713 */ + }, { + 2.326392730626404e-03, /* 714 */ + -2.182230729701992e-02, /* 715 */ + 8.999787054279679e-02, /* 716 */ + 9.824966556603907e-01, /* 717 */ + -6.798972114496460e-02, /* 718 */ + 1.587906076605559e-02, /* 719 */ + -1.000933529940309e-03, /* 720 */ + }, { + 2.204693346884742e-03, /* 721 */ + -2.084820662163316e-02, /* 722 */ + 8.600238900570113e-02, /* 723 */ + 9.838613626901140e-01, /* 724 */ + -6.570762981239750e-02, /* 725 */ + 1.536492054495343e-02, /* 726 */ + -9.818876196813738e-04, /* 727 */ + }, { + 2.085445350932246e-03, /* 728 */ + -1.988179224259554e-02, /* 729 */ + 8.203834253483586e-02, /* 730 */ + 9.851715720121177e-01, /* 731 */ + -6.338896008119892e-02, /* 732 */ + 1.484064741729164e-02, /* 733 */ + -9.613045409655687e-04, /* 734 */ + }, { + 1.968644590761548e-03, /* 735 */ + -1.892317656025402e-02, /* 736 */ + 7.810595860889319e-02, /* 737 */ + 9.864271338251378e-01, /* 738 */ + -6.103370062674356e-02, /* 739 */ + 1.430623515590003e-02, /* 740 */ + -9.391550794753652e-04, /* 741 */ + }, { + 1.854286094438386e-03, /* 742 */ + -1.797246925983164e-02, /* 743 */ + 7.420545964801417e-02, /* 744 */ + 9.876279045376605e-01, /* 745 */ + -5.864184495167603e-02, /* 746 */ + 1.376167971591675e-02, /* 747 */ + -9.154102439599487e-04, /* 748 */ + }, { + 1.742364080842615e-03, /* 749 */ + -1.702977731244917e-02, /* 750 */ + 7.033706300040359e-02, /* 751 */ + 9.887737467892981e-01, /* 752 */ + -5.621339140833288e-02, /* 753 */ + 1.320697925534576e-02, /* 754 */ + -8.900412800411573e-04, /* 755 */ + }, { + 1.632871970570320e-03, /* 756 */ + -1.609520497667476e-02, /* 757 */ + 6.650098092978608e-02, /* 758 */ + 9.898645294712379e-01, /* 759 */ + -5.374834322045772e-02, /* 760 */ + 1.264213415529629e-02, /* 761 */ + -8.630196840440353e-04, /* 762 */ + }, { + 1.525802396992804e-03, /* 763 */ + -1.516885380059888e-02, /* 764 */ + 6.269742060370384e-02, /* 765 */ + 9.909001277457663e-01, /* 766 */ + -5.124670850420399e-02, /* 767 */ + 1.206714703989862e-02, /* 768 */ + -8.343172168478687e-04, /* 769 */ + }, { + 1.421147217468663e-03, /* 770 */ + -1.425082262442998e-02, /* 771 */ + 5.892658408265536e-02, /* 772 */ + 9.918804230648608e-01, /* 773 */ + -4.870850028841950e-02, /* 774 */ + 1.148202279588897e-02, /* 775 */ + -8.039059177537709e-04, /* 776 */ + }, { + 1.318897524704578e-03, /* 777 */ + -1.334120758360962e-02, /* 778 */ + 5.518866831007525e-02, /* 779 */ + 9.928053031878483e-01, /* 780 */ + -4.613373653420693e-02, /* 781 */ + 1.088676859185900e-02, /* 782 */ + -7.717581183646440e-04, /* 783 */ + }, { + 1.219043658260877e-03, /* 784 */ + -1.244010211244157e-02, /* 785 */ + 5.148386510315437e-02, /* 786 */ + 9.936746621981263e-01, /* 787 */ + -4.352244015375482e-02, /* 788 */ + 1.028139389716235e-02, /* 789 */ + -7.378464564734607e-04, /* 790 */ + }, { + 1.121575216197463e-03, /* 791 */ + -1.154759694823357e-02, /* 792 */ + 4.781236114450060e-02, /* 793 */ + 9.944884005189432e-01, /* 794 */ + -4.087463902843420e-02, /* 795 */ + 9.665910500473909e-03, /* 796 */ + -7.021438899556850e-04, /* 797 */ + }, { + 1.026481066856231e-03, /* 798 */ + -1.066378013594614e-02, /* 799 */ + 4.417433797463730e-02, /* 800 */ + 9.952464249282380e-01, /* 801 */ + -3.819036602615411e-02, /* 802 */ + 9.040332527994370e-03, /* 803 */ + -6.646237106617679e-04, /* 804 */ + }, { + 9.337493607755644e-04, /* 805 */ + -9.788737033346933e-03, /* 806 */ + 4.056997198534163e-02, /* 807 */ + 9.959486485725322e-01, /* 808 */ + -3.546965901797275e-02, /* 809 */ + 8.404676461295810e-03, /* 810 */ + -6.252595583054893e-04, /* 811 */ + }, { + 8.433675427328470e-04, /* 812 */ + -8.922550316664762e-03, /* 813 */ + 3.699943441381923e-02, /* 814 */ + 9.965949909798753e-01, /* 815 */ + -3.271256089395777e-02, /* 816 */ + 7.758961154800972e-03, /* 817 */ + -5.840254343440151e-04, /* 818 */ + }, { + 7.553223639105668e-04, /* 819 */ + -8.065299986741607e-03, /* 820 */ + 3.346289133771520e-02, /* 821 */ + 9.971853780718406e-01, /* 822 */ + -2.991911957829145e-02, /* 823 */ + 7.103207852892033e-03, /* 824 */ + -5.408957158454127e-04, /* 825 */ + }, { + 6.695998941820378e-04, /* 826 */ + -7.217063375677063e-03, /* 827 */ + 2.996050367095970e-02, /* 828 */ + 9.977197421745680e-01, /* 829 */ + -2.708938804361575e-02, /* 830 */ + 6.437440206642007e-03, /* 831 */ + -4.958451693394876e-04, /* 832 */ + }, { + 5.861855345123588e-04, /* 833 */ + -6.377915153961920e-03, /* 834 */ + 2.649242716044695e-02, /* 835 */ + 9.981980220288543e-01, /* 836 */ + -2.422342432461263e-02, /* 837 */ + 5.761684290163624e-03, /* 838 */ + -4.488489646476928e-04, /* 839 */ + }, { + 5.050640294702417e-04, /* 840 */ + -5.547927338097411e-03, /* 841 */ + 2.305881238354565e-02, /* 842 */ + 9.986201627992864e-01, /* 843 */ + -2.132129153081513e-02, /* 844 */ + 5.075968616570653e-03, /* 845 */ + -3.998826886878056e-04, /* 846 */ + }, { + 4.262194798466813e-04, /* 847 */ + -4.727169298694500e-03, /* 848 */ + 1.965980474643937e-02, /* 849 */ + 9.989861160824184e-01, /* 850 */ + -1.838305785864478e-02, /* 851 */ + 4.380324153544939e-03, /* 852 */ + -3.489223592492433e-04, /* 853 */ + }, { + 3.496353553759901e-04, /* 854 */ + -3.915707769050846e-03, /* 855 */ + 1.629554448329466e-02, /* 856 */ + 9.992958399139888e-01, /* 857 */ + -1.540879660267084e-02, /* 858 */ + 3.674784338505265e-03, /* 859 */ + -2.959444387346577e-04, /* 860 */ + }, { + 2.752945075550529e-04, /* 861 */ + -3.113606854199215e-03, /* 862 */ + 1.296616665625557e-02, /* 863 */ + 9.995492987751797e-01, /* 864 */ + -1.239858616608813e-02, /* 865 */ + 2.959385093371052e-03, /* 866 */ + -2.409258478636185e-04, /* 867 */ + }, { + 2.031791825563283e-04, /* 868 */ + -2.320928040424877e-03, /* 869 */ + 9.671801156260738e-03, /* 870 */ + 9.997464635979135e-01, /* 871 */ + -9.352510070407542e-03, /* 872 */ + 2.234164838917225e-03, /* 873 */ + -1.838439793339892e-04, /* 874 */ + }, { + 1.332710342305255e-04, /* 875 */ + -1.537730205245651e-03, /* 876 */ + 6.412572704682764e-03, /* 877 */ + 9.998873117691878e-01, /* 878 */ + -6.270656964357692e-03, /* 879 */ + 1.499164508713334e-03, /* 880 */ + -1.246767114368607e-04, /* 881 */ + }, { + 6.555113719447324e-05, /* 882 */ + -7.640696278518945e-04, /* 883 */ + 3.188600855785918e-03, /* 884 */ + 9.999718271344488e-01, /* 885 */ + -3.153120631992235e-03, /* 886 */ + 7.544275626434484e-04, /* 887 */ + -6.340242162062585e-05, /* 888 */ + }, { + 1.930201848426478e-18, /* 889 */ + -1.515373497812483e-17, /* 890 */ + 3.164321107994089e-17, /* 891 */ + 1.000000000000000e+00, /* 892 */ + 3.164321107994089e-17, /* 893 */ + -1.515373497812483e-17, /* 894 */ + 1.930201848426478e-18, /* 895 */ + }, { + -6.340242162062585e-05, /* 896 */ + 7.544275626434484e-04, /* 897 */ + -3.153120631992235e-03, /* 898 */ + 9.999718271344488e-01, /* 899 */ + 3.188600855785918e-03, /* 900 */ + -7.640696278518945e-04, /* 901 */ + 6.555113719447324e-05, /* 902 */ + }, { + -1.246767114368607e-04, /* 903 */ + 1.499164508713334e-03, /* 904 */ + -6.270656964357692e-03, /* 905 */ + 9.998873117691878e-01, /* 906 */ + 6.412572704682764e-03, /* 907 */ + -1.537730205245651e-03, /* 908 */ + 1.332710342305255e-04, /* 909 */ + }, { + -1.838439793339892e-04, /* 910 */ + 2.234164838917225e-03, /* 911 */ + -9.352510070407542e-03, /* 912 */ + 9.997464635979135e-01, /* 913 */ + 9.671801156260738e-03, /* 914 */ + -2.320928040424877e-03, /* 915 */ + 2.031791825563283e-04, /* 916 */ + }, { + -2.409258478636185e-04, /* 917 */ + 2.959385093371052e-03, /* 918 */ + -1.239858616608813e-02, /* 919 */ + 9.995492987751797e-01, /* 920 */ + 1.296616665625557e-02, /* 921 */ + -3.113606854199215e-03, /* 922 */ + 2.752945075550529e-04, /* 923 */ + }, { + -2.959444387346577e-04, /* 924 */ + 3.674784338505265e-03, /* 925 */ + -1.540879660267084e-02, /* 926 */ + 9.992958399139888e-01, /* 927 */ + 1.629554448329466e-02, /* 928 */ + -3.915707769050846e-03, /* 929 */ + 3.496353553759901e-04, /* 930 */ + }, { + -3.489223592492433e-04, /* 931 */ + 4.380324153544939e-03, /* 932 */ + -1.838305785864478e-02, /* 933 */ + 9.989861160824184e-01, /* 934 */ + 1.965980474643937e-02, /* 935 */ + -4.727169298694500e-03, /* 936 */ + 4.262194798466813e-04, /* 937 */ + }, { + -3.998826886878056e-04, /* 938 */ + 5.075968616570653e-03, /* 939 */ + -2.132129153081513e-02, /* 940 */ + 9.986201627992864e-01, /* 941 */ + 2.305881238354565e-02, /* 942 */ + -5.547927338097411e-03, /* 943 */ + 5.050640294702417e-04, /* 944 */ + }, { + -4.488489646476928e-04, /* 945 */ + 5.761684290163624e-03, /* 946 */ + -2.422342432461263e-02, /* 947 */ + 9.981980220288543e-01, /* 948 */ + 2.649242716044695e-02, /* 949 */ + -6.377915153961920e-03, /* 950 */ + 5.861855345123588e-04, /* 951 */ + }, { + -4.958451693394876e-04, /* 952 */ + 6.437440206642007e-03, /* 953 */ + -2.708938804361575e-02, /* 954 */ + 9.977197421745680e-01, /* 955 */ + 2.996050367095970e-02, /* 956 */ + -7.217063375677063e-03, /* 957 */ + 6.695998941820378e-04, /* 958 */ + }, { + -5.408957158454127e-04, /* 959 */ + 7.103207852892033e-03, /* 960 */ + -2.991911957829145e-02, /* 961 */ + 9.971853780718406e-01, /* 962 */ + 3.346289133771520e-02, /* 963 */ + -8.065299986741607e-03, /* 964 */ + 7.553223639105668e-04, /* 965 */ + }, { + -5.840254343440151e-04, /* 966 */ + 7.758961154800972e-03, /* 967 */ + -3.271256089395777e-02, /* 968 */ + 9.965949909798753e-01, /* 969 */ + 3.699943441381923e-02, /* 970 */ + -8.922550316664762e-03, /* 971 */ + 8.433675427328470e-04, /* 972 */ + }, { + -6.252595583054893e-04, /* 973 */ + 8.404676461295810e-03, /* 974 */ + -3.546965901797275e-02, /* 975 */ + 9.959486485725322e-01, /* 976 */ + 4.056997198534163e-02, /* 977 */ + -9.788737033346933e-03, /* 978 */ + 9.337493607755644e-04, /* 979 */ + }, { + -6.646237106617679e-04, /* 980 */ + 9.040332527994370e-03, /* 981 */ + -3.819036602615411e-02, /* 982 */ + 9.952464249282380e-01, /* 983 */ + 4.417433797463730e-02, /* 984 */ + -1.066378013594614e-02, /* 985 */ + 1.026481066856231e-03, /* 986 */ + }, { + -7.021438899556850e-04, /* 987 */ + 9.665910500473909e-03, /* 988 */ + -4.087463902843420e-02, /* 989 */ + 9.944884005189432e-01, /* 990 */ + 4.781236114450060e-02, /* 991 */ + -1.154759694823357e-02, /* 992 */ + 1.121575216197463e-03, /* 993 */ + }, { + -7.378464564734607e-04, /* 994 */ + 1.028139389716235e-02, /* 995 */ + -4.352244015375482e-02, /* 996 */ + 9.936746621981263e-01, /* 997 */ + 5.148386510315437e-02, /* 998 */ + -1.244010211244157e-02, /* 999 */ + 1.219043658260877e-03, /* 1000 */ + }, { + -7.717581183646440e-04, /* 1001 */ + 1.088676859185900e-02, /* 1002 */ + -4.613373653420693e-02, /* 1003 */ + 9.928053031878483e-01, /* 1004 */ + 5.518866831007525e-02, /* 1005 */ + -1.334120758360962e-02, /* 1006 */ + 1.318897524704578e-03, /* 1007 */ + }, { + -8.039059177537709e-04, /* 1008 */ + 1.148202279588897e-02, /* 1009 */ + -4.870850028841950e-02, /* 1010 */ + 9.918804230648608e-01, /* 1011 */ + 5.892658408265536e-02, /* 1012 */ + -1.425082262442998e-02, /* 1013 */ + 1.421147217468663e-03, /* 1014 */ + }, { + -8.343172168478687e-04, /* 1015 */ + 1.206714703989862e-02, /* 1016 */ + -5.124670850420399e-02, /* 1017 */ + 9.909001277457663e-01, /* 1018 */ + 6.269742060370384e-02, /* 1019 */ + -1.516885380059888e-02, /* 1020 */ + 1.525802396992804e-03, /* 1021 */ + }, { + -8.630196840440353e-04, /* 1022 */ + 1.264213415529629e-02, /* 1023 */ + -5.374834322045772e-02, /* 1024 */ + 9.898645294712379e-01, /* 1025 */ + 6.650098092978608e-02, /* 1026 */ + -1.609520497667476e-02, /* 1027 */ + 1.632871970570320e-03, /* 1028 */ + }, { + -8.900412800411573e-04, /* 1029 */ + 1.320697925534576e-02, /* 1030 */ + -5.621339140833288e-02, /* 1031 */ + 9.887737467892981e-01, /* 1032 */ + 7.033706300040359e-02, /* 1033 */ + -1.702977731244917e-02, /* 1034 */ + 1.742364080842615e-03, /* 1035 */ + }, { + -9.154102439599487e-04, /* 1036 */ + 1.376167971591675e-02, /* 1037 */ + -5.864184495167603e-02, /* 1038 */ + 9.876279045376605e-01, /* 1039 */ + 7.420545964801417e-02, /* 1040 */ + -1.797246925983164e-02, /* 1041 */ + 1.854286094438386e-03, /* 1042 */ + }, { + -9.391550794753652e-04, /* 1043 */ + 1.430623515590003e-02, /* 1044 */ + -6.103370062674356e-02, /* 1045 */ + 9.864271338251378e-01, /* 1046 */ + 7.810595860889319e-02, /* 1047 */ + -1.892317656025402e-02, /* 1048 */ + 1.968644590761548e-03, /* 1049 */ + }, { + -9.613045409655687e-04, /* 1050 */ + 1.484064741729164e-02, /* 1051 */ + -6.338896008119892e-02, /* 1052 */ + 9.851715720121177e-01, /* 1053 */ + 8.203834253483586e-02, /* 1054 */ + -1.988179224259554e-02, /* 1055 */ + 2.085445350932246e-03, /* 1056 */ + }, { + -9.818876196813738e-04, /* 1057 */ + 1.536492054495343e-02, /* 1058 */ + -6.570762981239750e-02, /* 1059 */ + 9.838613626901140e-01, /* 1060 */ + 8.600238900570113e-02, /* 1061 */ + -2.084820662163316e-02, /* 1062 */ + 2.204693346884742e-03, /* 1063 */ + }, { + -1.000933529940309e-03, /* 1064 */ + 1.587906076605559e-02, /* 1065 */ + -6.798972114496460e-02, /* 1066 */ + 9.824966556603907e-01, /* 1067 */ + 8.999787054279679e-02, /* 1068 */ + -2.182230729701992e-02, /* 1069 */ + 2.326392730626404e-03, /* 1070 */ + }, { + -1.018471695349289e-03, /* 1071 */ + 1.638307646920682e-02, /* 1072 */ + -7.023525020767289e-02, /* 1073 */ + 9.810776069116667e-01, /* 1074 */ + 9.402455462310588e-02, /* 1075 */ + -2.280397915279337e-02, /* 1076 */ + 2.450546823661987e-03, /* 1077 */ + }, { + -1.034531735059858e-03, /* 1078 */ + 1.687697818327941e-02, /* 1079 */ + -7.244423790962552e-02, /* 1080 */ + 9.796043785969055e-01, /* 1081 */ + 9.808220369435408e-02, /* 1082 */ + -2.379310435741876e-02, /* 1083 */ + 2.577158106586916e-03, /* 1084 */ + }, { + -1.049143450059986e-03, /* 1085 */ + 1.736077855593420e-02, /* 1086 */ + -7.461670991575037e-02, /* 1087 */ + 9.780771390091908e-01, /* 1088 */ + 1.021705751909168e-01, /* 1089 */ + -2.478956236436741e-02, /* 1090 */ + 2.706228208853840e-03, /* 1091 */ + }, { + -1.062336809506350e-03, /* 1092 */ + 1.783449233185286e-02, /* 1093 */ + -7.675269662161305e-02, /* 1094 */ + 9.764960625566944e-01, /* 1095 */ + 1.062894215505674e-01, /* 1096 */ + -2.579322991323553e-02, /* 1097 */ + 2.837757898716223e-03, /* 1098 */ + }, { + -1.074141937101013e-03, /* 1099 */ + 1.829813633068257e-02, /* 1100 */ + -7.885223312755388e-02, /* 1101 */ + 9.748613297367402e-01, /* 1102 */ + 1.104384902320638e-01, /* 1103 */ + -2.680398103140338e-02, /* 1104 */ + 2.971747073353185e-03, /* 1105 */ + }, { + -1.084589097516370e-03, /* 1106 */ + 1.875172942470069e-02, /* 1107 */ + -8.091535921215573e-02, /* 1108 */ + 9.731731271089683e-01, /* 1109 */ + 1.146175237335723e-01, /* 1110 */ + -2.782168703623990e-02, /* 1111 */ + 3.108194749179169e-03, /* 1112 */ + }, { + -1.093708682872210e-03, /* 1113 */ + 1.919529251620448e-02, /* 1114 */ + -8.294211930504999e-02, /* 1115 */ + 9.714316472676046e-01, /* 1116 */ + 1.188262596119302e-01, /* 1117 */ + -2.884621653785256e-02, /* 1118 */ + 3.247099052342608e-03, /* 1119 */ + }, { + -1.101531199268716e-03, /* 1120 */ + 1.962884851463336e-02, /* 1121 */ + -8.493256245906648e-02, /* 1122 */ + 9.696370888128401e-01, /* 1123 */ + 1.230644305027427e-01, /* 1124 */ + -2.987743544238687e-02, /* 1125 */ + 3.388457209417186e-03, /* 1126 */ + }, { + -1.108087253379183e-03, /* 1127 */ + 2.005242231342951e-02, /* 1128 */ + -8.688674232173496e-02, /* 1129 */ + 9.677896563213259e-01, /* 1130 */ + 1.273317641413156e-01, /* 1131 */ + -3.091520695587671e-02, /* 1132 */ + 3.532265538289867e-03, /* 1133 */ + }, { + -1.113407539106230e-03, /* 1134 */ + 2.046604076664311e-02, /* 1135 */ + -8.880471710614442e-02, /* 1136 */ + 9.658895603157857e-01, /* 1137 */ + 1.316279833844193e-01, /* 1138 */ + -3.195939158864695e-02, /* 1139 */ + 3.678519439249078e-03, /* 1140 */ + }, { + -1.117522824305172e-03, /* 1141 */ + 2.086973266528943e-02, /* 1142 */ + -9.068654956116820e-02, /* 1143 */ + 9.639370172337561e-01, /* 1144 */ + 1.359528062328858e-01, /* 1145 */ + -3.300984716027202e-02, /* 1146 */ + 3.827213386276986e-03, /* 1147 */ + }, { + -1.120463937578314e-03, /* 1148 */ + 2.126352871346326e-02, /* 1149 */ + -9.253230694106100e-02, /* 1150 */ + 9.619322493954562e-01, /* 1151 */ + 1.403059458550339e-01, /* 1152 */ + -3.406642880509015e-02, /* 1153 */ + 3.978340918549750e-03, /* 1154 */ + }, { + -1.122261755143747e-03, /* 1155 */ + 2.164746150421816e-02, /* 1156 */ + -9.434206097443590e-02, /* 1157 */ + 9.598754849707929e-01, /* 1158 */ + 1.446871106109222e-01, /* 1159 */ + -3.512898897827717e-02, /* 1160 */ + 4.131894632149082e-03, /* 1161 */ + }, { + -1.122947187782295e-03, /* 1162 */ + 2.202156549521620e-02, /* 1163 */ + -9.611588783262809e-02, /* 1164 */ + 9.577669579455091e-01, /* 1165 */ + 1.490960040774270e-01, /* 1166 */ + -3.619737746247935e-02, /* 1167 */ + 4.287866171989037e-03, /* 1168 */ + }, { + -1.122551167866203e-03, /* 1169 */ + 2.238587698415551e-02, /* 1170 */ + -9.785386809745342e-02, /* 1171 */ + 9.556069080864779e-01, /* 1172 */ + 1.535323250741442e-01, /* 1173 */ + -3.727144137500895e-02, /* 1174 */ + 4.446246223961389e-03, /* 1175 */ + }, { + -1.121104636473070e-03, /* 1176 */ + 2.274043408398138e-02, /* 1177 */ + -9.955608672836883e-02, /* 1178 */ + 9.533955809061508e-01, /* 1179 */ + 1.579957676901108e-01, /* 1180 */ + -3.835102517560170e-02, /* 1181 */ + 4.607024507303565e-03, /* 1182 */ + }, { + -1.118638530588553e-03, /* 1183 */ + 2.308527669788855e-02, /* 1184 */ + -1.012226330290420e-01, /* 1185 */ + 9.511332276261677e-01, /* 1186 */ + 1.624860213113444e-01, /* 1187 */ + -3.943597067473980e-02, /* 1188 */ + 4.770189767192131e-03, /* 1189 */ + }, { + -1.115183770401296e-03, /* 1190 */ + 2.342044649412001e-02, /* 1191 */ + -1.028536006133390e-01, /* 1192 */ + 9.488201051401287e-01, /* 1193 */ + 1.670027706491982e-01, /* 1194 */ + -4.052611704253924e-02, /* 1195 */ + 4.935729767565705e-03, /* 1196 */ + }, { + -1.110771246693530e-03, /* 1197 */ + 2.374598688057016e-02, /* 1198 */ + -1.044490873707362e-01, /* 1199 */ + 9.464564759755407e-01, /* 1200 */ + 1.715456957695286e-01, /* 1201 */ + -4.162130081820462e-02, /* 1202 */ + 5.103631284180478e-03, /* 1203 */ + }, { + -1.105431808330667e-03, /* 1204 */ + 2.406194297919816e-02, /* 1205 */ + -1.060091954311663e-01, /* 1206 */ + 9.440426082549445e-01, /* 1207 */ + 1.761144721226713e-01, /* 1208 */ + -4.272135592005138e-02, /* 1209 */ + 5.273880097902076e-03, /* 1210 */ + }, { + -1.099196249853307e-03, /* 1211 */ + 2.436836160025825e-02, /* 1212 */ + -1.075340311293039e-01, /* 1213 */ + 9.415787756562223e-01, /* 1214 */ + 1.807087705742239e-01, /* 1215 */ + -4.382611365609584e-02, /* 1216 */ + 5.446460988236670e-03, /* 1217 */ + }, { + -1.092095299174878e-03, /* 1218 */ + 2.466529121635406e-02, /* 1219 */ + -1.090237049683018e-01, /* 1220 */ + 9.390652573721028e-01, /* 1221 */ + 1.853282574366316e-01, /* 1222 */ + -4.493540273521546e-02, /* 1223 */ + 5.621357727104864e-03, /* 1224 */ + }, { + -1.084159605388224e-03, /* 1225 */ + 2.495278193632289e-02, /* 1226 */ + -1.104783315829824e-01, /* 1227 */ + 9.365023380688623e-01, /* 1228 */ + 1.899725945015709e-01, /* 1229 */ + -4.604904927887769e-02, /* 1230 */ + 5.798553072861818e-03, /* 1231 */ + }, { + -1.075419726684316e-03, /* 1232 */ + 2.523088547895752e-02, /* 1233 */ + -1.118980297024964e-01, /* 1234 */ + 9.338903078442332e-01, /* 1235 */ + 1.946414390731309e-01, /* 1236 */ + -4.716687683344033e-02, /* 1237 */ + 5.978028764566336e-03, /* 1238 */ + }, { + -1.065906118386268e-03, /* 1239 */ + 2.549965514657118e-02, /* 1240 */ + -1.132829221124536e-01, /* 1241 */ + 9.312294621845263e-01, /* 1242 */ + 1.993344440017834e-01, /* 1243 */ + -4.828870638302148e-02, /* 1244 */ + 6.159765516502505e-03, /* 1245 */ + }, { + -1.055649121101780e-03, /* 1246 */ + 2.575914579841332e-02, /* 1247 */ + -1.146331356165368e-01, /* 1248 */ + 9.285201019209733e-01, /* 1249 */ + 2.040512577191446e-01, /* 1250 */ + -4.941435636294156e-02, /* 1251 */ + 6.343743012956645e-03, /* 1252 */ + }, { + -1.044678948997064e-03, /* 1253 */ + 2.600941382394202e-02, /* 1254 */ + -1.159488009976052e-01, /* 1255 */ + 9.257625331852983e-01, /* 1256 */ + 2.087915242735169e-01, /* 1257 */ + -5.054364267373540e-02, /* 1258 */ + 6.529939903253073e-03, /* 1259 */ + }, { + -1.033025678195301e-03, /* 1260 */ + 2.625051711596052e-02, /* 1261 */ + -1.172300529782974e-01, /* 1262 */ + 9.229570673645258e-01, /* 1263 */ + 2.135548833662130e-01, /* 1264 */ + -5.167637869573667e-02, /* 1265 */ + 6.718333797051150e-03, /* 1266 */ + }, { + -1.020719235302619e-03, /* 1267 */ + 2.648251504362370e-02, /* 1268 */ + -1.184770301811412e-01, /* 1269 */ + 9.201040210550299e-01, /* 1270 */ + 2.183409703886530e-01, /* 1271 */ + -5.281237530423245e-02, /* 1272 */ + 6.908901259906972e-03, /* 1273 */ + }, { + -1.007789386064493e-03, /* 1274 */ + 2.670546842532187e-02, /* 1275 */ + -1.196898750881803e-01, /* 1276 */ + 9.172037160158394e-01, /* 1277 */ + 2.231494164602341e-01, /* 1278 */ + -5.395144088518943e-02, /* 1279 */ + 7.101617809102288e-03, /* 1280 */ + }, { + -9.942657241554641e-04, /* 1281 */ + 2.691943950144822e-02, /* 1282 */ + -1.208687340001249e-01, /* 1283 */ + 9.142564791211991e-01, /* 1284 */ + 2.279798484669641e-01, /* 1285 */ + -5.509338135155091e-02, /* 1286 */ + 7.296457909743918e-03, /* 1287 */ + }, { + -9.801776601050557e-04, /* 1288 */ + 2.712449190705641e-02, /* 1289 */ + -1.220137569950366e-01, /* 1290 */ + 9.112626423123993e-01, /* 1291 */ + 2.328318891008579e-01, /* 1292 */ + -5.623800016010321e-02, /* 1293 */ + 7.493394971135955e-03, /* 1294 */ + }, { + -9.655544103626029e-04, /* 1295 */ + 2.732069064441559e-02, /* 1296 */ + -1.231250978865554e-01, /* 1297 */ + 9.082225425488828e-01, /* 1298 */ + 2.377051569000891e-01, /* 1299 */ + -5.738509832891316e-02, /* 1300 */ + 7.692401343427691e-03, /* 1301 */ + }, { + -9.504249865037702e-04, /* 1302 */ + 2.750810205546858e-02, /* 1303 */ + -1.242029141816779e-01, /* 1304 */ + 9.051365217586359e-01, /* 1305 */ + 2.425992662898926e-01, /* 1306 */ + -5.853447445533320e-02, /* 1307 */ + 7.893448314540197e-03, /* 1308 */ + }, { + -9.348181845814541e-04, /* 1309 */ + 2.768679379420070e-02, /* 1310 */ + -1.252473670380959e-01, /* 1311 */ + 9.020049267878701e-01, /* 1312 */ + 2.475138276242130e-01, /* 1313 */ + -5.968592473457642e-02, /* 1314 */ + 8.096506107373698e-03, /* 1315 */ + }, { + -9.187625746236699e-04, /* 1316 */ + 2.785683479892532e-02, /* 1317 */ + -1.262586212211042e-01, /* 1318 */ + 8.988281093500092e-01, /* 1319 */ + 2.524484472280946e-01, /* 1320 */ + -6.083924297885757e-02, /* 1321 */ + 8.301543877298635e-03, /* 1322 */ + }, { + -9.022864902810273e-04, /* 1323 */ + 2.801829526449300e-02, /* 1324 */ + -1.272368450600864e-01, /* 1325 */ + 8.956064259739822e-01, /* 1326 */ + 2.574027274408040e-01, /* 1327 */ + -6.199422063710185e-02, /* 1328 */ + 8.508529709932666e-03, /* 1329 */ + }, { + -8.854180186263388e-04, /* 1330 */ + 2.817124661443064e-02, /* 1331 */ + -1.281822104045887e-01, /* 1332 */ + 8.923402379518393e-01, /* 1333 */ + 2.623762666596838e-01, /* 1334 */ + -6.315064681521791e-02, /* 1335 */ + 8.717430619206452e-03, /* 1336 */ + }, { + -8.681849901087664e-04, /* 1337 */ + 2.831576147301751e-02, /* 1338 */ + -1.290948925799897e-01, /* 1339 */ + 8.890299112856920e-01, /* 1340 */ + 2.673686593847286e-01, /* 1341 */ + -6.430830829693616e-02, /* 1342 */ + 8.928212545720045e-03, /* 1343 */ + }, { + -8.506149686650558e-04, /* 1344 */ + 2.845191363730427e-02, /* 1345 */ + -1.299750703427760e-01, /* 1346 */ + 8.856758166339943e-01, /* 1347 */ + 2.723794962638790e-01, /* 1348 */ + -6.546698956520861e-02, /* 1349 */ + 9.140840355392437e-03, /* 1350 */ + }, { + -8.327352419900550e-04, /* 1351 */ + 2.857977804908198e-02, /* 1352 */ + -1.308229258354336e-01, /* 1353 */ + 8.822783292571661e-01, /* 1354 */ + 2.774083641390264e-01, /* 1355 */ + -6.662647282417096e-02, /* 1356 */ + 9.355277838406877e-03, /* 1357 */ + }, { + -8.145728119690237e-04, /* 1358 */ + 2.869943076680737e-02, /* 1359 */ + -1.316386445409620e-01, /* 1360 */ + 8.788378289625756e-01, /* 1361 */ + 2.824548460927230e-01, /* 1362 */ + -6.778653802166430e-02, /* 1363 */ + 9.571487708453395e-03, /* 1364 */ + }, { + -7.961543852738185e-04, /* 1365 */ + 2.881094893749082e-02, /* 1366 */ + -1.324224152370240e-01, /* 1367 */ + 8.753547000488832e-01, /* 1368 */ + 2.875185214955909e-01, /* 1369 */ + -6.894696287231404e-02, /* 1370 */ + 9.789431602271271e-03, /* 1371 */ + }, { + -7.775063641252938e-04, /* 1372 */ + 2.891441076855387e-02, /* 1373 */ + -1.331744299497369e-01, /* 1374 */ + 8.718293312497631e-01, /* 1375 */ + 2.925989660544231e-01, /* 1376 */ + -7.010752288116626e-02, /* 1377 */ + 1.000907007949311e-02, /* 1378 */ + }, { + -7.586548372239626e-04, /* 1379 */ + 2.900989549966232e-02, /* 1380 */ + -1.338948839071171e-01, /* 1381 */ + 8.682621156770067e-01, /* 1382 */ + 2.976957518609703e-01, /* 1383 */ + -7.126799136787691e-02, /* 1384 */ + 1.023036262279292e-02, /* 1385 */ + }, { + -7.396255708510786e-04, /* 1386 */ + 2.909748337454149e-02, /* 1387 */ + -1.345839754921861e-01, /* 1388 */ + 8.646534507630197e-01, /* 1389 */ + 3.028084474414053e-01, /* 1390 */ + -7.242813949145467e-02, /* 1391 */ + 1.045326763833955e-02, /* 1392 */ + }, { + -7.204440001421441e-04, /* 1393 */ + 2.917725561278015e-02, /* 1394 */ + -1.352419061957486e-01, /* 1395 */ + 8.610037382027232e-01, /* 1396 */ + 3.079366178064618e-01, /* 1397 */ + -7.358773627555218e-02, /* 1398 */ + 1.067774245655789e-02, /* 1399 */ + }, { + -7.011352205348302e-04, /* 1400 */ + 2.924929438162952e-02, /* 1401 */ + -1.358688805688508e-01, /* 1402 */ + 8.573133838948686e-01, /* 1403 */ + 3.130798245022357e-01, /* 1404 */ + -7.474654863430663e-02, /* 1405 */ + 1.090374333319907e-02, /* 1406 */ + }, { + -6.817239793932266e-04, /* 1407 */ + 2.931368276780341e-02, /* 1408 */ + -1.364651061749298e-01, /* 1409 */ + 8.535827978827749e-01, /* 1410 */ + 3.182376256616457e-01, /* 1411 */ + -7.590434139872407e-02, /* 1412 */ + 1.113122545072202e-02, /* 1413 */ + }, { + -6.622346678102776e-04, /* 1414 */ + 2.937050474928606e-02, /* 1415 */ + -1.370307935416629e-01, /* 1416 */ + 8.498123942944995e-01, /* 1417 */ + 3.234095760565429e-01, /* 1418 */ + -7.706087734360774e-02, /* 1419 */ + 1.136014291998739e-02, /* 1420 */ + }, { + -6.426913125902382e-04, /* 1421 */ + 2.941984516715388e-02, /* 1422 */ + -1.375661561125266e-01, /* 1423 */ + 8.460025912824529e-01, /* 1424 */ + 3.285952271504655e-01, /* 1425 */ + -7.821591721502538e-02, /* 1426 */ + 1.159044878226562e-02, /* 1427 */ + }, { + -6.231175684128511e-04, /* 1428 */ + 2.946178969741759e-02, /* 1429 */ + -1.380714101980755e-01, /* 1430 */ + 8.421538109624669e-01, /* 1431 */ + 3.337941271520264e-01, /* 1432 */ + -7.936921975831460e-02, /* 1433 */ + 1.182209501156100e-02, /* 1434 */ + }, { + -6.035367101809801e-04, /* 1435 */ + 2.949642482289040e-02, /* 1436 */ + -1.385467749269494e-01, /* 1437 */ + 8.382664793523271e-01, /* 1438 */ + 3.390058210689310e-01, /* 1439 */ + -8.052054174662254e-02, /* 1440 */ + 1.205503251725260e-02, /* 1441 */ + }, { + -5.839716255532901e-04, /* 1442 */ + 2.952383780508903e-02, /* 1443 */ + -1.389924721966199e-01, /* 1444 */ + 8.343410263097801e-01, /* 1445 */ + 3.442298507626138e-01, /* 1446 */ + -8.166963800997633e-02, /* 1447 */ + 1.228921114705370e-02, /* 1448 */ + }, { + -5.644448076635753e-04, /* 1449 */ + 2.954411665617338e-02, /* 1450 */ + -1.394087266238854e-01, /* 1451 */ + 8.303778854700258e-01, /* 1452 */ + 3.494657550034874e-01, /* 1453 */ + -8.281626146488272e-02, /* 1454 */ + 1.252457969029095e-02, /* 1455 */ + }, { + -5.449783480282249e-04, /* 1456 */ + 2.955735011093100e-02, /* 1457 */ + -1.397957654951237e-01, /* 1458 */ + 8.263774941827043e-01, /* 1459 */ + 3.547130695267950e-01, /* 1460 */ + -8.396016314445182e-02, /* 1461 */ + 1.276108588150466e-02, /* 1462 */ + }, { + -5.255939296432825e-04, /* 1463 */ + 2.956362759881255e-02, /* 1464 */ + -1.401538187163136e-01, /* 1465 */ + 8.223402934483920e-01, /* 1466 */ + 3.599713270890607e-01, /* 1467 */ + -8.510109222904338e-02, /* 1468 */ + 1.299867640437090e-02, /* 1469 */ + }, { + -5.063128202724816e-04, /* 1470 */ + 2.956303921602418e-02, /* 1471 */ + -1.404831187628331e-01, /* 1472 */ + 8.182667278546104e-01, /* 1473 */ + 3.652400575251253e-01, /* 1474 */ + -8.623879607743025e-02, /* 1475 */ + 1.323729689594689e-02, /* 1476 */ + }, { + -4.871558659276469e-04, /* 1477 */ + 2.955567569768274e-02, /* 1478 */ + -1.407839006290460e-01, /* 1479 */ + 8.141572455113678e-01, /* 1480 */ + 3.705187878057628e-01, /* 1481 */ + -8.737302025847754e-02, /* 1482 */ + 1.347689195124034e-02, /* 1483 */ + }, { + -4.681434845426153e-04, /* 1484 */ + 2.954162839003991e-02, /* 1485 */ + -1.410564017776856e-01, /* 1486 */ + 8.100122979862356e-01, /* 1487 */ + 3.758070420958670e-01, /* 1488 */ + -8.850350858333141e-02, /* 1489 */ + 1.371740512810407e-02, /* 1490 */ + }, { + -4.492956598419907e-04, /* 1491 */ + 2.952098922278122e-02, /* 1492 */ + -1.413008620890449e-01, /* 1493 */ + 8.058323402389781e-01, /* 1494 */ + 3.811043418132007e-01, /* 1495 */ + -8.963000313811628e-02, /* 1496 */ + 1.395877895245621e-02, /* 1497 */ + }, { + -4.306319354058826e-04, /* 1498 */ + 2.949385068140553e-02, /* 1499 */ + -1.415175238099844e-01, /* 1500 */ + 8.016178305557399e-01, /* 1501 */ + 3.864102056876984e-01, /* 1502 */ + -9.075224431713386e-02, /* 1503 */ + 1.420095492382687e-02, /* 1504 */ + }, { + -4.121714089315939e-04, /* 1505 */ + 2.946030577969092e-02, /* 1506 */ + -1.417066315027663e-01, /* 1507 */ + 7.973692304828067e-01, /* 1508 */ + 3.917241498213130e-01, /* 1509 */ + -9.186997085656183e-02, /* 1510 */ + 1.444387352123256e-02, /* 1511 */ + }, { + -3.939327266934505e-04, /* 1512 */ + 2.942044803225294e-02, /* 1513 */ + -1.418684319937252e-01, /* 1514 */ + 7.930870047599514e-01, /* 1515 */ + 3.970456877483993e-01, /* 1516 */ + -9.298291986864780e-02, /* 1517 */ + 1.468747420937781e-02, /* 1518 */ + }, { + -3.759340782016456e-04, /* 1519 */ + 2.937437142720069e-02, /* 1520 */ + -1.420031743217853e-01, /* 1521 */ + 7.887716212533704e-01, /* 1522 */ + 4.023743304966226e-01, /* 1523 */ + -9.409082687639277e-02, /* 1524 */ + 1.493169544518567e-02, /* 1525 */ + }, { + -3.581931910609877e-04, /* 1526 */ + 2.932217039889637e-02, /* 1527 */ + -1.421111096868331e-01, /* 1528 */ + 7.844235508882289e-01, /* 1529 */ + 4.077095866483865e-01, /* 1530 */ + -9.519342584872197e-02, /* 1531 */ + 1.517647468465644e-02, /* 1532 */ + }, { + -3.407273260304884e-04, /* 1533 */ + 2.926393980082415e-02, /* 1534 */ + -1.421924913979572e-01, /* 1535 */ + 7.800432675808223e-01, /* 1536 */ + 4.130509624027673e-01, /* 1537 */ + -9.629044923613632e-02, /* 1538 */ + 1.542174839005620e-02, /* 1539 */ + }, { + -3.235532722844501e-04, /* 1540 */ + 2.919977487857369e-02, /* 1541 */ + -1.422475748215626e-01, /* 1542 */ + 7.756312481703652e-01, /* 1543 */ + 4.183979616379481e-01, /* 1544 */ + -9.738162800684201e-02, /* 1545 */ + 1.566745203743416e-02, /* 1546 */ + }, { + -3.066873428758958e-04, /* 1547 */ + 2.912977124294393e-02, /* 1548 */ + -1.422766173293711e-01, /* 1549 */ + 7.711879723504229e-01, /* 1550 */ + 4.237500859741424e-01, /* 1551 */ + -9.846669168335127e-02, /* 1552 */ + 1.591352012446994e-02, /* 1553 */ + }, { + -2.901453704029424e-04, /* 1554 */ + 2.905402484317260e-02, /* 1555 */ + -1.422798782463173e-01, /* 1556 */ + 7.667139225999916e-01, /* 1557 */ + 4.291068348369969e-01, /* 1558 */ + -9.954536837955191e-02, /* 1559 */ + 1.615988617865028e-02, /* 1560 */ + }, { + -2.739427028786713e-04, /* 1561 */ + 2.897263194029685e-02, /* 1562 */ + -1.422576187983489e-01, /* 1563 */ + 7.622095841142430e-01, /* 1564 */ + 4.344677055214644e-01, /* 1565 */ + -1.006173848382378e-01, /* 1566 */ + 1.640648276577594e-02, /* 1567 */ + }, { + -2.580941998051696e-04, /* 1568 */ + 2.888568908065016e-02, /* 1569 */ + -1.422101020601427e-01, /* 1570 */ + 7.576754447349440e-01, /* 1571 */ + 4.398321932561375e-01, /* 1572 */ + -1.016824664690990e-01, /* 1573 */ + 1.665324149879781e-02, /* 1574 */ + }, { + -2.426142284520608e-04, /* 1575 */ + 2.879329306950119e-02, /* 1576 */ + -1.421375929027446e-01, /* 1577 */ + 7.531119948805626e-01, /* 1578 */ + 4.451997912680325e-01, /* 1579 */ + -1.027403373871614e-01, /* 1580 */ + 1.690009304698279e-02, /* 1581 */ + }, { + -2.275166603400509e-04, /* 1582 */ + 2.869554094483946e-02, /* 1583 */ + -1.420403579411441e-01, /* 1584 */ + 7.485197274760710e-01, /* 1585 */ + 4.505699908478140e-01, /* 1586 */ + -1.037907204516760e-01, /* 1587 */ + 1.714696714540915e-02, /* 1588 */ + }, { + -2.128148679298167e-04, /* 1589 */ + 2.859252995131316e-02, /* 1590 */ + -1.419186654817930e-01, /* 1591 */ + 7.438991378824603e-01, /* 1592 */ + 4.559422814154492e-01, /* 1593 */ + -1.048333373054486e-01, /* 1594 */ + 1.739379260479069e-02, /* 1595 */ + }, { + -1.985217215164836e-04, /* 1596 */ + 2.848435751432410e-02, /* 1597 */ + -1.417727854700776e-01, /* 1598 */ + 7.392507238259753e-01, /* 1599 */ + 4.613161505862837e-01, /* 1600 */ + -1.058679084146052e-01, /* 1601 */ + 1.764049732162978e-02, /* 1602 */ + }, { + -1.846495863300355e-04, /* 1603 */ + 2.837112121428517e-02, /* 1604 */ + -1.416029894377547e-01, /* 1605 */ + 7.345749853270843e-01, /* 1606 */ + 4.666910842375266e-01, /* 1607 */ + -1.068941531087891e-01, /* 1608 */ + 1.788700828869835e-02, /* 1609 */ + }, { + -1.712103198416544e-04, /* 1610 */ + 2.825291876104482e-02, /* 1611 */ + -1.414095504503600e-01, /* 1612 */ + 7.298724246291924e-01, /* 1613 */ + 4.720665665751356e-01, /* 1614 */ + -1.079117896217818e-01, /* 1615 */ + 1.813325160584695e-02, /* 1616 */ + }, { + -1.582152692763085e-04, /* 1617 */ + 2.812984796848375e-02, /* 1618 */ + -1.411927430545998e-01, /* 1619 */ + 7.251435461271148e-01, /* 1620 */ + 4.774420802010910e-01, /* 1621 */ + -1.089205351325436e-01, /* 1622 */ + 1.837915249114045e-02, /* 1623 */ + }, { + -1.456752693314445e-04, /* 1624 */ + 2.800200672928881e-02, /* 1625 */ + -1.409528432257348e-01, /* 1626 */ + 7.203888562953171e-01, /* 1627 */ + 4.828171061810496e-01, /* 1628 */ + -1.099201058066671e-01, /* 1629 */ + 1.862463529232039e-02, /* 1630 */ + }, { + -1.336006401019428e-04, /* 1631 */ + 2.786949298990845e-02, /* 1632 */ + -1.406901283149657e-01, /* 1633 */ + 7.156088636159380e-01, /* 1634 */ + 4.881911241123659e-01, /* 1635 */ + -1.109102168382377e-01, /* 1636 */ + 1.886962349859249e-02, /* 1637 */ + }, { + -1.220011852110915e-04, /* 1638 */ + 2.773240472569498e-02, /* 1639 */ + -1.404048769968304e-01, /* 1640 */ + 7.108040785066047e-01, /* 1641 */ + 4.935636121924722e-01, /* 1642 */ + -1.118905824920952e-01, /* 1643 */ + 1.911403975273935e-02, /* 1644 */ + }, { + -1.108861901476344e-04, /* 1645 */ + 2.759083991623796e-02, /* 1646 */ + -1.400973692166212e-01, /* 1647 */ + 7.059750132480542e-01, /* 1648 */ + 4.989340472876037e-01, /* 1649 */ + -1.128609161464899e-01, /* 1650 */ + 1.935780586355643e-02, /* 1651 */ + }, { + -1.002644208085391e-04, /* 1652 */ + 2.744489652089330e-02, /* 1653 */ + -1.397678861378340e-01, /* 1654 */ + 7.011221819115717e-01, /* 1655 */ + 5.043019050018616e-01, /* 1656 */ + -1.138209303361282e-01, /* 1657 */ + 1.960084281861067e-02, /* 1658 */ + }, { + -9.014412224732896e-05, /* 1659 */ + 2.729467245451285e-02, /* 1660 */ + -1.394167100896560e-01, /* 1661 */ + 6.962461002862578e-01, /* 1662 */ + 5.096666597466010e-01, /* 1663 */ + -1.147703367955984e-01, /* 1664 */ + 1.984307079732106e-02, /* 1665 */ + }, { + -8.053301762762107e-05, /* 1666 */ + 2.714026556337870e-02, /* 1667 */ + -1.390441245145027e-01, /* 1668 */ + 6.913472858061384e-01, /* 1669 */ + 5.150277848101327e-01, /* 1670 */ + -1.157088465031742e-01, /* 1671 */ + 2.008440918435909e-02, /* 1672 */ + }, { + -7.143830738156712e-05, /* 1673 */ + 2.698177360134680e-02, /* 1674 */ + -1.386504139156142e-01, /* 1675 */ + 6.864262574771270e-01, /* 1676 */ + 5.203847524277286e-01, /* 1677 */ + -1.166361697249849e-01, /* 1678 */ + 2.032477658336845e-02, /* 1679 */ + }, { + -6.286666857267136e-05, /* 1680 */ + 2.681929420620386e-02, /* 1681 */ + -1.382358638047184e-01, /* 1682 */ + 6.814835358038533e-01, /* 1683 */ + 5.257370338519197e-01, /* 1684 */ + -1.175520160595501e-01, /* 1685 */ + 2.056409083100217e-02, /* 1686 */ + }, { + -5.482425446254296e-05, /* 1687 */ + 2.665292487624222e-02, /* 1688 */ + -1.378007606497726e-01, /* 1689 */ + 6.765196427163698e-01, /* 1690 */ + 5.310840994230748e-01, /* 1691 */ + -1.184560944826678e-01, /* 1692 */ + 2.080226901127631e-02, /* 1693 */ + }, { + -4.731669428110093e-05, /* 1694 */ + 2.648276294705649e-02, /* 1695 */ + -1.373453918227895e-01, /* 1696 */ + 6.715351014967476e-01, /* 1697 */ + 5.364254186402488e-01, /* 1698 */ + -1.193481133926526e-01, /* 1699 */ + 2.103922747023789e-02, /* 1700 */ + }, { + -4.034909319948082e-05, /* 1701 */ + 2.630890556856650e-02, /* 1702 */ + -1.368700455477614e-01, /* 1703 */ + 6.665304367055752e-01, /* 1704 */ + 5.417604602322907e-01, /* 1705 */ + -1.202277806559141e-01, /* 1706 */ + 2.127488183094605e-02, /* 1707 */ + }, { + -3.392603250514114e-05, /* 1708 */ + 2.613144968226988e-02, /* 1709 */ + -1.363750108486864e-01, /* 1710 */ + 6.615061741083712e-01, /* 1711 */ + 5.470886922291980e-01, /* 1712 */ + -1.210948036528713e-01, /* 1713 */ + 2.150914700876424e-02, /* 1714 */ + }, { + -2.805156997831240e-05, /* 1715 */ + 2.595049199872917e-02, /* 1716 */ + -1.358605774977103e-01, /* 1717 */ + 6.564628406019232e-01, /* 1718 */ + 5.524095820337069e-01, /* 1719 */ + -1.219488893241928e-01, /* 1720 */ + 2.174193722696236e-02, /* 1721 */ + }, { + -2.272924046911682e-05, /* 1722 */ + 2.576612897529643e-02, /* 1723 */ + -1.353270359633906e-01, /* 1724 */ + 6.514009641405650e-01, /* 1725 */ + 5.577225964931086e-01, /* 1726 */ + -1.227897442173580e-01, /* 1727 */ + 2.197316603262602e-02, /* 1728 */ + }, { + -1.796205667443242e-05, /* 1729 */ + 2.557845679407980e-02, /* 1730 */ + -1.347746773590917e-01, /* 1731 */ + 6.463210736624057e-01, /* 1732 */ + 5.630272019712755e-01, /* 1733 */ + -1.236170745335310e-01, /* 1734 */ + 2.220274631287172e-02, /* 1735 */ + }, { + -1.375251011368080e-05, /* 1736 */ + 2.538757134015553e-02, /* 1737 */ + -1.342037933915221e-01, /* 1738 */ + 6.412236990155202e-01, /* 1739 */ + 5.683228644208926e-01, /* 1740 */ + -1.244305861747393e-01, /* 1741 */ + 2.243059031136537e-02, /* 1742 */ + }, { + -1.010257230258042e-05, /* 1743 */ + 2.519356818002896e-02, /* 1744 */ + -1.336146763094198e-01, /* 1745 */ + 6.361093708841161e-01, /* 1746 */ + 5.736090494558752e-01, /* 1747 */ + -1.252299847913509e-01, /* 1748 */ + 2.265660964514263e-02, /* 1749 */ + }, { + -7.013696123785544e-06, /* 1750 */ + 2.499654254034837e-02, /* 1751 */ + -1.330076188523975e-01, /* 1752 */ + 6.309786207146856e-01, /* 1753 */ + 5.788852224239674e-01, /* 1754 */ + -1.260149758298408e-01, /* 1755 */ + 2.288071532172811e-02, /* 1756 */ + }, { + -4.486817393493708e-06, /* 1757 */ + 2.479658928687493e-02, /* 1758 */ + -1.323829141999531e-01, /* 1759 */ + 6.258319806421593e-01, /* 1760 */ + 5.841508484795060e-01, /* 1761 */ + -1.267852645808413e-01, /* 1762 */ + 2.310281775655174e-02, /* 1763 */ + }, { + -2.522356622734990e-06, /* 1764 */ + 2.459380290371239e-02, /* 1765 */ + -1.317408559206581e-01, /* 1766 */ + 6.206699834160688e-01, /* 1767 */ + 5.894053926563386e-01, /* 1768 */ + -1.275405562274653e-01, /* 1769 */ + 2.332282679065955e-02, /* 1770 */ + }, { + -1.120220972351802e-06, /* 1771 */ + 2.438827747279956e-02, /* 1772 */ + -1.310817379215285e-01, /* 1773 */ + 6.154931623267351e-01, /* 1774 */ + 5.946483199408858e-01, /* 1775 */ + -1.282805558938982e-01, /* 1776 */ + 2.354065170871666e-02, /* 1777 */ + }, { + -2.798064002790454e-07, /* 1778 */ + 2.418010665366927e-02, /* 1779 */ + -1.304058543975903e-01, /* 1780 */ + 6.103020511314905e-01, /* 1781 */ + 5.998790953453343e-01, /* 1782 */ + -1.290049686942476e-01, /* 1783 */ + 2.375620125729980e-02, /* 1784 */ + }, { + -0.000000000000000e+00, /* 1785 */ + 2.396938366347660e-02, /* 1786 */ + -1.297134997816453e-01, /* 1787 */ + 6.050971839809485e-01, /* 1788 */ + 6.050971839809485e-01, /* 1789 */ + -1.297134997816453e-01, /* 1790 */ + 2.396938366347660e-02, /* 1791 */ + } +}; + diff --git a/libs/fluidsynth/glib.c b/libs/fluidsynth/glib.c new file mode 100644 index 00000000000..cd938a4fb6b --- /dev/null +++ b/libs/fluidsynth/glib.c @@ -0,0 +1,106 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <glib.h> + +int g_vsnprintf( char *buffer, size_t size, const char *format, va_list args ) +{ + int ret = _vsnprintf( buffer, size - 1, format, args ); + if (ret >= 0 && ret < size) buffer[ret] = 0; + else buffer[0] = 0; + return ret; +} + +int g_snprintf( char *buffer, size_t size, const char *format, ... ) +{ + va_list args; + int ret; + + va_start( args, format ); + ret = g_vsnprintf( buffer, size, format, args ); + va_end( args ); + + return ret; +} + +double g_get_monotonic_time(void) +{ + static LARGE_INTEGER frequency = {0}; + LARGE_INTEGER counter; + + if (!frequency.QuadPart) QueryPerformanceFrequency( &frequency ); + QueryPerformanceCounter( &counter ); + + return counter.QuadPart * 1000000.0 / frequency.QuadPart; /* time in micros */ +} + +void g_usleep( unsigned int micros ) +{ + Sleep( micros / 1000 ); +} + +static DWORD CALLBACK g_thread_wrapper( void *args ) +{ + GThread *thread = args; + gpointer ret = thread->func( thread->data ); + if (!InterlockedDecrement( &thread->ref )) free( thread ); + return (UINT_PTR)ret; +} + +GThread *g_thread_try_new( const char *name, GThreadFunc func, gpointer data, GError **err ) +{ + GThread *thread; + + if (!(thread = calloc( 1, sizeof(*thread) ))) return NULL; + thread->ref = 2; + thread->func = func; + thread->data = data; + + if (!(thread->handle = CreateThread( NULL, 0, g_thread_wrapper, thread, 0, NULL ))) + { + free( thread ); + return NULL; + } + + return thread; +} + +void g_thread_unref( GThread *thread ) +{ + CloseHandle( thread->handle ); + if (!InterlockedDecrement( &thread->ref )) free( thread ); +} + +void g_thread_join( GThread *thread ) +{ + WaitForSingleObject( thread->handle, INFINITE ); + g_thread_unref( thread ); +} + +void g_clear_error( GError **error ) +{ + *error = NULL; +} + +int g_file_test( const char *path, int test ) +{ + DWORD attrs = GetFileAttributesA( path ); + if (test == G_FILE_TEST_EXISTS) return attrs != INVALID_FILE_ATTRIBUTES; + if (test == G_FILE_TEST_IS_REGULAR) return attrs == FILE_ATTRIBUTE_NORMAL; + return 0; +} diff --git a/libs/fluidsynth/glib.h b/libs/fluidsynth/glib.h new file mode 100644 index 00000000000..722173d825a --- /dev/null +++ b/libs/fluidsynth/glib.h @@ -0,0 +1,107 @@ +/* + * Copyright 2023 Rémi Bernon for CodeWeavers + * + * 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 Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stddef.h> +#include <stdarg.h> +#include <stdio.h> + +#include <windef.h> +#include <winbase.h> +#include <winsock.h> + +#include <stdint.h> +#include <stdlib.h> +#include <math.h> + +#define GLIB_CHECK_VERSION(x, y, z) ((x) < GLIB_MAJOR_VERSION || ((x) == GLIB_MAJOR_VERSION && (y) <= GLIB_MINOR_VERSION)) +#define GLIB_MAJOR_VERSION 2 +#define GLIB_MINOR_VERSION 54 + +#define G_BYTE_ORDER 1234 +#define G_BIG_ENDIAN 4321 +#define GINT32_FROM_LE(val) ((INT32)(val)) +#define GINT16_FROM_LE(val) ((INT16)(val)) + +#define G_UNLIKELY(expr) (expr) +#define G_LIKELY(expr) (expr) + +#define g_return_val_if_fail( cond, val ) do { if (!(cond)) return (val); } while (0) + +typedef void *gpointer; +typedef gpointer (*GThreadFunc)( gpointer data ); + +typedef struct _stat64 GStatBuf; +#define g_stat _stat64 + +typedef struct +{ + const char *message; +} GError; + +typedef struct +{ + LONG ref; + HANDLE handle; + GThreadFunc func; + gpointer data; +} GThread; + +extern int g_vsnprintf( char *buffer, size_t size, const char *format, va_list args ) __WINE_CRT_PRINTF_ATTR(3, 0); +extern int g_snprintf( char *buffer, size_t size, const char *format, ... ) __WINE_CRT_PRINTF_ATTR(3, 4); + +extern double g_get_monotonic_time(void); +extern void g_usleep( unsigned int micros ); + +extern GThread *g_thread_try_new( const char *name, GThreadFunc func, gpointer data, GError **err ); +extern void g_thread_unref( GThread *thread ); +extern void g_thread_join( GThread *thread ); +extern void g_clear_error( GError **error ); + +#define G_FILE_TEST_EXISTS 1 +#define G_FILE_TEST_IS_REGULAR 2 + +extern int g_file_test( const char *path, int test ); + +#define g_new( type, count ) calloc( (count), sizeof(type) ) +static void g_free( void *ptr ) { free( ptr ); } + +typedef SRWLOCK GMutex; +static void g_mutex_init( GMutex *mutex ) {} +static void g_mutex_clear( GMutex *mutex ) {} +static void g_mutex_lock( GMutex *mutex ) { AcquireSRWLockExclusive( mutex ); } +static void g_mutex_unlock( GMutex *mutex ) { ReleaseSRWLockExclusive( mutex ); } + +typedef CRITICAL_SECTION GRecMutex; +static void g_rec_mutex_init( GRecMutex *mutex ) { InitializeCriticalSection( mutex ); } +static void g_rec_mutex_clear( GRecMutex *mutex ) { DeleteCriticalSection( mutex ); } +static void g_rec_mutex_lock( GRecMutex *mutex ) { EnterCriticalSection( mutex ); } +static void g_rec_mutex_unlock( GRecMutex *mutex ) { LeaveCriticalSection( mutex ); } + +typedef CONDITION_VARIABLE GCond; +static void g_cond_init( GCond *cond ) {} +static void g_cond_clear( GCond *cond ) {} +static void g_cond_signal( GCond *cond ) { WakeConditionVariable( cond ); } +static void g_cond_broadcast( GCond *cond ) { WakeAllConditionVariable( cond ); } +static void g_cond_wait( GCond *cond, GMutex *mutex ) { SleepConditionVariableSRW( cond, mutex, INFINITE, 0 ); } + +static void g_atomic_int_inc( int *ptr ) { InterlockedIncrement( (LONG *)ptr ); } +static int g_atomic_int_add( int *ptr, int val ) { return InterlockedAdd( (LONG *)ptr, val ) - 1; } +static int g_atomic_int_get( int *ptr ) { return ReadAcquire( (LONG *)ptr ); } +static void g_atomic_int_set( int *ptr, int val ) { InterlockedExchange( (LONG *)ptr, val ); } +static int g_atomic_int_dec_and_test( int *ptr, int val ) { return !InterlockedAdd( (LONG *)ptr, -val ); } +static int g_atomic_int_compare_and_exchange( int *ptr, int cmp, int val ) { return InterlockedCompareExchange( (LONG *)ptr, val, cmp ) == cmp; } diff --git a/libs/fluidsynth/glib/gstdio.h b/libs/fluidsynth/glib/gstdio.h new file mode 100644 index 00000000000..e69de29bb2d diff --git a/libs/fluidsynth/include/fluidsynth.h b/libs/fluidsynth/include/fluidsynth.h new file mode 100644 index 00000000000..ffe901a78a1 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth.h @@ -0,0 +1,121 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_H +#define _FLUIDSYNTH_H + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUILD_SHARED_LIBS 0 + +#if (BUILD_SHARED_LIBS == 0) + #define FLUIDSYNTH_API // building static lib? no visibility control then +#elif defined(WIN32) + #if defined(FLUIDSYNTH_NOT_A_DLL) + #define FLUIDSYNTH_API + #elif defined(FLUIDSYNTH_DLL_EXPORTS) + #define FLUIDSYNTH_API __declspec(dllexport) + #else + #define FLUIDSYNTH_API __declspec(dllimport) + #endif + +#elif defined(MACOS9) +#define FLUIDSYNTH_API __declspec(export) + +#elif defined(__OS2__) +#define FLUIDSYNTH_API __declspec(dllexport) + +#elif defined(__GNUC__) +#define FLUIDSYNTH_API __attribute__ ((visibility ("default"))) + +#else +#define FLUIDSYNTH_API + +#endif + +#if defined(__GNUC__) || defined(__clang__) +# define FLUID_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) && _MSC_VER > 1200 +# define FLUID_DEPRECATED __declspec(deprecated) +#else +# define FLUID_DEPRECATED +#endif + + +/** + * @file fluidsynth.h + * @brief FluidSynth is a real-time synthesizer designed for SoundFont(R) files. + * + * This is the header of the fluidsynth library and contains the + * synthesizer's public API. + * + * Depending on how you want to use or extend the synthesizer you + * will need different API functions. You probably do not need all + * of them. Here is what you might want to do: + * + * - Embedded synthesizer: create a new synthesizer and send MIDI + * events to it. The sound goes directly to the audio output of + * your system. + * + * - Plugin synthesizer: create a synthesizer and send MIDI events + * but pull the audio back into your application. + * + * - SoundFont plugin: create a new type of "SoundFont" and allow + * the synthesizer to load your type of SoundFonts. + * + * - MIDI input: Create a MIDI handler to read the MIDI input on your + * machine and send the MIDI events directly to the synthesizer. + * + * - MIDI files: Open MIDI files and send the MIDI events to the + * synthesizer. + * + * - Command lines: You can send textual commands to the synthesizer. + * + * SoundFont(R) is a registered trademark of E-mu Systems, Inc. + */ + +#include "fluidsynth/types.h" +#include "fluidsynth/settings.h" +#include "fluidsynth/synth.h" +#include "fluidsynth/shell.h" +#include "fluidsynth/sfont.h" +#include "fluidsynth/audio.h" +#include "fluidsynth/event.h" +#include "fluidsynth/midi.h" +#include "fluidsynth/seq.h" +#include "fluidsynth/seqbind.h" +#include "fluidsynth/log.h" +#include "fluidsynth/misc.h" +#include "fluidsynth/mod.h" +#include "fluidsynth/gen.h" +#include "fluidsynth/voice.h" +#include "fluidsynth/version.h" +#include "fluidsynth/ladspa.h" + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_H */ diff --git a/libs/fluidsynth/include/fluidsynth/audio.h b/libs/fluidsynth/include/fluidsynth/audio.h new file mode 100644 index 00000000000..1c12ff792cc --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/audio.h @@ -0,0 +1,155 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_AUDIO_H +#define _FLUIDSYNTH_AUDIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 + * used if custom audio processing is desired before the audio is sent to the + * audio driver (although it is not as efficient). + * + * @sa @ref CreatingAudioDriver + * + * @{ + */ + +/** + * Callback function type used with new_fluid_audio_driver2() to allow for + * 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. + * @param fx Array of buffers to store effects audio to. Buffers may alias with buffers of \c out. + * @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). + * The buffers have already been zeroed-out. + * For further details please refer to fluid_synth_process(). + * + * @parblock + * @note Whereas fluid_synth_process() allows aliasing buffers, there is the guarantee 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 accidentally to the same buffer multiple times. + * @endparblock + * + * @parblock + * @note Also note that the Jack driver is currently the only driver that has dedicated @p fx buffers + * (but only if \setting{audio_jack_multi} is true). All other drivers do not provide @p fx buffers. + * In this case, users are encouraged to mix the effects into the provided dry buffers when calling + * fluid_synth_process(). + * @code{.cpp} +int myCallback(void *, int len, int nfx, float *fx[], int nout, float *out[]) +{ + int ret; + if(nfx == 0) + { + float *fxb[4] = {out[0], out[1], out[0], out[1]}; + ret = fluid_synth_process(synth, len, sizeof(fxb) / sizeof(fxb[0]), fxb, nout, out); + } + else + { + ret = fluid_synth_process(synth, len, nfx, fx, nout, out); + } + // ... client-code ... + return ret; +} + * @endcode + * For other possible use-cases refer to \ref fluidsynth_process.c . + * @endparblock + */ +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); + +FLUIDSYNTH_API fluid_audio_driver_t *new_fluid_audio_driver2(fluid_settings_t *settings, + fluid_audio_func_t func, + 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); +/** @} */ + +/** + * @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. + * + * + * @{ + */ + +/** @startlifecycle{File Renderer} */ +FLUIDSYNTH_API fluid_file_renderer_t *new_fluid_file_renderer(fluid_synth_t *synth); +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); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_AUDIO_H */ diff --git a/libs/fluidsynth/include/fluidsynth/event.h b/libs/fluidsynth/include/fluidsynth/event.h new file mode 100644 index 00000000000..dbc29c7e8aa --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/event.h @@ -0,0 +1,143 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_EVENT_H +#define _FLUIDSYNTH_EVENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup sequencer_events Sequencer Events + * @ingroup sequencer + * + * Create, modify, query and destroy sequencer events. + * + * @{ + */ + +/** + * Sequencer event type enumeration. + */ +enum fluid_seq_event_type +{ + FLUID_SEQ_NOTE = 0, /**< Note event with duration */ + FLUID_SEQ_NOTEON, /**< Note on event */ + FLUID_SEQ_NOTEOFF, /**< Note off event */ + FLUID_SEQ_ALLSOUNDSOFF, /**< All sounds off event */ + FLUID_SEQ_ALLNOTESOFF, /**< All notes off event */ + FLUID_SEQ_BANKSELECT, /**< Bank select message */ + FLUID_SEQ_PROGRAMCHANGE, /**< Program change message */ + FLUID_SEQ_PROGRAMSELECT, /**< Program select message */ + FLUID_SEQ_PITCHBEND, /**< Pitch bend message */ + FLUID_SEQ_PITCHWHEELSENS, /**< Pitch wheel sensitivity set message @since 1.1.0 was misspelled previously */ + FLUID_SEQ_MODULATION, /**< Modulation controller event */ + FLUID_SEQ_SUSTAIN, /**< Sustain controller event */ + FLUID_SEQ_CONTROLCHANGE, /**< MIDI control change event */ + FLUID_SEQ_PAN, /**< Stereo pan set event */ + FLUID_SEQ_VOLUME, /**< Volume set event */ + FLUID_SEQ_REVERBSEND, /**< Reverb send set event */ + FLUID_SEQ_CHORUSSEND, /**< Chorus send set event */ + FLUID_SEQ_TIMER, /**< Timer event (useful for giving a callback at a certain time) */ + FLUID_SEQ_CHANNELPRESSURE, /**< Channel aftertouch event @since 1.1.0 */ + 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 */ + FLUID_SEQ_SCALE, /**< Sets a new time scale for the sequencer @since 2.2.0 */ + 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 */ +/** @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); +FLUIDSYNTH_API void fluid_event_set_dest(fluid_event_t *evt, fluid_seq_id_t dest); + +/* Timer events */ +FLUIDSYNTH_API void fluid_event_timer(fluid_event_t *evt, void *data); + +/* Note events */ +FLUIDSYNTH_API void fluid_event_note(fluid_event_t *evt, int channel, + short key, short vel, + unsigned int duration); + +FLUIDSYNTH_API void fluid_event_noteon(fluid_event_t *evt, int channel, short key, short vel); +FLUIDSYNTH_API void fluid_event_noteoff(fluid_event_t *evt, int channel, short key); +FLUIDSYNTH_API void fluid_event_all_sounds_off(fluid_event_t *evt, int channel); +FLUIDSYNTH_API void fluid_event_all_notes_off(fluid_event_t *evt, int channel); + +/* Instrument selection */ +FLUIDSYNTH_API void fluid_event_bank_select(fluid_event_t *evt, int channel, short bank_num); +FLUIDSYNTH_API void fluid_event_program_change(fluid_event_t *evt, int channel, int preset_num); +FLUIDSYNTH_API void fluid_event_program_select(fluid_event_t *evt, int channel, unsigned int sfont_id, short bank_num, short preset_num); + +/* Real-time generic instrument controllers */ +FLUIDSYNTH_API +void fluid_event_control_change(fluid_event_t *evt, int channel, short control, int val); + +/* Real-time instrument controllers shortcuts */ +FLUIDSYNTH_API void fluid_event_pitch_bend(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_pitch_wheelsens(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_modulation(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_sustain(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_pan(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_volume(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_reverb_send(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_chorus_send(fluid_event_t *evt, int channel, int val); + +FLUIDSYNTH_API void fluid_event_key_pressure(fluid_event_t *evt, int channel, short key, int val); +FLUIDSYNTH_API void fluid_event_channel_pressure(fluid_event_t *evt, int channel, int val); +FLUIDSYNTH_API void fluid_event_system_reset(fluid_event_t *evt); + +/* Only when unregistering clients */ +FLUIDSYNTH_API void fluid_event_unregistering(fluid_event_t *evt); + +FLUIDSYNTH_API void fluid_event_scale(fluid_event_t *evt, double new_scale); +FLUIDSYNTH_API int fluid_event_from_midi_event(fluid_event_t *, const fluid_midi_event_t *); + +/* Accessing event data */ +FLUIDSYNTH_API int fluid_event_get_type(fluid_event_t *evt); +FLUIDSYNTH_API fluid_seq_id_t fluid_event_get_source(fluid_event_t *evt); +FLUIDSYNTH_API fluid_seq_id_t fluid_event_get_dest(fluid_event_t *evt); +FLUIDSYNTH_API int fluid_event_get_channel(fluid_event_t *evt); +FLUIDSYNTH_API short fluid_event_get_key(fluid_event_t *evt); +FLUIDSYNTH_API short fluid_event_get_velocity(fluid_event_t *evt); +FLUIDSYNTH_API short fluid_event_get_control(fluid_event_t *evt); +FLUIDSYNTH_API int fluid_event_get_value(fluid_event_t *evt); +FLUIDSYNTH_API int fluid_event_get_program(fluid_event_t *evt); +FLUIDSYNTH_API void *fluid_event_get_data(fluid_event_t *evt); +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 double fluid_event_get_scale(fluid_event_t *evt); +FLUIDSYNTH_API unsigned int fluid_event_get_sfont_id(fluid_event_t *evt); +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif /* _FLUIDSYNTH_EVENT_H */ diff --git a/libs/fluidsynth/include/fluidsynth/gen.h b/libs/fluidsynth/include/fluidsynth/gen.h new file mode 100644 index 00000000000..a04c36294a3 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/gen.h @@ -0,0 +1,134 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_GEN_H +#define _FLUIDSYNTH_GEN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup generators SoundFont Generators + * @ingroup soundfonts + * + * Functions and defines for SoundFont generator effects. + * + * @{ + */ + +/** + * Generator (effect) numbers (Soundfont 2.01 specifications section 8.1.3) + */ +enum fluid_gen_type +{ + GEN_STARTADDROFS, /**< Sample start address offset (0-32767) */ + GEN_ENDADDROFS, /**< Sample end address offset (-32767-0) */ + GEN_STARTLOOPADDROFS, /**< Sample loop start address offset (-32767-32767) */ + GEN_ENDLOOPADDROFS, /**< Sample loop end address offset (-32767-32767) */ + GEN_STARTADDRCOARSEOFS, /**< Sample start address coarse offset (X 32768) */ + GEN_MODLFOTOPITCH, /**< Modulation LFO to pitch */ + GEN_VIBLFOTOPITCH, /**< Vibrato LFO to pitch */ + GEN_MODENVTOPITCH, /**< Modulation envelope to pitch */ + GEN_FILTERFC, /**< Filter cutoff */ + GEN_FILTERQ, /**< Filter Q */ + GEN_MODLFOTOFILTERFC, /**< Modulation LFO to filter cutoff */ + GEN_MODENVTOFILTERFC, /**< Modulation envelope to filter cutoff */ + GEN_ENDADDRCOARSEOFS, /**< Sample end address coarse offset (X 32768) */ + GEN_MODLFOTOVOL, /**< Modulation LFO to volume */ + GEN_UNUSED1, /**< Unused */ + GEN_CHORUSSEND, /**< Chorus send amount */ + GEN_REVERBSEND, /**< Reverb send amount */ + GEN_PAN, /**< Stereo panning */ + GEN_UNUSED2, /**< Unused */ + GEN_UNUSED3, /**< Unused */ + GEN_UNUSED4, /**< Unused */ + GEN_MODLFODELAY, /**< Modulation LFO delay */ + GEN_MODLFOFREQ, /**< Modulation LFO frequency */ + GEN_VIBLFODELAY, /**< Vibrato LFO delay */ + GEN_VIBLFOFREQ, /**< Vibrato LFO frequency */ + GEN_MODENVDELAY, /**< Modulation envelope delay */ + GEN_MODENVATTACK, /**< Modulation envelope attack */ + GEN_MODENVHOLD, /**< Modulation envelope hold */ + GEN_MODENVDECAY, /**< Modulation envelope decay */ + GEN_MODENVSUSTAIN, /**< Modulation envelope sustain */ + GEN_MODENVRELEASE, /**< Modulation envelope release */ + GEN_KEYTOMODENVHOLD, /**< Key to modulation envelope hold */ + GEN_KEYTOMODENVDECAY, /**< Key to modulation envelope decay */ + GEN_VOLENVDELAY, /**< Volume envelope delay */ + GEN_VOLENVATTACK, /**< Volume envelope attack */ + GEN_VOLENVHOLD, /**< Volume envelope hold */ + GEN_VOLENVDECAY, /**< Volume envelope decay */ + GEN_VOLENVSUSTAIN, /**< Volume envelope sustain */ + GEN_VOLENVRELEASE, /**< Volume envelope release */ + GEN_KEYTOVOLENVHOLD, /**< Key to volume envelope hold */ + GEN_KEYTOVOLENVDECAY, /**< Key to volume envelope decay */ + GEN_INSTRUMENT, /**< Instrument ID (shouldn't be set by user) */ + GEN_RESERVED1, /**< Reserved */ + GEN_KEYRANGE, /**< MIDI note range */ + GEN_VELRANGE, /**< MIDI velocity range */ + GEN_STARTLOOPADDRCOARSEOFS, /**< Sample start loop address coarse offset (X 32768) */ + GEN_KEYNUM, /**< Fixed MIDI note number */ + GEN_VELOCITY, /**< Fixed MIDI velocity value */ + GEN_ATTENUATION, /**< Initial volume attenuation */ + GEN_RESERVED2, /**< Reserved */ + GEN_ENDLOOPADDRCOARSEOFS, /**< Sample end loop address coarse offset (X 32768) */ + GEN_COARSETUNE, /**< Coarse tuning */ + GEN_FINETUNE, /**< Fine tuning */ + GEN_SAMPLEID, /**< Sample ID (shouldn't be set by user) */ + GEN_SAMPLEMODE, /**< Sample mode flags */ + GEN_RESERVED3, /**< Reserved */ + GEN_SCALETUNE, /**< Scale tuning */ + GEN_EXCLUSIVECLASS, /**< Exclusive class number */ + GEN_OVERRIDEROOTKEY, /**< Sample root note override */ + + /** + * Initial Pitch + * + * @note This is not "standard" SoundFont generator, because it is not + * mentioned in the list of generators in the SF2 specifications. + * It is used by FluidSynth internally to compute the nominal pitch of + * a note on note-on event. By nature it shouldn't be allowed to be modulated, + * however the specification defines a default modulator having "Initial Pitch" + * as destination (cf. SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch). + * Thus it is impossible to cancel this default modulator, which would be required + * to let the MIDI Pitch Wheel controller modulate a different generator. + * In order to provide this flexibility, FluidSynth >= 2.1.0 uses a default modulator + * "Pitch Wheel to Fine Tune", rather than Initial Pitch. The same "compromise" can + * be found on the Audigy 2 ZS for instance. + */ + GEN_PITCH, + + GEN_CUSTOM_BALANCE, /**< Balance @note Not a real SoundFont generator */ + /* non-standard generator for an additional custom high- or low-pass filter */ + GEN_CUSTOM_FILTERFC, /**< Custom filter cutoff frequency */ + GEN_CUSTOM_FILTERQ, /**< Custom filter Q */ + + 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 +} +#endif +#endif /* _FLUIDSYNTH_GEN_H */ + diff --git a/libs/fluidsynth/include/fluidsynth/ladspa.h b/libs/fluidsynth/include/fluidsynth/ladspa.h new file mode 100644 index 00000000000..ae44f2b6d4b --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/ladspa.h @@ -0,0 +1,69 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_LADSPA_H +#define _FLUIDSYNTH_LADSPA_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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. + * 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); +FLUIDSYNTH_API int fluid_ladspa_reset(fluid_ladspa_fx_t *fx); +FLUIDSYNTH_API int fluid_ladspa_check(fluid_ladspa_fx_t *fx, char *err, int err_size); + +FLUIDSYNTH_API int fluid_ladspa_host_port_exists(fluid_ladspa_fx_t *fx, const char *name); + +FLUIDSYNTH_API int fluid_ladspa_add_buffer(fluid_ladspa_fx_t *fx, const char *name); +FLUIDSYNTH_API int fluid_ladspa_buffer_exists(fluid_ladspa_fx_t *fx, const char *name); + +FLUIDSYNTH_API int fluid_ladspa_add_effect(fluid_ladspa_fx_t *fx, const char *effect_name, + const char *lib_name, const char *plugin_name); +FLUIDSYNTH_API int fluid_ladspa_effect_can_mix(fluid_ladspa_fx_t *fx, const char *name); +FLUIDSYNTH_API int fluid_ladspa_effect_set_mix(fluid_ladspa_fx_t *fx, const char *name, int mix, float gain); +FLUIDSYNTH_API int fluid_ladspa_effect_port_exists(fluid_ladspa_fx_t *fx, const char *effect_name, const char *port_name); +FLUIDSYNTH_API int fluid_ladspa_effect_set_control(fluid_ladspa_fx_t *fx, const char *effect_name, + 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 +} +#endif + +#endif /* _FLUIDSYNTH_LADSPA_H */ + diff --git a/libs/fluidsynth/include/fluidsynth/log.h b/libs/fluidsynth/include/fluidsynth/log.h new file mode 100644 index 00000000000..ab5911e011c --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/log.h @@ -0,0 +1,97 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_LOG_H +#define _FLUIDSYNTH_LOG_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup logging Logging + * + * 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: + * + * @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); + * @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. + * + * @{ + */ + +/** + * FluidSynth log levels. + */ +enum fluid_log_level +{ + FLUID_PANIC, /**< The synth can't function correctly any more */ + FLUID_ERR, /**< Serious error occurred */ + FLUID_WARN, /**< Warning */ + FLUID_INFO, /**< Verbose informational messages */ + FLUID_DBG, /**< Debugging messages */ + 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(). + */ +typedef void (*fluid_log_function_t)(int level, const char *message, void *data); + +FLUIDSYNTH_API +fluid_log_function_t fluid_set_log_function(int level, fluid_log_function_t fun, void *data); + +FLUIDSYNTH_API void fluid_default_log_function(int level, const char *message, void *data); + +FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...) +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +__attribute__ ((format (printf, 2, 3))) +#endif +; +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_LOG_H */ diff --git a/libs/fluidsynth/include/fluidsynth/midi.h b/libs/fluidsynth/include/fluidsynth/midi.h new file mode 100644 index 00000000000..044eeebd9a7 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/midi.h @@ -0,0 +1,295 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_MIDI_H +#define _FLUIDSYNTH_MIDI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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); + +/** + * Generic callback function fired once by MIDI tick change. + * + * @param data User defined data pointer + * @param tick The current (zero-based) tick, which triggered the callback + * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * This callback is fired at a constant rate depending on the current BPM and PPQ. + * e.g. for PPQ = 192 and BPM = 140 the callback is fired 192 * 140 times per minute (448/sec). + * + * It can be used to sync external elements with the beat, + * or stop / loop the song on a given tick. + * Ticks being BPM-dependent, you can manipulate values such as bars or beats, + * without having to care about BPM. + * + * For example, this callback loops the song whenever it reaches the 5th bar : + * + * @code{.cpp} +int handle_tick(void *data, int tick) +{ + fluid_player_t *player = (fluid_player_t *)data; + int ppq = 192; // From MIDI header + int beatsPerBar = 4; // From the song's time signature + int loopBar = 5; + int loopTick = (loopBar - 1) * ppq * beatsPerBar; + + if (tick == loopTick) + { + return fluid_player_seek(player, 0); + } + + return FLUID_OK; +} + * @endcode + */ +typedef int (*handle_midi_tick_func_t)(void *data, int tick); +/** @} */ + +/** + * @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. + * + * @{ + */ +/** @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(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_channel(fluid_midi_event_t *evt, int chan); +FLUIDSYNTH_API int fluid_midi_event_get_channel(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_get_key(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_key(fluid_midi_event_t *evt, int key); +FLUIDSYNTH_API int fluid_midi_event_get_velocity(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_velocity(fluid_midi_event_t *evt, int vel); +FLUIDSYNTH_API int fluid_midi_event_get_control(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_control(fluid_midi_event_t *evt, int ctrl); +FLUIDSYNTH_API int fluid_midi_event_get_value(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_value(fluid_midi_event_t *evt, int val); +FLUIDSYNTH_API int fluid_midi_event_get_program(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_program(fluid_midi_event_t *evt, int val); +FLUIDSYNTH_API int fluid_midi_event_get_pitch(const fluid_midi_event_t *evt); +FLUIDSYNTH_API int fluid_midi_event_set_pitch(fluid_midi_event_t *evt, int val); +FLUIDSYNTH_API int fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, + int size, int dynamic); +FLUIDSYNTH_API int fluid_midi_event_set_text(fluid_midi_event_t *evt, + void *data, int size, int dynamic); +FLUIDSYNTH_API int fluid_midi_event_get_text(fluid_midi_event_t *evt, + void **data, int *size); +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 transformation and filtering of MIDI events. + * + * @{ + */ + +/** + * MIDI router rule type. + * + * @since 1.1.0 + */ +typedef enum +{ + FLUID_MIDI_ROUTER_RULE_NOTE, /**< MIDI note rule */ + FLUID_MIDI_ROUTER_RULE_CC, /**< MIDI controller rule */ + FLUID_MIDI_ROUTER_RULE_PROG_CHANGE, /**< MIDI program change rule */ + 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 */ + 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; + + +/** @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, + int min, int max, float mul, int add); +FLUIDSYNTH_API void fluid_midi_router_rule_set_param2(fluid_midi_router_rule_t *rule, + int min, int max, float mul, int add); +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. + * + * @{ + */ + +/** @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 */ + +/** @} */ + +/** + * @defgroup midi_player MIDI File Player + * @ingroup midi_input + * + * Parse standard MIDI files and emit MIDI events. + * + * @{ + */ + +/** + * MIDI File Player status enum. + * @since 1.1.0 + */ +enum fluid_player_status +{ + FLUID_PLAYER_READY, /**< Player is ready */ + FLUID_PLAYER_PLAYING, /**< Player is currently playing */ + FLUID_PLAYER_STOPPING, /**< Player is stopping, but hasn't finished yet (currently unused) */ + FLUID_PLAYER_DONE /**< Player is finished playing */ +}; + +/** + * MIDI File Player tempo enum. + * @since 2.2.0 + */ +enum fluid_player_set_tempo_type +{ + FLUID_PLAYER_TEMPO_INTERNAL, /**< Use midi file tempo set in midi file (120 bpm by default). Multiplied by a factor */ + FLUID_PLAYER_TEMPO_EXTERNAL_BPM, /**< Set player tempo in bpm, supersede midi file tempo */ + FLUID_PLAYER_TEMPO_EXTERNAL_MIDI, /**< Set player tempo in us per quarter note, supersede midi file tempo */ + FLUID_PLAYER_TEMPO_NBR /**< @internal Value defines the count of player tempo type (#fluid_player_set_tempo_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ +}; + +/** @startlifecycle{MIDI File 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); +FLUIDSYNTH_API int fluid_player_stop(fluid_player_t *player); +FLUIDSYNTH_API int fluid_player_join(fluid_player_t *player); +FLUIDSYNTH_API int fluid_player_set_loop(fluid_player_t *player, int loop); +FLUIDSYNTH_API int fluid_player_set_tempo(fluid_player_t *player, int tempo_type, double tempo); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_player_set_midi_tempo(fluid_player_t *player, int tempo); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_player_set_bpm(fluid_player_t *player, int bpm); +FLUIDSYNTH_API int fluid_player_set_playback_callback(fluid_player_t *player, handle_midi_event_func_t handler, void *handler_data); +FLUIDSYNTH_API int fluid_player_set_tick_callback(fluid_player_t *player, handle_midi_tick_func_t handler, void *handler_data); + +FLUIDSYNTH_API int fluid_player_get_status(fluid_player_t *player); +FLUIDSYNTH_API int fluid_player_get_current_tick(fluid_player_t *player); +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_division(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 +} +#endif + +#endif /* _FLUIDSYNTH_MIDI_H */ diff --git a/libs/fluidsynth/include/fluidsynth/misc.h b/libs/fluidsynth/include/fluidsynth/misc.h new file mode 100644 index 00000000000..f60bf61e014 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/misc.h @@ -0,0 +1,77 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_MISC_H +#define _FLUIDSYNTH_MISC_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup misc Miscellaneous + * + * Miscellaneous utility functions and defines + * + * @{ + */ + +/** + * Value that indicates success, used by most libfluidsynth functions. + * + * @note This was not publicly defined prior to libfluidsynth 1.1.0. When + * writing code which should also be compatible with older versions, something + * like the following can be used: + * + * @code + * #include <fluidsynth.h> + * + * #ifndef FLUID_OK + * #define FLUID_OK (0) + * #define FLUID_FAILED (-1) + * #endif + * @endcode + * + * @since 1.1.0 + */ +#define FLUID_OK (0) + +/** + * Value that indicates failure, used by most libfluidsynth functions. + * + * @note See #FLUID_OK for more details. + * + * @since 1.1.0 + */ +#define FLUID_FAILED (-1) + + +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 +} +#endif + +#endif /* _FLUIDSYNTH_MISC_H */ diff --git a/libs/fluidsynth/include/fluidsynth/mod.h b/libs/fluidsynth/include/fluidsynth/mod.h new file mode 100644 index 00000000000..a0965d0005f --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/mod.h @@ -0,0 +1,105 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_MOD_H +#define _FLUIDSYNTH_MOD_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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. + * + * Note: Bit values do not correspond to the SoundFont spec! Also note that + * #FLUID_MOD_GC and #FLUID_MOD_CC are in the flags field instead of the source field. + */ +enum fluid_mod_flags +{ + FLUID_MOD_POSITIVE = 0, /**< Mapping function is positive */ + FLUID_MOD_NEGATIVE = 1, /**< Mapping function is negative */ + FLUID_MOD_UNIPOLAR = 0, /**< Mapping function is unipolar */ + FLUID_MOD_BIPOLAR = 2, /**< Mapping function is bipolar */ + FLUID_MOD_LINEAR = 0, /**< Linear mapping function */ + FLUID_MOD_CONCAVE = 4, /**< Concave mapping function */ + FLUID_MOD_CONVEX = 8, /**< Convex mapping function */ + FLUID_MOD_SWITCH = 12, /**< Switch (on/off) mapping function */ + FLUID_MOD_GC = 0, /**< General controller source type (#fluid_mod_src) */ + FLUID_MOD_CC = 16, /**< MIDI CC controller (source will be a MIDI CC number) */ + + FLUID_MOD_SIN = 0x80, /**< Custom non-standard sinus mapping function */ +}; + +/** + * General controller (if #FLUID_MOD_GC in flags). This + * corresponds to SoundFont 2.04 PDF section 8.2.1 + */ +enum fluid_mod_src +{ + FLUID_MOD_NONE = 0, /**< No source controller */ + FLUID_MOD_VELOCITY = 2, /**< MIDI note-on velocity */ + FLUID_MOD_KEY = 3, /**< MIDI note-on note number */ + FLUID_MOD_KEYPRESSURE = 10, /**< MIDI key pressure */ + FLUID_MOD_CHANNELPRESSURE = 13, /**< MIDI channel pressure */ + FLUID_MOD_PITCHWHEEL = 14, /**< Pitch wheel */ + 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); +FLUIDSYNTH_API void fluid_mod_set_source2(fluid_mod_t *mod, int src, int flags); +FLUIDSYNTH_API void fluid_mod_set_dest(fluid_mod_t *mod, int dst); +FLUIDSYNTH_API void fluid_mod_set_amount(fluid_mod_t *mod, double amount); + +FLUIDSYNTH_API int fluid_mod_get_source1(const fluid_mod_t *mod); +FLUIDSYNTH_API int fluid_mod_get_flags1(const fluid_mod_t *mod); +FLUIDSYNTH_API int fluid_mod_get_source2(const fluid_mod_t *mod); +FLUIDSYNTH_API int fluid_mod_get_flags2(const fluid_mod_t *mod); +FLUIDSYNTH_API int fluid_mod_get_dest(const fluid_mod_t *mod); +FLUIDSYNTH_API double fluid_mod_get_amount(const fluid_mod_t *mod); + +FLUIDSYNTH_API int fluid_mod_test_identity(const fluid_mod_t *mod1, const fluid_mod_t *mod2); +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 +} +#endif +#endif /* _FLUIDSYNTH_MOD_H */ + diff --git a/libs/fluidsynth/include/fluidsynth/seq.h b/libs/fluidsynth/include/fluidsynth/seq.h new file mode 100644 index 00000000000..cdcc959010f --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/seq.h @@ -0,0 +1,92 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SEQ_H +#define _FLUIDSYNTH_SEQ_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 + * @param data User defined data registered with the client + * + * @note @p time may not be of the same tick value as the scheduled event! In fact, depending on + * the sequencer's scale and the synth's sample-rate, @p time may be a few ticks too late. Although this + * itself is inaudible, it is important to consider, + * when you use this callback for enqueuing additional events over and over again with + * fluid_sequencer_send_at(): If you enqueue new events with a relative tick value you might introduce + * a timing error, which causes your sequence to sound e.g. slower than it's supposed to be. If this is + * your use-case, make sure to enqueue events with an absolute tick value. + */ +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, + fluid_event_callback_t callback, void *data); +FLUIDSYNTH_API void fluid_sequencer_unregister_client(fluid_sequencer_t *seq, fluid_seq_id_t id); +FLUIDSYNTH_API int fluid_sequencer_count_clients(fluid_sequencer_t *seq); +FLUIDSYNTH_API fluid_seq_id_t fluid_sequencer_get_client_id(fluid_sequencer_t *seq, int index); +FLUIDSYNTH_API char *fluid_sequencer_get_client_name(fluid_sequencer_t *seq, fluid_seq_id_t id); +FLUIDSYNTH_API int fluid_sequencer_client_is_dest(fluid_sequencer_t *seq, fluid_seq_id_t id); +FLUIDSYNTH_API void fluid_sequencer_process(fluid_sequencer_t *seq, unsigned int msec); +FLUIDSYNTH_API void fluid_sequencer_send_now(fluid_sequencer_t *seq, fluid_event_t *evt); +FLUIDSYNTH_API +int fluid_sequencer_send_at(fluid_sequencer_t *seq, fluid_event_t *evt, + unsigned int time, int absolute); +FLUIDSYNTH_API +void fluid_sequencer_remove_events(fluid_sequencer_t *seq, fluid_seq_id_t source, fluid_seq_id_t dest, int type); +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 + +#endif /* _FLUIDSYNTH_SEQ_H */ diff --git a/libs/fluidsynth/include/fluidsynth/seqbind.h b/libs/fluidsynth/include/fluidsynth/seqbind.h new file mode 100644 index 00000000000..1a5098120ab --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/seqbind.h @@ -0,0 +1,45 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SEQBIND_H +#define _FLUIDSYNTH_SEQBIND_H + +#include "seq.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 +} +#endif +#endif /* _FLUIDSYNTH_SEQBIND_H */ + diff --git a/libs/fluidsynth/include/fluidsynth/settings.h b/libs/fluidsynth/include/fluidsynth/settings.h new file mode 100644 index 00000000000..78db7dc32b9 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/settings.h @@ -0,0 +1,194 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SETTINGS_H +#define _FLUIDSYNTH_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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. + * @code + * void + * my_synthesizer () + * { + * fluid_settings_t *settings; + * fluid_synth_t *synth; + * fluid_audio_driver_t *adriver; + * + * settings = new_fluid_settings (); + * fluid_settings_setstr(settings, "audio.driver", "alsa"); + * // ... change settings ... + * synth = new_fluid_synth (settings); + * adriver = new_fluid_audio_driver (settings, synth); + * // ... + * } + * @endcode + * @sa @ref CreatingSettings + * + * @{ + */ + +/** + * Hint FLUID_HINT_BOUNDED_BELOW indicates that the LowerBound field + * of the FLUID_PortRangeHint should be considered meaningful. The + * value in this field should be considered the (inclusive) lower + * bound of the valid range. If FLUID_HINT_SAMPLE_RATE is also + * specified then the value of LowerBound should be multiplied by the + * sample rate. + */ +#define FLUID_HINT_BOUNDED_BELOW 0x1 + +/** Hint FLUID_HINT_BOUNDED_ABOVE indicates that the UpperBound field + of the FLUID_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) upper + bound of the valid range. If FLUID_HINT_SAMPLE_RATE is also + specified then the value of UpperBound should be multiplied by the + sample rate. */ +#define FLUID_HINT_BOUNDED_ABOVE 0x2 + +/** + * Hint FLUID_HINT_TOGGLED indicates that the data item should be + * considered a Boolean toggle. Data less than or equal to zero should + * be considered `off' or `false,' and data above zero should be + * considered `on' or `true.' FLUID_HINT_TOGGLED may not be used in + * conjunction with any other hint. + */ +#define FLUID_HINT_TOGGLED 0x4 + +#define FLUID_HINT_OPTIONLIST 0x02 /**< Setting is a list of string options */ + + +/** + * Settings type + * + * Each setting has a defined type: numeric (double), integer, string or a + * set of values. The type of each setting can be retrieved using the + * function fluid_settings_get_type() + */ +enum fluid_types_enum +{ + FLUID_NO_TYPE = -1, /**< Undefined type */ + FLUID_NUM_TYPE, /**< Numeric (double) */ + FLUID_INT_TYPE, /**< Integer */ + FLUID_STR_TYPE, /**< String */ + 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); + +FLUIDSYNTH_API +int fluid_settings_get_hints(fluid_settings_t *settings, const char *name, int *val); + +FLUIDSYNTH_API +int fluid_settings_is_realtime(fluid_settings_t *settings, const char *name); + +FLUIDSYNTH_API +int fluid_settings_setstr(fluid_settings_t *settings, const char *name, const char *str); + +FLUIDSYNTH_API +int fluid_settings_copystr(fluid_settings_t *settings, const char *name, char *str, int len); + +FLUIDSYNTH_API +int fluid_settings_dupstr(fluid_settings_t *settings, const char *name, char **str); + +FLUIDSYNTH_API +int fluid_settings_getstr_default(fluid_settings_t *settings, const char *name, char **def); + +FLUIDSYNTH_API +int fluid_settings_str_equal(fluid_settings_t *settings, const char *name, const char *value); + +FLUIDSYNTH_API +int fluid_settings_setnum(fluid_settings_t *settings, const char *name, double val); + +FLUIDSYNTH_API +int fluid_settings_getnum(fluid_settings_t *settings, const char *name, double *val); + +FLUIDSYNTH_API +int fluid_settings_getnum_default(fluid_settings_t *settings, const char *name, double *val); + +FLUIDSYNTH_API +int fluid_settings_getnum_range(fluid_settings_t *settings, const char *name, + double *min, double *max); + +FLUIDSYNTH_API +int fluid_settings_setint(fluid_settings_t *settings, const char *name, int val); + +FLUIDSYNTH_API +int fluid_settings_getint(fluid_settings_t *settings, const char *name, int *val); + +FLUIDSYNTH_API +int fluid_settings_getint_default(fluid_settings_t *settings, const char *name, int *val); + +FLUIDSYNTH_API +int fluid_settings_getint_range(fluid_settings_t *settings, const char *name, + int *min, int *max); + +/** + * 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) + */ +typedef void (*fluid_settings_foreach_option_t)(void *data, const char *name, const char *option); + +FLUIDSYNTH_API +void fluid_settings_foreach_option(fluid_settings_t *settings, + const char *name, void *data, + fluid_settings_foreach_option_t func); +FLUIDSYNTH_API +int fluid_settings_option_count(fluid_settings_t *settings, const char *name); +FLUIDSYNTH_API char *fluid_settings_option_concat(fluid_settings_t *settings, + const char *name, + const char *separator); + +/** + * 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) + */ +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 +} +#endif + +#endif /* _FLUIDSYNTH_SETTINGS_H */ diff --git a/libs/fluidsynth/include/fluidsynth/sfont.h b/libs/fluidsynth/include/fluidsynth/sfont.h new file mode 100644 index 00000000000..6d0fd4c7a3e --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/sfont.h @@ -0,0 +1,362 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SFONT_H +#define _FLUIDSYNTH_SFONT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup soundfonts SoundFonts + * + * 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_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 + * and synthesized, which may not actually be SoundFont files, as long as they + * can be represented by the SoundFont synthesis model. + * + * To add a new SoundFont loader to the synthesizer, call + * fluid_synth_add_sfloader() and pass a pointer to an + * #fluid_sfloader_t instance created by new_fluid_sfloader(). + * On creation, you must specify a callback function \p load + * that will be called for every file attempting to load it and + * if successful returns a #fluid_sfont_t instance, or NULL if it fails. + * + * The #fluid_sfont_t structure contains a callback to obtain the + * name of the SoundFont. It contains two functions to iterate + * though the contained presets, and one function to obtain a + * preset corresponding to a bank and preset number. This + * function should return a #fluid_preset_t instance. + * + * The #fluid_preset_t instance contains some functions to obtain + * information from the preset (name, bank, number). The most + * important callback is the noteon function. The noteon function + * is called by fluidsynth internally and + * should call fluid_synth_alloc_voice() for every sample that has + * to be played. fluid_synth_alloc_voice() expects a pointer to a + * #fluid_sample_t instance and returns a pointer to the opaque + * #fluid_voice_t structure. To set or increment the values of a + * 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. + * + * @{ + */ + +/** + * Some notification enums for presets and samples. + */ +enum +{ + FLUID_PRESET_SELECTED, /**< Preset selected notify */ + FLUID_PRESET_UNSELECTED, /**< Preset unselected notify */ + FLUID_SAMPLE_DONE, /**< Sample no longer needed notify */ + FLUID_PRESET_PIN, /**< Request to pin preset samples to cache */ + FLUID_PRESET_UNPIN /**< Request to unpin preset samples from cache */ +}; + +/** + * 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. + */ +enum fluid_sample_type +{ + FLUID_SAMPLETYPE_MONO = 0x1, /**< Value used for mono samples */ + FLUID_SAMPLETYPE_RIGHT = 0x2, /**< Value used for right samples of a stereo pair */ + FLUID_SAMPLETYPE_LEFT = 0x4, /**< Value used for left samples of a stereo pair */ + FLUID_SAMPLETYPE_LINKED = 0x8, /**< Value used for linked sample, which is currently not supported */ + FLUID_SAMPLETYPE_OGG_VORBIS = 0x10, /**< Flag used for Ogg Vorbis compressed samples (non-standard compliant extension) as found in the program "sftools" developed by Werner Schweer from MuseScore @since 1.1.7 */ + FLUID_SAMPLETYPE_ROM = 0x8000 /**< Flag that indicates ROM samples, causing the sample to be ignored */ +}; + + +/** + * 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. + */ +typedef fluid_sfont_t *(*fluid_sfloader_load_t)(fluid_sfloader_t *loader, const char *filename); + +/** + * The free method should free the memory allocated for a fluid_sfloader_t instance in + * 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. + * + */ +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. + * + * @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); + +/** + * Reads \c count bytes to the specified buffer \c buf. + * + * @return returns #FLUID_OK if exactly \c count bytes were successfully read, else returns #FLUID_FAILED and leaves \a buf unmodified. + */ +typedef int (* fluid_sfloader_callback_read_t)(void *buf, fluid_long_long_t count, void *handle); + +/** + * 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); + +/** + * Closes the handle returned by #fluid_sfloader_callback_open_t and frees used resources. + * + * @return returns #FLUID_OK on success, #FLUID_FAILED on error + */ +typedef int (* fluid_sfloader_callback_close_t)(void *handle); + +/** @return returns current file offset or #FLUID_FAILED on error */ +typedef fluid_long_long_t (* fluid_sfloader_callback_tell_t)(void *handle); + + +FLUIDSYNTH_API int fluid_sfloader_set_callbacks(fluid_sfloader_t *loader, + fluid_sfloader_callback_open_t open, + fluid_sfloader_callback_read_t read, + fluid_sfloader_callback_seek_t seek, + fluid_sfloader_callback_tell_t tell, + fluid_sfloader_callback_close_t close); + +FLUIDSYNTH_API int fluid_sfloader_set_data(fluid_sfloader_t *loader, void *data); +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. + */ +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) + * @return Should return an allocated virtual preset or NULL if it could not + * be found. + */ +typedef fluid_preset_t *(*fluid_sfont_get_preset_t)(fluid_sfont_t *sfont, int bank, int prenum); + +/** + * Start virtual SoundFont preset iteration method. + * + * @param sfont Virtual SoundFont + * + * Starts/re-starts virtual preset iteration in a SoundFont. + */ +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 + * + * Returns preset information to the caller. The returned buffer is only valid until a subsequent + * call to this function. + */ +typedef fluid_preset_t *(*fluid_sfont_iteration_next_t)(fluid_sfont_t *sfont); + +/** + * 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); + + +/** @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, + fluid_sfont_iteration_next_t iter_next, + 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); + +FLUIDSYNTH_API int fluid_sfont_get_id(fluid_sfont_t *sfont); +FLUIDSYNTH_API const char *fluid_sfont_get_name(fluid_sfont_t *sfont); +FLUIDSYNTH_API fluid_preset_t *fluid_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum); +FLUIDSYNTH_API void fluid_sfont_iteration_start(fluid_sfont_t *sfont); +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 + * SoundFont, in the case of preset iteration). + */ +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 + */ +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 + */ +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 + * @param key MIDI note number (0-127) + * @param vel MIDI velocity (0-127) + * @return #FLUID_OK on success (0) or #FLUID_FAILED (-1) otherwise + * + * This method may be called from within synthesis context and therefore + * should be as efficient as possible and not perform any operations considered + * bad for realtime audio output (memory allocations and other OS calls). + * + * Call fluid_synth_alloc_voice() for every sample that has + * to be played. fluid_synth_alloc_voice() expects a pointer to a + * #fluid_sample_t structure and returns a pointer to the opaque + * #fluid_voice_t structure. To set or increment the values of a + * 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. Starting with FluidSynth 1.1.0 all voices + * created will be started at the same time. + */ +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. + * + * @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); + +/** @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, + fluid_preset_get_num_t get_num, + 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); + +FLUIDSYNTH_API const char *fluid_preset_get_name(fluid_preset_t *preset); +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); +FLUIDSYNTH_API int fluid_sample_set_sound_data(fluid_sample_t *sample, + short *data, + char *data24, + unsigned int nbframes, + unsigned int sample_rate, + short copy_data); + +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 + +#endif /* _FLUIDSYNTH_SFONT_H */ diff --git a/libs/fluidsynth/include/fluidsynth/shell.h b/libs/fluidsynth/include/fluidsynth/shell.h new file mode 100644 index 00000000000..cb555c9388f --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/shell.h @@ -0,0 +1,150 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SHELL_H +#define _FLUIDSYNTH_SHELL_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup command_interface Command Interface + * + * 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 (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); +/** @} */ + + +/** + * @defgroup command_handler Command Handler + * @ingroup command_interface + * @brief Handles text commands and reading of configuration files + * + * @{ + */ + +/** @startlifecycle{Command Handler} */ +FLUIDSYNTH_API +fluid_cmd_handler_t *new_fluid_cmd_handler(fluid_synth_t *synth, fluid_midi_router_t *router); + +FLUIDSYNTH_API +fluid_cmd_handler_t *new_fluid_cmd_handler2(fluid_settings_t *settings, fluid_synth_t *synth, + fluid_midi_router_t *router, fluid_player_t *player); + +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); + +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. + * + * @{ + */ + +/** @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 */ + +/** @} */ + + +/** + * @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. + * + * @{ + */ + +/** @startlifecycle{Command Server} */ +FLUIDSYNTH_API +fluid_server_t *new_fluid_server(fluid_settings_t *settings, + fluid_synth_t *synth, fluid_midi_router_t *router); + +FLUIDSYNTH_API +fluid_server_t *new_fluid_server2(fluid_settings_t *settings, + fluid_synth_t *synth, fluid_midi_router_t *router, + fluid_player_t *player); + +FLUIDSYNTH_API void delete_fluid_server(fluid_server_t *server); + +FLUIDSYNTH_API int fluid_server_join(fluid_server_t *server); +/** @endlifecycle */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_SHELL_H */ diff --git a/libs/fluidsynth/include/fluidsynth/synth.h b/libs/fluidsynth/include/fluidsynth/synth.h new file mode 100644 index 00000000000..84861ebd648 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/synth.h @@ -0,0 +1,552 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_SYNTH_H +#define _FLUIDSYNTH_SYNTH_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @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 + * the synthesizer characteristics. + * + * You have to load a SoundFont in order to hear any sound. For that + * you use the fluid_synth_sfload() function. + * + * You can use the audio driver functions to open + * the audio device and create a background audio thread. + * + * The API for sending MIDI events is probably what you expect: + * fluid_synth_noteon(), fluid_synth_noteoff(), ... + * + * @{ + */ + +/** @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); +/** @} */ + +/** + * @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); +FLUIDSYNTH_API int fluid_synth_get_cc(fluid_synth_t *synth, int chan, int ctrl, int *pval); +FLUIDSYNTH_API int fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len, + char *response, int *response_len, int *handled, int dryrun); +FLUIDSYNTH_API int fluid_synth_pitch_bend(fluid_synth_t *synth, int chan, int val); +FLUIDSYNTH_API int fluid_synth_get_pitch_bend(fluid_synth_t *synth, int chan, int *ppitch_bend); +FLUIDSYNTH_API int fluid_synth_pitch_wheel_sens(fluid_synth_t *synth, int chan, int val); +FLUIDSYNTH_API int fluid_synth_get_pitch_wheel_sens(fluid_synth_t *synth, int chan, int *pval); +FLUIDSYNTH_API int fluid_synth_program_change(fluid_synth_t *synth, int chan, int program); +FLUIDSYNTH_API int fluid_synth_channel_pressure(fluid_synth_t *synth, int chan, int val); +FLUIDSYNTH_API int fluid_synth_key_pressure(fluid_synth_t *synth, int chan, int key, int val); +FLUIDSYNTH_API int fluid_synth_bank_select(fluid_synth_t *synth, int chan, int bank); +FLUIDSYNTH_API int fluid_synth_sfont_select(fluid_synth_t *synth, int chan, int sfont_id); +FLUIDSYNTH_API +int fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, + int bank_num, int preset_num); +FLUIDSYNTH_API int +fluid_synth_program_select_by_sfont_name(fluid_synth_t *synth, int chan, + const char *sfont_name, int bank_num, + int preset_num); +FLUIDSYNTH_API +int fluid_synth_get_program(fluid_synth_t *synth, int chan, int *sfont_id, + int *bank_num, int *preset_num); +FLUIDSYNTH_API int fluid_synth_unset_program(fluid_synth_t *synth, int chan); +FLUIDSYNTH_API int fluid_synth_program_reset(fluid_synth_t *synth); +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 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, + 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 soundfont_management SoundFont Management + * @ingroup synth + * + * Functions to load and unload SoundFonts. + * + * @{ + */ +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); +FLUIDSYNTH_API int fluid_synth_sfunload(fluid_synth_t *synth, int id, int reset_presets); +FLUIDSYNTH_API int fluid_synth_add_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont); +FLUIDSYNTH_API int fluid_synth_remove_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont); +FLUIDSYNTH_API int fluid_synth_sfcount(fluid_synth_t *synth); +FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont(fluid_synth_t *synth, unsigned int num); +FLUIDSYNTH_API fluid_sfont_t *fluid_synth_get_sfont_by_id(fluid_synth_t *synth, int id); +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 reverb_effect Effect - Reverb + * @ingroup synth + * + * Functions for configuring the built-in reverb effect + * + * @{ + */ +FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_reverb_on(fluid_synth_t *synth, int on); +FLUIDSYNTH_API int fluid_synth_reverb_on(fluid_synth_t *synth, int fx_group, int on); + +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, + double damping, double width, double level); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level); + +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_reverb_roomsize(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_reverb_damp(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_reverb_level(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_reverb_width(fluid_synth_t *synth); + +FLUIDSYNTH_API int fluid_synth_set_reverb_group_roomsize(fluid_synth_t *synth, int fx_group, double roomsize); +FLUIDSYNTH_API int fluid_synth_set_reverb_group_damp(fluid_synth_t *synth, int fx_group, double damping); +FLUIDSYNTH_API int fluid_synth_set_reverb_group_width(fluid_synth_t *synth, int fx_group, double width); +FLUIDSYNTH_API int fluid_synth_set_reverb_group_level(fluid_synth_t *synth, int fx_group, double level); + +FLUIDSYNTH_API int fluid_synth_get_reverb_group_roomsize(fluid_synth_t *synth, int fx_group, double *roomsize); +FLUIDSYNTH_API int fluid_synth_get_reverb_group_damp(fluid_synth_t *synth, int fx_group, double *damping); +FLUIDSYNTH_API int fluid_synth_get_reverb_group_width(fluid_synth_t *synth, int fx_group, double *width); +FLUIDSYNTH_API int fluid_synth_get_reverb_group_level(fluid_synth_t *synth, int fx_group, double *level); + /** @} Reverb */ + + +/** + * @defgroup chorus_effect Effect - Chorus + * @ingroup synth + * + * Functions for configuring the built-in chorus effect + * + * @{ + */ + +/** + * Chorus modulation waveform type. + */ +enum fluid_chorus_mod +{ + FLUID_CHORUS_MOD_SINE = 0, /**< Sine wave chorus modulation */ + FLUID_CHORUS_MOD_TRIANGLE = 1 /**< Triangle wave chorus modulation */ +}; + + +FLUID_DEPRECATED FLUIDSYNTH_API void fluid_synth_set_chorus_on(fluid_synth_t *synth, int on); +FLUIDSYNTH_API int fluid_synth_chorus_on(fluid_synth_t *synth, int fx_group, int on); + +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus(fluid_synth_t *synth, int nr, double level, + double speed, double depth_ms, int type); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type); + +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_get_chorus_nr(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_chorus_level(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_chorus_speed(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API double fluid_synth_get_chorus_depth(fluid_synth_t *synth); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t *synth); /* see fluid_chorus_mod */ + +FLUIDSYNTH_API int fluid_synth_set_chorus_group_nr(fluid_synth_t *synth, int fx_group, int nr); +FLUIDSYNTH_API int fluid_synth_set_chorus_group_level(fluid_synth_t *synth, int fx_group, double level); +FLUIDSYNTH_API int fluid_synth_set_chorus_group_speed(fluid_synth_t *synth, int fx_group, double speed); +FLUIDSYNTH_API int fluid_synth_set_chorus_group_depth(fluid_synth_t *synth, int fx_group, double depth_ms); +FLUIDSYNTH_API int fluid_synth_set_chorus_group_type(fluid_synth_t *synth, int fx_group, int type); + +FLUIDSYNTH_API int fluid_synth_get_chorus_group_nr(fluid_synth_t *synth, int fx_group, int *nr); +FLUIDSYNTH_API int fluid_synth_get_chorus_group_level(fluid_synth_t *synth, int fx_group, double *level); +FLUIDSYNTH_API int fluid_synth_get_chorus_group_speed(fluid_synth_t *synth, int fx_group, double *speed); +FLUIDSYNTH_API int fluid_synth_get_chorus_group_depth(fluid_synth_t *synth, int fx_group, double *depth_ms); +FLUIDSYNTH_API int fluid_synth_get_chorus_group_type(fluid_synth_t *synth, int fx_group, int *type); +/** @} Chorus */ + +/** + * @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); +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); + +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); +FLUIDSYNTH_API float fluid_synth_get_gain(fluid_synth_t *synth); +FLUIDSYNTH_API int fluid_synth_set_polyphony(fluid_synth_t *synth, int polyphony); +FLUIDSYNTH_API int fluid_synth_get_polyphony(fluid_synth_t *synth); +FLUIDSYNTH_API int fluid_synth_get_active_voice_count(fluid_synth_t *synth); +FLUIDSYNTH_API int fluid_synth_get_internal_bufsize(fluid_synth_t *synth); + +FLUIDSYNTH_API +int fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method); + +/** + * Synthesis interpolation method. + */ +enum fluid_interp +{ + FLUID_INTERP_NONE = 0, /**< No interpolation: Fastest, but questionable audio quality */ + FLUID_INTERP_LINEAR = 1, /**< Straight-line interpolation: A bit slower, reasonable audio quality */ + FLUID_INTERP_4THORDER = 4, /**< Fourth-order interpolation, good quality, the default */ + FLUID_INTERP_7THORDER = 7, /**< Seventh-order interpolation */ + + FLUID_INTERP_DEFAULT = FLUID_INTERP_4THORDER, /**< Default interpolation method */ + FLUID_INTERP_HIGHEST = FLUID_INTERP_7THORDER, /**< Highest interpolation method */ +}; + +/** + * 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); +/** @} Synthesis Parameters */ + + +/** + * @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); +FLUIDSYNTH_API +int fluid_synth_activate_octave_tuning(fluid_synth_t *synth, int bank, int prog, + const char *name, const double *pitch, int apply); +FLUIDSYNTH_API +int fluid_synth_tune_notes(fluid_synth_t *synth, int bank, int prog, + int len, const int *keys, const double *pitch, int apply); +FLUIDSYNTH_API +int fluid_synth_activate_tuning(fluid_synth_t *synth, int chan, int bank, int prog, + int apply); +FLUIDSYNTH_API +int fluid_synth_deactivate_tuning(fluid_synth_t *synth, int chan, int apply); +FLUIDSYNTH_API void fluid_synth_tuning_iteration_start(fluid_synth_t *synth); +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 */ + + +/** + * @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. + * + * @warning + * If a concurrently running thread calls any other sound affecting synth function + * (e.g. fluid_synth_noteon(), fluid_synth_cc(), etc.) it is unspecified whether the event triggered by such a call + * will be effective in the recently synthesized audio. While this is inaudible when only requesting small chunks from the + * synth with every call (cf. fluid_synth_get_internal_bufsize()), it will become evident when requesting larger sample chunks: + * With larger sample chunks it will get harder for the synth to react on those spontaneously occurring events in time + * (like events received from a MIDI driver, or directly made synth API calls). + * In those real-time scenarios, prefer requesting smaller + * sample chunks from the synth with each call, to avoid poor quantization of your events in the synthesized audio. + * This issue is not applicable when using the MIDI player or sequencer for event dispatching. Also + * refer to the documentation of \setting{audio_period-size}. + * + * @{ + */ +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); +FLUIDSYNTH_API int fluid_synth_write_float(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr); +FLUID_DEPRECATED FLUIDSYNTH_API int fluid_synth_nwrite_float(fluid_synth_t *synth, int len, + float **left, float **right, + float **fx_left, float **fx_right); +FLUIDSYNTH_API int fluid_synth_process(fluid_synth_t *synth, int len, + int nfx, float *fx[], + int nout, float *out[]); +/** @} Audio Rendering */ + + +/** + * @defgroup iir_filter Effect - IIR Filter + * @ingroup synth + * + * Functions for configuring the built-in IIR filter effect + * + * @{ + */ + +/** + * Specifies the type of filter to use for the custom IIR filter + */ +enum fluid_iir_filter_type +{ + FLUID_IIR_DISABLED = 0, /**< Custom IIR filter is not operating */ + FLUID_IIR_LOWPASS, /**< Custom IIR filter is operating as low-pass filter */ + FLUID_IIR_HIGHPASS, /**< Custom IIR filter is operating as high-pass filter */ + FLUID_IIR_LAST /**< @internal Value defines the count of filter types (#fluid_iir_filter_type) @warning This symbol is not part of the public API and ABI stability guarantee and may change at any time! */ +}; + +/** + * Specifies optional settings to use for the custom IIR filter. Can be bitwise ORed. + */ +enum fluid_iir_filter_flags +{ + FLUID_IIR_Q_LINEAR = 1 << 0, /**< The Soundfont spec requires the filter Q to be interpreted in dB. If this flag is set the filter Q is instead assumed to be in a linear range */ + FLUID_IIR_Q_ZERO_OFF = 1 << 1, /**< If this flag the filter is switched off if Q == 0 (prior to any transformation) */ + FLUID_IIR_NO_GAIN_AMP = 1 << 2 /**< The Soundfont spec requires to correct the gain of the filter depending on the filter's Q. If this flag is set the filter gain will not be corrected. */ +}; + +FLUIDSYNTH_API int fluid_synth_set_custom_filter(fluid_synth_t *, int type, int flags); +/** @} IIR Filter */ + + + + +/** + * @defgroup channel_setup MIDI Channel Setup + * @ingroup synth + * + * The functions in this section provide interfaces to change the channel type + * and to configure basic channels, legato and portamento setups. + * + * @{ + */ + +/** @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 +{ + FLUID_CHANNEL_POLY_OFF = 0x01, /**< if flag is set, the basic channel is in mono on state, if not set poly is on */ + 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 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 */ + FLUID_CHANNEL_MODE_OMNION_POLY = FLUID_CHANNEL_MODE_MASK & (~FLUID_CHANNEL_OMNI_OFF & ~FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 0 */ + FLUID_CHANNEL_MODE_OMNION_MONO = FLUID_CHANNEL_MODE_MASK & (~FLUID_CHANNEL_OMNI_OFF & FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 1 */ + FLUID_CHANNEL_MODE_OMNIOFF_POLY = FLUID_CHANNEL_MODE_MASK & (FLUID_CHANNEL_OMNI_OFF & ~FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 2 */ + FLUID_CHANNEL_MODE_OMNIOFF_MONO = FLUID_CHANNEL_MODE_MASK & (FLUID_CHANNEL_OMNI_OFF | FLUID_CHANNEL_POLY_OFF), /**< corresponds to MIDI mode 3 */ + FLUID_CHANNEL_MODE_LAST /**< @internal Value defines the count of basic channel modes (#fluid_basic_channel_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_reset_basic_channel(fluid_synth_t *synth, int chan); + +FLUIDSYNTH_API int fluid_synth_get_basic_channel(fluid_synth_t *synth, int chan, + int *basic_chan_out, + int *mode_chan_out, + int *basic_val_out); +FLUIDSYNTH_API int fluid_synth_set_basic_channel(fluid_synth_t *synth, int chan, int mode, int val); + +/** @} 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 +{ + FLUID_CHANNEL_LEGATO_MODE_RETRIGGER, /**< Mode 0 - Release previous note, start a new note */ + FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER, /**< Mode 1 - On contiguous notes retrigger in attack section using current value, shape attack using current dynamic and make use of previous voices if any */ + FLUID_CHANNEL_LEGATO_MODE_LAST /**< @internal Value defines the count of legato modes (#fluid_channel_legato_mode) @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_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 */ + +/** @name Portamento Mode + * @{ + */ + +/** + * Indicates the portamento mode a channel is set to + */ +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 + @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 */ +}; + +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 +} +#endif + +#endif /* _FLUIDSYNTH_SYNTH_H */ diff --git a/libs/fluidsynth/include/fluidsynth/types.h b/libs/fluidsynth/include/fluidsynth/types.h new file mode 100644 index 00000000000..9c2aaadacaa --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/types.h @@ -0,0 +1,85 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_TYPES_H +#define _FLUIDSYNTH_TYPES_H + + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * @defgroup Types Types + * @brief Type declarations + * + * @{ + */ + +typedef struct _fluid_hashtable_t fluid_settings_t; /**< Configuration settings instance */ +typedef struct _fluid_synth_t fluid_synth_t; /**< Synthesizer instance */ +typedef struct _fluid_voice_t fluid_voice_t; /**< Synthesis voice instance */ +typedef struct _fluid_sfloader_t fluid_sfloader_t; /**< SoundFont loader plugin */ +typedef struct _fluid_sfont_t fluid_sfont_t; /**< SoundFont */ +typedef struct _fluid_preset_t fluid_preset_t; /**< SoundFont preset */ +typedef struct _fluid_sample_t fluid_sample_t; /**< SoundFont sample */ +typedef struct _fluid_mod_t fluid_mod_t; /**< SoundFont modulator */ +typedef struct _fluid_audio_driver_t fluid_audio_driver_t; /**< Audio driver instance */ +typedef struct _fluid_file_renderer_t fluid_file_renderer_t; /**< Audio file renderer instance */ +typedef struct _fluid_player_t fluid_player_t; /**< MIDI player instance */ +typedef struct _fluid_midi_event_t fluid_midi_event_t; /**< MIDI event */ +typedef struct _fluid_midi_driver_t fluid_midi_driver_t; /**< MIDI driver instance */ +typedef struct _fluid_midi_router_t fluid_midi_router_t; /**< MIDI router instance */ +typedef struct _fluid_midi_router_rule_t fluid_midi_router_rule_t; /**< MIDI router rule */ +typedef struct _fluid_hashtable_t fluid_cmd_hash_t; /**< Command handler hash table */ +typedef struct _fluid_shell_t fluid_shell_t; /**< Command shell */ +typedef struct _fluid_server_t fluid_server_t; /**< TCP/IP shell server instance */ +typedef struct _fluid_event_t fluid_event_t; /**< Sequencer event */ +typedef struct _fluid_sequencer_t fluid_sequencer_t; /**< Sequencer instance */ +typedef struct _fluid_ramsfont_t fluid_ramsfont_t; /**< RAM SoundFont */ +typedef struct _fluid_rampreset_t fluid_rampreset_t; /**< RAM SoundFont preset */ +typedef struct _fluid_cmd_handler_t fluid_cmd_handler_t; /**< Shell Command Handler */ +typedef struct _fluid_ladspa_fx_t fluid_ladspa_fx_t; /**< LADSPA effects instance */ +typedef struct _fluid_file_callbacks_t fluid_file_callbacks_t; /**< Callback struct to perform custom file loading of soundfonts */ + +typedef int fluid_istream_t; /**< Input stream descriptor */ +typedef int fluid_ostream_t; /**< Output stream descriptor */ + +typedef short fluid_seq_id_t; /**< Unique client IDs used by the sequencer and #fluid_event_t, obtained by fluid_sequencer_register_client() and fluid_sequencer_register_fluidsynth() */ + +#if defined(_MSC_VER) && (_MSC_VER < 1800) +typedef __int64 fluid_long_long_t; // even on 32bit windows +#else +/** + * A typedef for C99's type long long, which is at least 64-bit wide, as guaranteed by the C99. + * @p __int64 will be used as replacement for VisualStudio 2010 and older. + */ +typedef long long fluid_long_long_t; +#endif + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_TYPES_H */ diff --git a/libs/fluidsynth/include/fluidsynth/version.h b/libs/fluidsynth/include/fluidsynth/version.h new file mode 100644 index 00000000000..e7e4d4a5a62 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/version.h @@ -0,0 +1,47 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_VERSION_H +#define _FLUIDSYNTH_VERSION_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup misc + * + * @{ + */ +#define FLUIDSYNTH_VERSION "2.3.3" /**< String constant of libfluidsynth version. */ +#define FLUIDSYNTH_VERSION_MAJOR 2 /**< libfluidsynth major version integer constant. */ +#define FLUIDSYNTH_VERSION_MINOR 3 /**< libfluidsynth minor version integer constant. */ +#define FLUIDSYNTH_VERSION_MICRO 3 /**< libfluidsynth micro version integer constant. */ + +FLUIDSYNTH_API void fluid_version(int *major, int *minor, int *micro); +FLUIDSYNTH_API char* fluid_version_str(void); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_VERSION_H */ diff --git a/libs/fluidsynth/include/fluidsynth/voice.h b/libs/fluidsynth/include/fluidsynth/voice.h new file mode 100644 index 00000000000..1d31cbaeaf1 --- /dev/null +++ b/libs/fluidsynth/include/fluidsynth/voice.h @@ -0,0 +1,77 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUIDSYNTH_VOICE_H +#define _FLUIDSYNTH_VOICE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 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. + */ +enum fluid_voice_add_mod +{ + FLUID_VOICE_OVERWRITE, /**< Overwrite any existing matching modulator */ + FLUID_VOICE_ADD, /**< Add (sum) modulator amounts */ + FLUID_VOICE_DEFAULT /**< For default modulators only, no need to check for duplicates */ +}; + +FLUIDSYNTH_API void fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode); +FLUIDSYNTH_API float fluid_voice_gen_get(fluid_voice_t *voice, int gen); +FLUIDSYNTH_API void fluid_voice_gen_set(fluid_voice_t *voice, int gen, float val); +FLUIDSYNTH_API void fluid_voice_gen_incr(fluid_voice_t *voice, int gen, float val); + +FLUIDSYNTH_API unsigned int fluid_voice_get_id(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_get_channel(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_get_key(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_get_actual_key(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_get_velocity(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_get_actual_velocity(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_is_playing(const fluid_voice_t *voice); +FLUIDSYNTH_API int fluid_voice_is_on(const fluid_voice_t *voice); +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 +#endif /* _FLUIDSYNTH_VOICE_H */ + diff --git a/libs/fluidsynth/src/bindings/fluid_ladspa.h b/libs/fluidsynth/src/bindings/fluid_ladspa.h new file mode 100644 index 00000000000..80952d51ea9 --- /dev/null +++ b/libs/fluidsynth/src/bindings/fluid_ladspa.h @@ -0,0 +1,36 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_LADSPA_H +#define _FLUID_LADSPA_H + +#include "fluid_sys.h" + +fluid_ladspa_fx_t *new_fluid_ladspa_fx(fluid_real_t sample_rate, int buffer_size); +void delete_fluid_ladspa_fx(fluid_ladspa_fx_t *fx); + +int fluid_ladspa_set_sample_rate(fluid_ladspa_fx_t *fx, fluid_real_t sample_rate); + +void fluid_ladspa_run(fluid_ladspa_fx_t *fx, int block_count, int block_size); + +int fluid_ladspa_add_host_ports(fluid_ladspa_fx_t *fx, const char *prefix, + int num_buffers, fluid_real_t buffers[], int buf_stride); + +#endif /* _FLUID_LADSPA_H */ diff --git a/libs/fluidsynth/src/midi/fluid_midi.c b/libs/fluidsynth/src/midi/fluid_midi.c new file mode 100644 index 00000000000..c8ad9d2c5cd --- /dev/null +++ b/libs/fluidsynth/src/midi/fluid_midi.c @@ -0,0 +1,2851 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_midi.h" +#include "fluid_sys.h" +#include "fluid_synth.h" +#include "fluid_settings.h" + + +static int fluid_midi_event_length(unsigned char event); +static int fluid_isasciistring(char *s); +static long fluid_getlength(const unsigned char *s); + + +/* Read the entire contents of a file into memory, allocating enough memory + * for the file, and returning the length and the buffer. + * Note: This rewinds the file to the start before reading. + * Returns NULL if there was an error reading or allocating memory. + */ +typedef FILE *fluid_file; +static char *fluid_file_read_full(fluid_file fp, size_t *length); +static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic); +static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size); +#define READ_FULL_INITIAL_BUFLEN 1024 + +static fluid_track_t *new_fluid_track(int num); +static void delete_fluid_track(fluid_track_t *track); +static int fluid_track_set_name(fluid_track_t *track, char *name); +static int fluid_track_add_event(fluid_track_t *track, fluid_midi_event_t *evt); +static fluid_midi_event_t *fluid_track_next_event(fluid_track_t *track); +static int fluid_track_get_duration(fluid_track_t *track); +static int fluid_track_reset(fluid_track_t *track); + +static int fluid_player_add_track(fluid_player_t *player, fluid_track_t *track); +static int fluid_player_callback(void *data, unsigned int msec); +static int fluid_player_reset(fluid_player_t *player); +static int fluid_player_load(fluid_player_t *player, fluid_playlist_item *item); +static void fluid_player_advancefile(fluid_player_t *player); +static void fluid_player_playlist_load(fluid_player_t *player, unsigned int msec); +static void fluid_player_update_tempo(fluid_player_t *player); + +static fluid_midi_file *new_fluid_midi_file(const char *buffer, size_t length); +static void delete_fluid_midi_file(fluid_midi_file *mf); +static int fluid_midi_file_read_mthd(fluid_midi_file *midifile); +static int fluid_midi_file_load_tracks(fluid_midi_file *midifile, fluid_player_t *player); +static int fluid_midi_file_read_track(fluid_midi_file *mf, fluid_player_t *player, int num); +static int fluid_midi_file_read_event(fluid_midi_file *mf, fluid_track_t *track); +static int fluid_midi_file_read_varlen(fluid_midi_file *mf); +static int fluid_midi_file_getc(fluid_midi_file *mf); +static int fluid_midi_file_push(fluid_midi_file *mf, int c); +static int fluid_midi_file_read(fluid_midi_file *mf, void *buf, int len); +static int fluid_midi_file_skip(fluid_midi_file *mf, int len); +static int fluid_midi_file_eof(fluid_midi_file *mf); +static int fluid_midi_file_read_tracklen(fluid_midi_file *mf); +static int fluid_midi_file_eot(fluid_midi_file *mf); +static int fluid_midi_file_get_division(fluid_midi_file *midifile); + + +/*************************************************************** + * + * MIDIFILE + */ + +/** + * Check if a file is a MIDI file. + * @param filename Path to the file to check + * @return TRUE if it could be a MIDI file, FALSE otherwise + * + * The current implementation only checks for the "MThd" header in the file. + * It is useful only to distinguish between SoundFont and MIDI files. + */ +int fluid_is_midifile(const char *filename) +{ + FILE *fp; + uint32_t id; + int retcode = FALSE; + + do + { + if((fp = fluid_file_open(filename, NULL)) == NULL) + { + return retcode; + } + + if(FLUID_FREAD(&id, sizeof(id), 1, fp) != 1) + { + break; + } + + retcode = (id == FLUID_FOURCC('M', 'T', 'h', 'd')); + } + while(0); + + FLUID_FCLOSE(fp); + + return retcode; +} + +/** + * Return a new MIDI file handle for parsing an already-loaded MIDI file. + * @internal + * @param buffer Pointer to full contents of MIDI file (borrows the pointer). + * The caller must not free buffer until after the fluid_midi_file is deleted. + * @param length Size of the buffer in bytes. + * @return New MIDI file handle or NULL on error. + */ +fluid_midi_file * +new_fluid_midi_file(const char *buffer, size_t length) +{ + fluid_midi_file *mf; + + if(length > INT_MAX) + { + FLUID_LOG(FLUID_ERR, "Refusing to open a MIDI file which is bigger than 2GiB"); + return NULL; + } + + mf = FLUID_NEW(fluid_midi_file); + if(mf == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(mf, 0, sizeof(fluid_midi_file)); + + mf->c = -1; + mf->running_status = -1; + + mf->buffer = buffer; + mf->buf_len = (int)length; + mf->buf_pos = 0; + mf->eof = FALSE; + + if(fluid_midi_file_read_mthd(mf) != FLUID_OK) + { + FLUID_FREE(mf); + return NULL; + } + + return mf; +} + +static char * +fluid_file_read_full(fluid_file fp, size_t *length) +{ + size_t buflen; + char *buffer; + size_t n; + + /* Work out the length of the file in advance */ + if(FLUID_FSEEK(fp, 0, SEEK_END) != 0) + { + FLUID_LOG(FLUID_ERR, "File load: Could not seek within file"); + return NULL; + } + + buflen = ftell(fp); + + if(FLUID_FSEEK(fp, 0, SEEK_SET) != 0) + { + FLUID_LOG(FLUID_ERR, "File load: Could not seek within file"); + return NULL; + } + + FLUID_LOG(FLUID_DBG, "File load: Allocating %lu bytes", (unsigned long)buflen); + buffer = FLUID_MALLOC(buflen); + + if(buffer == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return NULL; + } + + n = FLUID_FREAD(buffer, 1, buflen, fp); + + if(n != buflen) + { + FLUID_LOG(FLUID_ERR, "Only read %lu bytes; expected %lu", (unsigned long)n, + (unsigned long)buflen); + FLUID_FREE(buffer); + return NULL; + }; + + *length = n; + + return buffer; +} + +/** + * Delete a MIDI file handle. + * @internal + * @param mf MIDI file handle to close and free. + */ +void +delete_fluid_midi_file(fluid_midi_file *mf) +{ + fluid_return_if_fail(mf != NULL); + + FLUID_FREE(mf); +} + +/* + * Gets the next byte in a MIDI file, taking into account previous running status. + * + * returns -1 if EOF or read error + */ +int +fluid_midi_file_getc(fluid_midi_file *mf) +{ + unsigned char c; + + if(mf->c >= 0) + { + c = mf->c; + mf->c = -1; + } + else + { + if(mf->buf_pos >= mf->buf_len) + { + mf->eof = TRUE; + return -1; + } + + c = mf->buffer[mf->buf_pos++]; + mf->trackpos++; + } + + return (int) c; +} + +/* + * Saves a byte to be returned the next time fluid_midi_file_getc() is called, + * when it is necessary according to running status. + */ +int +fluid_midi_file_push(fluid_midi_file *mf, int c) +{ + mf->c = c; + return FLUID_OK; +} + +/* + * fluid_midi_file_read + */ +int +fluid_midi_file_read(fluid_midi_file *mf, void *buf, int len) +{ + int num = len < mf->buf_len - mf->buf_pos + ? len : mf->buf_len - mf->buf_pos; + + if(num != len) + { + mf->eof = TRUE; + } + + if(num < 0) + { + num = 0; + } + + /* Note: Read bytes, even if there aren't enough, but only increment + * trackpos if successful (emulates old behaviour of fluid_midi_file_read) + */ + FLUID_MEMCPY(buf, mf->buffer + mf->buf_pos, num); + mf->buf_pos += num; + + if(num == len) + { + mf->trackpos += num; + } + +#if DEBUG + else + { + FLUID_LOG(FLUID_DBG, "Could not read the requested number of bytes"); + } + +#endif + return (num != len) ? FLUID_FAILED : FLUID_OK; +} + +/* + * fluid_midi_file_skip + */ +int +fluid_midi_file_skip(fluid_midi_file *mf, int skip) +{ + int new_pos = mf->buf_pos + skip; + + /* Mimic the behaviour of fseek: Error to seek past the start of file, but + * OK to seek past end (this just puts it into the EOF state). */ + if(new_pos < 0) + { + FLUID_LOG(FLUID_ERR, "Failed to seek position in file"); + return FLUID_FAILED; + } + + /* Clear the EOF flag, even if moved past the end of the file (this is + * consistent with the behaviour of fseek). */ + mf->eof = FALSE; + mf->buf_pos = new_pos; + return FLUID_OK; +} + +/* + * fluid_midi_file_eof + */ +int fluid_midi_file_eof(fluid_midi_file *mf) +{ + /* Note: This does not simply test whether the file read pointer is past + * the end of the file. It mimics the behaviour of feof by actually + * testing the stateful EOF condition, which is set to TRUE if getc or + * fread have attempted to read past the end (but not if they have + * precisely reached the end), but reset to FALSE upon a successful seek. + */ + return mf->eof; +} + +/* + * fluid_midi_file_read_mthd + */ +int +fluid_midi_file_read_mthd(fluid_midi_file *mf) +{ + char mthd[14]; + + if(fluid_midi_file_read(mf, mthd, sizeof(mthd)) != FLUID_OK) + { + return FLUID_FAILED; + } + + if((FLUID_STRNCMP(mthd, "MThd", 4) != 0) || (mthd[7] != 6) + || (mthd[9] > 2)) + { + FLUID_LOG(FLUID_ERR, + "Doesn't look like a MIDI file: invalid MThd header"); + return FLUID_FAILED; + } + + mf->type = mthd[9]; + mf->ntracks = (unsigned) mthd[11]; + mf->ntracks += (unsigned int)(mthd[10]) << 16; + + if((signed char)mthd[12] < 0) + { + mf->uses_smpte = 1; + mf->smpte_fps = -(signed char)mthd[12]; + mf->smpte_res = (unsigned) mthd[13]; + FLUID_LOG(FLUID_ERR, "File uses SMPTE timing -- Not implemented yet"); + return FLUID_FAILED; + } + else + { + mf->uses_smpte = 0; + mf->division = ((unsigned)mthd[12] << 8) | ((unsigned)mthd[13] & 0xff); + FLUID_LOG(FLUID_DBG, "Division=%d", mf->division); + } + + return FLUID_OK; +} + +/* + * fluid_midi_file_load_tracks + */ +int +fluid_midi_file_load_tracks(fluid_midi_file *mf, fluid_player_t *player) +{ + int i; + + for(i = 0; i < mf->ntracks; i++) + { + if(fluid_midi_file_read_track(mf, player, i) != FLUID_OK) + { + return FLUID_FAILED; + } + } + + return FLUID_OK; +} + +/* + * fluid_isasciistring + */ +int +fluid_isasciistring(char *s) +{ + /* From ctype.h */ +#define fluid_isascii(c) (((c) & ~0x7f) == 0) + + size_t i, len = FLUID_STRLEN(s); + + for(i = 0; i < len; i++) + { + if(!fluid_isascii(s[i])) + { + return 0; + } + } + + return 1; + +#undef fluid_isascii +} + +/* + * fluid_getlength + */ +long +fluid_getlength(const unsigned char *s) +{ + long i = 0; + i = s[3] | (s[2] << 8) | (s[1] << 16) | (s[0] << 24); + return i; +} + +/* + * fluid_midi_file_read_tracklen + */ +int +fluid_midi_file_read_tracklen(fluid_midi_file *mf) +{ + unsigned char length[5]; + + if(fluid_midi_file_read(mf, length, 4) != FLUID_OK) + { + return FLUID_FAILED; + } + + mf->tracklen = fluid_getlength(length); + mf->trackpos = 0; + mf->eot = 0; + return FLUID_OK; +} + +/* + * fluid_midi_file_eot + */ +int +fluid_midi_file_eot(fluid_midi_file *mf) +{ +#if DEBUG + + if(mf->trackpos > mf->tracklen) + { + printf("track overrun: %d > %d\n", mf->trackpos, mf->tracklen); + } + +#endif + return mf->eot || (mf->trackpos >= mf->tracklen); +} + +/* + * fluid_midi_file_read_track + */ +int +fluid_midi_file_read_track(fluid_midi_file *mf, fluid_player_t *player, int num) +{ + fluid_track_t *track; + unsigned char id[5], length[5]; + int found_track = 0; + int skip; + + if(fluid_midi_file_read(mf, id, 4) != FLUID_OK) + { + return FLUID_FAILED; + } + + id[4] = '\0'; + mf->dtime = 0; + + while(!found_track) + { + + if(fluid_isasciistring((char *) id) == 0) + { + FLUID_LOG(FLUID_ERR, + "A non-ascii track header found, corrupt file"); + return FLUID_FAILED; + + } + else if(FLUID_STRCMP((char *) id, "MTrk") == 0) + { + + found_track = 1; + + if(fluid_midi_file_read_tracklen(mf) != FLUID_OK) + { + return FLUID_FAILED; + } + + track = new_fluid_track(num); + + if(track == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + while(!fluid_midi_file_eot(mf)) + { + if(fluid_midi_file_read_event(mf, track) != FLUID_OK) + { + delete_fluid_track(track); + return FLUID_FAILED; + } + } + + /* Skip remaining track data, if any */ + if(mf->trackpos < mf->tracklen) + { + if(fluid_midi_file_skip(mf, mf->tracklen - mf->trackpos) != FLUID_OK) + { + delete_fluid_track(track); + return FLUID_FAILED; + } + } + + if(fluid_player_add_track(player, track) != FLUID_OK) + { + delete_fluid_track(track); + return FLUID_FAILED; + } + + } + else + { + found_track = 0; + + if(fluid_midi_file_read(mf, length, 4) != FLUID_OK) + { + return FLUID_FAILED; + } + + skip = fluid_getlength(length); + + /* fseek(mf->fp, skip, SEEK_CUR); */ + if(fluid_midi_file_skip(mf, skip) != FLUID_OK) + { + return FLUID_FAILED; + } + } + } + + if(fluid_midi_file_eof(mf)) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + return FLUID_OK; +} + +/* + * fluid_midi_file_read_varlen + */ +int +fluid_midi_file_read_varlen(fluid_midi_file *mf) +{ + int i; + int c; + mf->varlen = 0; + + for(i = 0;; i++) + { + if(i == 4) + { + FLUID_LOG(FLUID_ERR, "Invalid variable length number"); + return FLUID_FAILED; + } + + c = fluid_midi_file_getc(mf); + + if(c < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + if(c & 0x80) + { + mf->varlen |= (int)(c & 0x7F); + mf->varlen <<= 7; + } + else + { + mf->varlen += c; + break; + } + } + + return FLUID_OK; +} + +/* + * fluid_midi_file_read_event + */ +int +fluid_midi_file_read_event(fluid_midi_file *mf, fluid_track_t *track) +{ + int status; + int type; + int tempo; + unsigned char *metadata = NULL; + unsigned char *dyn_buf = NULL; + unsigned char static_buf[256]; + int nominator, denominator, clocks, notes; + fluid_midi_event_t *evt; + int channel = 0; + int param1 = 0; + int param2 = 0; + int size; + + /* read the delta-time of the event */ + if(fluid_midi_file_read_varlen(mf) != FLUID_OK) + { + return FLUID_FAILED; + } + + mf->dtime += mf->varlen; + + /* read the status byte */ + status = fluid_midi_file_getc(mf); + + if(status < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + /* not a valid status byte: use the running status instead */ + if((status & 0x80) == 0) + { + if((mf->running_status & 0x80) == 0) + { + FLUID_LOG(FLUID_ERR, "Undefined status and invalid running status"); + return FLUID_FAILED; + } + + fluid_midi_file_push(mf, status); + status = mf->running_status; + } + + /* check what message we have */ + + mf->running_status = status; + + if(status == MIDI_SYSEX) /* system exclusif */ + { + /* read the length of the message */ + if(fluid_midi_file_read_varlen(mf) != FLUID_OK) + { + return FLUID_FAILED; + } + + if(mf->varlen) + { + FLUID_LOG(FLUID_DBG, "%s: %d: alloc metadata, len = %d", __FILE__, + __LINE__, mf->varlen); + metadata = FLUID_MALLOC(mf->varlen + 1); + + if(metadata == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + /* read the data of the message */ + if(fluid_midi_file_read(mf, metadata, mf->varlen) != FLUID_OK) + { + FLUID_FREE(metadata); + return FLUID_FAILED; + } + + evt = new_fluid_midi_event(); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(metadata); + return FLUID_FAILED; + } + + evt->dtime = mf->dtime; + size = mf->varlen; + + if(metadata[mf->varlen - 1] == MIDI_EOX) + { + size--; + } + + /* Add SYSEX event and indicate that its dynamically allocated and should be freed with event */ + fluid_midi_event_set_sysex(evt, metadata, size, TRUE); + fluid_track_add_event(track, evt); + mf->dtime = 0; + } + + return FLUID_OK; + + } + else if(status == MIDI_META_EVENT) /* meta events */ + { + + int result = FLUID_OK; + + /* get the type of the meta message */ + type = fluid_midi_file_getc(mf); + + if(type < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + /* get the length of the data part */ + if(fluid_midi_file_read_varlen(mf) != FLUID_OK) + { + return FLUID_FAILED; + } + + if(mf->varlen < 255) + { + metadata = &static_buf[0]; + } + else + { + FLUID_LOG(FLUID_DBG, "%s: %d: alloc metadata, len = %d", __FILE__, + __LINE__, mf->varlen); + dyn_buf = FLUID_MALLOC(mf->varlen + 1); + + if(dyn_buf == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + metadata = dyn_buf; + } + + /* read the data */ + if(mf->varlen) + { + if(fluid_midi_file_read(mf, metadata, mf->varlen) != FLUID_OK) + { + if(dyn_buf) + { + FLUID_FREE(dyn_buf); + } + + return FLUID_FAILED; + } + } + + /* handle meta data */ + switch(type) + { + + case MIDI_COPYRIGHT: + metadata[mf->varlen] = 0; + break; + + case MIDI_TRACK_NAME: + metadata[mf->varlen] = 0; + fluid_track_set_name(track, (char *) metadata); + break; + + case MIDI_INST_NAME: + metadata[mf->varlen] = 0; + break; + + case MIDI_LYRIC: + case MIDI_TEXT: + { + void *tmp; + int size = mf->varlen + 1; + + /* NULL terminate strings for safety */ + metadata[size - 1] = '\0'; + + evt = new_fluid_midi_event(); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + result = FLUID_FAILED; + break; + } + + evt->dtime = mf->dtime; + + tmp = FLUID_MALLOC(size); + + if(tmp == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + delete_fluid_midi_event(evt); + evt = NULL; + result = FLUID_FAILED; + break; + } + + FLUID_MEMCPY(tmp, metadata, size); + + fluid_midi_event_set_sysex_LOCAL(evt, type, tmp, size, TRUE); + fluid_track_add_event(track, evt); + mf->dtime = 0; + } + break; + + case MIDI_MARKER: + break; + + case MIDI_CUE_POINT: + break; /* don't care much for text events */ + + case MIDI_EOT: + if(mf->varlen != 0) + { + FLUID_LOG(FLUID_ERR, "Invalid length for EndOfTrack event"); + result = FLUID_FAILED; + break; + } + + mf->eot = 1; + evt = new_fluid_midi_event(); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + result = FLUID_FAILED; + break; + } + + evt->dtime = mf->dtime; + evt->type = MIDI_EOT; + fluid_track_add_event(track, evt); + mf->dtime = 0; + break; + + case MIDI_SET_TEMPO: + if(mf->varlen != 3) + { + FLUID_LOG(FLUID_ERR, + "Invalid length for SetTempo meta event"); + result = FLUID_FAILED; + break; + } + + tempo = (metadata[0] << 16) + (metadata[1] << 8) + metadata[2]; + evt = new_fluid_midi_event(); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + result = FLUID_FAILED; + break; + } + + evt->dtime = mf->dtime; + evt->type = MIDI_SET_TEMPO; + evt->channel = 0; + evt->param1 = tempo; + evt->param2 = 0; + fluid_track_add_event(track, evt); + mf->dtime = 0; + break; + + case MIDI_SMPTE_OFFSET: + if(mf->varlen != 5) + { + FLUID_LOG(FLUID_ERR, + "Invalid length for SMPTE Offset meta event"); + result = FLUID_FAILED; + break; + } + + break; /* we don't use smtp */ + + case MIDI_TIME_SIGNATURE: + if(mf->varlen != 4) + { + FLUID_LOG(FLUID_ERR, + "Invalid length for TimeSignature meta event"); + result = FLUID_FAILED; + break; + } + + nominator = metadata[0]; + denominator = pow(2.0, (double) metadata[1]); + clocks = metadata[2]; + notes = metadata[3]; + + FLUID_LOG(FLUID_DBG, + "signature=%d/%d, metronome=%d, 32nd-notes=%d", + nominator, denominator, clocks, notes); + + break; + + case MIDI_KEY_SIGNATURE: + if(mf->varlen != 2) + { + FLUID_LOG(FLUID_ERR, + "Invalid length for KeySignature meta event"); + result = FLUID_FAILED; + break; + } + + /* We don't care about key signatures anyway */ + /* sf = metadata[0]; + mi = metadata[1]; */ + break; + + case MIDI_SEQUENCER_EVENT: + break; + + default: + break; + } + + if(dyn_buf) + { + FLUID_LOG(FLUID_DBG, "%s: %d: free metadata", __FILE__, __LINE__); + FLUID_FREE(dyn_buf); + } + + return result; + + } + else /* channel messages */ + { + + type = status & 0xf0; + channel = status & 0x0f; + + /* all channel message have at least 1 byte of associated data */ + if((param1 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + switch(type) + { + + case NOTE_ON: + if((param2 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + break; + + case NOTE_OFF: + if((param2 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + break; + + case KEY_PRESSURE: + if((param2 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + break; + + case CONTROL_CHANGE: + if((param2 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + break; + + case PROGRAM_CHANGE: + break; + + case CHANNEL_PRESSURE: + break; + + case PITCH_BEND: + if((param2 = fluid_midi_file_getc(mf)) < 0) + { + FLUID_LOG(FLUID_ERR, "Unexpected end of file"); + return FLUID_FAILED; + } + + param1 = ((param2 & 0x7f) << 7) | (param1 & 0x7f); + param2 = 0; + break; + + default: + /* Can't possibly happen !? */ + FLUID_LOG(FLUID_ERR, "Unrecognized MIDI event"); + return FLUID_FAILED; + } + + evt = new_fluid_midi_event(); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + evt->dtime = mf->dtime; + evt->type = type; + evt->channel = channel; + evt->param1 = param1; + evt->param2 = param2; + fluid_track_add_event(track, evt); + mf->dtime = 0; + } + + return FLUID_OK; +} + +/* + * fluid_midi_file_get_division + */ +int +fluid_midi_file_get_division(fluid_midi_file *midifile) +{ + return midifile->division; +} + +/****************************************************** + * + * fluid_track_t + */ + +/** + * Create a MIDI event structure. + * @return New MIDI event structure or NULL when out of memory. + */ +fluid_midi_event_t * +new_fluid_midi_event() +{ + fluid_midi_event_t *evt; + evt = FLUID_NEW(fluid_midi_event_t); + + if(evt == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + evt->dtime = 0; + evt->type = 0; + evt->channel = 0; + evt->param1 = 0; + evt->param2 = 0; + evt->next = NULL; + evt->paramptr = NULL; + return evt; +} + +/** + * Delete MIDI event structure. + * @param evt MIDI event structure + */ +void +delete_fluid_midi_event(fluid_midi_event_t *evt) +{ + fluid_midi_event_t *temp; + fluid_return_if_fail(evt != NULL); + + while(evt) + { + temp = evt->next; + + /* Dynamic SYSEX event? - free (param2 indicates if dynamic) */ + if((evt->type == MIDI_SYSEX || (evt-> type == MIDI_TEXT) || (evt->type == MIDI_LYRIC)) && + evt->paramptr && evt->param2) + { + FLUID_FREE(evt->paramptr); + } + + FLUID_FREE(evt); + evt = temp; + } +} + +/** + * Get the event type field of a MIDI event structure. + * @param evt MIDI event structure + * @return Event type field (MIDI status byte without channel) + */ +int +fluid_midi_event_get_type(const fluid_midi_event_t *evt) +{ + return evt->type; +} + +/** + * Set the event type field of a MIDI event structure. + * @param evt MIDI event structure + * @param type Event type field (MIDI status byte without channel) + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_type(fluid_midi_event_t *evt, int type) +{ + evt->type = type; + return FLUID_OK; +} + +/** + * Get the channel field of a MIDI event structure. + * @param evt MIDI event structure + * @return Channel field + */ +int +fluid_midi_event_get_channel(const fluid_midi_event_t *evt) +{ + return evt->channel; +} + +/** + * Set the channel field of a MIDI event structure. + * @param evt MIDI event structure + * @param chan MIDI channel field + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_channel(fluid_midi_event_t *evt, int chan) +{ + evt->channel = chan; + return FLUID_OK; +} + +/** + * Get the key field of a MIDI event structure. + * @param evt MIDI event structure + * @return MIDI note number (0-127) + */ +int +fluid_midi_event_get_key(const fluid_midi_event_t *evt) +{ + return evt->param1; +} + +/** + * Set the key field of a MIDI event structure. + * @param evt MIDI event structure + * @param v MIDI note number (0-127) + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_key(fluid_midi_event_t *evt, int v) +{ + evt->param1 = v; + return FLUID_OK; +} + +/** + * Get the velocity field of a MIDI event structure. + * @param evt MIDI event structure + * @return MIDI velocity number (0-127) + */ +int +fluid_midi_event_get_velocity(const fluid_midi_event_t *evt) +{ + return evt->param2; +} + +/** + * Set the velocity field of a MIDI event structure. + * @param evt MIDI event structure + * @param v MIDI velocity value + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_velocity(fluid_midi_event_t *evt, int v) +{ + evt->param2 = v; + return FLUID_OK; +} + +/** + * Get the control number of a MIDI event structure. + * @param evt MIDI event structure + * @return MIDI control number + */ +int +fluid_midi_event_get_control(const fluid_midi_event_t *evt) +{ + return evt->param1; +} + +/** + * Set the control field of a MIDI event structure. + * @param evt MIDI event structure + * @param v MIDI control number + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_control(fluid_midi_event_t *evt, int v) +{ + evt->param1 = v; + return FLUID_OK; +} + +/** + * Get the value field from a MIDI event structure. + * @param evt MIDI event structure + * @return Value field + */ +int +fluid_midi_event_get_value(const fluid_midi_event_t *evt) +{ + return evt->param2; +} + +/** + * Set the value field of a MIDI event structure. + * @param evt MIDI event structure + * @param v Value to assign + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_value(fluid_midi_event_t *evt, int v) +{ + evt->param2 = v; + return FLUID_OK; +} + +/** + * Get the program field of a MIDI event structure. + * @param evt MIDI event structure + * @return MIDI program number (0-127) + */ +int +fluid_midi_event_get_program(const fluid_midi_event_t *evt) +{ + return evt->param1; +} + +/** + * Set the program field of a MIDI event structure. + * @param evt MIDI event structure + * @param val MIDI program number (0-127) + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_program(fluid_midi_event_t *evt, int val) +{ + evt->param1 = val; + return FLUID_OK; +} + +/** + * Get the pitch field of a MIDI event structure. + * @param evt MIDI event structure + * @return Pitch value (14 bit value, 0-16383, 8192 is center) + */ +int +fluid_midi_event_get_pitch(const fluid_midi_event_t *evt) +{ + return evt->param1; +} + +/** + * Set the pitch field of a MIDI event structure. + * @param evt MIDI event structure + * @param val Pitch value (14 bit value, 0-16383, 8192 is center) + * @return Always returns FLUID_OK + */ +int +fluid_midi_event_set_pitch(fluid_midi_event_t *evt, int val) +{ + evt->param1 = val; + return FLUID_OK; +} + +/** + * Assign sysex data to a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to SYSEX data + * @param size Size of SYSEX data in bytes + * @param dynamic TRUE if the SYSEX data has been dynamically allocated and + * should be freed when the event is freed (only applies if event gets destroyed + * with delete_fluid_midi_event()) + * @return Always returns #FLUID_OK + */ +int +fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dynamic) +{ + fluid_midi_event_set_sysex_LOCAL(evt, MIDI_SYSEX, data, size, dynamic); + return FLUID_OK; +} + +/** + * Assign text data to a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to text data + * @param size Size of text data in bytes + * @param dynamic TRUE if the data has been dynamically allocated and + * should be freed when the event is freed via delete_fluid_midi_event() + * @return Always returns #FLUID_OK + * + * @since 2.0.0 + */ +int +fluid_midi_event_set_text(fluid_midi_event_t *evt, void *data, int size, int dynamic) +{ + fluid_midi_event_set_sysex_LOCAL(evt, MIDI_TEXT, data, size, dynamic); + return FLUID_OK; +} + +/** + * Get the text of a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to return text data on. + * @param size Pointer to return text size on. + * @return Returns #FLUID_OK if \p data and \p size previously set by + * fluid_midi_event_set_text() have been successfully retrieved. + * Else #FLUID_FAILED is returned and \p data and \p size are not changed. + * @since 2.0.3 + */ +int fluid_midi_event_get_text(fluid_midi_event_t *evt, void **data, int *size) +{ + fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); + fluid_return_val_if_fail(evt->type == MIDI_TEXT, FLUID_FAILED); + + fluid_midi_event_get_sysex_LOCAL(evt, data, size); + return FLUID_OK; +} + +/** + * Assign lyric data to a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to lyric data + * @param size Size of lyric data in bytes + * @param dynamic TRUE if the data has been dynamically allocated and + * should be freed when the event is freed via delete_fluid_midi_event() + * @return Always returns #FLUID_OK + * + * @since 2.0.0 + */ +int +fluid_midi_event_set_lyrics(fluid_midi_event_t *evt, void *data, int size, int dynamic) +{ + fluid_midi_event_set_sysex_LOCAL(evt, MIDI_LYRIC, data, size, dynamic); + return FLUID_OK; +} + +/** + * Get the lyric of a MIDI event structure. + * @param evt MIDI event structure + * @param data Pointer to return lyric data on. + * @param size Pointer to return lyric size on. + * @return Returns #FLUID_OK if \p data and \p size previously set by + * fluid_midi_event_set_lyrics() have been successfully retrieved. + * Else #FLUID_FAILED is returned and \p data and \p size are not changed. + * @since 2.0.3 + */ +int fluid_midi_event_get_lyrics(fluid_midi_event_t *evt, void **data, int *size) +{ + fluid_return_val_if_fail(evt != NULL, FLUID_FAILED); + fluid_return_val_if_fail(evt->type == MIDI_LYRIC, FLUID_FAILED); + + fluid_midi_event_get_sysex_LOCAL(evt, data, size); + return FLUID_OK; +} + +static void fluid_midi_event_set_sysex_LOCAL(fluid_midi_event_t *evt, int type, void *data, int size, int dynamic) +{ + evt->type = type; + evt->paramptr = data; + evt->param1 = size; + evt->param2 = dynamic; +} + +static void fluid_midi_event_get_sysex_LOCAL(fluid_midi_event_t *evt, void **data, int *size) +{ + if(data) + { + *data = evt->paramptr; + } + + if(size) + { + *size = evt->param1; + } +} + +/****************************************************** + * + * fluid_track_t + */ + +/* + * new_fluid_track + */ +fluid_track_t * +new_fluid_track(int num) +{ + fluid_track_t *track; + track = FLUID_NEW(fluid_track_t); + + if(track == NULL) + { + return NULL; + } + + track->name = NULL; + track->num = num; + track->first = NULL; + track->cur = NULL; + track->last = NULL; + track->ticks = 0; + return track; +} + +/* + * delete_fluid_track + */ +void +delete_fluid_track(fluid_track_t *track) +{ + fluid_return_if_fail(track != NULL); + + FLUID_FREE(track->name); + delete_fluid_midi_event(track->first); + FLUID_FREE(track); +} + +/* + * fluid_track_set_name + */ +int +fluid_track_set_name(fluid_track_t *track, char *name) +{ + size_t len; + + if(track->name != NULL) + { + FLUID_FREE(track->name); + } + + if(name == NULL) + { + track->name = NULL; + return FLUID_OK; + } + + len = FLUID_STRLEN(name); + track->name = FLUID_MALLOC(len + 1); + + if(track->name == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + FLUID_STRCPY(track->name, name); + return FLUID_OK; +} + +/* + * fluid_track_get_duration + */ +int +fluid_track_get_duration(fluid_track_t *track) +{ + int time = 0; + fluid_midi_event_t *evt = track->first; + + while(evt != NULL) + { + time += evt->dtime; + evt = evt->next; + } + + return time; +} + +/* + * fluid_track_add_event + */ +int +fluid_track_add_event(fluid_track_t *track, fluid_midi_event_t *evt) +{ + evt->next = NULL; + + if(track->first == NULL) + { + track->first = evt; + track->cur = evt; + track->last = evt; + } + else + { + track->last->next = evt; + track->last = evt; + } + + return FLUID_OK; +} + +/* + * fluid_track_next_event + */ +fluid_midi_event_t * +fluid_track_next_event(fluid_track_t *track) +{ + if(track->cur != NULL) + { + track->cur = track->cur->next; + } + + return track->cur; +} + +/* + * fluid_track_reset + */ +int +fluid_track_reset(fluid_track_t *track) +{ + track->ticks = 0; + track->cur = track->first; + return FLUID_OK; +} + +/* + * fluid_track_send_events + */ +static void +fluid_track_send_events(fluid_track_t *track, + fluid_synth_t *synth, + fluid_player_t *player, + unsigned int ticks, + int seek_ticks + ) +{ + fluid_midi_event_t *event; + int seeking = seek_ticks >= 0; + + if(seeking) + { + ticks = seek_ticks; /* update target ticks */ + + if(track->ticks > ticks) + { + fluid_track_reset(track); /* reset track if seeking backwards */ + } + } + + while(1) + { + + event = track->cur; + + if(event == NULL) + { + return; + } + + /* printf("track=%02d\tticks=%05u\ttrack=%05u\tdtime=%05u\tnext=%05u\n", */ + /* track->num, */ + /* ticks, */ + /* track->ticks, */ + /* event->dtime, */ + /* track->ticks + event->dtime); */ + + if(track->ticks + event->dtime > ticks) + { + return; + } + + track->ticks += event->dtime; + + if(!player || event->type == MIDI_EOT) + { + /* don't send EOT events to the callback */ + } + else if(seeking && track->ticks != ticks && (event->type == NOTE_ON || event->type == NOTE_OFF)) + { + /* skip on/off messages */ + } + else + { + if(player->playback_callback) + { + player->playback_callback(player->playback_userdata, event); + if(event->type == NOTE_ON && event->param2 != 0 && !player->channel_isplaying[event->channel]) + { + player->channel_isplaying[event->channel] = TRUE; + } + } + } + + if(event->type == MIDI_SET_TEMPO && player != NULL) + { + /* memorize the tempo change value coming from the MIDI file */ + fluid_atomic_int_set(&player->miditempo, event->param1); + fluid_player_update_tempo(player); + } + + fluid_track_next_event(track); + + } +} + +/****************************************************** + * + * fluid_player + */ +static void +fluid_player_handle_reset_synth(void *data, const char *name, int value) +{ + fluid_player_t *player = data; + fluid_return_if_fail(player != NULL); + + player->reset_synth_between_songs = value; +} + +/** + * Create a new MIDI player. + * @param synth Fluid synthesizer instance to create player for + * @return New MIDI player instance or NULL on error (out of memory) + */ +fluid_player_t * +new_fluid_player(fluid_synth_t *synth) +{ + int i; + fluid_player_t *player; + player = FLUID_NEW(fluid_player_t); + + if(player == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + fluid_atomic_int_set(&player->status, FLUID_PLAYER_READY); + fluid_atomic_int_set(&player->stopping, 0); + player->loop = 1; + player->ntracks = 0; + + for(i = 0; i < MAX_NUMBER_OF_TRACKS; i++) + { + player->track[i] = NULL; + } + + player->synth = synth; + player->system_timer = NULL; + player->sample_timer = NULL; + player->playlist = NULL; + player->currentfile = NULL; + player->division = 0; + + /* internal tempo (from MIDI file) in micro seconds per quarter note */ + player->sync_mode = 1; /* the player follows internal tempo change */ + player->miditempo = 500000; + /* external tempo in micro seconds per quarter note */ + player->exttempo = 500000; + /* tempo multiplier */ + player->multempo = 1.0F; + + player->deltatime = 4.0; + player->cur_msec = 0; + player->cur_ticks = 0; + player->end_msec = -1; + player->end_pedals_disabled = 0; + player->last_callback_ticks = -1; + fluid_atomic_int_set(&player->seek_ticks, -1); + fluid_player_set_playback_callback(player, fluid_synth_handle_midi_event, synth); + fluid_player_set_tick_callback(player, NULL, NULL); + player->use_system_timer = fluid_settings_str_equal(synth->settings, + "player.timing-source", "system"); + if(player->use_system_timer) + { + player->system_timer = new_fluid_timer((int) player->deltatime, + fluid_player_callback, player, TRUE, FALSE, TRUE); + + if(player->system_timer == NULL) + { + goto err; + } + } + else + { + player->sample_timer = new_fluid_sample_timer(player->synth, + fluid_player_callback, player); + + if(player->sample_timer == NULL) + { + goto err; + } + } + + fluid_settings_getint(synth->settings, "player.reset-synth", &i); + fluid_player_handle_reset_synth(player, NULL, i); + + fluid_settings_callback_int(synth->settings, "player.reset-synth", + fluid_player_handle_reset_synth, player); + + return player; + +err: + delete_fluid_player(player); + return NULL; +} + +/** + * Delete a MIDI player instance. + * @param player MIDI player instance + * @warning Do not call when the synthesizer associated to this \p player renders audio, + * i.e. an audio driver is running or any other synthesizer thread concurrently calls + * fluid_synth_process() or fluid_synth_nwrite_float() or fluid_synth_write_*() ! + */ +void +delete_fluid_player(fluid_player_t *player) +{ + fluid_list_t *q; + fluid_playlist_item *pi; + + fluid_return_if_fail(player != NULL); + + fluid_settings_callback_int(player->synth->settings, "player.reset-synth", + NULL, NULL); + + fluid_player_stop(player); + fluid_player_reset(player); + + delete_fluid_timer(player->system_timer); + delete_fluid_sample_timer(player->synth, player->sample_timer); + + while(player->playlist != NULL) + { + q = player->playlist->next; + pi = (fluid_playlist_item *) player->playlist->data; + FLUID_FREE(pi->filename); + FLUID_FREE(pi->buffer); + FLUID_FREE(pi); + delete1_fluid_list(player->playlist); + player->playlist = q; + } + + FLUID_FREE(player); +} + +/** + * Registers settings related to the MIDI player + */ +void +fluid_player_settings(fluid_settings_t *settings) +{ + /* player.timing-source can be either "system" (use system timer) + or "sample" (use timer based on number of written samples) */ + fluid_settings_register_str(settings, "player.timing-source", "sample", 0); + fluid_settings_add_option(settings, "player.timing-source", "sample"); + fluid_settings_add_option(settings, "player.timing-source", "system"); + + /* Selects whether the player should reset the synth between songs, or not. */ + fluid_settings_register_int(settings, "player.reset-synth", 1, 0, 1, FLUID_HINT_TOGGLED); +} + + +int +fluid_player_reset(fluid_player_t *player) +{ + int i; + + for(i = 0; i < MAX_NUMBER_OF_TRACKS; i++) + { + if(player->track[i] != NULL) + { + delete_fluid_track(player->track[i]); + player->track[i] = NULL; + } + } + + for(i = 0; i < MAX_NUMBER_OF_CHANNELS; i++) + { + player->channel_isplaying[i] = FALSE; + } + + /* player->current_file = NULL; */ + /* player->status = FLUID_PLAYER_READY; */ + /* player->loop = 1; */ + player->ntracks = 0; + player->division = 0; + player->miditempo = 500000; + player->deltatime = 4.0; + return 0; +} + +/* + * fluid_player_add_track + */ +int +fluid_player_add_track(fluid_player_t *player, fluid_track_t *track) +{ + if(player->ntracks < MAX_NUMBER_OF_TRACKS) + { + player->track[player->ntracks++] = track; + return FLUID_OK; + } + else + { + return FLUID_FAILED; + } +} + +/** + * 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 +fluid_player_set_playback_callback(fluid_player_t *player, + handle_midi_event_func_t handler, void *handler_data) +{ + player->playback_callback = handler; + player->playback_userdata = handler_data; + return FLUID_OK; +} + +/** + * Add a listener function for every MIDI tick change. + * + * @param player MIDI player instance + * @param handler Pointer to callback function + * @param handler_data Opaque parameter to be sent to the callback function + * @returns #FLUID_OK + * + * This callback is not set by default, but can optionally + * be changed to a user-defined function for intercepting all MIDI + * tick changes and react to them with precision. + * + * @since 2.2.0 + */ +int +fluid_player_set_tick_callback(fluid_player_t *player, handle_midi_tick_func_t handler, void *handler_data) +{ + player->tick_callback = handler; + player->tick_userdata = handler_data; + return FLUID_OK; +} + +/** + * Add a MIDI file to a player queue. + * @param player MIDI player instance + * @param midifile File name of the MIDI file to add + * @return #FLUID_OK or #FLUID_FAILED + */ +int +fluid_player_add(fluid_player_t *player, const char *midifile) +{ + fluid_playlist_item *pi = FLUID_MALLOC(sizeof(fluid_playlist_item)); + char *f = FLUID_STRDUP(midifile); + + if(!pi || !f) + { + FLUID_FREE(pi); + FLUID_FREE(f); + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + pi->filename = f; + pi->buffer = NULL; + pi->buffer_len = 0; + player->playlist = fluid_list_append(player->playlist, pi); + return FLUID_OK; +} + +/** + * Add a MIDI file to a player queue, from a buffer in memory. + * @param player MIDI player instance + * @param buffer Pointer to memory containing the bytes of a complete MIDI + * file. The data is copied, so the caller may free or modify it immediately + * without affecting the playlist. + * @param len Length of the buffer, in bytes. + * @return #FLUID_OK or #FLUID_FAILED + */ +int +fluid_player_add_mem(fluid_player_t *player, const void *buffer, size_t len) +{ + /* Take a copy of the buffer, so the caller can free immediately. */ + fluid_playlist_item *pi = FLUID_MALLOC(sizeof(fluid_playlist_item)); + void *buf_copy = FLUID_MALLOC(len); + + if(!pi || !buf_copy) + { + FLUID_FREE(pi); + FLUID_FREE(buf_copy); + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + FLUID_MEMCPY(buf_copy, buffer, len); + pi->filename = NULL; + pi->buffer = buf_copy; + pi->buffer_len = len; + player->playlist = fluid_list_append(player->playlist, pi); + return FLUID_OK; +} + +/* + * fluid_player_load + */ +int +fluid_player_load(fluid_player_t *player, fluid_playlist_item *item) +{ + fluid_midi_file *midifile; + char *buffer; + size_t buffer_length; + int buffer_owned; + + if(item->filename != NULL) + { + fluid_file fp; + /* This file is specified by filename; load the file from disk */ + FLUID_LOG(FLUID_DBG, "%s: %d: Loading midifile %s", __FILE__, __LINE__, + item->filename); + /* Read the entire contents of the file into the buffer */ + fp = FLUID_FOPEN(item->filename, "rb"); + + if(fp == NULL) + { + FLUID_LOG(FLUID_ERR, "Couldn't open the MIDI file"); + return FLUID_FAILED; + } + + buffer = fluid_file_read_full(fp, &buffer_length); + + FLUID_FCLOSE(fp); + + if(buffer == NULL) + { + return FLUID_FAILED; + } + + buffer_owned = 1; + } + else + { + /* This file is specified by a pre-loaded buffer; load from memory */ + FLUID_LOG(FLUID_DBG, "%s: %d: Loading midifile from memory (%p)", + __FILE__, __LINE__, item->buffer); + buffer = (char *) item->buffer; + buffer_length = item->buffer_len; + /* Do not free the buffer (it is owned by the playlist) */ + buffer_owned = 0; + } + + midifile = new_fluid_midi_file(buffer, buffer_length); + + if(midifile == NULL) + { + if(buffer_owned) + { + FLUID_FREE(buffer); + } + + return FLUID_FAILED; + } + + player->division = fluid_midi_file_get_division(midifile); + fluid_player_update_tempo(player); // Update deltatime + /*FLUID_LOG(FLUID_DBG, "quarter note division=%d\n", player->division); */ + + if(fluid_midi_file_load_tracks(midifile, player) != FLUID_OK) + { + if(buffer_owned) + { + FLUID_FREE(buffer); + } + + delete_fluid_midi_file(midifile); + return FLUID_FAILED; + } + + delete_fluid_midi_file(midifile); + + if(buffer_owned) + { + FLUID_FREE(buffer); + } + + return FLUID_OK; +} + +void +fluid_player_advancefile(fluid_player_t *player) +{ + if(player->playlist == NULL) + { + return; /* No files to play */ + } + + if(player->currentfile != NULL) + { + player->currentfile = fluid_list_next(player->currentfile); + } + + if(player->currentfile == NULL) + { + if(player->loop == 0) + { + return; /* We're done playing */ + } + + if(player->loop > 0) + { + player->loop--; + } + + player->currentfile = player->playlist; + } +} + +void +fluid_player_playlist_load(fluid_player_t *player, unsigned int msec) +{ + fluid_playlist_item *current_playitem; + int i; + + do + { + fluid_player_advancefile(player); + + if(player->currentfile == NULL) + { + /* Failed to find next song, probably since we're finished */ + fluid_atomic_int_set(&player->status, FLUID_PLAYER_DONE); + return; + } + + fluid_player_reset(player); + current_playitem = (fluid_playlist_item *) player->currentfile->data; + } + while(fluid_player_load(player, current_playitem) != FLUID_OK); + + /* Successfully loaded midi file */ + + player->begin_msec = msec; + player->start_msec = msec; + player->start_ticks = 0; + player->cur_ticks = 0; + + for(i = 0; i < player->ntracks; i++) + { + if(player->track[i] != NULL) + { + fluid_track_reset(player->track[i]); + } + } +} + +/* + * fluid_player_callback + */ +int +fluid_player_callback(void *data, unsigned int msec) +{ + int i; + int loadnextfile; + int status = FLUID_PLAYER_DONE; + fluid_midi_event_t mute_event; + fluid_player_t *player; + fluid_synth_t *synth; + player = (fluid_player_t *) data; + synth = player->synth; + + loadnextfile = player->currentfile == NULL ? 1 : 0; + + fluid_midi_event_set_type(&mute_event, CONTROL_CHANGE); + mute_event.param1 = ALL_SOUND_OFF; + mute_event.param2 = 1; + + if(fluid_player_get_status(player) != FLUID_PLAYER_PLAYING) + { + if(fluid_atomic_int_get(&player->stopping)) + { + for(i = 0; i < synth->midi_channels; i++) + { + if(player->channel_isplaying[i]) + { + fluid_midi_event_set_channel(&mute_event, i); + player->playback_callback(player->playback_userdata, &mute_event); + player->channel_isplaying[i] = FALSE; + } + } + fluid_atomic_int_set(&player->stopping, 0); + } + return 1; + } + do + { + float deltatime; + int seek_ticks; + + if(loadnextfile) + { + loadnextfile = 0; + fluid_player_playlist_load(player, msec); + + if(player->currentfile == NULL) + { + return 0; + } + } + + if(msec < player->cur_msec) + { + // overflow of fluid_synth_get_ticks() + FLUID_LOG(FLUID_ERR, "The maximum playback duration has been reached. Terminating player!"); + fluid_player_stop(player); + fluid_player_seek(player, 0); + player->cur_ticks = 0; + return 0; + } + + player->cur_msec = msec; + deltatime = fluid_atomic_float_get(&player->deltatime); + player->cur_ticks = (player->start_ticks + + (int)((double)(player->cur_msec - player->start_msec) + / deltatime + 0.5)); /* 0.5 to average overall error when casting */ + + seek_ticks = fluid_atomic_int_get(&player->seek_ticks); + if(seek_ticks >= 0) + { + for(i = 0; i < synth->midi_channels; i++) + { + if(player->channel_isplaying[i]) + { + fluid_midi_event_set_channel(&mute_event, i); + player->playback_callback(player->playback_userdata, &mute_event); + player->channel_isplaying[i] = FALSE; + } + } + } + + for(i = 0; i < player->ntracks; i++) + { + fluid_track_send_events(player->track[i], synth, player, player->cur_ticks, seek_ticks); + if(!fluid_track_eot(player->track[i])) + { + status = FLUID_PLAYER_PLAYING; + } + } + + if(seek_ticks >= 0) + { + player->start_ticks = seek_ticks; /* tick position of last tempo value (which is now) */ + player->cur_ticks = seek_ticks; + player->begin_msec = msec; /* only used to calculate the duration of playing */ + player->start_msec = msec; /* should be the (synth)-time of the last tempo change */ + fluid_atomic_int_set(&player->seek_ticks, -1); /* clear seek_ticks */ + } + + if(fluid_list_next(player->currentfile) == NULL && player->loop == 0) + { + /* Once we've run out of MIDI events, keep playing until no voices are active */ + if(status == FLUID_PLAYER_DONE && fluid_synth_get_active_voice_count(player->synth) > 0) + { + /* The first time we notice we've run out of MIDI events but there are still active voices, disable all hold pedals */ + if(!player->end_pedals_disabled) + { + for(i = 0; i < synth->midi_channels; i++) + { + fluid_synth_cc(player->synth, i, SUSTAIN_SWITCH, 0); + fluid_synth_cc(player->synth, i, SOSTENUTO_SWITCH, 0); + } + + player->end_pedals_disabled = 1; + } + + status = FLUID_PLAYER_PLAYING; + } + + /* Once no voices are active, if end_msec hasn't been scheduled, schedule it so we wait for reverb, etc to finish */ + if(status == FLUID_PLAYER_DONE && player->end_msec < 0) + { + player->end_msec = msec + FLUID_PLAYER_STOP_GRACE_MS; + } + /* If end_msec has been scheduled and is in the future, keep playing */ + if (player->end_msec >= 0 && msec < (unsigned int) player->end_msec) + { + status = FLUID_PLAYER_PLAYING; + } + } + + /* Once there's no reason to keep playing, we're actually done */ + if(status == FLUID_PLAYER_DONE) + { + FLUID_LOG(FLUID_DBG, "%s: %d: Duration=%.3f sec", __FILE__, + __LINE__, (msec - player->begin_msec) / 1000.0); + + if(player->reset_synth_between_songs) + { + fluid_synth_system_reset(player->synth); + } + + loadnextfile = 1; + } + + if (player->tick_callback != NULL && player->last_callback_ticks != player->cur_ticks) { + player->tick_callback(player->tick_userdata, player->cur_ticks); + player->last_callback_ticks = player->cur_ticks; + } + } + while(loadnextfile); + + /* do not update the status if the player has been stopped already */ + fluid_atomic_int_compare_and_exchange(&player->status, FLUID_PLAYER_PLAYING, status); + + return 1; +} + +/** + * Activates play mode for a MIDI player if not already playing. + * @param player MIDI player instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * If the list of files added to the player has completed its requested number of loops, + * the playlist will be restarted from the beginning with a loop count of 1. + */ +int +fluid_player_play(fluid_player_t *player) +{ + if(fluid_player_get_status(player) == FLUID_PLAYER_PLAYING || + player->playlist == NULL) + { + return FLUID_OK; + } + + if(!player->use_system_timer) + { + fluid_sample_timer_reset(player->synth, player->sample_timer); + } + + /* If we're at the end of the playlist and there are no loops left, loop once */ + if(player->currentfile == NULL && player->loop == 0) + { + player->loop = 1; + } + + player->end_msec = -1; + player->end_pedals_disabled = 0; + + fluid_atomic_int_set(&player->status, FLUID_PLAYER_PLAYING); + + return FLUID_OK; +} + +/** + * Pauses the MIDI playback. + * + * @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) +{ + fluid_atomic_int_set(&player->status, FLUID_PLAYER_DONE); + fluid_atomic_int_set(&player->stopping, 1); + fluid_player_seek(player, fluid_player_get_current_tick(player)); + return FLUID_OK; +} + +/** + * Get MIDI player status. + * @param player MIDI player instance + * @return Player status (#fluid_player_status) + * @since 1.1.0 + */ +int +fluid_player_get_status(fluid_player_t *player) +{ + return fluid_atomic_int_get(&player->status); +} + +/** + * Seek in the currently playing file. + * + * @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) +{ + if(ticks < 0 || (fluid_player_get_status(player) != FLUID_PLAYER_READY && ticks > fluid_player_get_total_ticks(player))) + { + return FLUID_FAILED; + } + + if(fluid_player_get_status(player) == FLUID_PLAYER_PLAYING) + { + if(fluid_atomic_int_compare_and_exchange(&player->seek_ticks, -1, ticks)) + { + // new seek position has been set, as no previous seek was in progress + return FLUID_OK; + } + } + else + { + // If the player is not currently playing, a new seek position can be set at any time. This allows + // the user to do: + // fluid_player_stop(); + // fluid_player_seek(0); // to beginning + fluid_atomic_int_set(&player->seek_ticks, ticks); + return FLUID_OK; + } + + // a previous seek is still in progress or hasn't been processed yet + return FLUID_FAILED; +} + + +/** + * 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 + * + * 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) +{ + player->loop = loop; + return FLUID_OK; +} + +/** + * update the MIDI player internal deltatime dependent of actual tempo. + * @param player MIDI player instance + */ +static void fluid_player_update_tempo(fluid_player_t *player) +{ + int tempo; /* tempo in micro seconds by quarter note */ + float deltatime; + + /* do nothing if the division is still unknown to avoid a div by zero */ + if(player->division == 0) + { + return; + } + + if(fluid_atomic_int_get(&player->sync_mode)) + { + /* take internal tempo from MIDI file */ + tempo = fluid_atomic_int_get(&player->miditempo); + /* compute deltattime (in ms) from current tempo and apply tempo multiplier */ + deltatime = (float)tempo / (float)player->division / (float)1000.0; + deltatime /= fluid_atomic_float_get(&player->multempo); /* multiply tempo */ + } + else + { + /* take external tempo */ + tempo = fluid_atomic_int_get(&player->exttempo); + /* compute deltattime (in ms) from current tempo */ + deltatime = (float)tempo / (float)player->division / (float)1000.0; + } + + fluid_atomic_float_set(&player->deltatime, deltatime); + + player->start_msec = player->cur_msec; + player->start_ticks = player->cur_ticks; + + FLUID_LOG(FLUID_DBG, + "tempo=%d, tick time=%f msec, cur time=%d msec, cur tick=%d", + tempo, player->deltatime, player->cur_msec, player->cur_ticks); + +} + +/** + * Set the tempo of a MIDI player. + * The player can be controlled by internal tempo coming from MIDI file tempo + * change or controlled by external tempo expressed in BPM or in micro seconds + * per quarter note. + * + * @param player MIDI player instance. Must be a valid pointer. + * @param tempo_type Must a be value of #fluid_player_set_tempo_type and indicates the + * meaning of tempo value and how the player will be controlled, see below. + * @param tempo Tempo value or multiplier. + * + * #FLUID_PLAYER_TEMPO_INTERNAL, the player will be controlled by internal + * MIDI file tempo changes. If there are no tempo change in the MIDI file a default + * value of 120 bpm is used. The @c tempo parameter is used as a multiplier factor + * that must be in the range (0.001 to 1000). + * For example, if the current MIDI file tempo is 120 bpm and the multiplier + * value is 0.5 then this tempo will be slowed down to 60 bpm. + * At creation, the player is set to be controlled by internal tempo with a + * multiplier factor set to 1.0. + * + * #FLUID_PLAYER_TEMPO_EXTERNAL_BPM, the player will be controlled by the + * external tempo value provided by the tempo parameter in bpm + * (i.e in quarter notes per minute) which must be in the range (1 to 60000000). + * + * #FLUID_PLAYER_TEMPO_EXTERNAL_MIDI, similar as FLUID_PLAYER_TEMPO_EXTERNAL_BPM, + * but the tempo parameter value is in micro seconds per quarter note which + * must be in the range (1 to 60000000). + * Using micro seconds per quarter note is convenient when the tempo value is + * derived from MIDI clock realtime messages. + * + * @note When the player is controlled by an external tempo + * (#FLUID_PLAYER_TEMPO_EXTERNAL_BPM or #FLUID_PLAYER_TEMPO_EXTERNAL_MIDI) it + * continues to memorize the most recent internal tempo change coming from the + * MIDI file so that next call to fluid_player_set_tempo() with + * #FLUID_PLAYER_TEMPO_INTERNAL will set the player to follow this internal + * tempo. + * + * @warning If the function is called when no MIDI file is loaded or currently playing, it + * would have caused a division by zero in fluidsynth 2.2.7 and earlier. Starting with 2.2.8, the + * new tempo change will be stashed and applied later. + * + * @return #FLUID_OK if success or #FLUID_FAILED otherwise (incorrect parameters). + * @since 2.2.0 + */ +int fluid_player_set_tempo(fluid_player_t *player, int tempo_type, double tempo) +{ + fluid_return_val_if_fail(player != NULL, FLUID_FAILED); + fluid_return_val_if_fail(tempo_type >= FLUID_PLAYER_TEMPO_INTERNAL, FLUID_FAILED); + fluid_return_val_if_fail(tempo_type < FLUID_PLAYER_TEMPO_NBR, FLUID_FAILED); + + switch(tempo_type) + { + /* set the player to be driven by internal tempo coming from MIDI file */ + case FLUID_PLAYER_TEMPO_INTERNAL: + /* check if the multiplier is in correct range */ + fluid_return_val_if_fail(tempo >= MIN_TEMPO_MULTIPLIER, FLUID_FAILED); + fluid_return_val_if_fail(tempo <= MAX_TEMPO_MULTIPLIER, FLUID_FAILED); + + /* set the tempo multiplier */ + fluid_atomic_float_set(&player->multempo, (float)tempo); + fluid_atomic_int_set(&player->sync_mode, 1); /* set internal mode */ + break; + + /* set the player to be driven by external tempo */ + case FLUID_PLAYER_TEMPO_EXTERNAL_BPM: /* value in bpm */ + case FLUID_PLAYER_TEMPO_EXTERNAL_MIDI: /* value in us/quarter note */ + /* check if tempo is in correct range */ + fluid_return_val_if_fail(tempo >= MIN_TEMPO_VALUE, FLUID_FAILED); + fluid_return_val_if_fail(tempo <= MAX_TEMPO_VALUE, FLUID_FAILED); + + /* set the tempo value */ + if(tempo_type == FLUID_PLAYER_TEMPO_EXTERNAL_BPM) + { + tempo = 60000000L / tempo; /* convert tempo in us/quarter note */ + } + fluid_atomic_int_set(&player->exttempo, (int)tempo); + fluid_atomic_int_set(&player->sync_mode, 0); /* set external mode */ + break; + + default: /* shouldn't happen */ + break; + } + + /* update deltatime depending of current tempo */ + fluid_player_update_tempo(player); + + return FLUID_OK; +} + +/** + * Set the tempo of a MIDI player. + * @param player MIDI player instance + * @param tempo Tempo to set playback speed to (in microseconds per quarter note, as per MIDI file spec) + * @return Always returns #FLUID_OK + * @note Tempo change events contained in the MIDI file can override the specified tempo at any time! + * @deprecated Use fluid_player_set_tempo() instead. + */ +int fluid_player_set_midi_tempo(fluid_player_t *player, int tempo) +{ + player->miditempo = tempo; + + fluid_player_update_tempo(player); + return FLUID_OK; +} + +/** + * Set the tempo of a MIDI player in beats per minute. + * @param player MIDI player instance + * @param bpm Tempo in beats per minute + * @return Always returns #FLUID_OK + * @note Tempo change events contained in the MIDI file can override the specified BPM at any time! + * @deprecated Use fluid_player_set_tempo() instead. + */ +int fluid_player_set_bpm(fluid_player_t *player, int bpm) +{ + if(bpm <= 0) + { + return FLUID_FAILED; /* to avoid a division by 0 */ + } + + return fluid_player_set_midi_tempo(player, 60000000L / bpm); +} + +/** + * Wait for a MIDI player until the playback has been stopped. + * @param player MIDI player instance + * @return Always #FLUID_OK + * + * @warning The player may still be used by a concurrently running synth context. Hence it is + * not safe to immediately delete the player afterwards. Also refer to delete_fluid_player(). + */ +int +fluid_player_join(fluid_player_t *player) +{ + while(fluid_player_get_status(player) != FLUID_PLAYER_DONE) + { + fluid_msleep(10); + } + return FLUID_OK; +} + +/** + * Get the number of tempo ticks passed. + * @param player MIDI player instance + * @return The number of tempo ticks passed + * @since 1.1.7 + */ +int fluid_player_get_current_tick(fluid_player_t *player) +{ + return player->cur_ticks; +} + +/** + * Looks through all available MIDI tracks and gets the absolute tick of the very last event to play. + * @param player MIDI player instance + * @return Total tick count of the sequence + * @since 1.1.7 + */ +int fluid_player_get_total_ticks(fluid_player_t *player) +{ + int i; + int maxTicks = 0; + + for(i = 0; i < player->ntracks; i++) + { + if(player->track[i] != NULL) + { + int ticks = fluid_track_get_duration(player->track[i]); + + if(ticks > maxTicks) + { + maxTicks = ticks; + } + } + } + + return maxTicks; +} + +/** + * Get the tempo currently used by a MIDI player. + * The player can be controlled by internal tempo coming from MIDI file tempo + * change or controlled by external tempo see fluid_player_set_tempo(). + * @param player MIDI player instance. Must be a valid pointer. + * @return MIDI player tempo in BPM or FLUID_FAILED if error. + * @since 1.1.7 + */ +int fluid_player_get_bpm(fluid_player_t *player) +{ + + int midi_tempo = fluid_player_get_midi_tempo(player); + + if(midi_tempo > 0) + { + midi_tempo = 60000000L / midi_tempo; /* convert in bpm */ + } + + return midi_tempo; +} + +/** + * Get the division currently used by a MIDI player. + * The player can be controlled by internal tempo coming from MIDI file tempo + * change or controlled by external tempo see fluid_player_set_tempo(). + * @param player MIDI player instance. Must be a valid pointer. + * @return MIDI player division or FLUID_FAILED if error. + * @since 2.3.2 + */ +int fluid_player_get_division(fluid_player_t *player) +{ + return player->division; +} + +/** + * Get the tempo currently used by a MIDI player. + * The player can be controlled by internal tempo coming from MIDI file tempo + * change or controlled by external tempo see fluid_player_set_tempo(). + + * @param player MIDI player instance. Must be a valid pointer. + * @return Tempo of the MIDI player (in microseconds per quarter note, as per + * MIDI file spec) or FLUID_FAILED if error. + * @since 1.1.7 + */ +int fluid_player_get_midi_tempo(fluid_player_t *player) +{ + int midi_tempo; /* value to return */ + + fluid_return_val_if_fail(player != NULL, FLUID_FAILED); + + midi_tempo = fluid_atomic_int_get(&player->exttempo); + /* look if the player is internally synced */ + if(fluid_atomic_int_get(&player->sync_mode)) + { + midi_tempo = (int)((float)fluid_atomic_int_get(&player->miditempo)/ + fluid_atomic_float_get(&player->multempo)); + } + + return midi_tempo; +} + +/************************************************************************ + * MIDI PARSER + * + */ + +/* + * new_fluid_midi_parser + */ +fluid_midi_parser_t * +new_fluid_midi_parser() +{ + fluid_midi_parser_t *parser; + parser = FLUID_NEW(fluid_midi_parser_t); + + if(parser == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + parser->status = 0; /* As long as the status is 0, the parser won't do anything -> no need to initialize all the fields. */ + return parser; +} + +/* + * delete_fluid_midi_parser + */ +void +delete_fluid_midi_parser(fluid_midi_parser_t *parser) +{ + fluid_return_if_fail(parser != NULL); + + FLUID_FREE(parser); +} + +/** + * Parse a MIDI stream one character at a time. + * @param parser Parser instance + * @param c Next character in MIDI stream + * @return A parsed MIDI event or NULL if none. Event is internal and should + * not be modified or freed and is only valid until next call to this function. + * @internal Do not expose this function to the public API. It would allow downstream + * apps to abuse fluidsynth as midi parser, e.g. feeding it with rawmidi and pull out + * the needed midi information using the getter functions of fluid_midi_event_t. + * This parser however is incomplete as it e.g. only provides a limited buffer to + * store and process SYSEX data (i.e. doesn't allow arbitrary lengths) + */ +fluid_midi_event_t * +fluid_midi_parser_parse(fluid_midi_parser_t *parser, unsigned char c) +{ + fluid_midi_event_t *event; + + /* Real-time messages (0xF8-0xFF) can occur anywhere, even in the middle + * of another message. */ + if(c >= 0xF8) + { + parser->event.type = c; + parser->status = 0; /* clear the status */ + return &parser->event; + } + + /* Status byte? - If previous message not yet complete, it is discarded (re-sync). */ + if(c & 0x80) + { + /* Any status byte terminates SYSEX messages (not just 0xF7) */ + if(parser->status == MIDI_SYSEX && parser->nr_bytes > 0) + { + event = &parser->event; + fluid_midi_event_set_sysex(event, parser->data, parser->nr_bytes, + FALSE); + } + else + { + event = NULL; + } + + if(c < 0xF0) /* Voice category message? */ + { + parser->channel = c & 0x0F; + parser->status = c & 0xF0; + + /* The event consumes x bytes of data... (subtract 1 for the status byte) */ + parser->nr_bytes_total = fluid_midi_event_length(parser->status) + - 1; + + parser->nr_bytes = 0; /* 0 bytes read so far */ + } + else if(c == MIDI_SYSEX) + { + parser->status = MIDI_SYSEX; + parser->nr_bytes = 0; + } + else + { + parser->status = 0; /* Discard other system messages (0xF1-0xF7) */ + } + + return event; /* Return SYSEX event or NULL */ + } + + /* Data/parameter byte */ + + /* Discard data bytes for events we don't care about */ + if(parser->status == 0) + { + return NULL; + } + + /* Max data size exceeded? (SYSEX messages only really) */ + if(parser->nr_bytes == FLUID_MIDI_PARSER_MAX_DATA_SIZE) + { + parser->status = 0; /* Discard the rest of the message */ + return NULL; + } + + /* Store next byte */ + parser->data[parser->nr_bytes++] = c; + + /* Do we still need more data to get this event complete? */ + if(parser->status == MIDI_SYSEX || parser->nr_bytes < parser->nr_bytes_total) + { + return NULL; + } + + /* Event is complete, return it. + * Running status byte MIDI feature is also handled here. */ + parser->event.type = parser->status; + parser->event.channel = parser->channel; + parser->nr_bytes = 0; /* Reset data size, in case there are additional running status messages */ + + switch(parser->status) + { + case NOTE_OFF: + case NOTE_ON: + case KEY_PRESSURE: + case CONTROL_CHANGE: + case PROGRAM_CHANGE: + case CHANNEL_PRESSURE: + parser->event.param1 = parser->data[0]; /* For example key number */ + parser->event.param2 = parser->data[1]; /* For example velocity */ + break; + + case PITCH_BEND: + /* Pitch-bend is transmitted with 14-bit precision. */ + parser->event.param1 = (parser->data[1] << 7) | parser->data[0]; + break; + + default: /* Unlikely */ + return NULL; + } + + return &parser->event; +} + +/* Purpose: + * Returns the length of a MIDI message. */ +static int +fluid_midi_event_length(unsigned char event) +{ + switch(event & 0xF0) + { + case NOTE_OFF: + case NOTE_ON: + case KEY_PRESSURE: + case CONTROL_CHANGE: + case PITCH_BEND: + return 3; + + case PROGRAM_CHANGE: + case CHANNEL_PRESSURE: + return 2; + } + + switch(event) + { + case MIDI_TIME_CODE: + case MIDI_SONG_SELECT: + case 0xF4: + case 0xF5: + return 2; + + case MIDI_TUNE_REQUEST: + return 1; + + case MIDI_SONG_POSITION: + return 3; + } + + return 1; +} diff --git a/libs/fluidsynth/src/midi/fluid_midi.h b/libs/fluidsynth/src/midi/fluid_midi.h new file mode 100644 index 00000000000..bd3b4e8a1b1 --- /dev/null +++ b/libs/fluidsynth/src/midi/fluid_midi.h @@ -0,0 +1,384 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_MIDI_H +#define _FLUID_MIDI_H + +#include "fluidsynth_priv.h" +#include "fluid_sys.h" +#include "fluid_list.h" + +typedef struct _fluid_midi_parser_t fluid_midi_parser_t; + +fluid_midi_parser_t *new_fluid_midi_parser(void); +void delete_fluid_midi_parser(fluid_midi_parser_t *parser); +fluid_midi_event_t *fluid_midi_parser_parse(fluid_midi_parser_t *parser, unsigned char c); + + +/*************************************************************** + * + * CONSTANTS & ENUM + */ + + +#define MAX_NUMBER_OF_TRACKS 128 +#define MAX_NUMBER_OF_CHANNELS 16 + +enum fluid_midi_event_type +{ + /* channel messages */ + NOTE_OFF = 0x80, + NOTE_ON = 0x90, + KEY_PRESSURE = 0xa0, + CONTROL_CHANGE = 0xb0, + PROGRAM_CHANGE = 0xc0, + CHANNEL_PRESSURE = 0xd0, + PITCH_BEND = 0xe0, + /* system exclusive */ + MIDI_SYSEX = 0xf0, + /* system common - never in midi files */ + MIDI_TIME_CODE = 0xf1, + MIDI_SONG_POSITION = 0xf2, + MIDI_SONG_SELECT = 0xf3, + MIDI_TUNE_REQUEST = 0xf6, + MIDI_EOX = 0xf7, + /* system real-time - never in midi files */ + MIDI_SYNC = 0xf8, + MIDI_TICK = 0xf9, + MIDI_START = 0xfa, + MIDI_CONTINUE = 0xfb, + MIDI_STOP = 0xfc, + MIDI_ACTIVE_SENSING = 0xfe, + MIDI_SYSTEM_RESET = 0xff, + /* meta event - for midi files only */ + MIDI_META_EVENT = 0xff +}; + +enum fluid_midi_control_change +{ + BANK_SELECT_MSB = 0x00, + MODULATION_MSB = 0x01, + BREATH_MSB = 0x02, + FOOT_MSB = 0x04, + PORTAMENTO_TIME_MSB = 0x05, + DATA_ENTRY_MSB = 0x06, + VOLUME_MSB = 0x07, + BALANCE_MSB = 0x08, + PAN_MSB = 0x0A, + EXPRESSION_MSB = 0x0B, + EFFECTS1_MSB = 0x0C, + EFFECTS2_MSB = 0x0D, + GPC1_MSB = 0x10, /* general purpose controller */ + GPC2_MSB = 0x11, + GPC3_MSB = 0x12, + GPC4_MSB = 0x13, + BANK_SELECT_LSB = 0x20, + MODULATION_WHEEL_LSB = 0x21, + BREATH_LSB = 0x22, + FOOT_LSB = 0x24, + PORTAMENTO_TIME_LSB = 0x25, + DATA_ENTRY_LSB = 0x26, + VOLUME_LSB = 0x27, + BALANCE_LSB = 0x28, + PAN_LSB = 0x2A, + EXPRESSION_LSB = 0x2B, + EFFECTS1_LSB = 0x2C, + EFFECTS2_LSB = 0x2D, + GPC1_LSB = 0x30, + GPC2_LSB = 0x31, + GPC3_LSB = 0x32, + GPC4_LSB = 0x33, + SUSTAIN_SWITCH = 0x40, + PORTAMENTO_SWITCH = 0x41, + SOSTENUTO_SWITCH = 0x42, + SOFT_PEDAL_SWITCH = 0x43, + LEGATO_SWITCH = 0x44, + HOLD2_SWITCH = 0x45, + SOUND_CTRL1 = 0x46, + SOUND_CTRL2 = 0x47, + SOUND_CTRL3 = 0x48, + SOUND_CTRL4 = 0x49, + SOUND_CTRL5 = 0x4A, + SOUND_CTRL6 = 0x4B, + SOUND_CTRL7 = 0x4C, + SOUND_CTRL8 = 0x4D, + SOUND_CTRL9 = 0x4E, + SOUND_CTRL10 = 0x4F, + GPC5 = 0x50, + GPC6 = 0x51, + GPC7 = 0x52, + GPC8 = 0x53, + PORTAMENTO_CTRL = 0x54, + EFFECTS_DEPTH1 = 0x5B, + EFFECTS_DEPTH2 = 0x5C, + EFFECTS_DEPTH3 = 0x5D, + EFFECTS_DEPTH4 = 0x5E, + EFFECTS_DEPTH5 = 0x5F, + DATA_ENTRY_INCR = 0x60, + DATA_ENTRY_DECR = 0x61, + NRPN_LSB = 0x62, + NRPN_MSB = 0x63, + RPN_LSB = 0x64, + RPN_MSB = 0x65, + ALL_SOUND_OFF = 0x78, + ALL_CTRL_OFF = 0x79, + LOCAL_CONTROL = 0x7A, + ALL_NOTES_OFF = 0x7B, + OMNI_OFF = 0x7C, + OMNI_ON = 0x7D, + POLY_OFF = 0x7E, + POLY_ON = 0x7F +}; + +/* General MIDI RPN event numbers (LSB, MSB = 0) */ +enum midi_rpn_event +{ + RPN_PITCH_BEND_RANGE = 0x00, + RPN_CHANNEL_FINE_TUNE = 0x01, + RPN_CHANNEL_COARSE_TUNE = 0x02, + RPN_TUNING_PROGRAM_CHANGE = 0x03, + RPN_TUNING_BANK_SELECT = 0x04, + RPN_MODULATION_DEPTH_RANGE = 0x05 +}; + +enum midi_meta_event +{ + MIDI_TEXT = 0x01, + MIDI_COPYRIGHT = 0x02, + MIDI_TRACK_NAME = 0x03, + MIDI_INST_NAME = 0x04, + MIDI_LYRIC = 0x05, + MIDI_MARKER = 0x06, + MIDI_CUE_POINT = 0x07, + MIDI_EOT = 0x2f, + MIDI_SET_TEMPO = 0x51, + MIDI_SMPTE_OFFSET = 0x54, + MIDI_TIME_SIGNATURE = 0x58, + MIDI_KEY_SIGNATURE = 0x59, + MIDI_SEQUENCER_EVENT = 0x7f +}; + +/* MIDI SYSEX useful manufacturer values */ +enum midi_sysex_manuf +{ + MIDI_SYSEX_MANUF_ROLAND = 0x41, /**< Roland manufacturer ID */ + MIDI_SYSEX_MANUF_YAMAHA = 0x43, + MIDI_SYSEX_UNIV_NON_REALTIME = 0x7E, /**< Universal non realtime message */ + MIDI_SYSEX_UNIV_REALTIME = 0x7F /**< Universal realtime message */ +}; + +#define MIDI_SYSEX_DEVICE_ID_ALL 0x7F /**< Device ID used in SYSEX messages to indicate all devices */ + +/* SYSEX sub-ID #1 which follows device ID */ +#define MIDI_SYSEX_MIDI_TUNING_ID 0x08 /**< Sysex sub-ID #1 for MIDI tuning messages */ +#define MIDI_SYSEX_GM_ID 0x09 /**< Sysex sub-ID #1 for General MIDI messages */ +#define MIDI_SYSEX_GS_ID 0x42 /**< Model ID (GS) serving as sub-ID #1 for GS messages*/ +#define MIDI_SYSEX_XG_ID 0x4C /**< Model ID (XG) serving as sub-ID #1 for XG messages*/ + +/** + * SYSEX tuning message IDs. + */ +enum midi_sysex_tuning_msg_id +{ + MIDI_SYSEX_TUNING_BULK_DUMP_REQ = 0x00, /**< Bulk tuning dump request (non-realtime) */ + MIDI_SYSEX_TUNING_BULK_DUMP = 0x01, /**< Bulk tuning dump response (non-realtime) */ + MIDI_SYSEX_TUNING_NOTE_TUNE = 0x02, /**< Tuning note change message (realtime) */ + MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK = 0x03, /**< Bulk tuning dump request (with bank, non-realtime) */ + MIDI_SYSEX_TUNING_BULK_DUMP_BANK = 0x04, /**< Bulk tuning dump response (with bank, non-realtime) */ + MIDI_SYSEX_TUNING_OCTAVE_DUMP_1BYTE = 0x05, /**< Octave tuning dump using 1 byte values (non-realtime) */ + MIDI_SYSEX_TUNING_OCTAVE_DUMP_2BYTE = 0x06, /**< Octave tuning dump using 2 byte values (non-realtime) */ + MIDI_SYSEX_TUNING_NOTE_TUNE_BANK = 0x07, /**< Tuning note change message (with bank, realtime/non-realtime) */ + MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE = 0x08, /**< Octave tuning message using 1 byte values (realtime/non-realtime) */ + MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE = 0x09 /**< Octave tuning message using 2 byte values (realtime/non-realtime) */ +}; + +/* General MIDI sub-ID #2 */ +#define MIDI_SYSEX_GM_ON 0x01 /**< Enable GM mode */ +#define MIDI_SYSEX_GM_OFF 0x02 /**< Disable GM mode */ +#define MIDI_SYSEX_GM2_ON 0x03 /**< Enable GM2 mode */ +#define MIDI_SYSEX_GS_DT1 0x12 /**< GS DT1 command */ + +enum fluid_driver_status +{ + FLUID_MIDI_READY, + FLUID_MIDI_LISTENING, + FLUID_MIDI_DONE +}; + +/*************************************************************** + * + * TYPE DEFINITIONS & FUNCTION DECLARATIONS + */ + +/* + * fluid_midi_event_t + */ +struct _fluid_midi_event_t +{ + fluid_midi_event_t *next; /* Link to next event */ + void *paramptr; /* Pointer parameter (for SYSEX data), size is stored to param1, param2 indicates if pointer should be freed (dynamic if TRUE) */ + unsigned int dtime; /* Delay (ticks) between this and previous event. midi tracks. */ + unsigned int param1; /* First parameter */ + unsigned int param2; /* Second parameter */ + unsigned char type; /* MIDI event type */ + unsigned char channel; /* MIDI channel */ +}; + + +/* + * fluid_track_t + */ +struct _fluid_track_t +{ + char *name; + int num; + fluid_midi_event_t *first; + fluid_midi_event_t *cur; + fluid_midi_event_t *last; + unsigned int ticks; +}; + +typedef struct _fluid_track_t fluid_track_t; + +#define fluid_track_eot(track) ((track)->cur == NULL) + + +/* + * fluid_playlist_item + * Used as the `data' elements of the fluid_player.playlist. + * Represents either a filename or a pre-loaded memory buffer. + * Exactly one of `filename' and `buffer' is non-NULL. + */ +typedef struct +{ + char *filename; /** Name of file (owned); NULL if data pre-loaded */ + void *buffer; /** The MIDI file data (owned); NULL if filename */ + size_t buffer_len; /** Number of bytes in buffer; 0 if filename */ +} fluid_playlist_item; + +/* range of tempo values */ +#define MIN_TEMPO_VALUE (1.0f) +#define MAX_TEMPO_VALUE (60000000.0f) +/* range of tempo multiplier values */ +#define MIN_TEMPO_MULTIPLIER (0.001f) +#define MAX_TEMPO_MULTIPLIER (1000.0f) + +/* + * fluid_player + */ +struct _fluid_player_t +{ + fluid_atomic_int_t status; + fluid_atomic_int_t stopping; /* Flag for sending all_notes_off when player is stopped */ + int ntracks; + fluid_track_t *track[MAX_NUMBER_OF_TRACKS]; + fluid_synth_t *synth; + fluid_timer_t *system_timer; + fluid_sample_timer_t *sample_timer; + + int loop; /* -1 = loop infinitely, otherwise times left to loop the playlist */ + fluid_list_t *playlist; /* List of fluid_playlist_item* objects */ + fluid_list_t *currentfile; /* points to an item in files, or NULL if not playing */ + + char use_system_timer; /* if zero, use sample timers, otherwise use system clock timer */ + char reset_synth_between_songs; /* 1 if system reset should be sent to the synth between songs. */ + fluid_atomic_int_t seek_ticks; /* new position in tempo ticks (midi ticks) for seeking */ + int start_ticks; /* the number of tempo ticks passed at the last tempo change */ + int cur_ticks; /* the number of tempo ticks passed */ + int last_callback_ticks; /* the last tick number that was passed to player->tick_callback */ + int begin_msec; /* the time (msec) of the beginning of the file */ + int start_msec; /* the start time of the last tempo change */ + unsigned int cur_msec; /* the current time */ + int end_msec; /* when >=0, playback is extended until this time (for, e.g., reverb) */ + char end_pedals_disabled; /* 1 once the pedals have been released after the last midi event, 0 otherwise */ + /* sync mode: indicates the tempo mode the player is driven by (see fluid_player_set_tempo()): + 1, the player is driven by internal tempo (miditempo). This is the default. + 0, the player is driven by external tempo (exttempo) + */ + int sync_mode; + /* miditempo: internal tempo coming from MIDI file tempo change events + (in micro seconds per quarter note) + */ + int miditempo; /* as indicated by MIDI SetTempo: n 24th of a usec per midi-clock. bravo! */ + /* exttempo: external tempo set by fluid_player_set_tempo() (in micro seconds per quarter note) */ + int exttempo; + /* multempo: tempo multiplier set by fluid_player_set_tempo() */ + float multempo; + float deltatime; /* milliseconds per midi tick. depends on current tempo mode (see sync_mode) */ + unsigned int division; + + handle_midi_event_func_t playback_callback; /* function fired on each midi event as it is played */ + void *playback_userdata; /* pointer to user-defined data passed to playback_callback function */ + handle_midi_tick_func_t tick_callback; /* function fired on each tick change */ + void *tick_userdata; /* pointer to user-defined data passed to tick_callback function */ + + int channel_isplaying[MAX_NUMBER_OF_CHANNELS]; /* flags indicating channels on which notes have played */ +}; + +#define FLUID_PLAYER_STOP_GRACE_MS 2000 + +void fluid_player_settings(fluid_settings_t *settings); + + +/* + * fluid_midi_file + */ +typedef struct +{ + const char *buffer; /* Entire contents of MIDI file (borrowed) */ + int buf_len; /* Length of buffer, in bytes */ + int buf_pos; /* Current read position in contents buffer */ + int eof; /* The "end of file" condition */ + int running_status; + int c; + int type; + int ntracks; + int uses_smpte; + unsigned int smpte_fps; + unsigned int smpte_res; + unsigned int division; /* If uses_SMPTE == 0 then division is + ticks per beat (quarter-note) */ + double tempo; /* Beats per second (SI rules =) */ + int tracklen; + int trackpos; + int eot; + int varlen; + int dtime; +} fluid_midi_file; + + + +#define FLUID_MIDI_PARSER_MAX_DATA_SIZE 1024 /**< Maximum size of MIDI parameters/data (largest is SYSEX data) */ + +/* + * fluid_midi_parser_t + */ +struct _fluid_midi_parser_t +{ + unsigned char status; /* Identifies the type of event, that is currently received ('Noteon', 'Pitch Bend' etc). */ + unsigned char channel; /* The channel of the event that is received (in case of a channel event) */ + unsigned int nr_bytes; /* How many bytes have been read for the current event? */ + unsigned int nr_bytes_total; /* How many bytes does the current event type include? */ + unsigned char data[FLUID_MIDI_PARSER_MAX_DATA_SIZE]; /* The parameters or SYSEX data */ + fluid_midi_event_t event; /* The event, that is returned to the MIDI driver. */ +}; + + +#endif /* _FLUID_MIDI_H */ diff --git a/libs/fluidsynth/src/midi/fluid_midi_router.h b/libs/fluidsynth/src/midi/fluid_midi_router.h new file mode 100644 index 00000000000..5a5068d5ea9 --- /dev/null +++ b/libs/fluidsynth/src/midi/fluid_midi_router.h @@ -0,0 +1,32 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +/* Author: Markus Nentwig, nentwig@users.sourceforge.net + */ + +#ifndef _FLUID_MIDIROUTER_H +#define _FLUID_MIDIROUTER_H + +#include "fluidsynth_priv.h" +#include "fluid_midi.h" +#include "fluid_sys.h" + + +#endif diff --git a/libs/fluidsynth/src/rvoice/fluid_adsr_env.c b/libs/fluidsynth/src/rvoice/fluid_adsr_env.c new file mode 100644 index 00000000000..00bdd40f220 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_adsr_env.c @@ -0,0 +1,39 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_adsr_env.h" + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_adsr_env_set_data) +{ + fluid_adsr_env_t *env = obj; + fluid_adsr_env_section_t section = param[0].i; + unsigned int count = param[1].i; + fluid_real_t coeff = param[2].real; + fluid_real_t increment = param[3].real; + fluid_real_t min = param[4].real; + fluid_real_t max = param[5].real; + + env->data[section].count = count; + env->data[section].coeff = coeff; + env->data[section].increment = increment; + env->data[section].min = min; + env->data[section].max = max; +} + diff --git a/libs/fluidsynth/src/rvoice/fluid_adsr_env.h b/libs/fluidsynth/src/rvoice/fluid_adsr_env.h new file mode 100644 index 00000000000..5e99c6bf3c3 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_adsr_env.h @@ -0,0 +1,167 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_ADSR_ENVELOPE_H +#define _FLUID_ADSR_ENVELOPE_H + +#include "fluidsynth_priv.h" +#include "fluid_sys.h" + +/* + * envelope data + */ +struct _fluid_env_data_t +{ + unsigned int count; + fluid_real_t coeff; + fluid_real_t increment; + fluid_real_t min; + fluid_real_t max; +}; + +/* Indices for envelope tables */ +enum fluid_voice_envelope_index +{ + FLUID_VOICE_ENVDELAY, + FLUID_VOICE_ENVATTACK, + FLUID_VOICE_ENVHOLD, + FLUID_VOICE_ENVDECAY, + FLUID_VOICE_ENVSUSTAIN, + FLUID_VOICE_ENVRELEASE, + FLUID_VOICE_ENVFINISHED, + FLUID_VOICE_ENVLAST +}; + +typedef enum fluid_voice_envelope_index fluid_adsr_env_section_t; + +typedef struct _fluid_adsr_env_t fluid_adsr_env_t; + +struct _fluid_adsr_env_t +{ + fluid_env_data_t data[FLUID_VOICE_ENVLAST]; + unsigned int count; + fluid_real_t val; /* the current value of the envelope */ + fluid_adsr_env_section_t section; +}; + +/* For performance, all functions are inlined */ + +static FLUID_INLINE void +fluid_adsr_env_calc(fluid_adsr_env_t *env) +{ + fluid_env_data_t *env_data; + fluid_real_t x; + + env_data = &env->data[env->section]; + + /* skip to the next section of the envelope if necessary */ + while(env->count >= env_data->count) + { + // If we're switching envelope stages from decay to sustain, force the value to be the end value of the previous stage + // Hmm, should this only apply to volenv? It was so before refactoring, so keep it for now. [DH] + // No, must apply to both, otherwise some voices may sound detuned. [TM] (https://github.com/FluidSynth/fluidsynth/issues/1059) + if(env->section == FLUID_VOICE_ENVDECAY) + { + env->val = env_data->min * env_data->coeff; + } + + env_data = &env->data[++env->section]; + env->count = 0; + } + + /* calculate the envelope value and check for valid range */ + x = env_data->coeff * env->val + env_data->increment; + + if(x < env_data->min) + { + x = env_data->min; + env->section++; + env->count = 0; + } + else if(x > env_data->max) + { + x = env_data->max; + env->section++; + env->count = 0; + } + else + { + env->count++; + } + + env->val = x; +} + +/* This one cannot be inlined since it is referenced in + the event queue */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_adsr_env_set_data); + +static FLUID_INLINE void +fluid_adsr_env_reset(fluid_adsr_env_t *env) +{ + env->count = 0; + env->section = FLUID_VOICE_ENVDELAY; + env->val = 0.0f; +} + +static FLUID_INLINE fluid_real_t +fluid_adsr_env_get_val(fluid_adsr_env_t *env) +{ + return env->val; +} + +static FLUID_INLINE void +fluid_adsr_env_set_val(fluid_adsr_env_t *env, fluid_real_t val) +{ + env->val = val; +} + +static FLUID_INLINE fluid_adsr_env_section_t +fluid_adsr_env_get_section(fluid_adsr_env_t *env) +{ + return env->section; +} + +static FLUID_INLINE void +fluid_adsr_env_set_section(fluid_adsr_env_t *env, + fluid_adsr_env_section_t section) +{ + env->section = section; + env->count = 0; +} + +/* Used for determining which voice to kill. + Returns max amplitude from now, and forward in time. +*/ +static FLUID_INLINE fluid_real_t +fluid_adsr_env_get_max_val(fluid_adsr_env_t *env) +{ + if(env->section > FLUID_VOICE_ENVATTACK) + { + return env->val * 1000; + } + else + { + return env->data[FLUID_VOICE_ENVATTACK].max; + } +} + +#endif + diff --git a/libs/fluidsynth/src/rvoice/fluid_chorus.c b/libs/fluidsynth/src/rvoice/fluid_chorus.c new file mode 100644 index 00000000000..c80dc9e98c1 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_chorus.c @@ -0,0 +1,1071 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe, Markus Nentwig 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +/* + based on a chorus implementation made by Juergen Mueller And Sundry Contributors in 1998 + + CHANGES + + - Adapted for fluidsynth, Peter Hanappe, March 2002 + + - Variable delay line implementation using bandlimited + interpolation, code reorganization: Markus Nentwig May 2002 + + - Complete rewrite using lfo computed on the fly, first order all-pass + interpolator and adding stereo unit: Jean-Jacques Ceresa, Jul 2019 + */ + + +/* + * Chorus effect. + * + * Flow diagram scheme for n delays ( 1 <= n <= MAX_CHORUS ): + * + * ________ + * direct signal (not implemented) >-->| | + * _________ | | + * mono | | | | + * input ---+---->| delay 1 |-------------------------->| Stereo |---> right + * | |_________| | | output + * | /|\ | Unit | + * : | | | + * : +-----------------+ |(width) | + * : | Delay control 1 |<-+ | | + * : +-----------------+ | | |---> left + * | _________ | | | output + * | | | | | | + * +---->| delay n |-------------------------->| | + * |_________| | | | + * /|\ | |________| + * | | +--------------+ /|\ + * +-----------------+ | |mod depth (ms)| | + * | Delay control n |<-*--|lfo speed (Hz)| gain-out + * +-----------------+ +--------------+ + * + * + * The delay i is controlled by a sine or triangle modulation i ( 1 <= i <= n). + * + * The chorus unit process a monophonic input signal and produces stereo output + * controlled by WIDTH macro. + * Actually WIDTH is fixed to maximum value. But in the future, we could add a + * setting (e.g "synth.chorus.width") allowing the user to get a gradually stereo + * effect from minimum (monophonic) to maximum stereo effect. + * + * Delays lines are implemented using only one line for all chorus blocks. + * Each chorus block has it own lfo (sinus/triangle). Each lfo are out of phase + * to produce uncorrelated signal at the output of the delay line (this simulates + * the presence of individual line for each block). Each lfo modulates the length + * of the line using a depth modulation value and lfo frequency value common to + * all lfos. + * + * LFO modulators are computed on the fly, instead of using lfo lookup table. + * The advantages are: + * - Avoiding a lost of 608272 memory bytes when lfo speed is low (0.3Hz). + * - Allows to diminish the lfo speed lower limit to 0.1Hz instead of 0.3Hz. + * A speed of 0.1 is interesting for chorus. Using a lookuptable for 0.1Hz + * would require too much memory (1824816 bytes). + * - Interpolation make use of first order all-pass interpolator instead of + * bandlimited interpolation. + * - Although lfo modulator is computed on the fly, cpu load is lower than + * using lfo lookup table with bandlimited interpolator. + */ + +#include "fluid_chorus.h" +#include "fluid_sys.h" + + +/*------------------------------------------------------------------------------------- + Private +--------------------------------------------------------------------------------------*/ +// #define DEBUG_PRINT // allows message to be printed on the console. + +#define MAX_CHORUS 99 /* number maximum of block */ +#define MAX_LEVEL 10 /* max output level */ +#define MIN_SPEED_HZ 0.1 /* min lfo frequency (Hz) */ +#define MAX_SPEED_HZ 5 /* max lfo frequency (Hz) */ + +/* WIDTH [0..10] value define a stereo separation between left and right. + When 0, the output is monophonic. When > 0 , the output is stereophonic. + Actually WIDTH is fixed to maximum value. But in the future we could add a setting to + allow a gradually stereo effect from minimum (monophonic) to maximum stereo effect. +*/ +#define WIDTH 10 + +/* SCALE_WET_WIDTH is a compensation weight factor to get an output + amplitude (wet) rather independent of the width setting. + 0: the output amplitude is fully dependent on the width setting. + >0: the output amplitude is less dependent on the width setting. + With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather + independent of width setting (see fluid_chorus_set()). + */ +#define SCALE_WET_WIDTH 0.2f +#define SCALE_WET 1.0f + +#define MAX_SAMPLES 2048 /* delay length in sample (46.4 ms at sample rate: 44100Hz).*/ +#define LOW_MOD_DEPTH 176 /* low mod_depth/2 in samples */ +#define HIGH_MOD_DEPTH MAX_SAMPLES/2 /* high mod_depth in sample */ +#define RANGE_MOD_DEPTH (HIGH_MOD_DEPTH - LOW_MOD_DEPTH) + +/* Important min max values for MOD_RATE */ +/* mod rate define the rate at which the modulator is updated. Examples + 50: the modulator is updated every 50 samples (less cpu cycles expensive). + 1: the modulator is updated every sample (more cpu cycles expensive). +*/ +/* MOD_RATE acceptable for max lfo speed (5Hz) and max modulation depth (46.6 ms) */ +#define LOW_MOD_RATE 5 /* MOD_RATE acceptable for low modulation depth (8 ms) */ +#define HIGH_MOD_RATE 4 /* MOD_RATE acceptable for max modulation depth (46.6 ms) */ + /* and max lfo speed (5 Hz) */ +#define RANGE_MOD_RATE (HIGH_MOD_RATE - LOW_MOD_RATE) + +/* some chorus cpu_load measurement dependent of modulation rate: mod_rate + (number of chorus blocks: 2) + + No stero unit: + mod_rate | chorus cpu load(%) | one voice cpu load (%) + ---------------------------------------------------- + 50 | 0.204 | + 5 | 0.256 | 0.169 + 1 | 0.417 | + + With stero unit: + mod_rate | chorus cpu load(%) | one voice cpu load (%) + ---------------------------------------------------- + 50 | 0.220 | + 5 | 0.274 | 0.169 + 1 | 0.465 | + +*/ + +/* + Number of samples to add to the desired length of the delay line. This + allows to take account of rounding error interpolation when using large + modulation depth. + 1 is sufficient for max modulation depth (46.6 ms) and max lfo speed (5 Hz). +*/ +//#define INTERP_SAMPLES_NBR 0 +#define INTERP_SAMPLES_NBR 1 + + +/*----------------------------------------------------------------------------- + Sinusoidal modulator +-----------------------------------------------------------------------------*/ +/* modulator */ +typedef struct +{ + fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ + fluid_real_t buffer1; /* buffer1 */ + fluid_real_t buffer2; /* buffer2 */ + fluid_real_t reset_buffer2;/* reset value of buffer2 */ +} sinus_modulator; + +/*----------------------------------------------------------------------------- + Triangle modulator +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t freq; /* Osc. Frequency (in Hertz) */ + fluid_real_t val; /* internal current value */ + fluid_real_t inc; /* increment value */ +} triang_modulator; + +/*----------------------------------------------------------------------------- + modulator +-----------------------------------------------------------------------------*/ +typedef struct +{ + /*-------------*/ + int line_out; /* current line out position for this modulator */ + /*-------------*/ + sinus_modulator sinus; /* sinus lfo */ + triang_modulator triang; /* triangle lfo */ + /*-------------------------*/ + /* first order All-Pass interpolator members */ + fluid_real_t frac_pos_mod; /* fractional position part between samples */ + /* previous value used when interpolating using fractional */ + fluid_real_t buffer; +} modulator; + +/* Private data for SKEL file */ +struct _fluid_chorus_t +{ + int type; + fluid_real_t depth_ms; + fluid_real_t level; + fluid_real_t speed_Hz; + int number_blocks; + fluid_real_t sample_rate; + + /* width control: 0 monophonic, > 0 more stereophonic */ + fluid_real_t width; + fluid_real_t wet1, wet2; + + fluid_real_t *line; /* buffer line */ + int size; /* effective internal size (in samples) */ + + int line_in; /* line in position */ + + /* center output position members */ + fluid_real_t center_pos_mod; /* center output position modulated by modulator */ + int mod_depth; /* modulation depth (in samples) */ + + /* variable rate control of center output position */ + int index_rate; /* index rate to know when to update center_pos_mod */ + int mod_rate; /* rate at which center_pos_mod is updated */ + + /* modulator member */ + modulator mod[MAX_CHORUS]; /* sinus/triangle modulator */ +}; + +/*----------------------------------------------------------------------------- + Sets the frequency of sinus oscillator. + + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param phase initial phase of the oscillator in degree (0 to 360). +-----------------------------------------------------------------------------*/ +static void set_sinus_frequency(sinus_modulator *mod, + float freq, float sample_rate, float phase) +{ + fluid_real_t w = 2 * FLUID_M_PI * freq / sample_rate; /* initial angle */ + fluid_real_t a; + + mod->a1 = 2 * FLUID_COS(w); + + a = (2 * FLUID_M_PI / 360) * phase; + + mod->buffer2 = FLUID_SIN(a - w); /* y(n-1) = sin(-initial angle) */ + mod->buffer1 = FLUID_SIN(a); /* y(n) = sin(initial phase) */ + mod->reset_buffer2 = FLUID_SIN(FLUID_M_PI / 2 - w); /* reset value for PI/2 */ +} + +/*----------------------------------------------------------------------------- + Gets current value of sinus modulator: + y(n) = a1 . y(n-1) - y(n-2) + out = a1 . buffer1 - buffer2 + + @param mod pointer on modulator structure. + @return current value of the modulator sine wave. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) +{ + fluid_real_t out; + out = mod->a1 * mod->buffer1 - mod->buffer2; + mod->buffer2 = mod->buffer1; + + if(out >= 1.0f) /* reset in case of instability near PI/2 */ + { + out = 1.0f; /* forces output to the right value */ + mod->buffer2 = mod->reset_buffer2; + } + + if(out <= -1.0f) /* reset in case of instability near -PI/2 */ + { + out = -1.0f; /* forces output to the right value */ + mod->buffer2 = - mod->reset_buffer2; + } + + mod->buffer1 = out; + return out; +} + +/*----------------------------------------------------------------------------- + Set the frequency of triangular oscillator + The frequency is converted in a slope value. + The initial value is set according to frac_phase which is a position + in the period relative to the beginning of the period. + For example: 0 is the beginning of the period, 1/4 is at 1/4 of the period + relative to the beginning. + + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param frac_phase initial phase (see comment above). +-----------------------------------------------------------------------------*/ +static void set_triangle_frequency(triang_modulator *mod, float freq, + float sample_rate, float frac_phase) +{ + fluid_real_t ns_period; /* period in numbers of sample */ + + if(freq <= 0.0) + { + freq = 0.5f; + } + + mod->freq = freq; + + ns_period = sample_rate / freq; + + /* the slope of a triangular osc (0 up to +1 down to -1 up to 0....) is equivalent + to the slope of a saw osc (0 -> +4) */ + mod->inc = 4 / ns_period; /* positive slope */ + + /* The initial value and the sign of the slope depend of initial phase: + initial value = = (ns_period * frac_phase) * slope + */ + mod->val = ns_period * frac_phase * mod->inc; + + if(1.0 <= mod->val && mod->val < 3.0) + { + mod->val = 2.0 - mod->val; /* 1.0 down to -1.0 */ + mod->inc = -mod->inc; /* negative slope */ + } + else if(3.0 <= mod->val) + { + mod->val = mod->val - 4.0; /* -1.0 up to +1.0. */ + } + + /* else val < 1.0 */ +} + +/*----------------------------------------------------------------------------- + Get current value of triangular oscillator + y(n) = y(n-1) + dy + + @param mod pointer on triang_modulator structure. + @return current value. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_triang(triang_modulator *mod) +{ + mod->val = mod->val + mod->inc ; + + if(mod->val >= 1.0) + { + mod->inc = -mod->inc; + return 1.0; + } + + if(mod->val <= -1.0) + { + mod->inc = -mod->inc; + return -1.0; + } + + return mod->val; +} +/*----------------------------------------------------------------------------- + Reads the sample value out of the modulated delay line. + + @param chorus pointer on chorus unit. + @param mod pointer on modulator structure. + @return current value. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_delay(fluid_chorus_t *chorus, + modulator *mod) +{ + fluid_real_t out_index; /* new modulated index position */ + int int_out_index; /* integer part of out_index */ + fluid_real_t out; /* value to return */ + + /* Checks if the modulator must be updated (every mod_rate samples). */ + /* Important: center_pos_mod must be used immediately for the + first sample. So, mdl->index_rate must be initialized + to mdl->mod_rate (new_mod_delay_line()) */ + + if(chorus->index_rate >= chorus->mod_rate) + { + /* out_index = center position (center_pos_mod) + sinus waweform */ + if(chorus->type == FLUID_CHORUS_MOD_SINE) + { + out_index = chorus->center_pos_mod + + get_mod_sinus(&mod->sinus) * chorus->mod_depth; + } + else + { + out_index = chorus->center_pos_mod + + get_mod_triang(&mod->triang) * chorus->mod_depth; + } + + /* extracts integer part in int_out_index */ + if(out_index >= 0.0f) + { + int_out_index = (int)out_index; /* current integer part */ + + /* forces read index (line_out) with integer modulation value */ + /* Boundary check and circular motion as needed */ + if((mod->line_out = int_out_index) >= chorus->size) + { + mod->line_out -= chorus->size; + } + } + else /* negative */ + { + int_out_index = (int)(out_index - 1); /* previous integer part */ + /* forces read index (line_out) with integer modulation value */ + /* circular motion as needed */ + mod->line_out = int_out_index + chorus->size; + } + + /* extracts fractionnal part. (it will be used when interpolating + between line_out and line_out +1) and memorize it. + Memorizing is necessary for modulation rate above 1 */ + mod->frac_pos_mod = out_index - int_out_index; + } + + /* First order all-pass interpolation ----------------------------------*/ + /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ + /* begins interpolation: read current sample */ + out = chorus->line[mod->line_out]; + + /* updates line_out to the next sample. + Boundary check and circular motion as needed */ + if(++mod->line_out >= chorus->size) + { + mod->line_out -= chorus->size; + } + + /* Fractional interpolation between next sample (at next position) and + previous output added to current sample. + */ + out += mod->frac_pos_mod * (chorus->line[mod->line_out] - mod->buffer); + mod->buffer = out; /* memorizes current output */ + return out; +} + +/*----------------------------------------------------------------------------- + Push a sample val into the delay line + + @param dl delay line to push value into. + @param val the value to push into dl. +-----------------------------------------------------------------------------*/ +#define push_in_delay_line(dl, val) \ +{\ + dl->line[dl->line_in] = val;\ + /* Incrementation and circular motion if necessary */\ + if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ +}\ + +/*----------------------------------------------------------------------------- + Initialize : mod_rate, center_pos_mod, and index rate + + center_pos_mod is initialized so that the delay between center_pos_mod and + line_in is: mod_depth + INTERP_SAMPLES_NBR. + + @param chorus pointer on chorus unit. +-----------------------------------------------------------------------------*/ +static void set_center_position(fluid_chorus_t *chorus) +{ + int center; + + /* Sets the modulation rate. This rate defines how often + the center position (center_pos_mod ) is modulated . + The value is expressed in samples. The default value is 1 that means that + center_pos_mod is updated at every sample. + For example with a value of 2, the center position position will be + updated only one time every 2 samples only. + */ + chorus->mod_rate = LOW_MOD_RATE; /* default modulation rate */ + + /* compensate mod rate for high modulation depth */ + if(chorus->mod_depth > LOW_MOD_DEPTH) + { + int delta_mod_depth = (chorus->mod_depth - LOW_MOD_DEPTH); + chorus->mod_rate += (delta_mod_depth * RANGE_MOD_RATE) / RANGE_MOD_DEPTH; + } + + /* Initializes the modulated center position (center_pos_mod) so that: + - the delay between center_pos_mod and line_in is: + mod_depth + INTERP_SAMPLES_NBR. + */ + center = chorus->line_in - (INTERP_SAMPLES_NBR + chorus->mod_depth); + + if(center < 0) + { + center += chorus->size; + } + + chorus->center_pos_mod = (fluid_real_t)center; + + /* index rate to control when to update center_pos_mod */ + /* Important: must be set to get center_pos_mod immediately used for the + reading of first sample (see get_mod_delay()) */ + chorus->index_rate = chorus->mod_rate; +} + +/*----------------------------------------------------------------------------- + Update internal parameters dependent of sample rate. + - mod_depth. + - mod_rate, center_pos_mod, and index rate. + - modulators frequency. + + @param chorus, pointer on chorus unit. +-----------------------------------------------------------------------------*/ +static void update_parameters_from_sample_rate(fluid_chorus_t *chorus) +{ + int i; + + /* initialize modulation depth (peak to peak) (in samples) */ + /* convert modulation depth in ms to sample number */ + chorus->mod_depth = (int)(chorus->depth_ms / 1000.0 + * chorus->sample_rate); + + /* the delay line is fixed. So we reduce mod_depth (if necessary) */ + if(chorus->mod_depth > MAX_SAMPLES) + { + FLUID_LOG(FLUID_WARN, "chorus: Too high depth. Setting it to max (%d).", + MAX_SAMPLES); + chorus->mod_depth = MAX_SAMPLES; + /* set depth_ms to maximum to avoid spamming console with above warning */ + chorus->depth_ms = (chorus->mod_depth * 1000) / chorus->sample_rate; + } + + chorus->mod_depth /= 2; /* amplitude is peak to peek / 2 */ +#ifdef DEBUG_PRINT + printf("depth_ms:%f, depth_samples/2:%d\n", chorus->depth_ms, chorus->mod_depth); +#endif + + /* Initializes the modulated center position: + mod_rate, center_pos_mod, and index rate. + */ + set_center_position(chorus); /* must be called before set_xxxx_frequency() */ +#ifdef DEBUG_PRINT + printf("mod_rate:%d\n", chorus->mod_rate); +#endif + + /* initialize modulator frequency */ + for(i = 0; i < chorus->number_blocks; i++) + { + set_sinus_frequency(&chorus->mod[i].sinus, + chorus->speed_Hz * chorus->mod_rate, + chorus->sample_rate, + /* phase offset between modulators waveform */ + (float)((360.0f / (float) chorus->number_blocks) * i)); + + set_triangle_frequency(&chorus->mod[i].triang, + chorus->speed_Hz * chorus->mod_rate, + chorus->sample_rate, + /* phase offset between modulators waveform */ + (float)i / chorus->number_blocks); + } +} + +/*----------------------------------------------------------------------------- + Modulated delay line initialization. + + Sets the length line ( alloc delay samples). + Remark: the function sets the internal size according to the length delay_length. + The size is augmented by INTERP_SAMPLES_NBR to take account of interpolation. + + @param chorus, pointer on chorus unit. + @param delay_length the length of the delay line in samples. + @return FLUID_OK if success , FLUID_FAILED if memory error. + + Return FLUID_OK if success, FLUID_FAILED if memory error. +-----------------------------------------------------------------------------*/ +static int new_mod_delay_line(fluid_chorus_t *chorus, int delay_length) +{ + /*-----------------------------------------------------------------------*/ + /* checks parameter */ + if(delay_length < 1) + { + return FLUID_FAILED; + } + + chorus->mod_depth = 0; + /*----------------------------------------------------------------------- + allocates delay_line and initialize members: - line, size, line_in... + */ + /* total size of the line: size = INTERP_SAMPLES_NBR + delay_length */ + chorus->size = delay_length + INTERP_SAMPLES_NBR; + chorus->line = FLUID_ARRAY(fluid_real_t, chorus->size); + + if(! chorus->line) + { + return FLUID_FAILED; + } + + /* clears the buffer: + - delay line + - interpolator member: buffer, frac_pos_mod + */ + fluid_chorus_reset(chorus); + + /* Initializes line_in to the start of the buffer */ + chorus->line_in = 0; + /*------------------------------------------------------------------------ + Initializes modulation members: + - modulation rate (the speed at which center_pos_mod is modulated: mod_rate + - modulated center position: center_pos_mod + - index rate to know when to update center_pos_mod:index_rate + -------------------------------------------------------------------------*/ + /* Initializes the modulated center position: + mod_rate, center_pos_mod, and index rate + */ + set_center_position(chorus); + + return FLUID_OK; +} + +/*----------------------------------------------------------------------------- + API +------------------------------------------------------------------------------*/ +/** + * Create the chorus unit. Once created the chorus have no parameters set, so + * fluid_chorus_set() must be called at least one time after calling + * new_fluid_chorus(). + * + * @param sample_rate, audio sample rate in Hz. + * @return pointer on chorus unit. + */ +fluid_chorus_t * +new_fluid_chorus(fluid_real_t sample_rate) +{ + fluid_chorus_t *chorus; + + chorus = FLUID_NEW(fluid_chorus_t); + + if(chorus == NULL) + { + FLUID_LOG(FLUID_PANIC, "chorus: Out of memory"); + return NULL; + } + + FLUID_MEMSET(chorus, 0, sizeof(fluid_chorus_t)); + + chorus->sample_rate = sample_rate; + +#ifdef DEBUG_PRINT + printf("fluid_chorus_t:%d bytes\n", sizeof(fluid_chorus_t)); + printf("fluid_real_t:%d bytes\n", sizeof(fluid_real_t)); +#endif + +#ifdef DEBUG_PRINT + printf("NEW_MOD\n"); +#endif + + if(new_mod_delay_line(chorus, MAX_SAMPLES) == FLUID_FAILED) + { + delete_fluid_chorus(chorus); + return NULL; + } + + return chorus; +} + +/** + * Delete the chorus unit. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + */ +void +delete_fluid_chorus(fluid_chorus_t *chorus) +{ + fluid_return_if_fail(chorus != NULL); + + FLUID_FREE(chorus->line); + FLUID_FREE(chorus); +} + +/** + * Clear the internal delay line and associate filter. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + */ +void +fluid_chorus_reset(fluid_chorus_t *chorus) +{ + int i; + unsigned int u; + + /* reset delay line */ + for(i = 0; i < chorus->size; i++) + { + chorus->line[i] = 0; + } + + /* reset modulators's allpass filter */ + for(u = 0; u < FLUID_N_ELEMENTS(chorus->mod); u++) + { + /* initializes 1st order All-Pass interpolator members */ + chorus->mod[u].buffer = 0; /* previous delay sample value */ + chorus->mod[u].frac_pos_mod = 0; /* fractional position (between consecutives sample) */ + } +} + +/** + * Set one or more chorus parameters. + * + * @param chorus Chorus instance. + * @param set Flags indicating which chorus parameters to set (#fluid_chorus_set_t). + * @param nr Chorus voice count (0-99, CPU time consumption proportional to + * this value). + * @param level Chorus level (0.0-10.0). + * @param speed Chorus speed in Hz (0.1-5.0). + * @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). + * @param type Chorus waveform type (#fluid_chorus_mod). + */ +void +fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, + fluid_real_t speed, fluid_real_t depth_ms, int type) +{ + if(set & FLUID_CHORUS_SET_NR) /* number of block */ + { + chorus->number_blocks = nr; + } + + if(set & FLUID_CHORUS_SET_LEVEL) /* output level */ + { + chorus->level = level; + } + + if(set & FLUID_CHORUS_SET_SPEED) /* lfo frequency (in Hz) */ + { + chorus->speed_Hz = speed; + } + + if(set & FLUID_CHORUS_SET_DEPTH) /* modulation depth (in ms) */ + { + chorus->depth_ms = depth_ms; + } + + if(set & FLUID_CHORUS_SET_TYPE) /* lfo shape (sinus, triangle) */ + { + chorus->type = type; + } + + /* check min , max parameters */ + if(chorus->number_blocks < 0) + { + FLUID_LOG(FLUID_WARN, "chorus: number blocks must be >=0! Setting value to 0."); + chorus->number_blocks = 0; + } + else if(chorus->number_blocks > MAX_CHORUS) + { + FLUID_LOG(FLUID_WARN, "chorus: number blocks larger than max. allowed! Setting value to %d.", + MAX_CHORUS); + chorus->number_blocks = MAX_CHORUS; + } + + if(chorus->speed_Hz < MIN_SPEED_HZ) + { + FLUID_LOG(FLUID_WARN, "chorus: speed is too low (min %f)! Setting value to min.", + (double) MIN_SPEED_HZ); + chorus->speed_Hz = MIN_SPEED_HZ; + } + else if(chorus->speed_Hz > MAX_SPEED_HZ) + { + FLUID_LOG(FLUID_WARN, "chorus: speed must be below %f Hz! Setting value to max.", + (double) MAX_SPEED_HZ); + chorus->speed_Hz = MAX_SPEED_HZ; + } + + if(chorus->depth_ms < 0.0) + { + FLUID_LOG(FLUID_WARN, "chorus: depth must be positive! Setting value to 0."); + chorus->depth_ms = 0.0; + } + + if(chorus->level < 0.0) + { + FLUID_LOG(FLUID_WARN, "chorus: level must be positive! Setting value to 0."); + chorus->level = 0.0; + } + else if(chorus->level > MAX_LEVEL) + { + FLUID_LOG(FLUID_WARN, "chorus: level must be < 10. A reasonable level is << 1! " + "Setting it to 0.1."); + chorus->level = 0.1; + } + + /* update parameters dependent of sample rate */ + update_parameters_from_sample_rate(chorus); + +#ifdef DEBUG_PRINT + printf("lfo type:%d\n", chorus->type); + printf("speed_Hz:%f\n", chorus->speed_Hz); +#endif + + /* Initialize the lfo waveform */ + if((chorus->type != FLUID_CHORUS_MOD_SINE) && + (chorus->type != FLUID_CHORUS_MOD_TRIANGLE)) + { + FLUID_LOG(FLUID_WARN, "chorus: Unknown modulation type. Using sinewave."); + chorus->type = FLUID_CHORUS_MOD_SINE; + } + +#ifdef DEBUG_PRINT + + if(chorus->type == FLUID_CHORUS_MOD_SINE) + { + printf("lfo: sinus\n"); + } + else + { + printf("lfo: triangle\n"); + } + + printf("nr:%d\n", chorus->number_blocks); +#endif + + /* Recalculate internal values after parameters change */ + + /* + Note: + Actually WIDTH is fixed to maximum value. But in the future we could add a setting + "synth.chorus.width" to allow a gradually stereo effect from minimum (monophonic) to + maximum stereo effect. + If this setting will be added, remove the following instruction. + */ + chorus->width = WIDTH; + { + /* The stereo amplitude equation (wet1 and wet2 below) have a + tendency to produce high amplitude with high width values ( 1 < width < 10). + This results in an unwanted noisy output clipped by the audio card. + To avoid this dependency, we divide by (1 + chorus->width * SCALE_WET_WIDTH) + Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), + the output amplitude (wet) seems rather independent of width setting */ + + fluid_real_t wet = chorus->level * SCALE_WET ; + + /* wet1 and wet2 are used by the stereo effect controlled by the width setting + for producing a stereo ouptput from a monophonic chorus signal. + Please see the note above about a side effect tendency */ + + if(chorus->number_blocks > 1) + { + wet = wet / (1.0f + chorus->width * SCALE_WET_WIDTH); + chorus->wet1 = wet * (chorus->width / 2.0f + 0.5f); + chorus->wet2 = wet * ((1.0f - chorus->width) / 2.0f); +#ifdef DEBUG_PRINT + printf("width:%f\n", chorus->width); + + if(chorus->width > 0) + { + printf("nr > 1, width > 0 => out stereo\n"); + } + else + { + printf("nr > 1, width:0 =>out mono\n"); + } + +#endif + } + else + { + /* only one chorus block */ + if(chorus->width == 0.0) + { + /* wet1 and wet2 should make stereo output monomophic */ + chorus->wet1 = chorus->wet2 = wet; + } + else + { + /* for width > 0, wet1 and wet2 should make stereo output stereo + with only one block. This will only possible by inverting + the unique signal on each left and right output. + Note however that with only one block, it isn't possible to + have a graduate width effect */ + chorus->wet1 = wet; + chorus->wet2 = -wet; /* inversion */ + } + +#ifdef DEBUG_PRINT + printf("width:%f\n", chorus->width); + + if(chorus->width != 0) + { + printf("one block, width > 0 => out stereo\n"); + } + else + { + printf("one block, width:0 => out mono\n"); + } + +#endif + } + } +} + +/* +* Applies a sample rate change on the chorus. +* Note that while the chorus is used by calling any fluid_chorus_processXXX() +* function, calling fluid_chorus_samplerate_change() isn't multi task safe. +* To deal properly with this issue follow the steps: +* 1) Stop chorus processing (i.e disable calling to any fluid_chorus_processXXX(). +* chorus functions. +* 2) Change sample rate by calling fluid_chorus_samplerate_change(). +* 3) Restart chorus processing (i.e enabling calling any fluid_chorus_processXXX() +* chorus functions. +* +* Another solution is to substitute step (2): +* 2.1) delete the chorus by calling delete_fluid_chorus(). +* 2.2) create the chorus by calling new_fluid_chorus(). +* +* @param chorus pointer on the chorus. +* @param sample_rate new sample rate value. +*/ +void +fluid_chorus_samplerate_change(fluid_chorus_t *chorus, fluid_real_t sample_rate) +{ + chorus->sample_rate = sample_rate; + + /* update parameters dependent of sample rate */ + update_parameters_from_sample_rate(chorus); +} + +/** + * Process chorus by mixing the result in output buffer. + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. + * @param left_out, right_out, pointers on stereo output buffers of + * FLUID_BUFSIZE samples. + */ +void fluid_chorus_processmix(fluid_chorus_t *chorus, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int sample_index; + int i; + fluid_real_t d_out[2]; /* output stereo Left and Right */ + + /* foreach sample, process output sample then input sample */ + for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) + { + fluid_real_t out; /* block output */ + + d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ + +#if 0 + /* Debug: Listen to the chorus signal only */ + left_out[sample_index] = 0; + right_out[sample_index] = 0; +#endif + + ++chorus->index_rate; /* modulator rate */ + + /* foreach chorus block, process output sample */ + for(i = 0; i < chorus->number_blocks; i++) + { + /* get sample from the output of modulated delay line */ + out = get_mod_delay(chorus, &chorus->mod[i]); + + /* accumulate out into stereo unit input */ + d_out[i & 1] += out; + } + + /* update modulator index rate and output center position */ + if(chorus->index_rate >= chorus->mod_rate) + { + chorus->index_rate = 0; /* clear modulator index rate */ + + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) + { + chorus->center_pos_mod -= chorus->size; + } + } + + /* Adjust stereo input level in case of number_blocks odd: + In those case, d_out[1] level is lower than d_out[0], so we need to + add out value to d_out[1] to have d_out[0] and d_out[1] balanced. + */ + if((i & 1) && i > 2) // i = 3,5,7... + { + d_out[1] += out ; + } + + /* Write the current input sample into the circular buffer. + * Note that 'in' may be aliased with 'left_out'. Hence this must be done + * before "processing stereo unit" (below). This ensures input buffer + * not being overwritten by stereo unit output. + */ + push_in_delay_line(chorus, in[sample_index]); + + /* process stereo unit */ + /* Add the chorus stereo unit d_out to left and right output */ + left_out[sample_index] += d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; + right_out[sample_index] += d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; + } +} + +/** + * Process chorus by putting the result in output buffer (no mixing). + * @param chorus pointer on chorus unit returned by new_fluid_chorus(). + * @param in, pointer on monophonic input buffer of FLUID_BUFSIZE samples. + * @param left_out, right_out, pointers on stereo output buffers of + * FLUID_BUFSIZE samples. + */ +/* Duplication of code ... (replaces sample data instead of mixing) */ +void fluid_chorus_processreplace(fluid_chorus_t *chorus, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int sample_index; + int i; + fluid_real_t d_out[2]; /* output stereo Left and Right */ + + /* foreach sample, process output sample then input sample */ + for(sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) + { + fluid_real_t out; /* block output */ + + d_out[0] = d_out[1] = 0.0f; /* clear stereo unit input */ + +#if 0 + /* Debug: Listen to the chorus signal only */ + left_out[sample_index] = 0; + right_out[sample_index] = 0; +#endif + + ++chorus->index_rate; /* modulator rate */ + + /* foreach chorus block, process output sample */ + for(i = 0; i < chorus->number_blocks; i++) + { + /* get sample from the output of modulated delay line */ + out = get_mod_delay(chorus, &chorus->mod[i]); + + /* accumulate out into stereo unit input */ + d_out[i & 1] += out; + } + + /* update modulator index rate and output center position */ + if(chorus->index_rate >= chorus->mod_rate) + { + chorus->index_rate = 0; /* clear modulator index rate */ + + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((chorus->center_pos_mod += chorus->mod_rate) >= chorus->size) + { + chorus->center_pos_mod -= chorus->size; + } + } + + /* Adjust stereo input level in case of number_blocks odd: + In those case, d_out[1] level is lower than d_out[0], so we need to + add out value to d_out[1] to have d_out[0] and d_out[1] balanced. + */ + if((i & 1) && i > 2) // i = 3,5,7... + { + d_out[1] += out ; + } + + /* Write the current input sample into the circular buffer. + * Note that 'in' may be aliased with 'left_out'. Hence this must be done + * before "processing stereo unit" (below). This ensures input buffer + * not being overwritten by stereo unit output. + */ + push_in_delay_line(chorus, in[sample_index]); + + /* process stereo unit */ + /* store the chorus stereo unit d_out to left and right output */ + left_out[sample_index] = d_out[0] * chorus->wet1 + d_out[1] * chorus->wet2; + right_out[sample_index] = d_out[1] * chorus->wet1 + d_out[0] * chorus->wet2; + } +} diff --git a/libs/fluidsynth/src/rvoice/fluid_chorus.h b/libs/fluidsynth/src/rvoice/fluid_chorus.h new file mode 100644 index 00000000000..c6d247fa5f3 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_chorus.h @@ -0,0 +1,80 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_CHORUS_H +#define _FLUID_CHORUS_H + +#include "fluidsynth_priv.h" + + +typedef struct _fluid_chorus_t fluid_chorus_t; + +/* enum describing each chorus parameter */ +enum fluid_chorus_param +{ + FLUID_CHORUS_NR, /**< number of delay line */ + FLUID_CHORUS_LEVEL, /**< output level */ + FLUID_CHORUS_SPEED, /**< lfo frequency */ + FLUID_CHORUS_DEPTH, /**< modulation depth */ + FLUID_CHORUS_TYPE, /**< type of waveform */ + FLUID_CHORUS_PARAM_LAST /* number of enum fluid_chorus_param */ +}; + +/* return a bit flag from param: 2^param */ +#define FLUID_CHORPARAM_TO_SETFLAG(param) (1 << param) + +/** Flags for fluid_chorus_set() */ +typedef enum +{ + FLUID_CHORUS_SET_NR = FLUID_CHORPARAM_TO_SETFLAG(FLUID_CHORUS_NR), + FLUID_CHORUS_SET_LEVEL = FLUID_CHORPARAM_TO_SETFLAG(FLUID_CHORUS_LEVEL), + FLUID_CHORUS_SET_SPEED = FLUID_CHORPARAM_TO_SETFLAG(FLUID_CHORUS_SPEED), + FLUID_CHORUS_SET_DEPTH = FLUID_CHORPARAM_TO_SETFLAG(FLUID_CHORUS_DEPTH), + FLUID_CHORUS_SET_TYPE = FLUID_CHORPARAM_TO_SETFLAG(FLUID_CHORUS_TYPE), + + /** Value for fluid_chorus_set() which sets all chorus parameters. */ + FLUID_CHORUS_SET_ALL = FLUID_CHORUS_SET_NR + | FLUID_CHORUS_SET_LEVEL + | FLUID_CHORUS_SET_SPEED + | FLUID_CHORUS_SET_DEPTH + | FLUID_CHORUS_SET_TYPE, +} fluid_chorus_set_t; + +/* + * chorus + */ +fluid_chorus_t *new_fluid_chorus(fluid_real_t sample_rate); +void delete_fluid_chorus(fluid_chorus_t *chorus); +void fluid_chorus_reset(fluid_chorus_t *chorus); + +void fluid_chorus_set(fluid_chorus_t *chorus, int set, int nr, fluid_real_t level, + fluid_real_t speed, fluid_real_t depth_ms, int type); +void +fluid_chorus_samplerate_change(fluid_chorus_t *chorus, fluid_real_t sample_rate); + +void fluid_chorus_processmix(fluid_chorus_t *chorus, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); +void fluid_chorus_processreplace(fluid_chorus_t *chorus, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); + + + +#endif /* _FLUID_CHORUS_H */ diff --git a/libs/fluidsynth/src/rvoice/fluid_iir_filter.c b/libs/fluidsynth/src/rvoice/fluid_iir_filter.c new file mode 100644 index 00000000000..0535cbf275a --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_iir_filter.c @@ -0,0 +1,419 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_iir_filter.h" +#include "fluid_sys.h" +#include "fluid_conv.h" + +/** + * Applies a low- or high-pass filter with variable cutoff frequency and quality factor + * for a given biquad transfer function: + * b0 + b1*z^-1 + b2*z^-2 + * H(z) = ------------------------ + * a0 + a1*z^-1 + a2*z^-2 + * + * Also modifies filter state accordingly. + * @param iir_filter Filter parameter + * @param dsp_buf Pointer to the synthesized audio data + * @param count Count of samples in dsp_buf + */ +/* + * Variable description: + * - dsp_a1, dsp_a2: Filter coefficients for the the previously filtered output signal + * - dsp_b0, dsp_b1, dsp_b2: Filter coefficients for input signal + * - coefficients normalized to a0 + * + * A couple of variables are used internally, their results are discarded: + * - dsp_i: Index through the output buffer + * - dsp_centernode: delay line for the IIR filter + * - dsp_hist1: same + * - dsp_hist2: same + */ +void +fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, + fluid_real_t *dsp_buf, int count) +{ + if(iir_filter->type == FLUID_IIR_DISABLED || iir_filter->q_lin == 0) + { + return; + } + else + { + /* IIR filter sample history */ + fluid_real_t dsp_hist1 = iir_filter->hist1; + fluid_real_t dsp_hist2 = iir_filter->hist2; + + /* IIR filter coefficients */ + fluid_real_t dsp_a1 = iir_filter->a1; + fluid_real_t dsp_a2 = iir_filter->a2; + fluid_real_t dsp_b02 = iir_filter->b02; + fluid_real_t dsp_b1 = iir_filter->b1; + int dsp_filter_coeff_incr_count = iir_filter->filter_coeff_incr_count; + + fluid_real_t dsp_centernode; + int dsp_i; + + /* filter (implement the voice filter according to SoundFont standard) */ + + /* Check for denormal number (too close to zero). */ + if(FLUID_FABS(dsp_hist1) < 1e-20f) + { + dsp_hist1 = 0.0f; /* FIXME JMG - Is this even needed? */ + } + + /* Two versions of the filter loop. One, while the filter is + * changing towards its new setting. The other, if the filter + * doesn't change. + */ + + if(dsp_filter_coeff_incr_count > 0) + { + fluid_real_t dsp_a1_incr = iir_filter->a1_incr; + fluid_real_t dsp_a2_incr = iir_filter->a2_incr; + fluid_real_t dsp_b02_incr = iir_filter->b02_incr; + fluid_real_t dsp_b1_incr = iir_filter->b1_incr; + + + /* Increment is added to each filter coefficient filter_coeff_incr_count times. */ + for(dsp_i = 0; dsp_i < count; dsp_i++) + { + /* The filter is implemented in Direct-II form. */ + dsp_centernode = dsp_buf[dsp_i] - dsp_a1 * dsp_hist1 - dsp_a2 * dsp_hist2; + dsp_buf[dsp_i] = dsp_b02 * (dsp_centernode + dsp_hist2) + dsp_b1 * dsp_hist1; + dsp_hist2 = dsp_hist1; + dsp_hist1 = dsp_centernode; + + if(dsp_filter_coeff_incr_count-- > 0) + { + fluid_real_t old_b02 = dsp_b02; + dsp_a1 += dsp_a1_incr; + dsp_a2 += dsp_a2_incr; + dsp_b02 += dsp_b02_incr; + dsp_b1 += dsp_b1_incr; + + /* Compensate history to avoid the filter going havoc with large frequency changes */ + if(iir_filter->compensate_incr && FLUID_FABS(dsp_b02) > 0.001f) + { + fluid_real_t compensate = old_b02 / dsp_b02; + dsp_hist1 *= compensate; + dsp_hist2 *= compensate; + } + } + } /* for dsp_i */ + } + else /* The filter parameters are constant. This is duplicated to save time. */ + { + for(dsp_i = 0; dsp_i < count; dsp_i++) + { + /* The filter is implemented in Direct-II form. */ + dsp_centernode = dsp_buf[dsp_i] - dsp_a1 * dsp_hist1 - dsp_a2 * dsp_hist2; + dsp_buf[dsp_i] = dsp_b02 * (dsp_centernode + dsp_hist2) + dsp_b1 * dsp_hist1; + dsp_hist2 = dsp_hist1; + dsp_hist1 = dsp_centernode; + } + } + + iir_filter->hist1 = dsp_hist1; + iir_filter->hist2 = dsp_hist2; + iir_filter->a1 = dsp_a1; + iir_filter->a2 = dsp_a2; + iir_filter->b02 = dsp_b02; + iir_filter->b1 = dsp_b1; + iir_filter->filter_coeff_incr_count = dsp_filter_coeff_incr_count; + + fluid_check_fpe("voice_filter"); + } +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init) +{ + fluid_iir_filter_t *iir_filter = obj; + enum fluid_iir_filter_type type = param[0].i; + enum fluid_iir_filter_flags flags = param[1].i; + + iir_filter->type = type; + iir_filter->flags = flags; + + if(type != FLUID_IIR_DISABLED) + { + fluid_iir_filter_reset(iir_filter); + } +} + +void +fluid_iir_filter_reset(fluid_iir_filter_t *iir_filter) +{ + iir_filter->hist1 = 0; + iir_filter->hist2 = 0; + iir_filter->last_fres = -1.; + iir_filter->q_lin = 0; + iir_filter->filter_startup = 1; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres) +{ + fluid_iir_filter_t *iir_filter = obj; + fluid_real_t fres = param[0].real; + + iir_filter->fres = fres; + iir_filter->last_fres = -1.; +} + +static fluid_real_t fluid_iir_filter_q_from_dB(fluid_real_t q_dB) +{ + /* The generator contains 'centibels' (1/10 dB) => divide by 10 to + * obtain dB */ + q_dB /= 10.0f; + + /* Range: SF2.01 section 8.1.3 # 8 (convert from cB to dB => /10) */ + fluid_clip(q_dB, 0.0f, 96.0f); + + /* Short version: Modify the Q definition in a way, that a Q of 0 + * dB leads to no resonance hump in the freq. response. + * + * Long version: From SF2.01, page 39, item 9 (initialFilterQ): + * "The gain at the cutoff frequency may be less than zero when + * zero is specified". Assume q_dB=0 / q_lin=1: If we would leave + * q as it is, then this results in a 3 dB hump slightly below + * fc. At fc, the gain is exactly the DC gain (0 dB). What is + * (probably) meant here is that the filter does not show a + * resonance hump for q_dB=0. In this case, the corresponding + * q_lin is 1/sqrt(2)=0.707. The filter should have 3 dB of + * attenuation at fc now. In this case Q_dB is the height of the + * resonance peak not over the DC gain, but over the frequency + * response of a non-resonant filter. This idea is implemented as + * follows: */ + q_dB -= 3.01f; + + /* The 'sound font' Q is defined in dB. The filter needs a linear + q. Convert. */ + return FLUID_POW(10.0f, q_dB / 20.0f); +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q) +{ + fluid_iir_filter_t *iir_filter = obj; + fluid_real_t q = param[0].real; + int flags = iir_filter->flags; + + if(flags & FLUID_IIR_Q_ZERO_OFF && q <= 0.0) + { + q = 0; + } + else if(flags & FLUID_IIR_Q_LINEAR) + { + /* q is linear (only for user-defined filter) + * increase to avoid Q being somewhere between zero and one, + * which results in some strange amplified lowpass signal + */ + q++; + } + else + { + q = fluid_iir_filter_q_from_dB(q); + } + + iir_filter->q_lin = q; + iir_filter->filter_gain = 1.0; + + if(!(flags & FLUID_IIR_NO_GAIN_AMP)) + { + /* SF 2.01 page 59: + * + * The SoundFont specs ask for a gain reduction equal to half the + * height of the resonance peak (Q). For example, for a 10 dB + * resonance peak, the gain is reduced by 5 dB. This is done by + * multiplying the total gain with sqrt(1/Q). `Sqrt' divides dB + * by 2 (100 lin = 40 dB, 10 lin = 20 dB, 3.16 lin = 10 dB etc) + * The gain is later factored into the 'b' coefficients + * (numerator of the filter equation). This gain factor depends + * only on Q, so this is the right place to calculate it. + */ + iir_filter->filter_gain /= FLUID_SQRT(q); + } + + /* The synthesis loop will have to recalculate the filter coefficients. */ + iir_filter->last_fres = -1.; +} + +static FLUID_INLINE void +fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t *iir_filter, + int transition_samples, + fluid_real_t output_rate) +{ + /* FLUID_IIR_Q_LINEAR may switch the filter off by setting Q==0 */ + if(iir_filter->q_lin == 0) + { + return; + } + else + { + /* + * Those equations from Robert Bristow-Johnson's `Cookbook + * formulae for audio EQ biquad filter coefficients', obtained + * from Harmony-central.com / Computer / Programming. They are + * the result of the bilinear transform on an analogue filter + * prototype. To quote, `BLT frequency warping has been taken + * into account for both significant frequency relocation and for + * bandwidth readjustment'. */ + + fluid_real_t omega = (fluid_real_t)(2.0 * M_PI) * + (iir_filter->last_fres / output_rate); + fluid_real_t sin_coeff = FLUID_SIN(omega); + fluid_real_t cos_coeff = FLUID_COS(omega); + fluid_real_t alpha_coeff = sin_coeff / (2.0f * iir_filter->q_lin); + fluid_real_t a0_inv = 1.0f / (1.0f + alpha_coeff); + + /* Calculate the filter coefficients. All coefficients are + * normalized by a0. Think of `a1' as `a1/a0'. + * + * Here a couple of multiplications are saved by reusing common expressions. + * The original equations should be: + * iir_filter->b0=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; + * iir_filter->b1=(1.-cos_coeff)*a0_inv*iir_filter->filter_gain; + * iir_filter->b2=(1.-cos_coeff)*a0_inv*0.5*iir_filter->filter_gain; */ + + /* "a" coeffs are same for all 3 available filter types */ + fluid_real_t a1_temp = -2.0f * cos_coeff * a0_inv; + fluid_real_t a2_temp = (1.0f - alpha_coeff) * a0_inv; + + fluid_real_t b02_temp, b1_temp; + + switch(iir_filter->type) + { + case FLUID_IIR_HIGHPASS: + b1_temp = (1.0f + cos_coeff) * a0_inv * iir_filter->filter_gain; + + /* both b0 -and- b2 */ + b02_temp = b1_temp * 0.5f; + + b1_temp *= -1.0f; + break; + + case FLUID_IIR_LOWPASS: + b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; + + /* both b0 -and- b2 */ + b02_temp = b1_temp * 0.5f; + break; + + default: + /* filter disabled, should never get here */ + return; + } + + iir_filter->compensate_incr = 0; + + if(iir_filter->filter_startup || (transition_samples == 0)) + { + /* The filter is calculated, because the voice was started up. + * In this case set the filter coefficients without delay. + */ + iir_filter->a1 = a1_temp; + iir_filter->a2 = a2_temp; + iir_filter->b02 = b02_temp; + iir_filter->b1 = b1_temp; + iir_filter->filter_coeff_incr_count = 0; + iir_filter->filter_startup = 0; +// printf("Setting initial filter coefficients.\n"); + } + else + { + + /* The filter frequency is changed. Calculate an increment + * factor, so that the new setting is reached after one buffer + * length. x_incr is added to the current value FLUID_BUFSIZE + * times. The length is arbitrarily chosen. Longer than one + * buffer will sacrifice some performance, though. Note: If + * the filter is still too 'grainy', then increase this number + * at will. + */ + + iir_filter->a1_incr = (a1_temp - iir_filter->a1) / transition_samples; + iir_filter->a2_incr = (a2_temp - iir_filter->a2) / transition_samples; + iir_filter->b02_incr = (b02_temp - iir_filter->b02) / transition_samples; + iir_filter->b1_incr = (b1_temp - iir_filter->b1) / transition_samples; + + if(FLUID_FABS(iir_filter->b02) > 0.0001f) + { + fluid_real_t quota = b02_temp / iir_filter->b02; + iir_filter->compensate_incr = quota < 0.5f || quota > 2.f; + } + + /* Have to add the increments filter_coeff_incr_count times. */ + iir_filter->filter_coeff_incr_count = transition_samples; + } + + fluid_check_fpe("voice_write filter calculation"); + } +} + + +void fluid_iir_filter_calc(fluid_iir_filter_t *iir_filter, + fluid_real_t output_rate, + fluid_real_t fres_mod) +{ + fluid_real_t fres; + + /* calculate the frequency of the resonant filter in Hz */ + fres = fluid_ct2hz(iir_filter->fres + fres_mod); + + /* FIXME - Still potential for a click during turn on, can we interpolate + between 20khz cutoff and 0 Q? */ + + /* I removed the optimization of turning the filter off when the + * resonance frequency is above the maximum frequency. Instead, the + * filter frequency is set to a maximum of 0.45 times the sampling + * rate. For a 44100 kHz sampling rate, this amounts to 19845 + * Hz. The reason is that there were problems with anti-aliasing when the + * synthesizer was run at lower sampling rates. Thanks to Stephan + * Tassart for pointing me to this bug. By turning the filter on and + * clipping the maximum filter frequency at 0.45*srate, the filter + * is used as an anti-aliasing filter. */ + + if(fres > 0.45f * output_rate) + { + fres = 0.45f * output_rate; + } + else if(fres < 5.f) + { + fres = 5.f; + } + + /* if filter enabled and there is a significant frequency change.. */ + if(iir_filter->type != FLUID_IIR_DISABLED && FLUID_FABS(fres - iir_filter->last_fres) > 0.01f) + { + /* The filter coefficients have to be recalculated (filter + * parameters have changed). Recalculation for various reasons is + * forced by setting last_fres to -1. The flag filter_startup + * indicates, that the DSP loop runs for the first time, in this + * case, the filter is set directly, instead of smoothly fading + * between old and new settings. */ + iir_filter->last_fres = fres; + fluid_iir_filter_calculate_coefficients(iir_filter, FLUID_BUFSIZE, + output_rate); + } + + + fluid_check_fpe("voice_write DSP coefficients"); + +} + diff --git a/libs/fluidsynth/src/rvoice/fluid_iir_filter.h b/libs/fluidsynth/src/rvoice/fluid_iir_filter.h new file mode 100644 index 00000000000..355d197f1cb --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_iir_filter.h @@ -0,0 +1,75 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_IIR_FILTER_H +#define _FLUID_IIR_FILTER_H + +#include "fluidsynth_priv.h" + +typedef struct _fluid_iir_filter_t fluid_iir_filter_t; + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_init); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_fres); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_iir_filter_set_q); + +void fluid_iir_filter_apply(fluid_iir_filter_t *iir_filter, + fluid_real_t *dsp_buf, int dsp_buf_count); + +void fluid_iir_filter_reset(fluid_iir_filter_t *iir_filter); + +void fluid_iir_filter_calc(fluid_iir_filter_t *iir_filter, + fluid_real_t output_rate, + fluid_real_t fres_mod); + +/* We can't do information hiding here, as fluid_voice_t includes the struct + without a pointer. */ +struct _fluid_iir_filter_t +{ + enum fluid_iir_filter_type type; /* specifies the type of this filter */ + enum fluid_iir_filter_flags flags; /* additional flags to customize this filter */ + + /* filter coefficients */ + /* The coefficients are normalized to a0. */ + /* b0 and b2 are identical => b02 */ + fluid_real_t b02; /* b0 / a0 */ + fluid_real_t b1; /* b1 / a0 */ + fluid_real_t a1; /* a0 / a0 */ + fluid_real_t a2; /* a1 / a0 */ + + fluid_real_t b02_incr; + fluid_real_t b1_incr; + fluid_real_t a1_incr; + fluid_real_t a2_incr; + int filter_coeff_incr_count; + int compensate_incr; /* Flag: If set, must compensate history */ + fluid_real_t hist1, hist2; /* Sample history for the IIR filter */ + int filter_startup; /* Flag: If set, the filter will be set directly. + Else it changes smoothly. */ + + fluid_real_t fres; /* the resonance frequency, in cents (not absolute cents) */ + fluid_real_t last_fres; /* Current resonance frequency of the IIR filter */ + /* Serves as a flag: A deviation between fres and last_fres */ + /* indicates, that the filter has to be recalculated. */ + fluid_real_t q_lin; /* the q-factor on a linear scale */ + fluid_real_t filter_gain; /* Gain correction factor, depends on q */ +}; + +#endif + diff --git a/libs/fluidsynth/src/rvoice/fluid_lfo.c b/libs/fluidsynth/src/rvoice/fluid_lfo.c new file mode 100644 index 00000000000..ae21cdd0f72 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_lfo.c @@ -0,0 +1,17 @@ +#include "fluid_lfo.h" + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_incr) +{ + fluid_lfo_t *lfo = obj; + fluid_real_t increment = param[0].real; + + lfo->increment = increment; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_delay) +{ + fluid_lfo_t *lfo = obj; + unsigned int delay = param[0].i; + + lfo->delay = delay; +} diff --git a/libs/fluidsynth/src/rvoice/fluid_lfo.h b/libs/fluidsynth/src/rvoice/fluid_lfo.h new file mode 100644 index 00000000000..b9a9ca6eaaf --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_lfo.h @@ -0,0 +1,75 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_LFO_H +#define _FLUID_LFO_H + +#include "fluid_sys.h" + +typedef struct _fluid_lfo_t fluid_lfo_t; + +struct _fluid_lfo_t +{ + fluid_real_t val; /* the current value of the LFO */ + unsigned int delay; /* the delay of the lfo in samples */ + fluid_real_t increment; /* the lfo frequency is converted to a per-buffer increment */ +}; + +static FLUID_INLINE void +fluid_lfo_reset(fluid_lfo_t *lfo) +{ + lfo->val = 0.0f; +} + +// These two cannot be inlined since they're used by event_dispatch +DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_incr); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_lfo_set_delay); + +static FLUID_INLINE fluid_real_t +fluid_lfo_get_val(fluid_lfo_t *lfo) +{ + return lfo->val; +} + +static FLUID_INLINE void +fluid_lfo_calc(fluid_lfo_t *lfo, unsigned int cur_delay) +{ + if(cur_delay < lfo->delay) + { + return; + } + + lfo->val += lfo->increment; + + if(lfo->val > (fluid_real_t) 1.0) + { + lfo->increment = -lfo->increment; + lfo->val = (fluid_real_t) 2.0 - lfo->val; + } + else if(lfo->val < (fluid_real_t) -1.0) + { + lfo->increment = -lfo->increment; + lfo->val = (fluid_real_t) -2.0 - lfo->val; + } + +} + +#endif + diff --git a/libs/fluidsynth/src/rvoice/fluid_phase.h b/libs/fluidsynth/src/rvoice/fluid_phase.h new file mode 100644 index 00000000000..44df6b249fc --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_phase.h @@ -0,0 +1,113 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_PHASE_H +#define _FLUID_PHASE_H + +/* + * phase + */ + +#define FLUID_INTERP_BITS 8 +#define FLUID_INTERP_BITS_MASK 0xff000000 +#define FLUID_INTERP_BITS_SHIFT 24 + + +#define FLUID_FRACT_MAX ((double)4294967296.0) + +/* fluid_phase_t +* Purpose: +* Playing pointer for voice playback +* +* When a sample is played back at a different pitch, the playing pointer in the +* source sample will not advance exactly one sample per output sample. +* This playing pointer is implemented using fluid_phase_t. +* It is a 64 bit number. The higher 32 bits contain the 'index' (number of +* the current sample), the lower 32 bits the fractional part. +*/ +typedef uint64_t fluid_phase_t; + +/* Purpose: + * Set a to b. + * a: fluid_phase_t + * b: fluid_phase_t + */ +#define fluid_phase_set(a,b) a=b; + +#define fluid_phase_set_int(a, b) ((a) = ((uint64_t)(b)) << 32) + +/* Purpose: + * Sets the phase a to a phase increment given in b. + * For example, assume b is 0.9. After setting a to it, adding a to + * the playing pointer will advance it by 0.9 samples. */ +#define fluid_phase_set_float(a, b) \ + (a) = (((uint64_t)(b)) << 32) \ + | (uint32_t) (((double)(b) - (int)(b)) * (double)FLUID_FRACT_MAX) + +/* create a fluid_phase_t from an index and a fraction value */ +#define fluid_phase_from_index_fract(index, fract) \ + ((((uint64_t)(index)) << 32) + (fract)) + +/* Purpose: + * Return the index and the fractional part, respectively. */ +#define fluid_phase_index(_x) \ + ((unsigned int)((_x) >> 32)) +#define fluid_phase_fract(_x) \ + ((uint32_t)((_x) & 0xFFFFFFFF)) + +/* Get the phase index with fractional rounding */ +#define fluid_phase_index_round(_x) \ + ((unsigned int)(((_x) + 0x80000000) >> 32)) + + +/* Purpose: + * Takes the fractional part of the argument phase and + * calculates the corresponding position in the interpolation table. + * The fractional position of the playing pointer is calculated with a quite high + * resolution (32 bits). It would be unpractical to keep a set of interpolation + * coefficients for each possible fractional part... + */ +#define fluid_phase_fract_to_tablerow(_x) \ + ((unsigned int)(fluid_phase_fract(_x) & FLUID_INTERP_BITS_MASK) >> FLUID_INTERP_BITS_SHIFT) + +#define fluid_phase_double(_x) \ + ((double)(fluid_phase_index(_x)) + ((double)fluid_phase_fract(_x) / FLUID_FRACT_MAX)) + +/* Purpose: + * Advance a by a step of b (both are fluid_phase_t). + */ +#define fluid_phase_incr(a, b) a += b + +/* Purpose: + * Subtract b from a (both are fluid_phase_t). + */ +#define fluid_phase_decr(a, b) a -= b + +/* Purpose: + * Subtract b samples from a. + */ +#define fluid_phase_sub_int(a, b) ((a) -= (uint64_t)(b) << 32) + +/* Purpose: + * Creates the expression a.index++. */ +#define fluid_phase_index_plusplus(a) (((a) += 0x100000000LL) + +#endif /* _FLUID_PHASE_H */ diff --git a/libs/fluidsynth/src/rvoice/fluid_rev.c b/libs/fluidsynth/src/rvoice/fluid_rev.c new file mode 100644 index 00000000000..11bc760832a --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rev.c @@ -0,0 +1,1523 @@ +/****************************************************************************** + * FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * + * FDN REVERB + * + * Freeverb used by fluidsynth (v.1.1.10 and previous) is based on + * Schroeder-Moorer reverberator: + * https://ccrma.stanford.edu/~jos/pasp/Freeverb.html + * + * This FDN reverberation is based on jot FDN reverberator. + * https://ccrma.stanford.edu/~jos/Reverb/FDN_Late_Reverberation.html + * Like Freeverb it is a late reverb which is convenient for Fluidsynth. + * + * + * .-------------------. + * .-----------------| | + * | - | Feedback | + * | .--------------| Matrix | + * | | |___________________| + * | | /|\ /|\ + * |/ | .---------. .-------. | - | .------. + * .->+ ---->| Delay 0 |-|L.P.F 0|--*-------->| |-> out + * .---------. | | |_________| |_______| | | | left + * |Tone | | | - - | |Stereo| + * In ->|corrector|--* | - - | | unit | + * mono |_________| | |/ .---------. .-------. | | |-> out + * ---->+ ->| Delay 7 |-|L.P.F 7|--------*-->| | right + * |_________| |_______| |______| + * /|\ /|\ /|\ /|\ + * | | | | + * roomsize --/ | width --/ | + * damp ------/ level ------/ + * + * It takes a monophonic input and produces a stereo output. + * + * The parameters are the same than for Freeverb. + * Also the default response of these parameters are the same than for Freeverb: + * - roomsize (0 to 1): control the reverb time from 0.7 to 12.5 s. + * This reverberation time is ofen called T60DC. + * + * - damp (0 to 1): controls the reverb time frequency dependency. + * This controls the reverb time for the frequency sample rate/2 + * + * When 0, the reverb time for high frequencies is the same as + * for DC frequency. + * When > 0, high frequencies have less reverb time than lower frequencies. + * + * - width (0 to 100): controls the left/right output separation. + * When 0, there are no separation and the signal on left and right. + * output is the same. This sounds like a monophonic signal. + * When 100, the separation between left and right is maximum. + * + * - level (0 to 1), controls the output level reverberation. + * + * This FDN reverb produces a better quality reverberation tail than Freeverb with + * far less ringing by using modulated delay lines that help to cancel + * the building of a lot of resonances in the reverberation tail even when + * using only 8 delays lines (NBR_DELAYS = 8) (default). + * + * The frequency density (often called "modal density" is one property that + * contributes to sound quality. Although 8 lines give good result, using 12 delays + * lines brings the overall frequency density quality a bit higher. + * This quality augmentation is noticeable particularly when using long reverb time + * (roomsize = 1) on solo instrument with long release time. Of course the cpu load + * augmentation is +50% relatively to 8 lines. + * + * As a general rule the reverberation tail quality is easier to perceive by ear + * when using: + * - percussive instruments (i.e piano and others). + * - long reverb time (roomsize = 1). + * - no damping (damp = 0). + * - Using headphone. Avoid using loud speaker, you will be quickly misguided by the + * natural reverberation of the room in which you are. + * + * The cpu load for 8 lines is a bit lower than for freeverb (- 3%), + * but higher for 12 lines (+ 41%). + * + * + * The memory consumption is less than for freeverb + * (see the results table below). + * + * Two macros are usable at compiler time: + * - NBR_DELAYS: number of delay lines. 8 (default) or 12. + * - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response of + * roomsize parameter. + * When this macro is not defined (the default), roomsize has the same + * response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). + * + * When this macro is defined, roomsize behaves linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). + * This linear response is convenient when using GUI controls. + * + * -------------------------------------------------------------------------- + * Compare table: + * Note: the cpu load in % are relative each to other. These values are + * given by the fluidsynth profile commands. + * -------------------------------------------------------------------------- + * reverb | NBR_DELAYS | Performances | memory size | quality + * | | (cpu_load: %) | (bytes)(see note) | + * ========================================================================== + * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing + * | 2 x 4 all-pass | | | + * ----------|--------------------------------------------------------------- + * FDN | 8 | 0.650 % | 112480 | far less + * modulated | |(feeverb - 3%) | (56% freeverb) | ringing + * |--------------------------------------------------------------- + * | 12 | 0.942 % | 168720 | best than + * | |(freeverb + 41%) | (82 %freeverb) | 8 lines + *--------------------------------------------------------------------------- + * + * Note: + * Values in this column is the memory consumption for sample rate <= 44100Hz. + * For sample rate > 44100Hz , multiply these values by (sample rate / 44100Hz). + * For example: for sample rate 96000Hz, the memory consumed is 244760 bytes + * + *---------------------------------------------------------------------------- + * 'Denormalise' method to avoid loss of performance. + * -------------------------------------------------- + * According to music-dsp thread 'Denormalise', Pentium processors + * have a hardware 'feature', that is of interest here, related to + * numeric underflow. We have a recursive filter. The output decays + * exponentially, if the input stops. So the numbers get smaller and + * smaller... At some point, they reach 'denormal' level. This will + * lead to drastic spikes in the CPU load. The effect was reproduced + * with the reverb - sometimes the average load over 10 s doubles!!. + * + * The 'undenormalise' macro fixes the problem: As soon as the number + * is close enough to denormal level, the macro forces the number to + * 0.0f. The original macro is: + * + * #define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f + * + * This will zero out a number when it reaches the denormal level. + * Advantage: Maximum dynamic range Disadvantage: We'll have to check + * every sample, expensive. The alternative macro comes from a later + * mail from Jon Watte. It will zap a number before it reaches + * denormal level. Jon suggests to run it once per block instead of + * every sample. + */ + +/* Denormalising part II: + * + * Another method fixes the problem cheaper: Use a small DC-offset in + * the filter calculations. Now the signals converge not against 0, + * but against the offset. The constant offset is invisible from the + * outside world (i.e. it does not appear at the output. There is a + * very small turn-on transient response, which should not cause + * problems. + */ +#include "fluid_rev.h" +#include "fluid_sys.h" + +/*---------------------------------------------------------------------------- + Configuration macros at compiler time. + + 3 macros are usable at compiler time: + - NBR_DELAYs: number of delay lines. 8 (default) or 12. + - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response for + roomsize parameter. + - DENORMALISING enable denormalising handling. +-----------------------------------------------------------------------------*/ +//#define INFOS_PRINT /* allows message to be printed on the console. */ + +/* Number of delay lines (must be only 8 or 12) + 8 is the default. + 12 produces a better quality but is +50% cpu expensive. +*/ +#define NBR_DELAYS 8 /* default*/ + +/* response curve of parameter roomsize */ +/* + The default response is the same as Freeverb: + - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). + + when ROOMSIZE_RESPONSE_LINEAR is defined, the response is: + - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). +*/ +//#define ROOMSIZE_RESPONSE_LINEAR + +/* DENORMALISING enable denormalising handling */ +#define DENORMALISING + +#ifdef DENORMALISING +#define DC_OFFSET 1e-8f +#else +#define DC_OFFSET 0.0f +#endif + +/*---------------------------------------------------------------------------- + Initial internal reverb settings (at reverb creation time) +-----------------------------------------------------------------------------*/ +/* SCALE_WET_WIDTH is a compensation weight factor to get an output + amplitude (wet) rather independent of the width setting. + 0: the output amplitude is fully dependent on the width setting. + >0: the output amplitude is less dependent on the width setting. + With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather + independent of width setting (see fluid_revmodel_update()). + */ +#define SCALE_WET_WIDTH 0.2f + +/* It is best to inject the input signal less ofen. This contributes to obtain +a flatter response on comb filter. So the input gain is set to 0.1 rather 1.0. */ +#define FIXED_GAIN 0.1f /* input gain */ + +/* SCALE_WET is adjusted to 5.0 to get internal output level equivalent to freeverb */ +#define SCALE_WET 5.0f /* scale output gain */ + +/*---------------------------------------------------------------------------- + Internal FDN late reverb settings +-----------------------------------------------------------------------------*/ + +/*-- Reverberation time settings ---------------------------------- + MIN_DC_REV_TIME est defined egal to the minimum value of freeverb: + MAX_DC_REV_TIME est defined egal to the maximum value of freeverb: + T60DC is computed from gi and the longest delay line in freeverb: L8 = 1617 + T60 = -3 * Li * T / log10(gi) + T60 = -3 * Li * / (log10(gi) * sr) + + - Li: length of comb filter delay line. + - sr: sample rate. + - gi: the feedback gain. + + The minimum value for freeverb correspond to gi = 0.7. + with Mi = 1617, sr at 44100 Hz, and gi = 0.7 => MIN_DC_REV_TIME = 0.7 s + + The maximum value for freeverb correspond to gi = 0.98. + with Mi = 1617, sr at 44100 Hz, and gi = 0.98 => MAX_DC_REV_TIME = 12.5 s +*/ + +#define MIN_DC_REV_TIME 0.7f /* minimum T60DC reverb time: seconds */ +#define MAX_DC_REV_TIME 12.5f /* maximumm T60DC time in seconds */ +#define RANGE_REV_TIME (MAX_DC_REV_TIME - MIN_DC_REV_TIME) + +/* macro to compute internal reverberation time versus roomsize parameter */ +#define GET_DC_REV_TIME(roomsize) (MIN_DC_REV_TIME + RANGE_REV_TIME * roomsize) + +/*-- Modulation related settings ----------------------------------*/ +/* For many instruments, the range for MOD_FREQ and MOD_DEPTH should be: + + MOD_DEPTH: [3..6] (in samples). + MOD_FREQ: [0.5 ..2.0] (in Hz). + + Values below the lower limits are often not sufficient to cancel unwanted + "ringing"(resonant frequency). + Values above upper limits augment the unwanted "chorus". + + With NBR_DELAYS to 8: + MOD_DEPTH must be >= 4 to cancel the unwanted "ringing".[4..6]. + With NBR_DELAYS to 12: + MOD_DEPTH to 3 is sufficient to cancel the unwanted "ringing".[3..6] +*/ +#define MOD_DEPTH 4 /* modulation depth (samples)*/ +#define MOD_RATE 50 /* modulation rate (samples)*/ +#define MOD_FREQ 1.0f /* modulation frequency (Hz) */ +/* + Number of samples to add to the desired length of a delay line. This + allow to take account of modulation interpolation. + 1 is sufficient with MOD_DEPTH equal to 4. +*/ +#define INTERP_SAMPLES_NBR 1 + +/* phase offset between modulators waveform */ +#define MOD_PHASE (360.0f/(float) NBR_DELAYS) + +#if (NBR_DELAYS == 8) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1129 +#elif (NBR_DELAYS == 12) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1093 + #define DELAY_L8 1129 + #define DELAY_L9 1151 + #define DELAY_L10 1171 + #define DELAY_L11 1187 +#endif + + +/*---------------------------------------------------------------------------*/ +/* The FDN late feed back matrix: A + T + A = P - 2 / N * u * u + N N N N + + N: the matrix dimension (i.e NBR_DELAYS). + P: permutation matrix. + u: is a column vector of 1. + +*/ +#define FDN_MATRIX_FACTOR (fluid_real_t)(-2.0 / NBR_DELAYS) + +/*---------------------------------------------------------------------------- + Internal FDN late structures and static functions +-----------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------- + Delay absorbent low pass filter +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t buffer; + fluid_real_t b0, a1; /* filter coefficients */ +} fdn_delay_lpf; + +/*----------------------------------------------------------------------------- + Sets coefficients for delay absorbent low pass filter. + @param lpf pointer on low pass filter structure. + @param b0,a1 coefficients. +-----------------------------------------------------------------------------*/ +static void set_fdn_delay_lpf(fdn_delay_lpf *lpf, + fluid_real_t b0, fluid_real_t a1) +{ + lpf->b0 = b0; + lpf->a1 = a1; +} + +/*----------------------------------------------------------------------------- + Process delay absorbent low pass filter. + @param mod_delay modulated delay line. + @param in, input sample. + @param out output sample. +-----------------------------------------------------------------------------*/ +/* process low pass damping filter (input, output, delay) */ +#define process_damping_filter(in,out,mod_delay) \ +{\ + out = in * mod_delay->dl.damping.b0 - mod_delay->dl.damping.buffer * \ + mod_delay->dl.damping.a1;\ + mod_delay->dl.damping.buffer = out;\ +}\ + + +/*----------------------------------------------------------------------------- + Delay line : + The delay line is composed of the line plus an absorbent low pass filter + to get frequency dependent reverb time. +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t *line; /* buffer line */ + int size; /* effective internal size (in samples) */ + /*-------------*/ + int line_in; /* line in position */ + int line_out; /* line out position */ + /*-------------*/ + fdn_delay_lpf damping; /* damping low pass filter */ +} delay_line; + + +/*----------------------------------------------------------------------------- + Clears a delay line to DC_OFFSET float value. + @param dl pointer on delay line structure +-----------------------------------------------------------------------------*/ +static void clear_delay_line(delay_line *dl) +{ + int i; + + for(i = 0; i < dl->size; i++) + { + dl->line[i] = DC_OFFSET; + } +} + +/*----------------------------------------------------------------------------- + Push a sample val into the delay line +-----------------------------------------------------------------------------*/ +#define push_in_delay_line(dl, val) \ +{\ + dl->line[dl->line_in] = val;\ + /* Incrementation and circular motion if necessary */\ + if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ +}\ + +/*----------------------------------------------------------------------------- + Modulator for modulated delay line +-----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------- + Sinusoidal modulator +-----------------------------------------------------------------------------*/ +/* modulator are integrated in modulated delay line */ +typedef struct +{ + fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ + fluid_real_t buffer1; /* buffer1 */ + fluid_real_t buffer2; /* buffer2 */ + fluid_real_t reset_buffer2;/* reset value of buffer2 */ +} sinus_modulator; + +/*----------------------------------------------------------------------------- + Sets the frequency of sinus oscillator. + + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param phase initial phase of the oscillator in degree (0 to 360). +-----------------------------------------------------------------------------*/ +static void set_mod_frequency(sinus_modulator *mod, + float freq, float sample_rate, float phase) +{ + fluid_real_t w = 2 * FLUID_M_PI * freq / sample_rate; /* initial angle */ + fluid_real_t a; + + mod->a1 = 2 * FLUID_COS(w); + + a = (2 * FLUID_M_PI / 360) * phase; + + mod->buffer2 = FLUID_SIN(a - w); /* y(n-1) = sin(-initial angle) */ + mod->buffer1 = FLUID_SIN(a); /* y(n) = sin(initial phase) */ + mod->reset_buffer2 = FLUID_SIN(FLUID_M_PI / 2 - w); /* reset value for PI/2 */ +} + +/*----------------------------------------------------------------------------- + Gets current value of sinus modulator: + y(n) = a1 . y(n-1) - y(n-2) + out = a1 . buffer1 - buffer2 + + @param pointer on modulator structure. + @return current value of the modulator sine wave. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_sinus(sinus_modulator *mod) +{ + fluid_real_t out; + out = mod->a1 * mod->buffer1 - mod->buffer2; + mod->buffer2 = mod->buffer1; + + if(out >= 1.0f) /* reset in case of instability near PI/2 */ + { + out = 1.0f; /* forces output to the right value */ + mod->buffer2 = mod->reset_buffer2; + } + + if(out <= -1.0f) /* reset in case of instability near -PI/2 */ + { + out = -1.0f; /* forces output to the right value */ + mod->buffer2 = - mod->reset_buffer2; + } + + mod->buffer1 = out; + return out; +} + +/*----------------------------------------------------------------------------- + Modulated delay line. The line is composed of: + - the delay line with its damping low pass filter. + - the sinusoidal modulator. + - center output position modulated by the modulator. + - variable rate control of center output position. + - first order All-Pass interpolator. +-----------------------------------------------------------------------------*/ +typedef struct +{ + /* delay line with damping low pass filter member */ + delay_line dl; /* delayed line */ + /*---------------------------*/ + /* Sinusoidal modulator member */ + sinus_modulator mod; /* sinus modulator */ + /*-------------------------*/ + /* center output position members */ + fluid_real_t center_pos_mod; /* center output position modulated by modulator */ + int mod_depth; /* modulation depth (in samples) */ + /*-------------------------*/ + /* variable rate control of center output position */ + int index_rate; /* index rate to know when to update center_pos_mod */ + int mod_rate; /* rate at which center_pos_mod is updated */ + /*-------------------------*/ + /* first order All-Pass interpolator members */ + fluid_real_t frac_pos_mod; /* fractional position part between samples) */ + /* previous value used when interpolating using fractional */ + fluid_real_t buffer; +} mod_delay_line; + +/*----------------------------------------------------------------------------- + Return norminal delay length + + @param mdl, pointer on modulated delay line. +-----------------------------------------------------------------------------*/ +static int get_mod_delay_line_length(mod_delay_line *mdl) +{ + return (mdl->dl.size - mdl->mod_depth - INTERP_SAMPLES_NBR); +} + +/*----------------------------------------------------------------------------- + Reads the sample value out of the modulated delay line. + @param mdl, pointer on modulated delay line. + @return the sample value. +-----------------------------------------------------------------------------*/ +static FLUID_INLINE fluid_real_t get_mod_delay(mod_delay_line *mdl) +{ + fluid_real_t out_index; /* new modulated index position */ + int int_out_index; /* integer part of out_index */ + fluid_real_t out; /* value to return */ + + /* Checks if the modulator must be updated (every mod_rate samples). */ + /* Important: center_pos_mod must be used immediately for the + first sample. So, mdl->index_rate must be initialized + to mdl->mod_rate (set_mod_delay_line()) */ + + if(++mdl->index_rate >= mdl->mod_rate) + { + mdl->index_rate = 0; + + /* out_index = center position (center_pos_mod) + sinus waweform */ + out_index = mdl->center_pos_mod + + get_mod_sinus(&mdl->mod) * mdl->mod_depth; + + /* extracts integer part in int_out_index */ + if(out_index >= 0.0f) + { + int_out_index = (int)out_index; /* current integer part */ + + /* forces read index (line_out) with integer modulation value */ + /* Boundary check and circular motion as needed */ + if((mdl->dl.line_out = int_out_index) >= mdl->dl.size) + { + mdl->dl.line_out -= mdl->dl.size; + } + } + else /* negative */ + { + int_out_index = (int)(out_index - 1); /* previous integer part */ + /* forces read index (line_out) with integer modulation value */ + /* circular motion as needed */ + mdl->dl.line_out = int_out_index + mdl->dl.size; + } + + /* extracts fractionnal part. (it will be used when interpolating + between line_out and line_out +1) and memorize it. + Memorizing is necessary for modulation rate above 1 */ + mdl->frac_pos_mod = out_index - int_out_index; + + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((mdl->center_pos_mod += mdl->mod_rate) >= mdl->dl.size) + { + mdl->center_pos_mod -= mdl->dl.size; + } + } + + /* First order all-pass interpolation ----------------------------------*/ + /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ + /* begins interpolation: read current sample */ + out = mdl->dl.line[mdl->dl.line_out]; + + /* updates line_out to the next sample. + Boundary check and circular motion as needed */ + if(++mdl->dl.line_out >= mdl->dl.size) + { + mdl->dl.line_out -= mdl->dl.size; + } + + /* Fractional interpolation between next sample (at next position) and + previous output added to current sample. + */ + out += mdl->frac_pos_mod * (mdl->dl.line[mdl->dl.line_out] - mdl->buffer); + mdl->buffer = out; /* memorizes current output */ + return out; +} + +/*----------------------------------------------------------------------------- + Late structure +-----------------------------------------------------------------------------*/ +struct _fluid_late +{ + fluid_real_t samplerate; /* sample rate */ + fluid_real_t sample_rate_max; /* sample rate maximum */ + /*----- High pass tone corrector -------------------------------------*/ + fluid_real_t tone_buffer; + fluid_real_t b1, b2; + /*----- Modulated delay lines lines ----------------------------------*/ + mod_delay_line mod_delay_lines[NBR_DELAYS]; + /*-----------------------------------------------------------------------*/ + /* Output coefficients for separate Left and right stereo outputs */ + fluid_real_t out_left_gain[NBR_DELAYS]; /* Left delay lines' output gains */ + fluid_real_t out_right_gain[NBR_DELAYS];/* Right delay lines' output gains*/ +}; + +typedef struct _fluid_late fluid_late; +/*----------------------------------------------------------------------------- + fluidsynth reverb structure +-----------------------------------------------------------------------------*/ +struct _fluid_revmodel_t +{ + /* reverb parameters */ + fluid_real_t roomsize; /* acting on reverb time */ + fluid_real_t damp; /* acting on frequency dependent reverb time */ + fluid_real_t level, wet1, wet2; /* output level */ + fluid_real_t width; /* width stereo separation */ + + /* fdn reverberation structure */ + fluid_late late; +}; + +/*----------------------------------------------------------------------------- + Updates Reverb time and absorbent filters coefficients from parameters: + + @param late pointer on late structure. + @param roomsize (0 to 1): acting on reverb time. + @param damping (0 to 1): acting on absorbent damping filter. + + Design formulas: + https://ccrma.stanford.edu/~jos/Reverb/First_Order_Delay_Filter_Design.html + https://ccrma.stanford.edu/~jos/Reverb/Tonal_Correction_Filter.html +-----------------------------------------------------------------------------*/ +static void update_rev_time_damping(fluid_late *late, + fluid_real_t roomsize, fluid_real_t damp) +{ + int i; + fluid_real_t sample_period = 1 / late->samplerate; /* Sampling period */ + int delay_length; /* delay length */ + fluid_real_t dc_rev_time; /* Reverb time at 0 Hz (in seconds) */ + + fluid_real_t alpha, alpha2; + + /*-------------------------------------------- + Computes dc_rev_time and alpha + ----------------------------------------------*/ + { + fluid_real_t gi_tmp, ai_tmp; +#ifdef ROOMSIZE_RESPONSE_LINEAR + /* roomsize parameter behave linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 10 s). + * This linear response is convenient when using GUI controls. + */ + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + dc_rev_time = GET_DC_REV_TIME(roomsize); + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); + /* computes gi_tmp from dc_rev_time using relation E2 */ + gi_tmp = FLUID_POW(10, -3 * delay_length * + sample_period / dc_rev_time); /* E2 */ +#else + /* roomsize parameters have the same response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 10 s). + */ + { + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + fluid_real_t gi_min, gi_max; + + /* values gi_min et gi_max are computed using E2 for the line with + maximum delay */ + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[NBR_DELAYS - 1]); + gi_max = FLUID_POW(10, (-3 * delay_length / MAX_DC_REV_TIME) * + sample_period); /* E2 */ + gi_min = FLUID_POW(10, (-3 * delay_length / MIN_DC_REV_TIME) * + sample_period); /* E2 */ + /* gi = f(roomsize, gi_max, gi_min) */ + gi_tmp = gi_min + roomsize * (gi_max - gi_min); + /* Computes T60DC from gi using inverse of relation E2.*/ + dc_rev_time = -3 * FLUID_M_LN10 * delay_length * sample_period / FLUID_LOGF(gi_tmp); + } +#endif /* ROOMSIZE_RESPONSE_LINEAR */ + /*-------------------------------------------- + Computes alpha + ----------------------------------------------*/ + /* Computes alpha from damp,ai_tmp,gi_tmp using relation R */ + /* - damp (0 to 1) controls concave reverb time for fs/2 frequency (T60DC to 0) */ + ai_tmp = 1.0f * damp; + + /* Preserve the square of R */ + alpha2 = 1.f / (1.f - ai_tmp / ((20.f / 80.f) * FLUID_LOGF(gi_tmp))); + + alpha = FLUID_SQRT(alpha2); /* R */ + } + + /* updates tone corrector coefficients b1,b2 from alpha */ + { + /* + Beta = (1 - alpha) / (1 + alpha) + b1 = 1/(1-beta) + b2 = beta * b1 + */ + fluid_real_t beta = (1 - alpha) / (1 + alpha); + late->b1 = 1 / (1 - beta); + late->b2 = beta * late->b1; + late->tone_buffer = 0.0f; + } + + /* updates damping coefficients of all lines (gi , ai) from dc_rev_time, alpha */ + for(i = 0; i < NBR_DELAYS; i++) + { + fluid_real_t gi, ai; + + /* delay length */ + delay_length = get_mod_delay_line_length(&late->mod_delay_lines[i]); + + /* iir low pass filter gain */ + gi = FLUID_POW(10, -3 * delay_length * sample_period / dc_rev_time); + + /* iir low pass filter feedback gain */ + ai = (20.f / 80.f) * FLUID_LOGF(gi) * (1.f - 1.f / alpha2); + + /* b0 = gi * (1 - ai), a1 = - ai */ + set_fdn_delay_lpf(&late->mod_delay_lines[i].dl.damping, + gi * (1.f - ai), -ai); + } +} + +/*----------------------------------------------------------------------------- + Updates stereo coefficients + @param late pointer on late structure + @param wet level integrated in stereo coefficients. +-----------------------------------------------------------------------------*/ +static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) +{ + int i; + fluid_real_t wet; + + for(i = 0; i < NBR_DELAYS; i++) + { + /* delay lines output gains vectors Left and Right + + L R + 0 | 1 1| + 1 |-1 1| + 2 | 1 -1| + 3 |-1 -1| + + 4 | 1 1| + 5 |-1 1| + stereo gain = 6 | 1 -1| + 7 |-1 -1| + + 8 | 1 1| + 9 |-1 1| + 10| 1 -1| + 11|-1 -1| + */ + + /* for left line: 00, ,02, ,04, ,06, ,08, ,10, ,12,... left_gain = +1 */ + /* for left line: ,01, ,03, ,05, ,07, ,09, ,11,... left_gain = -1 */ + wet = wet1; + if(i & 1) + { + wet = -wet1; + } + late->out_left_gain[i] = wet; + + /* for right line: 00,01, ,04,05, ,08,09, ,12,13 right_gain = +1 */ + /* for right line: ,02 ,03, ,06,07, ,10,11,... right_gain = -1 */ + wet = wet1; + if(i & 2) + { + wet = -wet1; + } + late->out_right_gain[i] = wet; + } +} + +/*----------------------------------------------------------------------------- + fluid_late destructor. + @param late pointer on late structure. +-----------------------------------------------------------------------------*/ +static void delete_fluid_rev_late(fluid_late *late) +{ + int i; + fluid_return_if_fail(late != NULL); + + /* free the delay lines */ + for(i = 0; i < NBR_DELAYS; i++) + { + FLUID_FREE(late->mod_delay_lines[i].dl.line); + } +} + + +/* Nominal delay lines length table (in samples) */ +static const int nom_delay_length[NBR_DELAYS] = +{ + DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, + DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, +#if (NBR_DELAYS == 12) + DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 +#endif +}; + +/* + 1)"modal density" is one property that contributes to the quality of the reverb tail. + The more is the modal density, the less are unwanted resonant frequencies + build during the decay time: modal density = total delay / sample rate. + + Delay line's length given by static table delay_length[] are nominal + to get minimum modal density of 0.15 at sample rate 44100Hz. + Here we set length_factor to 2 to multiply this nominal modal + density by 2. This leads to a default modal density of 0.15 * 2 = 0.3 for + sample rate <= 44100. + + For sample rate > 44100, length_factor is multiplied by + sample_rate / 44100. This ensures that the default modal density keeps inchanged. + (Without this compensation, the default modal density would be diminished for + new sample rate change above 44100Hz). + + 2)Modulated delay line contributes to diminish resonnant frequencies (often called "ringing"). + Modulation depth (mod_depth) is set to nominal value of MOD_DEPTH at sample rate 44100Hz. + For sample rate > 44100, mod_depth is multiplied by sample_rate / 44100. This ensures + that the effect of modulated delay line remains inchanged. +*/ +static void compensate_from_sample_rate(fluid_real_t sample_rate, + fluid_real_t *mod_depth, + fluid_real_t *length_factor) +{ + *mod_depth = MOD_DEPTH; + *length_factor = 2.0f; + if(sample_rate > 44100.0f) + { + fluid_real_t sample_rate_factor = sample_rate/44100.0f; + *length_factor *= sample_rate_factor; + *mod_depth *= sample_rate_factor; + } +} + +/*----------------------------------------------------------------------------- + Creates all modulated lines. + @param late, pointer on the fnd late reverb to initialize. + @param sample_rate_max, the maximum audio sample rate expected. + @return FLUID_OK if success, FLUID_FAILED otherwise. +-----------------------------------------------------------------------------*/ +static int create_mod_delay_lines(fluid_late *late, + fluid_real_t sample_rate_max) +{ + int i; + + fluid_real_t mod_depth, length_factor; + + /* compute mod_depth, length factor */ + compensate_from_sample_rate(sample_rate_max, &mod_depth, &length_factor); + + late->sample_rate_max = sample_rate_max; + +#ifdef INFOS_PRINT // allows message to be printed on the console. + printf("length_factor:%f, mod_depth:%f\n", length_factor, mod_depth); + /* Print: modal density and total memory bytes */ + { + int i; + int total_delay = 0; /* total delay in samples */ + for (i = 0; i < NBR_DELAYS; i++) + { + int length = (length_factor * nom_delay_length[i]) + + mod_depth + INTERP_SAMPLES_NBR; + total_delay += length; + } + + /* modal density and total memory bytes */ + printf("modal density:%f, total delay:%d, total memory:%d bytes\n", + total_delay / sample_rate_max ,total_delay , + total_delay * sizeof(fluid_real_t)); + } +#endif + + for(i = 0; i < NBR_DELAYS; i++) /* for each delay line */ + { + int delay_length = nom_delay_length[i] * length_factor; + mod_delay_line *mdl = &late->mod_delay_lines[i]; + + /*-------------------------------------------------------------------*/ + /* checks parameter */ + if(delay_length < 1) + { + return FLUID_FAILED; + } + + /* limits mod_depth to the requested delay length */ + if(mod_depth >= delay_length) + { + FLUID_LOG(FLUID_INFO, + "fdn reverb: modulation depth has been limited"); + mod_depth = delay_length - 1; + } + + /*--------------------------------------------------------------------- + allocates delay lines + */ + + /* real size of the line in use (in samples): + size = INTERP_SAMPLES_NBR + mod_depth + delay_length */ + mdl->dl.size = delay_length + mod_depth + INTERP_SAMPLES_NBR; + mdl->dl.line = FLUID_ARRAY(fluid_real_t, mdl->dl.size); + + if(! mdl->dl.line) + { + return FLUID_FAILED; + } + } + return FLUID_OK; +} + +/*----------------------------------------------------------------------------- + Initialize all modulated lines. + @param late, pointer on the fnd late reverb to initialize. + @param sample_rate, the audio sample rate. + @return FLUID_OK if success, FLUID_FAILED otherwise. +-----------------------------------------------------------------------------*/ +static void initialize_mod_delay_lines(fluid_late *late, fluid_real_t sample_rate) +{ + int i; + fluid_real_t mod_depth, length_factor; + + /* update delay line parameter dependent of sample rate */ + late->samplerate = sample_rate; + + /* compute mod_depth, length factor */ + compensate_from_sample_rate(sample_rate, &mod_depth, &length_factor); + + for(i = 0; i < NBR_DELAYS; i++) /* for each delay line */ + { + mod_delay_line *mdl = &late->mod_delay_lines[i]; + int delay_length = nom_delay_length[i] * length_factor; + + /* limits mod_depth to the requested delay length */ + if(mod_depth >= delay_length) + { + mod_depth = delay_length - 1; + } + + mdl->mod_depth = mod_depth; + + clear_delay_line(&mdl->dl); /* clears the buffer */ + + /* Initializes line_in to the start of the buffer */ + mdl->dl.line_in = 0; + + /* Initializes line_out index INTERP_SAMPLES_NBR samples after + line_in so that the delay between line_out and line_in is: + mod_depth + delay_length + */ + mdl->dl.line_out = mdl->dl.line_in + INTERP_SAMPLES_NBR; + + /* Damping low pass filter ------------------------------------------*/ + mdl->dl.damping.buffer = 0; + + /*--------------------------------------------------------------------- + Initializes modulation members: + - modulated center position: center_pos_mod + - modulation rate (the speed at which center_pos_mod is modulated: mod_rate + - index rate to know when to update center_pos_mod:index_rate + - interpolator member: buffer, frac_pos_mod + ---------------------------------------------------------------------*/ + /* Initializes the modulated center position (center_pos_mod) so that: + - the delay between line_out and center_pos_mod is mod_depth. + - the delay between center_pos_mod and line_in is delay_length. + */ + mdl->center_pos_mod = (fluid_real_t) INTERP_SAMPLES_NBR + mod_depth; + + /* Sets the modulation rate. This rate defines how often + the center position (center_pos_mod ) is modulated . + The value is expressed in samples. The default value is 1 that means that + center_pos_mod is updated at every sample. + For example with a value of 2, the center position position will be + updated only one time every 2 samples only. + */ + if(MOD_RATE < 1 || MOD_RATE > mdl->dl.size) + { + FLUID_LOG(FLUID_INFO, "fdn reverb: modulation rate is out of range"); + mdl->mod_rate = 1; /* default modulation rate: every one sample */ + } + else + { + mdl->mod_rate = MOD_RATE; + } + + /* index rate to control when to update center_pos_mod. + Important: must be set to get center_pos_mod immediately used for + the reading of first sample (see get_mod_delay()) + */ + mdl->index_rate = mdl->mod_rate; + + /* initializes first order All-Pass interpolator members */ + mdl->buffer = 0; /* previous delay sample value */ + mdl->frac_pos_mod = 0; /* frac. position (between consecutives sample) */ + + + /* Sets local Modulators parameters: frequency and phase. + Each modulateur are shifted of MOD_PHASE degree + */ + set_mod_frequency(&mdl->mod, + MOD_FREQ * MOD_RATE, + sample_rate, + (float)(MOD_PHASE * i)); + } +} + +/* + Clears the delay lines. + + @param rev pointer on the reverb. +*/ +static void +fluid_revmodel_init(fluid_revmodel_t *rev) +{ + int i; + + /* clears all the delay lines */ + for(i = 0; i < NBR_DELAYS; i ++) + { + clear_delay_line(&rev->late.mod_delay_lines[i].dl); + } +} + + +/* + updates internal parameters. + + @param rev pointer on the reverb. +*/ +static void +fluid_revmodel_update(fluid_revmodel_t *rev) +{ + /* Recalculate internal values after parameters change */ + + /* The stereo amplitude equation (wet1 and wet2 below) have a + tendency to produce high amplitude with high width values ( 1 < width < 100). + This results in an unwanted noisy output clipped by the audio card. + To avoid this dependency, we divide by (1 + rev->width * SCALE_WET_WIDTH) + Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), + the output amplitude (wet) seems rather independent of width setting */ + fluid_real_t wet = (rev->level * SCALE_WET) / + (1.0f + rev->width * SCALE_WET_WIDTH); + + /* wet1 and wet2 are used by the stereo effect controlled by the width setting + for producing a stereo ouptput from a monophonic reverb signal. + Please see the note above about a side effect tendency */ + + rev->wet1 = wet * (rev->width / 2.0f + 0.5f); + rev->wet2 = wet * ((1.0f - rev->width) / 2.0f); + + /* integrates wet1 in stereo coefficient (this will save one multiply) */ + update_stereo_coefficient(&rev->late, rev->wet1); + + if(rev->wet1 > 0.0f) + { + rev->wet2 /= rev->wet1; + } + + /* Reverberation time and damping */ + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); +} + +/*---------------------------------------------------------------------------- + Reverb API +-----------------------------------------------------------------------------*/ +/* +* Creates a reverb. Once created the reverb have no parameters set, so +* fluid_revmodel_set() must be called at least one time after calling +* new_fluid_revmodel(). +* +* @param sample_rate_max maximum sample rate expected in Hz. +* +* @param sample_rate actual sample rate needed in Hz. +* @return pointer on the new reverb or NULL if memory error. +* Reverb API. +*/ +fluid_revmodel_t * +new_fluid_revmodel(fluid_real_t sample_rate_max, fluid_real_t sample_rate) +{ + fluid_revmodel_t *rev; + + if(sample_rate <= 0) + { + return NULL; + } + + rev = FLUID_NEW(fluid_revmodel_t); + + if(rev == NULL) + { + return NULL; + } + + FLUID_MEMSET(&rev->late, 0, sizeof(fluid_late)); + + /*-------------------------------------------------------------------------- + Create fdn late reverb. + */ + + /* update minimum value for sample_rate_max */ + if(sample_rate > sample_rate_max) + { + sample_rate_max = sample_rate; + } + + /*-------------------------------------------------------------------------- + Allocate the modulated delay lines + */ + if(create_mod_delay_lines(&rev->late, sample_rate_max) == FLUID_FAILED) + { + delete_fluid_revmodel(rev); + return NULL; + } + + /*-------------------------------------------------------------------------- + Initialize the fdn reverb + */ + /* Initialize all modulated lines. */ + initialize_mod_delay_lines(&rev->late, sample_rate); + + return rev; +} + +/* +* free the reverb. +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling delete_fluid_revmodel() isn't multi task safe because +* delay line are freed. To deal properly with this issue follow the steps: +* +* 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). +* reverb functions. +* 2) Delete the reverb by calling delete_fluid_revmodel(). +* +* @param rev pointer on reverb to free. +* Reverb API. +*/ +void +delete_fluid_revmodel(fluid_revmodel_t *rev) +{ + fluid_return_if_fail(rev != NULL); + delete_fluid_rev_late(&rev->late); + FLUID_FREE(rev); +} + +/* +* Sets one or more reverb parameters. Note this must be called at least one +* time after calling new_fluid_revmodel() and before any call to +* fluid_revmodel_processXXX() and fluid_revmodel_samplerate_change(). +* +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling fluid_revmodel_set() could produce audible clics. +* If this is a problem, optionally call fluid_revmodel_reset() before calling +* fluid_revmodel_set(). +* +* @param rev Reverb instance. +* @param set One or more flags from #fluid_revmodel_set_t indicating what +* parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters). +* @param roomsize Reverb room size. +* @param damping Reverb damping. +* @param width Reverb width. +* @param level Reverb level. +* +* Reverb API. +*/ +void +fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, + fluid_real_t damping, fluid_real_t width, fluid_real_t level) +{ + fluid_return_if_fail(rev != NULL); + + /*-----------------------------------*/ + if(set & FLUID_REVMODEL_SET_ROOMSIZE) + { + fluid_clip(roomsize, 0.0f, 1.0f); + rev->roomsize = roomsize; + } + + /*-----------------------------------*/ + if(set & FLUID_REVMODEL_SET_DAMPING) + { + fluid_clip(damping, 0.0f, 1.0f); + rev->damp = damping; + } + + /*-----------------------------------*/ + if(set & FLUID_REVMODEL_SET_WIDTH) + { + rev->width = width; + } + + /*-----------------------------------*/ + if(set & FLUID_REVMODEL_SET_LEVEL) + { + fluid_clip(level, 0.0f, 1.0f); + rev->level = level; + } + + /* updates internal parameters */ + fluid_revmodel_update(rev); +} + +/* +* Applies a sample rate change on the reverb. +* fluid_revmodel_set() must be called at least one time before calling +* this function. +* +* Note that while the reverb is used by calling any fluid_revmodel_processXXX() +* function, calling fluid_revmodel_samplerate_change() isn't multi task safe. +* To deal properly with this issue follow the steps: +* 1) Stop reverb processing (i.e disable calling of any fluid_revmodel_processXXX(). +* reverb functions. +* Optionally, call fluid_revmodel_reset() to damp the reverb. +* 2) Change sample rate by calling fluid_revmodel_samplerate_change(). +* 3) Restart reverb processing (i.e enabling calling of any fluid_revmodel_processXXX() +* reverb functions. +* +* Another solution is to substitute step (2): +* 2.1) delete the reverb by calling delete_fluid_revmodel(). +* 2.2) create the reverb by calling new_fluid_revmodel(). +* +* The best solution would be that this function be called only by the same task +* calling fluid_revmodel_processXXX(). +* +* @param rev the reverb. +* @param sample_rate new sample rate value. Must be <= sample_rate_max +* @return FLUID_OK if success, FLUID_FAILED if new sample rate is greater +* then the maximumum sample rate set at creation time. The reverb will +* continue to work but with possible lost of quality. +* If this is a problem, the caller should follow steps 2.1 and 2.2. +* Reverb API. +*/ +int +fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate) +{ + int status = FLUID_OK; + + fluid_return_val_if_fail(rev != NULL, FLUID_FAILED); + + if(sample_rate > rev->late.sample_rate_max) + { + FLUID_LOG(FLUID_WARN, + "fdn reverb: sample rate %.0f Hz is deduced to %.0f Hz\n", + sample_rate, rev->late.sample_rate_max); + + /* Reduce sample rate to the maximum value set at creation time. + The reverb will continue to work with possible lost of quality. + */ + sample_rate = rev->late.sample_rate_max; + status = FLUID_FAILED; + } + + /* Initialize all modulated lines according to sample rate change. */ + initialize_mod_delay_lines(&rev->late, sample_rate); + + /* updates damping filter coefficients according to sample rate change */ + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); + + return status; +} + +/* +* Damps the reverb by clearing the delay lines. +* @param rev the reverb. +* +* Reverb API. +*/ +void +fluid_revmodel_reset(fluid_revmodel_t *rev) +{ + fluid_return_if_fail(rev != NULL); + + fluid_revmodel_init(rev); +} + +/*----------------------------------------------------------------------------- +* fdn reverb process replace. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE sample). +* @param left_out stereo left processed output (FLUID_BUFSIZE sample). +* @param right_out stereo right processed output (FLUID_BUFSIZE sample). +* +* The processed reverb is replacing anything there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void +fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix computation */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) + { + /* stereo output */ + out_left = out_right = 0; + +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction. + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in delay_out[]. + - also matrix_factor is computed (to simplify further matrix product) + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These will be of use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line.Each input is a combination of + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } + + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + + /* Calculates stereo output REPLACING anything already there: */ + /* + left_out[k] = out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] = out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right, so we simplify previous + relation by suppression of one multiply as this: + + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; + */ + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; + } +} + + +/*----------------------------------------------------------------------------- +* fdn reverb process mix. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE samples). +* @param left_out stereo left processed output (FLUID_BUFSIZE samples). +* @param right_out stereo right processed output (FLUID_BUFSIZE samples). +* +* The processed reverb is mixed in out with samples already there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix term */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) + { + /* stereo output */ + out_left = out_right = 0; +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in local delay_out[]. + - also matrix_factor is computed (to simplify further matrix product). + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These will be of use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line. Each input is a combination of: + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } + + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + /* Calculates stereo output MIXING anything already there: */ + /* + left_out[k] += out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] += out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right, so we simplify previous + relation by suppression of one multiply as this: + + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + */ + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + } +} diff --git a/libs/fluidsynth/src/rvoice/fluid_rev.h b/libs/fluidsynth/src/rvoice/fluid_rev.h new file mode 100644 index 00000000000..35c9cf664f8 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rev.h @@ -0,0 +1,91 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_REV_H +#define _FLUID_REV_H + +#include "fluidsynth_priv.h" + +typedef struct _fluid_revmodel_t fluid_revmodel_t; + +/* enum describing each reverb parameter */ +enum fluid_reverb_param +{ + FLUID_REVERB_ROOMSIZE, /**< reverb time */ + FLUID_REVERB_DAMP, /**< high frequency damping */ + FLUID_REVERB_WIDTH, /**< stereo width */ + FLUID_REVERB_LEVEL, /**< output level */ + FLUID_REVERB_PARAM_LAST /* number of enum fluid_reverb_param */ +}; + +/* return a bit flag from param: 2^param */ +#define FLUID_REVPARAM_TO_SETFLAG(param) (1 << param) + +/** Flags for fluid_revmodel_set() */ +typedef enum +{ + FLUID_REVMODEL_SET_ROOMSIZE = FLUID_REVPARAM_TO_SETFLAG(FLUID_REVERB_ROOMSIZE), + FLUID_REVMODEL_SET_DAMPING = FLUID_REVPARAM_TO_SETFLAG(FLUID_REVERB_DAMP), + FLUID_REVMODEL_SET_WIDTH = FLUID_REVPARAM_TO_SETFLAG(FLUID_REVERB_WIDTH), + FLUID_REVMODEL_SET_LEVEL = FLUID_REVPARAM_TO_SETFLAG(FLUID_REVERB_LEVEL), + + /** Value for fluid_revmodel_set() which sets all reverb parameters. */ + FLUID_REVMODEL_SET_ALL = FLUID_REVMODEL_SET_LEVEL + | FLUID_REVMODEL_SET_WIDTH + | FLUID_REVMODEL_SET_DAMPING + | FLUID_REVMODEL_SET_ROOMSIZE, +} fluid_revmodel_set_t; + +/* + * reverb preset + */ +typedef struct _fluid_revmodel_presets_t +{ + const char *name; + fluid_real_t roomsize; + fluid_real_t damp; + fluid_real_t width; + fluid_real_t level; +} fluid_revmodel_presets_t; + + +/* + * reverb + */ +fluid_revmodel_t * +new_fluid_revmodel(fluid_real_t sample_rate_max, fluid_real_t sample_rate); + +void delete_fluid_revmodel(fluid_revmodel_t *rev); + +void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); + +void fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); + +void fluid_revmodel_reset(fluid_revmodel_t *rev); + +void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, + fluid_real_t damping, fluid_real_t width, fluid_real_t level); + +int fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate); + +#endif /* _FLUID_REV_H */ diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice.c b/libs/fluidsynth/src/rvoice/fluid_rvoice.c new file mode 100644 index 00000000000..403a5558795 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice.c @@ -0,0 +1,937 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_rvoice.h" +#include "fluid_conv.h" +#include "fluid_sys.h" + + +static void fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks); + +/** + * @return -1 if voice is quiet, 0 if voice has finished, 1 otherwise + */ +static FLUID_INLINE int +fluid_rvoice_calc_amp(fluid_rvoice_t *voice) +{ + fluid_real_t target_amp; /* target amplitude */ + + if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVDELAY) + { + return -1; /* The volume amplitude is in hold phase. No sound is produced. */ + } + + if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK) + { + /* the envelope is in the attack section: ramp linearly to max value. + * A positive modlfo_to_vol should increase volume (negative attenuation). + */ + target_amp = fluid_cb2amp(voice->dsp.attenuation) + * fluid_cb2amp(fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol) + * fluid_adsr_env_get_val(&voice->envlfo.volenv); + } + else + { + fluid_real_t amplitude_that_reaches_noise_floor; + fluid_real_t amp_max; + + target_amp = fluid_cb2amp(voice->dsp.attenuation) + * fluid_cb2amp(FLUID_PEAK_ATTENUATION * (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv)) + + fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol); + + /* We turn off a voice, if the volume has dropped low enough. */ + + /* A voice can be turned off, when an estimate for the volume + * (upper bound) falls below that volume, that will drop the + * sample below the noise floor. + */ + + /* If the loop amplitude is known, we can use it if the voice loop is within + * the sample loop + */ + + /* Is the playing pointer already in the loop? */ + if(voice->dsp.has_looped) + { + amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_loop; + } + else + { + amplitude_that_reaches_noise_floor = voice->dsp.amplitude_that_reaches_noise_floor_nonloop; + } + + /* voice->attenuation_min is a lower boundary for the attenuation + * now and in the future (possibly 0 in the worst case). Now the + * amplitude of sample and volenv cannot exceed amp_max (since + * volenv_val can only drop): + */ + + amp_max = fluid_cb2amp(voice->dsp.min_attenuation_cB) * + fluid_adsr_env_get_val(&voice->envlfo.volenv); + + /* And if amp_max is already smaller than the known amplitude, + * which will attenuate the sample below the noise floor, then we + * can safely turn off the voice. Duh. */ + if(amp_max < amplitude_that_reaches_noise_floor) + { + return 0; + } + } + + /* Volume increment to go from voice->amp to target_amp in FLUID_BUFSIZE steps */ + voice->dsp.amp_incr = (target_amp - voice->dsp.amp) / FLUID_BUFSIZE; + + fluid_check_fpe("voice_write amplitude calculation"); + + /* no volume and not changing? - No need to process */ + if((voice->dsp.amp == 0.0f) && (voice->dsp.amp_incr == 0.0f)) + { + return -1; + } + + return 1; +} + + +/* these should be the absolute minimum that FluidSynth can deal with */ +#define FLUID_MIN_LOOP_SIZE 2 +#define FLUID_MIN_LOOP_PAD 0 + +#define FLUID_SAMPLESANITY_CHECK (1 << 0) +#define FLUID_SAMPLESANITY_STARTUP (1 << 1) + +/* Purpose: + * + * Make sure, that sample start / end point and loop points are in + * proper order. When starting up, calculate the initial phase. + * TODO: Investigate whether this can be moved from rvoice to voice. + */ +static void +fluid_rvoice_check_sample_sanity(fluid_rvoice_t *voice) +{ + int min_index_nonloop = (int) voice->dsp.sample->start; + int max_index_nonloop = (int) voice->dsp.sample->end; + + /* make sure we have enough samples surrounding the loop */ + int min_index_loop = (int) voice->dsp.sample->start + FLUID_MIN_LOOP_PAD; + int max_index_loop = (int) voice->dsp.sample->end - FLUID_MIN_LOOP_PAD + 1; /* 'end' is last valid sample, loopend can be + 1 */ + fluid_check_fpe("voice_check_sample_sanity start"); + +#if 0 + printf("Sample from %i to %i\n", voice->dsp.sample->start, voice->dsp.sample->end); + printf("Sample loop from %i %i\n", voice->dsp.sample->loopstart, voice->dsp.sample->loopend); + printf("Playback from %i to %i\n", voice->dsp.start, voice->dsp.end); + printf("Playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend); +#endif + + /* Keep the start point within the sample data */ + if(voice->dsp.start < min_index_nonloop) + { + voice->dsp.start = min_index_nonloop; + } + else if(voice->dsp.start > max_index_nonloop) + { + voice->dsp.start = max_index_nonloop; + } + + /* Keep the end point within the sample data */ + if(voice->dsp.end < min_index_nonloop) + { + voice->dsp.end = min_index_nonloop; + } + else if(voice->dsp.end > max_index_nonloop) + { + voice->dsp.end = max_index_nonloop; + } + + /* Keep start and end point in the right order */ + if(voice->dsp.start > voice->dsp.end) + { + int temp = voice->dsp.start; + voice->dsp.start = voice->dsp.end; + voice->dsp.end = temp; + /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of start / end points!"); */ + } + + /* Zero length? */ + if(voice->dsp.start == voice->dsp.end) + { + fluid_rvoice_voiceoff(voice, NULL); + return; + } + + if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) + || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) + { + /* Keep the loop start point within the sample data */ + if(voice->dsp.loopstart < min_index_loop) + { + voice->dsp.loopstart = min_index_loop; + } + else if(voice->dsp.loopstart > max_index_loop) + { + voice->dsp.loopstart = max_index_loop; + } + + /* Keep the loop end point within the sample data */ + if(voice->dsp.loopend < min_index_loop) + { + voice->dsp.loopend = min_index_loop; + } + else if(voice->dsp.loopend > max_index_loop) + { + voice->dsp.loopend = max_index_loop; + } + + /* Keep loop start and end point in the right order */ + if(voice->dsp.loopstart > voice->dsp.loopend) + { + int temp = voice->dsp.loopstart; + voice->dsp.loopstart = voice->dsp.loopend; + voice->dsp.loopend = temp; + /*FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Changing order of loop points!"); */ + } + + /* Loop too short? Then don't loop. */ + if(voice->dsp.loopend < voice->dsp.loopstart + FLUID_MIN_LOOP_SIZE) + { + voice->dsp.samplemode = FLUID_UNLOOPED; + } + + /* The loop points may have changed. Obtain a new estimate for the loop volume. */ + /* Is the voice loop within the sample loop? */ + if((int)voice->dsp.loopstart >= (int)voice->dsp.sample->loopstart + && (int)voice->dsp.loopend <= (int)voice->dsp.sample->loopend) + { + /* Is there a valid peak amplitude available for the loop, and can we use it? */ + if(voice->dsp.sample->amplitude_that_reaches_noise_floor_is_valid && voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE) + { + voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.sample->amplitude_that_reaches_noise_floor / voice->dsp.synth_gain; + } + else + { + /* Worst case */ + voice->dsp.amplitude_that_reaches_noise_floor_loop = voice->dsp.amplitude_that_reaches_noise_floor_nonloop; + }; + }; + + } /* if sample mode is looped */ + + /* Run startup specific code (only once, when the voice is started) */ + if(voice->dsp.check_sample_sanity_flag & FLUID_SAMPLESANITY_STARTUP) + { + if(max_index_loop - min_index_loop < FLUID_MIN_LOOP_SIZE) + { + if((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) + || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) + { + voice->dsp.samplemode = FLUID_UNLOOPED; + } + } + + /* Set the initial phase of the voice (using the result from the + start offset modulators). */ + fluid_phase_set_int(voice->dsp.phase, voice->dsp.start); + } /* if startup */ + + /* Is this voice run in loop mode, or does it run straight to the + end of the waveform data? */ + if(((voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE) && + (fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE)) + || (voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE)) + { + /* Yes, it will loop as soon as it reaches the loop point. In + * this case we must prevent, that the playback pointer (phase) + * happens to end up beyond the 2nd loop point, because the + * point has moved. The DSP algorithm is unable to cope with + * that situation. So if the phase is beyond the 2nd loop + * point, set it to the start of the loop. No way to avoid some + * noise here. Note: If the sample pointer ends up -before the + * first loop point- instead, then the DSP loop will just play + * the sample, enter the loop and proceed as expected => no + * actions required. + */ + int index_in_sample = fluid_phase_index(voice->dsp.phase); + + if(index_in_sample >= voice->dsp.loopend) + { + /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Phase after 2nd loop point!"); */ + fluid_phase_set_int(voice->dsp.phase, voice->dsp.loopstart); + } + } + + /* FLUID_LOG(FLUID_DBG, "Loop / sample sanity check: Sample from %i to %i, loop from %i to %i", voice->dsp.start, voice->dsp.end, voice->dsp.loopstart, voice->dsp.loopend); */ + + /* Sample sanity has been assured. Don't check again, until some + sample parameter is changed by modulation. */ + voice->dsp.check_sample_sanity_flag = 0; +#if 0 + printf("Sane? playback loop from %i to %i\n", voice->dsp.loopstart, voice->dsp.loopend); +#endif + fluid_check_fpe("voice_check_sample_sanity"); +} + + +/** + * Synthesize a voice to a buffer. + * + * @param voice rvoice to synthesize + * @param dsp_buf Audio buffer to synthesize to (#FLUID_BUFSIZE in length) + * @return Count of samples written to dsp_buf. (-1 means voice is currently + * quiet, 0 .. #FLUID_BUFSIZE-1 means voice finished.) + * + * Panning, reverb and chorus are processed separately. The dsp interpolation + * routine is in (fluid_rvoice_dsp.c). + */ +int +fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) +{ + int ticks = voice->envlfo.ticks; + int count, is_looping; + fluid_real_t modenv_val; + + /******************* sample sanity check **********/ + + if(!voice->dsp.sample) + { + return 0; + } + + if(voice->dsp.check_sample_sanity_flag) + { + fluid_rvoice_check_sample_sanity(voice); + } + + /******************* noteoff check ****************/ + + if(voice->envlfo.noteoff_ticks != 0 && + voice->envlfo.ticks >= voice->envlfo.noteoff_ticks) + { + fluid_rvoice_noteoff_LOCAL(voice, 0); + } + + voice->envlfo.ticks += FLUID_BUFSIZE; + + /******************* vol env **********************/ + + fluid_adsr_env_calc(&voice->envlfo.volenv); + fluid_check_fpe("voice_write vol env"); + + if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVFINISHED) + { + return 0; + } + + /******************* mod env **********************/ + + fluid_adsr_env_calc(&voice->envlfo.modenv); + fluid_check_fpe("voice_write mod env"); + + /******************* lfo **********************/ + + fluid_lfo_calc(&voice->envlfo.modlfo, ticks); + fluid_check_fpe("voice_write mod LFO"); + fluid_lfo_calc(&voice->envlfo.viblfo, ticks); + fluid_check_fpe("voice_write vib LFO"); + + /******************* amplitude **********************/ + + count = fluid_rvoice_calc_amp(voice); + + if(count <= 0) + { + return count; /* return -1 if voice is quiet, 0 if voice has finished */ + } + + /******************* phase **********************/ + + /* SF2.04 section 8.1.2 #26: + * attack of modEnv is convex ?!? + */ + modenv_val = (fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + ? fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)) + : fluid_adsr_env_get_val(&voice->envlfo.modenv); + /* Calculate the number of samples, that the DSP loop advances + * through the original waveform with each step in the output + * buffer. It is the ratio between the frequencies of original + * waveform and output waveform.*/ + voice->dsp.phase_incr = fluid_ct2hz_real(voice->dsp.pitch + + voice->dsp.pitchoffset + + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch + + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch + + modenv_val * voice->envlfo.modenv_to_pitch) + / voice->dsp.root_pitch_hz; + + /******************* portamento ****************/ + /* pitchoffset is updated if enabled. + Pitchoffset will be added to dsp pitch at next phase calculation time */ + + /* In most cases portamento will be disabled. Thus first verify that portamento is + * enabled before updating pitchoffset and before disabling portamento when necessary, + * in order to keep the performance loss at minimum. + * If the algorithm would first update pitchoffset and then verify if portamento + * needs to be disabled, there would be a significant performance drop on a x87 FPU + */ + if(voice->dsp.pitchinc > 0.0f) + { + /* portamento is enabled, so update pitchoffset */ + voice->dsp.pitchoffset += voice->dsp.pitchinc; + + /* when pitchoffset reaches 0.0f, portamento is disabled */ + if(voice->dsp.pitchoffset > 0.0f) + { + voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f; + } + } + else if(voice->dsp.pitchinc < 0.0f) + { + /* portamento is enabled, so update pitchoffset */ + voice->dsp.pitchoffset += voice->dsp.pitchinc; + + /* when pitchoffset reaches 0.0f, portamento is disabled */ + if(voice->dsp.pitchoffset < 0.0f) + { + voice->dsp.pitchoffset = voice->dsp.pitchinc = 0.0f; + } + } + + fluid_check_fpe("voice_write phase calculation"); + + /* if phase_incr is not advancing, set it to the minimum fraction value (prevent stuckage) */ + if(voice->dsp.phase_incr == 0) + { + voice->dsp.phase_incr = 1; + } + + /* voice is currently looping? */ + is_looping = voice->dsp.samplemode == FLUID_LOOP_DURING_RELEASE + || (voice->dsp.samplemode == FLUID_LOOP_UNTIL_RELEASE + && fluid_adsr_env_get_section(&voice->envlfo.volenv) < FLUID_VOICE_ENVRELEASE); + + /*********************** run the dsp chain ************************ + * The sample is mixed with the output buffer. + * The buffer has to be filled from 0 to FLUID_BUFSIZE-1. + * Depending on the position in the loop and the loop size, this + * may require several runs. */ + + switch(voice->dsp.interp_method) + { + case FLUID_INTERP_NONE: + count = fluid_rvoice_dsp_interpolate_none(&voice->dsp, dsp_buf, is_looping); + break; + + case FLUID_INTERP_LINEAR: + count = fluid_rvoice_dsp_interpolate_linear(&voice->dsp, dsp_buf, is_looping); + break; + + case FLUID_INTERP_4THORDER: + default: + count = fluid_rvoice_dsp_interpolate_4th_order(&voice->dsp, dsp_buf, is_looping); + break; + + case FLUID_INTERP_7THORDER: + count = fluid_rvoice_dsp_interpolate_7th_order(&voice->dsp, dsp_buf, is_looping); + break; + } + + fluid_check_fpe("voice_write interpolation"); + + if(count == 0) + { + return count; + } + + /*************** resonant filter ******************/ + + fluid_iir_filter_calc(&voice->resonant_filter, voice->dsp.output_rate, + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_fc + + modenv_val * voice->envlfo.modenv_to_fc); + + fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); + + /* additional custom filter - only uses the fixed modulator, no lfos... */ + fluid_iir_filter_calc(&voice->resonant_custom_filter, voice->dsp.output_rate, 0); + fluid_iir_filter_apply(&voice->resonant_custom_filter, dsp_buf, count); + + return count; +} + +/** + * Initialize buffers up to (and including) bufnum + */ +static int +fluid_rvoice_buffers_check_bufnum(fluid_rvoice_buffers_t *buffers, unsigned int bufnum) +{ + unsigned int i; + + if(bufnum < buffers->count) + { + return FLUID_OK; + } + + if(bufnum >= FLUID_RVOICE_MAX_BUFS) + { + return FLUID_FAILED; + } + + for(i = buffers->count; i <= bufnum; i++) + { + buffers->bufs[i].target_amp = 0.0f; + buffers->bufs[i].current_amp = 0.0f; + } + + buffers->count = bufnum + 1; + return FLUID_OK; +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_amp) +{ + fluid_rvoice_buffers_t *buffers = obj; + unsigned int bufnum = param[0].i; + fluid_real_t value = param[1].real; + + if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) + { + return; + } + + buffers->bufs[bufnum].target_amp = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_mapping) +{ + fluid_rvoice_buffers_t *buffers = obj; + unsigned int bufnum = param[0].i; + int mapping = param[1].i; + + if(fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) + { + return; + } + + buffers->bufs[bufnum].mapping = mapping; +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_reset) +{ + fluid_rvoice_t *voice = obj; + + voice->dsp.has_looped = 0; + voice->envlfo.ticks = 0; + voice->envlfo.noteoff_ticks = 0; + voice->dsp.amp = 0.0f; /* The last value of the volume envelope, used to + calculate the volume increment during + processing */ + + /* legato initialization */ + voice->dsp.pitchoffset = 0.0; /* portamento initialization */ + voice->dsp.pitchinc = 0.0; + + /* mod env initialization*/ + fluid_adsr_env_reset(&voice->envlfo.modenv); + + /* vol env initialization */ + fluid_adsr_env_reset(&voice->envlfo.volenv); + + /* Fixme: Retrieve from any other existing + voice on this channel to keep LFOs in + unison? */ + fluid_lfo_reset(&voice->envlfo.viblfo); + fluid_lfo_reset(&voice->envlfo.modlfo); + + /* Clear sample history in filter */ + fluid_iir_filter_reset(&voice->resonant_filter); + fluid_iir_filter_reset(&voice->resonant_custom_filter); + + /* Force setting of the phase at the first DSP loop run + * This cannot be done earlier, because it depends on modulators. + [DH] Is that comment really true? */ + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_noteoff) +{ + fluid_rvoice_t *rvoice = obj; + unsigned int min_ticks = param[0].i; + + fluid_rvoice_noteoff_LOCAL(rvoice, min_ticks); +} + +static void +fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) +{ + if(min_ticks > voice->envlfo.ticks) + { + /* Delay noteoff */ + voice->envlfo.noteoff_ticks = min_ticks; + return; + } + + voice->envlfo.noteoff_ticks = 0; + + if(fluid_adsr_env_get_section(&voice->envlfo.volenv) == FLUID_VOICE_ENVATTACK) + { + /* A voice is turned off during the attack section of the volume + * envelope. The attack section ramps up linearly with + * amplitude. The other sections use logarithmic scaling. Calculate new + * volenv_val to achieve equivalent amplitude during the release phase + * for seamless volume transition. + */ + if(fluid_adsr_env_get_val(&voice->envlfo.volenv) > 0) + { + fluid_real_t lfo = fluid_lfo_get_val(&voice->envlfo.modlfo) * -voice->envlfo.modlfo_to_vol; + fluid_real_t amp = fluid_adsr_env_get_val(&voice->envlfo.volenv) * fluid_cb2amp(lfo); + fluid_real_t env_value = - (((-200.f / FLUID_M_LN10) * FLUID_LOGF(amp) - lfo) / FLUID_PEAK_ATTENUATION - 1); + fluid_clip(env_value, 0.0f, 1.0f); + fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value); + } + } + + if(fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + { + /* A voice is turned off during the attack section of the modulation + * envelope. The attack section use convex scaling with pitch and filter + * frequency cutoff (see fluid_rvoice_write(): modenv_val = fluid_convex(127 * modenv.val) + * The other sections use linear scaling: modenv_val = modenv.val + * + * Calculate new modenv.val to achieve equivalent modenv_val during the release phase + * for seamless pitch and filter frequency cutoff transition. + */ + if(fluid_adsr_env_get_val(&voice->envlfo.modenv) > 0) + { + fluid_real_t env_value = fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)); + fluid_clip(env_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, env_value); + } + } + + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE); + fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE); +} + +/** + * skips to Attack section + * + * Updates vol and attack data + * Correction on volume val to achieve equivalent amplitude at noteOn legato + * + * @param voice the synthesis voice to be updated +*/ +static FLUID_INLINE void fluid_rvoice_local_retrigger_attack(fluid_rvoice_t *voice) +{ + /* skips to Attack section */ + /* Once in Attack section, current count must be reset, to be sure + that the section will be not be prematurely finished. */ + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVATTACK); + { + /* Correction on volume val to achieve equivalent amplitude at noteOn legato */ + fluid_env_data_t *env_data; + fluid_real_t peak = fluid_cb2amp(voice->dsp.attenuation); + fluid_real_t prev_peak = fluid_cb2amp(voice->dsp.prev_attenuation); + voice->envlfo.volenv.val = (voice->envlfo.volenv.val * prev_peak) / peak; + /* Correction on slope direction for Attack section */ + env_data = &voice->envlfo.volenv.data[FLUID_VOICE_ENVATTACK]; + + if(voice->envlfo.volenv.val <= 1.0f) + { + /* slope attack for legato note needs to be positive from val up to 1 */ + env_data->increment = 1.0f / env_data->count; + env_data->min = -1.0f; + env_data->max = 1.0f; + } + else + { + /* slope attack for legato note needs to be negative: from val down to 1 */ + env_data->increment = -voice->envlfo.volenv.val / env_data->count; + env_data->min = 1.0f; + env_data->max = voice->envlfo.volenv.val; + } + } +} + +/** + * Used by legato Mode : multi_retrigger + * see fluid_synth_noteon_mono_legato_multi_retrigger() + * @param voice the synthesis voice to be updated +*/ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) +{ + fluid_rvoice_t *voice = obj; + int section; /* volume or modulation section */ + + /*------------------------------------------------------------------------- + Section skip for volume envelope + --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.volenv); + if(section >= FLUID_VOICE_ENVHOLD) + { + /* DECAY, SUSTAIN,RELEASE section use logarithmic scaling. Calculates new + volenv_val to achieve equivalent amplitude during the attack phase + for seamless volume transition. */ + fluid_real_t amp_cb, env_value; + amp_cb = FLUID_PEAK_ATTENUATION * + (1.0f - fluid_adsr_env_get_val(&voice->envlfo.volenv)); + env_value = fluid_cb2amp(amp_cb); /* a bit of optimization */ + fluid_clip(env_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value); + /* next, skips to Attack section */ + } + + /* skips to Attack section from any section */ + /* Update vol and attack data */ + fluid_rvoice_local_retrigger_attack(voice); + + /*------------------------------------------------------------------------- + Section skip for modulation envelope + --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.modenv); + if(section >= FLUID_VOICE_ENVHOLD) + { + /* DECAY, SUSTAIN,RELEASE section use linear scaling. + Since v 2.1 , as recommended by soundfont 2.01/2.4 spec, ATTACK section + uses convex shape (see fluid_rvoice_write() - fluid_convex()). + Calculate new modenv value (new_value) for seamless attack transition. + Here we need the inverse of fluid_convex() function defined as: + new_value = pow(10, (1 - current_val) . FLUID_PEAK_ATTENUATION / -200 . 2.0) + For performance reason we use fluid_cb2amp(Val) = pow(10, val/-200) with + val = (1 - current_val) . FLUID_PEAK_ATTENUATION / 2.0 + */ + fluid_real_t new_value; /* new modenv value */ + new_value = fluid_cb2amp((1.0f - fluid_adsr_env_get_val(&voice->envlfo.modenv)) + * FLUID_PEAK_ATTENUATION / 2.0); + fluid_clip(new_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, new_value); + } + /* Skips from any section to ATTACK section */ + fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVATTACK); +} + +/** + * sets the portamento dsp parameters: dsp.pitchoffset, dsp.pitchinc + * @param voice rvoice to set portamento. + * @param countinc increment count number. + * @param pitchoffset pitch offset to apply to voice dsp.pitch. + * + * Notes: + * 1) To get continuous portamento between consecutive noteOn (n1,n2,n3...), + * pitchoffset is accumulated in current dsp pitchoffset. + * 2) And to get constant portamento duration, dsp pitch increment is updated. +*/ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_portamento) +{ + fluid_rvoice_t *voice = obj; + unsigned int countinc = param[0].i; + fluid_real_t pitchoffset = param[1].real; + + if(countinc) + { + voice->dsp.pitchoffset += pitchoffset; + voice->dsp.pitchinc = - voice->dsp.pitchoffset / countinc; + } + + /* Then during the voice processing (in fluid_rvoice_write()), + dsp.pitchoffset will be incremented by dsp pitchinc. */ +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_output_rate) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.output_rate = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_interp_method) +{ + fluid_rvoice_t *voice = obj; + int value = param[0].i; + + voice->dsp.interp_method = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_root_pitch_hz) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.root_pitch_hz = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_pitch) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.pitch = value; +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_attenuation) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.prev_attenuation = voice->dsp.attenuation; + voice->dsp.attenuation = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_min_attenuation_cB) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.min_attenuation_cB = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_viblfo_to_pitch) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.viblfo_to_pitch = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_pitch) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.modlfo_to_pitch = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_vol) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.modlfo_to_vol = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_fc) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.modlfo_to_fc = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_fc) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.modenv_to_fc = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_pitch) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->envlfo.modenv_to_pitch = value; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_synth_gain) +{ + fluid_rvoice_t *voice = obj; + fluid_real_t value = param[0].real; + + voice->dsp.synth_gain = value; + + /* For a looped sample, this value will be overwritten as soon as the + * loop parameters are initialized (they may depend on modulators). + * This value can be kept, it is a worst-case estimate. + */ + voice->dsp.amplitude_that_reaches_noise_floor_nonloop = FLUID_NOISE_FLOOR / value; + voice->dsp.amplitude_that_reaches_noise_floor_loop = FLUID_NOISE_FLOOR / value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_start) +{ + fluid_rvoice_t *voice = obj; + int value = param[0].i; + + voice->dsp.start = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_end) +{ + fluid_rvoice_t *voice = obj; + int value = param[0].i; + + voice->dsp.end = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopstart) +{ + fluid_rvoice_t *voice = obj; + int value = param[0].i; + + voice->dsp.loopstart = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopend) +{ + fluid_rvoice_t *voice = obj; + int value = param[0].i; + + voice->dsp.loopend = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_samplemode) +{ + fluid_rvoice_t *voice = obj; + enum fluid_loop value = param[0].i; + + voice->dsp.samplemode = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_sample) +{ + fluid_rvoice_t *voice = obj; + fluid_sample_t *value = param[0].ptr; + + voice->dsp.sample = value; + + if(value) + { + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP; + } +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_voiceoff) +{ + fluid_rvoice_t *voice = obj; + + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVFINISHED); + fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVFINISHED); +} + + diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice.h b/libs/fluidsynth/src/rvoice/fluid_rvoice.h new file mode 100644 index 00000000000..610afd72529 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice.h @@ -0,0 +1,231 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_RVOICE_H +#define _FLUID_RVOICE_H + +#include "fluidsynth_priv.h" +#include "fluid_iir_filter.h" +#include "fluid_adsr_env.h" +#include "fluid_lfo.h" +#include "fluid_phase.h" +#include "fluid_sfont.h" + +typedef struct _fluid_rvoice_envlfo_t fluid_rvoice_envlfo_t; +typedef struct _fluid_rvoice_dsp_t fluid_rvoice_dsp_t; +typedef struct _fluid_rvoice_buffers_t fluid_rvoice_buffers_t; +typedef struct _fluid_rvoice_t fluid_rvoice_t; + +/* Smallest amplitude that can be perceived (full scale is +/- 0.5) + * 16 bits => 96+4=100 dB dynamic range => 0.00001 + * 24 bits => 144-4 = 140 dB dynamic range => 1.e-7 + * 1.e-7 * 2 == 2.e-7 :) + */ +#define FLUID_NOISE_FLOOR ((fluid_real_t)2.e-7) + +enum fluid_loop +{ + FLUID_UNLOOPED = 0, + FLUID_LOOP_DURING_RELEASE = 1, + FLUID_NOTUSED = 2, + FLUID_LOOP_UNTIL_RELEASE = 3 +}; + +/* + * rvoice ticks-based parameters + * These parameters must be updated even if the voice is currently quiet. + */ +struct _fluid_rvoice_envlfo_t +{ + /* Note-off minimum length */ + unsigned int ticks; + unsigned int noteoff_ticks; + + /* vol env */ + fluid_adsr_env_t volenv; + + /* mod env */ + fluid_adsr_env_t modenv; + fluid_real_t modenv_to_fc; + fluid_real_t modenv_to_pitch; + + /* mod lfo */ + fluid_lfo_t modlfo; + fluid_real_t modlfo_to_fc; + fluid_real_t modlfo_to_pitch; + fluid_real_t modlfo_to_vol; + + /* vib lfo */ + fluid_lfo_t viblfo; + fluid_real_t viblfo_to_pitch; +}; + +/* + * rvoice parameters needed for dsp interpolation + */ +struct _fluid_rvoice_dsp_t +{ + /* interpolation method, as in fluid_interp in fluidsynth.h */ + enum fluid_interp interp_method; + enum fluid_loop samplemode; + + /* Flag that is set as soon as the first loop is completed. */ + char has_looped; + + /* Flag that initiates, that sample-related parameters have to be checked. */ + char check_sample_sanity_flag; + + fluid_sample_t *sample; + + /* sample and loop start and end points (offset in sample memory). */ + int start; + int end; + int loopstart; + int loopend; /* Note: first point following the loop (superimposed on loopstart) */ + + /* Stuff needed for portamento calculations */ + fluid_real_t pitchoffset; /* the portamento range in midicents */ + fluid_real_t pitchinc; /* the portamento increment in midicents */ + + /* Stuff needed for phase calculations */ + + fluid_real_t pitch; /* the pitch in midicents */ + fluid_real_t root_pitch_hz; + fluid_real_t output_rate; + + /* Stuff needed for amplitude calculations */ + + fluid_real_t attenuation; /* the attenuation in centibels */ + fluid_real_t prev_attenuation; /* the previous attenuation in centibels + used by fluid_rvoice_multi_retrigger_attack() */ + fluid_real_t min_attenuation_cB; /* Estimate on the smallest possible attenuation + * during the lifetime of the voice */ + fluid_real_t amplitude_that_reaches_noise_floor_nonloop; + fluid_real_t amplitude_that_reaches_noise_floor_loop; + fluid_real_t synth_gain; /* master gain */ + + /* Dynamic input to the interpolator below */ + + fluid_real_t amp; /* current linear amplitude */ + fluid_real_t amp_incr; /* amplitude increment value for the next FLUID_BUFSIZE samples */ + + fluid_phase_t phase; /* the phase (current sample offset) of the sample wave */ + fluid_real_t phase_incr; /* the phase increment for the next FLUID_BUFSIZE samples */ +}; + +/* Currently left, right, reverb, chorus. To be changed if we + ever add surround positioning, or stereo reverb/chorus */ +#define FLUID_RVOICE_MAX_BUFS (4) + +/* + * rvoice mixer-related parameters + */ +struct _fluid_rvoice_buffers_t +{ + unsigned int count; /* Number of records in "bufs" */ + struct + { + /* the actual, linearly interpolated amplitude with which the dsp sample should be mixed into the buf */ + fluid_real_t current_amp; + + /* the desired amplitude [...] mixed into the buf (directly set by e.g. rapidly changing PAN events) */ + fluid_real_t target_amp; + + /* Mapping to mixdown buffer index */ + int mapping; + } bufs[FLUID_RVOICE_MAX_BUFS]; +}; + + +/* + * Hard real-time parameters needed to synthesize a voice + */ +struct _fluid_rvoice_t +{ + fluid_rvoice_envlfo_t envlfo; + fluid_rvoice_dsp_t dsp; + fluid_iir_filter_t resonant_filter; /* IIR resonant dsp filter */ + fluid_iir_filter_t resonant_custom_filter; /* optional custom/general-purpose IIR resonant filter */ + fluid_rvoice_buffers_t buffers; +}; + + +int fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf); + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_amp); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_buffers_set_mapping); + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_noteoff); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_voiceoff); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_reset); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_portamento); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_output_rate); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_interp_method); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_root_pitch_hz); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_pitch); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_attenuation); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_min_attenuation_cB); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_viblfo_to_pitch); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_pitch); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_vol); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modlfo_to_fc); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_fc); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_modenv_to_pitch); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_synth_gain); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_start); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_end); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopstart); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_loopend); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_samplemode); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_set_sample); + +/* defined in fluid_rvoice_dsp.c */ +void fluid_rvoice_dsp_config(void); +int fluid_rvoice_dsp_interpolate_none(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); +int fluid_rvoice_dsp_interpolate_linear(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); +int fluid_rvoice_dsp_interpolate_4th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); +int fluid_rvoice_dsp_interpolate_7th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int is_looping); + + +/* + * Combines the most significant 16 bit part of a sample with a potentially present + * least sig. 8 bit part in order to create a 24 bit sample. + */ +static FLUID_INLINE int32_t +fluid_rvoice_get_sample(const short int *dsp_msb, const char *dsp_lsb, unsigned int idx) +{ + /* cast sample to unsigned type, so we can safely shift and bitwise or + * without relying on undefined behaviour (should never happen anyway ofc...) */ + uint32_t msb = (uint32_t)dsp_msb[idx]; + uint8_t lsb = 0U; + + /* most soundfonts have 16 bit samples, assume that it's unlikely we + * experience 24 bit samples here */ + if(FLUID_UNLIKELY(dsp_lsb != NULL)) + { + lsb = (uint8_t)dsp_lsb[idx]; + } + + return (int32_t)((msb << 8) | lsb); +} + +#endif diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp.c b/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp.c new file mode 100644 index 00000000000..b43a0f19077 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp.c @@ -0,0 +1,636 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_sys.h" +#include "fluid_phase.h" +#include "fluid_rvoice.h" +#include "fluid_rvoice_dsp_tables.inc.h" + +/* Purpose: + * + * Interpolates audio data (obtains values between the samples of the original + * waveform data). + * + * Variables loaded from the voice structure (assigned in fluid_rvoice_write()): + * - dsp_data: Pointer to the original waveform data + * - dsp_phase: The position in the original waveform data. + * This has an integer and a fractional part (between samples). + * - dsp_phase_incr: For each output sample, the position in the original + * waveform advances by dsp_phase_incr. This also has an integer + * part and a fractional part. + * If a sample is played at root pitch (no pitch change), + * dsp_phase_incr is integer=1 and fractional=0. + * - dsp_amp: The current amplitude envelope value. + * - dsp_amp_incr: The changing rate of the amplitude envelope. + * + * A couple of variables are used internally, their results are discarded: + * - dsp_i: Index through the output buffer + * - dsp_buf: Output buffer of floating point values (FLUID_BUFSIZE in length) + */ + +/* Interpolation (find a value between two samples of the original waveform) */ + +static FLUID_INLINE fluid_real_t +fluid_rvoice_get_float_sample(const short int *dsp_msb, const char *dsp_lsb, unsigned int idx) +{ + int32_t sample = fluid_rvoice_get_sample(dsp_msb, dsp_lsb, idx); + return (fluid_real_t)sample; +} + +/* No interpolation. Just take the sample, which is closest to + * the playback pointer. Questionable quality, but very + * efficient. */ +int +fluid_rvoice_dsp_interpolate_none(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) +{ + fluid_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + char *dsp_data24 = voice->sample->data24; + fluid_real_t dsp_amp = voice->amp; + fluid_real_t dsp_amp_incr = voice->amp_incr; + unsigned int dsp_i = 0; + unsigned int dsp_phase_index; + unsigned int end_index; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); + + end_index = looping ? voice->loopend - 1 : voice->end; + + while(1) + { + dsp_phase_index = fluid_phase_index_round(dsp_phase); /* round to nearest point */ + + /* interpolate sequence of sample points */ + for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) + { + dsp_buf[dsp_i] = dsp_amp * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index_round(dsp_phase); /* round to nearest point */ + dsp_amp += dsp_amp_incr; + } + + /* break out if not looping (buffer may not be full) */ + if(!looping) + { + break; + } + + /* go back to loop start */ + if(dsp_phase_index > end_index) + { + fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); + voice->has_looped = 1; + } + + /* break out if filled buffer */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + } + + voice->phase = dsp_phase; + voice->amp = dsp_amp; + + return (dsp_i); +} + +/* Straight line interpolation. + * Returns number of samples processed (usually FLUID_BUFSIZE but could be + * smaller if end of sample occurs). + */ +int +fluid_rvoice_dsp_interpolate_linear(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) +{ + fluid_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + char *dsp_data24 = voice->sample->data24; + fluid_real_t dsp_amp = voice->amp; + fluid_real_t dsp_amp_incr = voice->amp_incr; + unsigned int dsp_i = 0; + unsigned int dsp_phase_index; + unsigned int end_index; + fluid_real_t point; + const fluid_real_t *FLUID_RESTRICT coeffs; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); + + /* last index before 2nd interpolation point must be specially handled */ + end_index = (looping ? voice->loopend - 1 : voice->end) - 1; + + /* 2nd interpolation point to use at end of loop or sample */ + if(looping) + { + point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); /* loop start */ + } + else + { + point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); /* duplicate end for samples no longer looping */ + } + + while(1) + { + dsp_phase_index = fluid_phase_index(dsp_phase); + + /* interpolate the sequence of sample points */ + for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) + { + coeffs = interp_coeff_linear[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + /* break out if buffer filled */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index++; /* we're now interpolating the last point */ + + /* interpolate within last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = interp_coeff_linear[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[1] * point); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; /* increment amplitude */ + } + + if(!looping) + { + break; /* break out if not looping (end of sample) */ + } + + /* go back to loop start (if past */ + if(dsp_phase_index > end_index) + { + fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); + voice->has_looped = 1; + } + + /* break out if filled buffer */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index--; /* set end back to second to last sample point */ + } + + voice->phase = dsp_phase; + voice->amp = dsp_amp; + + return (dsp_i); +} + +/* 4th order (cubic) interpolation. + * Returns number of samples processed (usually FLUID_BUFSIZE but could be + * smaller if end of sample occurs). + */ +int +fluid_rvoice_dsp_interpolate_4th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) +{ + fluid_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + char *dsp_data24 = voice->sample->data24; + fluid_real_t dsp_amp = voice->amp; + fluid_real_t dsp_amp_incr = voice->amp_incr; + unsigned int dsp_i = 0; + unsigned int dsp_phase_index; + unsigned int start_index, end_index; + fluid_real_t start_point, end_point1, end_point2; + const fluid_real_t *FLUID_RESTRICT coeffs; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); + + /* last index before 4th interpolation point must be specially handled */ + end_index = (looping ? voice->loopend - 1 : voice->end) - 2; + + if(voice->has_looped) /* set start_index and start point if looped or not */ + { + start_index = voice->loopstart; + start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); /* last point in loop (wrap around) */ + } + else + { + start_index = voice->start; + start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->start); /* just duplicate the point */ + } + + /* get points off the end (loop start if looping, duplicate point if end) */ + if(looping) + { + end_point1 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); + end_point2 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 1); + } + else + { + end_point1 = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); + end_point2 = end_point1; + } + + while(1) + { + dsp_phase_index = fluid_phase_index(dsp_phase); + + /* interpolate first sample point (start or loop start) if needed */ + for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * + (coeffs[0] * start_point + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + /* interpolate the sequence of sample points */ + for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) + { + coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * + (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + /* break out if buffer filled */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index++; /* we're now interpolating the 2nd to last point */ + + /* interpolate within 2nd to last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * + (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[3] * end_point1); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + end_index++; /* we're now interpolating the last point */ + + /* interpolate within the last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = interp_coeff[fluid_phase_fract_to_tablerow(dsp_phase)]; + dsp_buf[dsp_i] = dsp_amp * + (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[2] * end_point1 + + coeffs[3] * end_point2); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + if(!looping) + { + break; /* break out if not looping (end of sample) */ + } + + /* go back to loop start */ + if(dsp_phase_index > end_index) + { + fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); + + if(!voice->has_looped) + { + voice->has_looped = 1; + start_index = voice->loopstart; + start_point = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); + } + } + + /* break out if filled buffer */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index -= 2; /* set end back to third to last sample point */ + } + + voice->phase = dsp_phase; + voice->amp = dsp_amp; + + return (dsp_i); +} + +/* 7th order interpolation. + * Returns number of samples processed (usually FLUID_BUFSIZE but could be + * smaller if end of sample occurs). + */ +int +fluid_rvoice_dsp_interpolate_7th_order(fluid_rvoice_dsp_t *voice, fluid_real_t *FLUID_RESTRICT dsp_buf, int looping) +{ + fluid_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + char *dsp_data24 = voice->sample->data24; + fluid_real_t dsp_amp = voice->amp; + fluid_real_t dsp_amp_incr = voice->amp_incr; + unsigned int dsp_i = 0; + unsigned int dsp_phase_index; + unsigned int start_index, end_index; + fluid_real_t start_points[3], end_points[3]; + const fluid_real_t *FLUID_RESTRICT coeffs; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float(dsp_phase_incr, voice->phase_incr); + + /* add 1/2 sample to dsp_phase since 7th order interpolation is centered on + * the 4th sample point */ + fluid_phase_incr(dsp_phase, (fluid_phase_t)0x80000000); + + /* last index before 7th interpolation point must be specially handled */ + end_index = (looping ? voice->loopend - 1 : voice->end) - 3; + + if(voice->has_looped) /* set start_index and start point if looped or not */ + { + start_index = voice->loopstart; + start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); + start_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 2); + start_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 3); + } + else + { + start_index = voice->start; + start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->start); /* just duplicate the start point */ + start_points[1] = start_points[0]; + start_points[2] = start_points[0]; + } + + /* get the 3 points off the end (loop start if looping, duplicate point if end) */ + if(looping) + { + end_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart); + end_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 1); + end_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopstart + 2); + } + else + { + end_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->end); + end_points[1] = end_points[0]; + end_points[2] = end_points[0]; + } + + while(1) + { + dsp_phase_index = fluid_phase_index(dsp_phase); + + /* interpolate first sample point (start or loop start) if needed */ + for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * start_points[2] + + coeffs[1] * start_points[1] + + coeffs[2] * start_points[0] + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + start_index++; + + /* interpolate 2nd to first sample point (start or loop start) if needed */ + for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * start_points[1] + + coeffs[1] * start_points[0] + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + start_index++; + + /* interpolate 3rd to first sample point (start or loop start) if needed */ + for(; dsp_phase_index == start_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * start_points[0] + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + start_index -= 2; /* set back to original start index */ + + + /* interpolate the sequence of sample points */ + for(; dsp_i < FLUID_BUFSIZE && dsp_phase_index <= end_index; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + + coeffs[6] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 3)); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + /* break out if buffer filled */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index++; /* we're now interpolating the 3rd to last point */ + + /* interpolate within 3rd to last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 2) + + coeffs[6] * end_points[0]); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + end_index++; /* we're now interpolating the 2nd to last point */ + + /* interpolate within 2nd to last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index + 1) + + coeffs[5] * end_points[0] + + coeffs[6] * end_points[1]); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + end_index++; /* we're now interpolating the last point */ + + /* interpolate within last point */ + for(; dsp_phase_index <= end_index && dsp_i < FLUID_BUFSIZE; dsp_i++) + { + coeffs = sinc_table7[fluid_phase_fract_to_tablerow(dsp_phase)]; + + dsp_buf[dsp_i] = dsp_amp + * (coeffs[0] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 3) + + coeffs[1] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 2) + + coeffs[2] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index - 1) + + coeffs[3] * fluid_rvoice_get_float_sample(dsp_data, dsp_data24, dsp_phase_index) + + coeffs[4] * end_points[0] + + coeffs[5] * end_points[1] + + coeffs[6] * end_points[2]); + + /* increment phase and amplitude */ + fluid_phase_incr(dsp_phase, dsp_phase_incr); + dsp_phase_index = fluid_phase_index(dsp_phase); + dsp_amp += dsp_amp_incr; + } + + if(!looping) + { + break; /* break out if not looping (end of sample) */ + } + + /* go back to loop start */ + if(dsp_phase_index > end_index) + { + fluid_phase_sub_int(dsp_phase, voice->loopend - voice->loopstart); + + if(!voice->has_looped) + { + voice->has_looped = 1; + start_index = voice->loopstart; + start_points[0] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 1); + start_points[1] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 2); + start_points[2] = fluid_rvoice_get_float_sample(dsp_data, dsp_data24, voice->loopend - 3); + } + } + + /* break out if filled buffer */ + if(dsp_i >= FLUID_BUFSIZE) + { + break; + } + + end_index -= 3; /* set end back to 4th to last sample point */ + } + + /* sub 1/2 sample from dsp_phase since 7th order interpolation is centered on + * the 4th sample point (correct back to real value) */ + fluid_phase_decr(dsp_phase, (fluid_phase_t)0x80000000); + + voice->phase = dsp_phase; + voice->amp = dsp_amp; + + return (dsp_i); +} diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp_tables.h b/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp_tables.h new file mode 100644 index 00000000000..befc9faf4d0 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_dsp_tables.h @@ -0,0 +1,8 @@ + +#ifndef _FLUID_RVOICE_DSP_TABLES_H +#define _FLUID_RVOICE_DSP_TABLES_H + +#define FLUID_INTERP_MAX 256 +#define SINC_INTERP_ORDER 7 /* 7th order constant */ + +#endif diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_event.c b/libs/fluidsynth/src/rvoice/fluid_rvoice_event.c new file mode 100644 index 00000000000..e60115f3617 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_event.c @@ -0,0 +1,202 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_rvoice_event.h" +#include "fluid_rvoice.h" +#include "fluid_rvoice_mixer.h" +#include "fluid_iir_filter.h" +#include "fluid_lfo.h" +#include "fluid_adsr_env.h" + +static int fluid_rvoice_eventhandler_push_LOCAL(fluid_rvoice_eventhandler_t *handler, const fluid_rvoice_event_t *src_event); + +static FLUID_INLINE void +fluid_rvoice_event_dispatch(fluid_rvoice_event_t *event) +{ + event->method(event->object, event->param); +} + + +/** + * In order to be able to push more than one event atomically, + * use push for all events, then use flush to commit them to the + * queue. If threadsafe is false, all events are processed immediately. */ +int +fluid_rvoice_eventhandler_push_int_real(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_function_t method, void *object, int intparam, + fluid_real_t realparam) +{ + fluid_rvoice_event_t local_event; + + local_event.method = method; + local_event.object = object; + local_event.param[0].i = intparam; + local_event.param[1].real = realparam; + + return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); +} + +int +fluid_rvoice_eventhandler_push(fluid_rvoice_eventhandler_t *handler, fluid_rvoice_function_t method, void *object, fluid_rvoice_param_t param[MAX_EVENT_PARAMS]) +{ + fluid_rvoice_event_t local_event; + + local_event.method = method; + local_event.object = object; + FLUID_MEMCPY(&local_event.param, param, sizeof(*param) * MAX_EVENT_PARAMS); + + return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); +} + +int +fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_function_t method, void *object, void *ptr) +{ + fluid_rvoice_event_t local_event; + + local_event.method = method; + local_event.object = object; + local_event.param[0].ptr = ptr; + + return fluid_rvoice_eventhandler_push_LOCAL(handler, &local_event); +} + +static int fluid_rvoice_eventhandler_push_LOCAL(fluid_rvoice_eventhandler_t *handler, const fluid_rvoice_event_t *src_event) +{ + fluid_rvoice_event_t *event; + int old_queue_stored = fluid_atomic_int_add(&handler->queue_stored, 1); + + event = fluid_ringbuffer_get_inptr(handler->queue, old_queue_stored); + + if(event == NULL) + { + fluid_atomic_int_add(&handler->queue_stored, -1); + FLUID_LOG(FLUID_WARN, "Ringbuffer full, try increasing synth.polyphony!"); + return FLUID_FAILED; // Buffer full... + } + + FLUID_MEMCPY(event, src_event, sizeof(*event)); + + return FLUID_OK; +} + + +void +fluid_rvoice_eventhandler_finished_voice_callback(fluid_rvoice_eventhandler_t *eventhandler, fluid_rvoice_t *rvoice) +{ + fluid_rvoice_t **vptr = fluid_ringbuffer_get_inptr(eventhandler->finished_voices, 0); + + if(vptr == NULL) + { + return; // Buffer full + } + + *vptr = rvoice; + fluid_ringbuffer_next_inptr(eventhandler->finished_voices, 1); +} + +fluid_rvoice_eventhandler_t * +new_fluid_rvoice_eventhandler(int queuesize, + int finished_voices_size, int bufs, int fx_bufs, int fx_units, + fluid_real_t sample_rate_max, fluid_real_t sample_rate, + int extra_threads, int prio) +{ + fluid_rvoice_eventhandler_t *eventhandler = FLUID_NEW(fluid_rvoice_eventhandler_t); + + if(eventhandler == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + eventhandler->mixer = NULL; + eventhandler->queue = NULL; + eventhandler->finished_voices = NULL; + + fluid_atomic_int_set(&eventhandler->queue_stored, 0); + + eventhandler->finished_voices = new_fluid_ringbuffer(finished_voices_size, + sizeof(fluid_rvoice_t *)); + + if(eventhandler->finished_voices == NULL) + { + goto error_recovery; + } + + eventhandler->queue = new_fluid_ringbuffer(queuesize, sizeof(fluid_rvoice_event_t)); + + if(eventhandler->queue == NULL) + { + goto error_recovery; + } + + eventhandler->mixer = new_fluid_rvoice_mixer(bufs, fx_bufs, fx_units, + sample_rate_max, sample_rate, eventhandler, extra_threads, prio); + + if(eventhandler->mixer == NULL) + { + goto error_recovery; + } + + return eventhandler; + +error_recovery: + delete_fluid_rvoice_eventhandler(eventhandler); + return NULL; +} + +int +fluid_rvoice_eventhandler_dispatch_count(fluid_rvoice_eventhandler_t *handler) +{ + return fluid_ringbuffer_get_count(handler->queue); +} + + +/** + * Call fluid_rvoice_event_dispatch for all events in queue + * @return number of events dispatched + */ +int +fluid_rvoice_eventhandler_dispatch_all(fluid_rvoice_eventhandler_t *handler) +{ + fluid_rvoice_event_t *event; + int result = 0; + + while(NULL != (event = fluid_ringbuffer_get_outptr(handler->queue))) + { + fluid_rvoice_event_dispatch(event); + result++; + fluid_ringbuffer_next_outptr(handler->queue); + } + + return result; +} + + +void +delete_fluid_rvoice_eventhandler(fluid_rvoice_eventhandler_t *handler) +{ + fluid_return_if_fail(handler != NULL); + + delete_fluid_rvoice_mixer(handler->mixer); + delete_fluid_ringbuffer(handler->queue); + delete_fluid_ringbuffer(handler->finished_voices); + FLUID_FREE(handler); +} diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_event.h b/libs/fluidsynth/src/rvoice/fluid_rvoice_event.h new file mode 100644 index 00000000000..225e9069e13 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_event.h @@ -0,0 +1,114 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_RVOICE_EVENT_H +#define _FLUID_RVOICE_EVENT_H + +#include "fluidsynth_priv.h" +#include "fluid_rvoice_mixer.h" +#include "fluid_ringbuffer.h" + +typedef struct _fluid_rvoice_event_t fluid_rvoice_event_t; + +struct _fluid_rvoice_event_t +{ + fluid_rvoice_function_t method; + void *object; + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; +}; + +/* + * Bridge between the renderer thread and the midi state thread. + * fluid_rvoice_eventhandler_fetch_all() can be called in parallel + * with fluid_rvoice_eventhandler_push/flush() + */ +struct _fluid_rvoice_eventhandler_t +{ + fluid_ringbuffer_t *queue; /**< List of fluid_rvoice_event_t */ + fluid_atomic_int_t queue_stored; /**< Extras pushed but not flushed */ + fluid_ringbuffer_t *finished_voices; /**< return queue from handler, list of fluid_rvoice_t* */ + fluid_rvoice_mixer_t *mixer; +}; + +fluid_rvoice_eventhandler_t *new_fluid_rvoice_eventhandler( + int queuesize, int finished_voices_size, int bufs, + int fx_bufs, int fx_units, fluid_real_t sample_rate_max, fluid_real_t sample_rate, int, int); + +void delete_fluid_rvoice_eventhandler(fluid_rvoice_eventhandler_t *); + +int fluid_rvoice_eventhandler_dispatch_all(fluid_rvoice_eventhandler_t *); +int fluid_rvoice_eventhandler_dispatch_count(fluid_rvoice_eventhandler_t *); +void fluid_rvoice_eventhandler_finished_voice_callback(fluid_rvoice_eventhandler_t *eventhandler, + fluid_rvoice_t *rvoice); + +static FLUID_INLINE void +fluid_rvoice_eventhandler_flush(fluid_rvoice_eventhandler_t *handler) +{ + int queue_stored = fluid_atomic_int_get(&handler->queue_stored); + + if(queue_stored > 0) + { + fluid_atomic_int_set(&handler->queue_stored, 0); + fluid_ringbuffer_next_inptr(handler->queue, queue_stored); + } +} + +/** + * @return next finished voice, or NULL if nothing in queue + */ +static FLUID_INLINE fluid_rvoice_t * +fluid_rvoice_eventhandler_get_finished_voice(fluid_rvoice_eventhandler_t *handler) +{ + void *result = fluid_ringbuffer_get_outptr(handler->finished_voices); + + if(result == NULL) + { + return NULL; + } + + result = * (fluid_rvoice_t **) result; + fluid_ringbuffer_next_outptr(handler->finished_voices); + return result; +} + + +int fluid_rvoice_eventhandler_push_int_real(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_function_t method, void *object, int intparam, + fluid_real_t realparam); + +int fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_function_t method, void *object, void *ptr); + +int fluid_rvoice_eventhandler_push(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_function_t method, void *object, + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]); + +static FLUID_INLINE void +fluid_rvoice_eventhandler_add_rvoice(fluid_rvoice_eventhandler_t *handler, + fluid_rvoice_t *rvoice) +{ + fluid_rvoice_eventhandler_push_ptr(handler, fluid_rvoice_mixer_add_voice, + handler->mixer, rvoice); +} + + + +#endif diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.c b/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.c new file mode 100644 index 00000000000..c1e2fb2e827 --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.c @@ -0,0 +1,1727 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_rvoice_mixer.h" +#include "fluid_rvoice.h" +#include "fluid_sys.h" +#include "fluid_rev.h" +#include "fluid_chorus.h" +#include "fluid_ladspa.h" +#include "fluid_synth.h" + + +// If less than x voices, the thread overhead is larger than the gain, +// so don't activate the thread(s). +#define VOICES_PER_THREAD 8 + +typedef struct _fluid_mixer_buffers_t fluid_mixer_buffers_t; + +struct _fluid_mixer_buffers_t +{ + fluid_rvoice_mixer_t *mixer; /**< Owner of object */ +#if ENABLE_MIXER_THREADS + fluid_thread_t *thread; /**< Thread object */ + fluid_atomic_int_t ready; /**< Atomic: buffers are ready for mixing */ +#endif + + fluid_rvoice_t **finished_voices; /* List of voices who have finished */ + int finished_voice_count; + + fluid_real_t *local_buf; + + int buf_count; + int fx_buf_count; + + /** buffer to store the left part of a stereo channel to. + * Specifically a two dimensional array, containing \c buf_count sample buffers + * (i.e. for each synth.audio-groups), of which each contains + * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) + * @note Each sample buffer is aligned to the FLUID_DEFAULT_ALIGNMENT + * boundary provided that this pointer points to an aligned buffer. + * So make sure to access the sample buffer by first aligning this + * pointer using fluid_align_ptr() + */ + fluid_real_t *left_buf; + + /** dito, but for right part of a stereo channel */ + fluid_real_t *right_buf; + + /** buffer to store the left part of a stereo effects channel to. + * Specifically a two dimensional array, containing \c fx_buf_count buffers + * (i.e. for each synth.effects-channels), of which each buffer contains + * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT audio items (=samples) + */ + fluid_real_t *fx_left_buf; + fluid_real_t *fx_right_buf; +}; + +typedef struct _fluid_mixer_fx_t fluid_mixer_fx_t; + +struct _fluid_mixer_fx_t +{ + fluid_revmodel_t *reverb; /**< Reverb unit */ + /* reverb shadow parameters here will be returned if queried */ + double reverb_param[FLUID_REVERB_PARAM_LAST]; + int reverb_on; /* reverb on/off */ + + fluid_chorus_t *chorus; /**< Chorus unit */ + /* chorus shadow parameters here will be returned if queried */ + double chorus_param[FLUID_CHORUS_PARAM_LAST]; + int chorus_on; /* chorus on/off */ +}; + +struct _fluid_rvoice_mixer_t +{ + fluid_mixer_fx_t *fx; + + fluid_mixer_buffers_t buffers; /**< Used by mixer only: own buffers */ + fluid_rvoice_eventhandler_t *eventhandler; + + fluid_rvoice_t **rvoices; /**< Read-only: Voices array, sorted so that all nulls are last */ + int polyphony; /**< Read-only: Length of voices array */ + int active_voices; /**< Read-only: Number of non-null voices */ + int current_blockcount; /**< Read-only: how many blocks to process this time */ + int fx_units; + int with_reverb; /**< Should the synth use the built-in reverb unit? */ + int with_chorus; /**< Should the synth use the built-in chorus unit? */ + int mix_fx_to_out; /**< Should the effects be mixed in with the primary output? */ + +#ifdef LADSPA + fluid_ladspa_fx_t *ladspa_fx; /**< Used by mixer only: Effects unit for LADSPA support. Never created or freed */ +#endif + +#if ENABLE_MIXER_THREADS +// int sleeping_threads; /**< Atomic: number of threads currently asleep */ +// int active_threads; /**< Atomic: number of threads in the thread loop */ + fluid_atomic_int_t threads_should_terminate; /**< Atomic: Set to TRUE when threads should terminate */ + fluid_atomic_int_t current_rvoice; /**< Atomic: for the threads to know next voice to */ + fluid_cond_t *wakeup_threads; /**< Signalled when the threads should wake up */ + fluid_cond_mutex_t *wakeup_threads_m; /**< wakeup_threads mutex companion */ + fluid_cond_t *thread_ready; /**< Signalled from thread, when the thread has a buffer ready for mixing */ + fluid_cond_mutex_t *thread_ready_m; /**< thread_ready mutex companion */ + + int thread_count; /**< Number of extra mixer threads for multi-core rendering */ + fluid_mixer_buffers_t *threads; /**< Array of mixer threads (thread_count in length) */ +#endif +}; + +#if ENABLE_MIXER_THREADS +static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer); +static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level); +#endif + +static FLUID_INLINE void +fluid_rvoice_mixer_process_fx(fluid_rvoice_mixer_t *mixer, int current_blockcount) +{ + // Making those variables const causes gcc to fail with "variable is predetermined ‘shared’ for ‘shared’". + // Not explicitly marking them shared makes it fail for clang and MSVC... + /*const*/ int fx_channels_per_unit = mixer->buffers.fx_buf_count / mixer->fx_units; + /*const*/ int dry_count = mixer->buffers.buf_count; /* dry buffers count */ + /*const*/ int mix_fx_to_out = mixer->mix_fx_to_out; /* get mix_fx_to_out mode */ + + void (*reverb_process_func)(fluid_revmodel_t *rev, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); + void (*chorus_process_func)(fluid_chorus_t *chorus, const fluid_real_t *in, fluid_real_t *left_out, fluid_real_t *right_out); + + fluid_real_t *out_rev_l, *out_rev_r, *out_ch_l, *out_ch_r; + + // all dry unprocessed mono input is stored in the left channel + fluid_real_t *in_rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + fluid_real_t *in_ch = in_rev; + + fluid_profile_ref_var(prof_ref); + +#ifdef LADSPA + + /* Run the signal through the LADSPA Fx unit. The buffers have already been + * set up in fluid_rvoice_mixer_set_ladspa. */ + if(mixer->ladspa_fx) + { + fluid_ladspa_run(mixer->ladspa_fx, current_blockcount, FLUID_BUFSIZE); + fluid_check_fpe("LADSPA"); + } + +#endif + + if(mix_fx_to_out) + { + // mix effects to first stereo channel + out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); + out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); + + reverb_process_func = fluid_revmodel_processmix; + chorus_process_func = fluid_chorus_processmix; + } + else + { + // replace effects into respective stereo effects channel + out_ch_l = out_rev_l = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + out_ch_r = out_rev_r = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); + + reverb_process_func = fluid_revmodel_processreplace; + chorus_process_func = fluid_chorus_processreplace; + } + + if(mixer->with_reverb || mixer->with_chorus) + { +#if ENABLE_MIXER_THREADS && !defined(WITH_PROFILING) + int fx_mixer_threads = mixer->fx_units; + fluid_clip(fx_mixer_threads, 1, mixer->thread_count + 1); + #pragma omp parallel default(none) shared(mixer, reverb_process_func, chorus_process_func, dry_count, current_blockcount, mix_fx_to_out, fx_channels_per_unit) firstprivate(in_rev, in_ch, out_rev_l, out_rev_r, out_ch_l, out_ch_r) num_threads(fx_mixer_threads) +#endif + { + int i, f; + int buf_idx; /* buffer index */ + int samp_idx; /* sample index in buffer */ + int dry_idx = 0; /* dry buffer index */ + int sample_count; /* sample count to process */ + if(mixer->with_reverb) + { +#if ENABLE_MIXER_THREADS && !defined(WITH_PROFILING) + #pragma omp for schedule(static) +#endif + for(f = 0; f < mixer->fx_units; f++) + { + if(!mixer->fx[f].reverb_on) + { + continue; /* this reverb unit is disabled */ + } + + buf_idx = f * fx_channels_per_unit + SYNTH_REVERB_CHANNEL; + samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE; + sample_count = current_blockcount * FLUID_BUFSIZE; + + /* in mix mode, map fx out_rev at index f to a dry buffer at index dry_idx */ + if(mix_fx_to_out) + { + /* dry buffer mapping, should be done more flexible in the future */ + dry_idx = (f % dry_count) * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE; + } + + for(i = 0; i < sample_count; i += FLUID_BUFSIZE, samp_idx += FLUID_BUFSIZE) + { + reverb_process_func(mixer->fx[f].reverb, + &in_rev[samp_idx], + mix_fx_to_out ? &out_rev_l[dry_idx + i] : &out_rev_l[samp_idx], + mix_fx_to_out ? &out_rev_r[dry_idx + i] : &out_rev_r[samp_idx]); + } + } // implicit omp barrier - required, because out_rev_l aliases with out_ch_l + + fluid_profile(FLUID_PROF_ONE_BLOCK_REVERB, prof_ref, 0, + current_blockcount * FLUID_BUFSIZE); + } + + if(mixer->with_chorus) + { +#if ENABLE_MIXER_THREADS && !defined(WITH_PROFILING) + #pragma omp for schedule(static) +#endif + for(f = 0; f < mixer->fx_units; f++) + { + if(!mixer->fx[f].chorus_on) + { + continue; /* this chorus unit is disabled */ + } + + buf_idx = f * fx_channels_per_unit + SYNTH_CHORUS_CHANNEL; + samp_idx = buf_idx * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE; + sample_count = current_blockcount * FLUID_BUFSIZE; + + /* in mix mode, map fx out_ch at index f to a dry buffer at index dry_idx */ + if(mix_fx_to_out) + { + /* dry buffer mapping, should be done more flexible in the future */ + dry_idx = (f % dry_count) * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE; + } + + for(i = 0; i < sample_count; i += FLUID_BUFSIZE, samp_idx += FLUID_BUFSIZE) + { + chorus_process_func(mixer->fx[f].chorus, + &in_ch [samp_idx], + mix_fx_to_out ? &out_ch_l[dry_idx + i] : &out_ch_l[samp_idx], + mix_fx_to_out ? &out_ch_r[dry_idx + i] : &out_ch_r[samp_idx]); + } + } + + fluid_profile(FLUID_PROF_ONE_BLOCK_CHORUS, prof_ref, 0, + current_blockcount * FLUID_BUFSIZE); + } + } + } +} + +/** + * Glue to get fluid_rvoice_buffers_mix what it wants + * Note: Make sure outbufs has 2 * (buf_count + fx_buf_count) elements before calling + */ +static FLUID_INLINE int +fluid_mixer_buffers_prepare(fluid_mixer_buffers_t *buffers, fluid_real_t **outbufs) +{ + fluid_real_t *base_ptr; + int i; + const int fx_channels_per_unit = buffers->fx_buf_count / buffers->mixer->fx_units; + const int offset = buffers->buf_count * 2; + int with_reverb = buffers->mixer->with_reverb; + int with_chorus = buffers->mixer->with_chorus; + + /* Set up the reverb and chorus buffers only when the effect is enabled or + * when LADSPA is active. Nonexisting buffers are detected in the DSP loop. + * Not sending the effect signals saves some time in that case. */ +#ifdef LADSPA + int with_ladspa = (buffers->mixer->ladspa_fx != NULL); + with_reverb = (with_reverb | with_ladspa); + with_chorus = (with_chorus | with_ladspa); +#endif + + // all the dry, non-processed mono audio for effects is to be stored in the left buffers + base_ptr = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < buffers->mixer->fx_units; i++) + { + int fx_idx = i * fx_channels_per_unit; + + outbufs[offset + fx_idx + SYNTH_REVERB_CHANNEL] = + (with_reverb) + ? &base_ptr[(fx_idx + SYNTH_REVERB_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] + : NULL; + + outbufs[offset + fx_idx + SYNTH_CHORUS_CHANNEL] = + (with_chorus) + ? &base_ptr[(fx_idx + SYNTH_CHORUS_CHANNEL) * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT] + : NULL; + } + + /* The output associated with a MIDI channel is wrapped around + * using the number of 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. + * + * For example: Assume that the number of groups is set to 2. + * Then MIDI channel 1, 3, 5, 7 etc. go to output 1, channels 2, + * 4, 6, 8 etc to output 2. Or assume 3 groups: Then MIDI + * channels 1, 4, 7, 10 etc go to output 1; 2, 5, 8, 11 etc to + * output 2, 3, 6, 9, 12 etc to output 3. + */ + base_ptr = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < buffers->buf_count; i++) + { + outbufs[i * 2] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; + } + + base_ptr = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < buffers->buf_count; i++) + { + outbufs[i * 2 + 1] = &base_ptr[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; + } + + return offset + buffers->fx_buf_count; +} + + +static FLUID_INLINE void +fluid_finish_rvoice(fluid_mixer_buffers_t *buffers, fluid_rvoice_t *rvoice) +{ + if(buffers->finished_voice_count < buffers->mixer->polyphony) + { + buffers->finished_voices[buffers->finished_voice_count++] = rvoice; + } + else + { + FLUID_LOG(FLUID_ERR, "Exceeded finished voices array, try increasing polyphony"); + } +} + +static void +fluid_mixer_buffer_process_finished_voices(fluid_mixer_buffers_t *buffers) +{ + int i, j; + + for(i = 0; i < buffers->finished_voice_count; i++) + { + fluid_rvoice_t *v = buffers->finished_voices[i]; + int av = buffers->mixer->active_voices; + + for(j = 0; j < av; j++) + { + if(v == buffers->mixer->rvoices[j]) + { + av--; + + /* Pack the array */ + if(j < av) + { + buffers->mixer->rvoices[j] = buffers->mixer->rvoices[av]; + } + } + } + + buffers->mixer->active_voices = av; + + fluid_rvoice_eventhandler_finished_voice_callback(buffers->mixer->eventhandler, v); + } + + buffers->finished_voice_count = 0; +} + +static FLUID_INLINE void fluid_rvoice_mixer_process_finished_voices(fluid_rvoice_mixer_t *mixer) +{ +#if ENABLE_MIXER_THREADS + int i; + + for(i = 0; i < mixer->thread_count; i++) + { + fluid_mixer_buffer_process_finished_voices(&mixer->threads[i]); + } + +#endif + fluid_mixer_buffer_process_finished_voices(&mixer->buffers); +} + + +static FLUID_INLINE fluid_real_t * +get_dest_buf(fluid_rvoice_buffers_t *buffers, int index, + fluid_real_t **dest_bufs, int dest_bufcount) +{ + int j = buffers->bufs[index].mapping; + + if(j >= dest_bufcount || j < 0) + { + return NULL; + } + + return dest_bufs[j]; +} + +/** + * Mix samples down from internal dsp_buf to output buffers + * + * @param buffers Destination buffer(s) + * @param dsp_buf Mono sample source + * @param start_block starting sample in dsp_buf + * @param sample_count number of samples to mix following \c start_block + * @param dest_bufs Array of buffers to mixdown to + * @param dest_bufcount Length of dest_bufs (i.e count of buffers) + */ +static void +fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t *buffers, + const fluid_real_t *FLUID_RESTRICT dsp_buf, + int start_block, int sample_count, + fluid_real_t **dest_bufs, int dest_bufcount) +{ + /* buffers count to mixdown to */ + int bufcount = buffers->count; + int i, dsp_i; + + /* if there is nothing to mix, return immediately */ + if(sample_count <= 0 || dest_bufcount <= 0) + { + return; + } + + FLUID_ASSERT((uintptr_t)dsp_buf % FLUID_DEFAULT_ALIGNMENT == 0); + FLUID_ASSERT((uintptr_t)(&dsp_buf[start_block * FLUID_BUFSIZE]) % FLUID_DEFAULT_ALIGNMENT == 0); + + /* mixdown for each buffer */ + for(i = 0; i < bufcount; i++) + { + fluid_real_t *FLUID_RESTRICT buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount); + fluid_real_t target_amp = buffers->bufs[i].target_amp; + fluid_real_t current_amp = buffers->bufs[i].current_amp; + fluid_real_t amp_incr; + + if(buf == NULL || (current_amp == 0.0f && target_amp == 0.0f)) + { + continue; + } + + amp_incr = (target_amp - current_amp) / FLUID_BUFSIZE; + + FLUID_ASSERT((uintptr_t)buf % FLUID_DEFAULT_ALIGNMENT == 0); + + /* Mixdown sample_count samples in the current buffer buf + * + * For the first FLUID_BUFSIZE samples, we linearly interpolate the buffers amplitude to + * avoid clicks/pops when rapidly changing the channels panning (issue 768). + * + * We could have squashed this into one single loop by using an if clause within the loop body. + * But it seems like having two separate loops is easier for compilers to understand, and therefore + * auto-vectorizing the loops. + */ + if(sample_count < FLUID_BUFSIZE) + { + // scalar loop variant, the voice will have finished afterwards + for(dsp_i = 0; dsp_i < sample_count; dsp_i++) + { + buf[start_block * FLUID_BUFSIZE + dsp_i] += current_amp * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; + current_amp += amp_incr; + } + } + else + { + // here goes the vectorizable loop + #pragma omp simd aligned(dsp_buf,buf:FLUID_DEFAULT_ALIGNMENT) + for(dsp_i = 0; dsp_i < FLUID_BUFSIZE; dsp_i++) + { + // We cannot simply increment current_amp by amp_incr during every iteration, as this would create a dependency and prevent vectorization. + buf[start_block * FLUID_BUFSIZE + dsp_i] += (current_amp + amp_incr * dsp_i) * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; + } + + // we have reached the target_amp + if(target_amp > 0) + { + /* Note, that this loop could be unrolled by FLUID_BUFSIZE elements */ + #pragma omp simd aligned(dsp_buf,buf:FLUID_DEFAULT_ALIGNMENT) + for(dsp_i = FLUID_BUFSIZE; dsp_i < sample_count; dsp_i++) + { + // Index by blocks (not by samples) to let the compiler know that we always start accessing + // buf and dsp_buf at the FLUID_BUFSIZE*sizeof(fluid_real_t) byte boundary and never somewhere + // in between. + // A good compiler should understand: Aha, so I don't need to add a peel loop when vectorizing + // this loop. Great. + buf[start_block * FLUID_BUFSIZE + dsp_i] += target_amp * dsp_buf[start_block * FLUID_BUFSIZE + dsp_i]; + } + } + } + + buffers->bufs[i].current_amp = target_amp; + } +} + +/** + * Synthesize one voice and add to buffer. + * NOTE: If return value is less than blockcount*FLUID_BUFSIZE, that means + * voice has been finished, removed and possibly replaced with another voice. + */ +static FLUID_INLINE void +fluid_mixer_buffers_render_one(fluid_mixer_buffers_t *buffers, + fluid_rvoice_t *rvoice, fluid_real_t **dest_bufs, + unsigned int dest_bufcount, fluid_real_t *src_buf, int blockcount) +{ + int i, total_samples = 0, last_block_mixed = 0; + + for(i = 0; i < blockcount; i++) + { + /* render one block in src_buf */ + int s = fluid_rvoice_write(rvoice, &src_buf[FLUID_BUFSIZE * i]); + + if(s == -1) + { + /* the voice is silent, mix back all the previously rendered sound */ + fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, + total_samples - (last_block_mixed * FLUID_BUFSIZE), + dest_bufs, dest_bufcount); + + last_block_mixed = i + 1; /* future block start index to mix from */ + total_samples += FLUID_BUFSIZE; /* accumulate samples count rendered */ + } + else + { + /* the voice wasn't quiet. Some samples have been rendered [0..FLUID_BUFSIZE] */ + total_samples += s; + + if(s < FLUID_BUFSIZE) + { + /* voice has finished */ + break; + } + } + } + + /* Now mix the remaining blocks from last_block_mixed to total_sample */ + fluid_rvoice_buffers_mix(&rvoice->buffers, src_buf, last_block_mixed, + total_samples - (last_block_mixed * FLUID_BUFSIZE), + dest_bufs, dest_bufcount); + + if(total_samples < blockcount * FLUID_BUFSIZE) + { + /* voice has finished */ + fluid_finish_rvoice(buffers, rvoice); + } +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_add_voice) +{ + int i; + fluid_rvoice_mixer_t *mixer = obj; + fluid_rvoice_t *voice = param[0].ptr; + + if(mixer->active_voices < mixer->polyphony) + { + mixer->rvoices[mixer->active_voices++] = voice; + return; // success + } + + /* See if any voices just finished, if so, take its place. + This can happen in voice overflow conditions. */ + for(i = 0; i < mixer->active_voices; i++) + { + if(mixer->rvoices[i] == voice) + { + FLUID_LOG(FLUID_ERR, "Internal error: Trying to replace an existing rvoice in fluid_rvoice_mixer_add_voice?!"); + return; + } + + if(mixer->rvoices[i]->envlfo.volenv.section == FLUID_VOICE_ENVFINISHED) + { + fluid_finish_rvoice(&mixer->buffers, mixer->rvoices[i]); + mixer->rvoices[i] = voice; + return; // success + } + } + + /* This should never happen */ + FLUID_LOG(FLUID_ERR, "Trying to exceed polyphony in fluid_rvoice_mixer_add_voice"); +} + +static int +fluid_mixer_buffers_update_polyphony(fluid_mixer_buffers_t *buffers, int value) +{ + void *newptr; + + if(buffers->finished_voice_count > value) + { + return FLUID_FAILED; + } + + newptr = FLUID_REALLOC(buffers->finished_voices, value * sizeof(fluid_rvoice_t *)); + + if(newptr == NULL && value > 0) + { + return FLUID_FAILED; + } + + buffers->finished_voices = newptr; + return FLUID_OK; +} + +/** + * Update polyphony - max number of voices (NOTE: not hard real-time capable) + * @return FLUID_OK or FLUID_FAILED + */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_polyphony) +{ + void *newptr; + fluid_rvoice_mixer_t *handler = obj; + int value = param[0].i; + + if(handler->active_voices > value) + { + return /*FLUID_FAILED*/; + } + + newptr = FLUID_REALLOC(handler->rvoices, value * sizeof(fluid_rvoice_t *)); + + if(newptr == NULL) + { + return /*FLUID_FAILED*/; + } + + handler->rvoices = newptr; + + if(fluid_mixer_buffers_update_polyphony(&handler->buffers, value) + == FLUID_FAILED) + { + return /*FLUID_FAILED*/; + } + +#if ENABLE_MIXER_THREADS + { + int i; + + for(i = 0; i < handler->thread_count; i++) + { + if(fluid_mixer_buffers_update_polyphony(&handler->threads[i], value) + == FLUID_FAILED) + { + return /*FLUID_FAILED*/; + } + } + } +#endif + + handler->polyphony = value; + /*return FLUID_OK*/; +} + + +static void +fluid_render_loop_singlethread(fluid_rvoice_mixer_t *mixer, int blockcount) +{ + int i; + FLUID_DECLARE_VLA(fluid_real_t *, bufs, + mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2); + int bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs); + + fluid_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); + + fluid_profile_ref_var(prof_ref); + + for(i = 0; i < mixer->active_voices; i++) + { + fluid_mixer_buffers_render_one(&mixer->buffers, mixer->rvoices[i], bufs, + bufcount, local_buf, blockcount); + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, + blockcount * FLUID_BUFSIZE); + } +} + +static FLUID_INLINE void +fluid_mixer_buffers_zero(fluid_mixer_buffers_t *buffers, int current_blockcount) +{ + int i, size = current_blockcount * FLUID_BUFSIZE * sizeof(fluid_real_t); + + /* TODO: Optimize by only zero out the buffers we actually use later on. */ + int buf_count = buffers->buf_count, fx_buf_count = buffers->fx_buf_count; + + fluid_real_t *FLUID_RESTRICT buf_l = fluid_align_ptr(buffers->left_buf, FLUID_DEFAULT_ALIGNMENT); + fluid_real_t *FLUID_RESTRICT buf_r = fluid_align_ptr(buffers->right_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < buf_count; i++) + { + FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); + FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); + } + + buf_l = fluid_align_ptr(buffers->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + buf_r = fluid_align_ptr(buffers->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < fx_buf_count; i++) + { + FLUID_MEMSET(&buf_l[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); + FLUID_MEMSET(&buf_r[i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE], 0, size); + } +} + +static int +fluid_mixer_buffers_init(fluid_mixer_buffers_t *buffers, fluid_rvoice_mixer_t *mixer) +{ + static const int samplecount = FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT; + + buffers->mixer = mixer; + buffers->buf_count = mixer->buffers.buf_count; + buffers->fx_buf_count = mixer->buffers.fx_buf_count; + + /* Local mono voice buf */ + buffers->local_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, samplecount, FLUID_DEFAULT_ALIGNMENT); + + /* Left and right audio buffers */ + + buffers->left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); + buffers->right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); + + if((buffers->local_buf == NULL) || (buffers->left_buf == NULL) || (buffers->right_buf == NULL)) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + /* Effects audio buffers */ + + buffers->fx_left_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); + buffers->fx_right_buf = FLUID_ARRAY_ALIGNED(fluid_real_t, buffers->fx_buf_count * samplecount, FLUID_DEFAULT_ALIGNMENT); + + if((buffers->fx_left_buf == NULL) || (buffers->fx_right_buf == NULL)) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + buffers->finished_voices = NULL; + + if(fluid_mixer_buffers_update_polyphony(buffers, mixer->polyphony) + == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + return 1; +} + +/** + * Note: Not hard real-time capable (calls malloc) + */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_samplerate) +{ + fluid_rvoice_mixer_t *mixer = obj; + fluid_real_t samplerate = param[1].real; // because fluid_synth_update_mixer() puts real into arg2 + + int i; + + for(i = 0; i < mixer->fx_units; i++) + { + if(mixer->fx[i].chorus) + { + fluid_chorus_samplerate_change(mixer->fx[i].chorus, samplerate); + } + + if(mixer->fx[i].reverb) + { + fluid_revmodel_samplerate_change(mixer->fx[i].reverb, samplerate); + + /* + fluid_revmodel_samplerate_change() shouldn't fail if the reverb was created + with sample_rate_max set to the maximum sample rate indicated in the settings. + If this condition isn't respected, the reverb will continue to work but with + lost of quality. + */ + } + } + +#if LADSPA + + if(mixer->ladspa_fx != NULL) + { + fluid_ladspa_set_sample_rate(mixer->ladspa_fx, samplerate); + } + +#endif +} + + +/** + * @param buf_count number of primary stereo buffers + * @param fx_buf_count number of stereo effect buffers + */ +fluid_rvoice_mixer_t * +new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, int fx_units, + fluid_real_t sample_rate_max, + fluid_real_t sample_rate, + fluid_rvoice_eventhandler_t *evthandler, + int extra_threads, int prio) +{ + int i; + fluid_rvoice_mixer_t *mixer = FLUID_NEW(fluid_rvoice_mixer_t); + + if(mixer == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(mixer, 0, sizeof(fluid_rvoice_mixer_t)); + mixer->eventhandler = evthandler; + mixer->fx_units = fx_units; + mixer->buffers.buf_count = buf_count; + mixer->buffers.fx_buf_count = fx_buf_count * fx_units; + + /* allocate the reverb module */ + mixer->fx = FLUID_ARRAY(fluid_mixer_fx_t, fx_units); + + if(mixer->fx == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + + FLUID_MEMSET(mixer->fx, 0, fx_units * sizeof(*mixer->fx)); + + for(i = 0; i < fx_units; i++) + { + /* create reverb and chorus units */ + mixer->fx[i].reverb = new_fluid_revmodel(sample_rate_max, sample_rate); + mixer->fx[i].chorus = new_fluid_chorus(sample_rate); + + if(mixer->fx[i].reverb == NULL || mixer->fx[i].chorus == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + } + + if(!fluid_mixer_buffers_init(&mixer->buffers, mixer)) + { + goto error_recovery; + } + +#if ENABLE_MIXER_THREADS + mixer->thread_ready = new_fluid_cond(); + mixer->wakeup_threads = new_fluid_cond(); + mixer->thread_ready_m = new_fluid_cond_mutex(); + mixer->wakeup_threads_m = new_fluid_cond_mutex(); + + if(!mixer->thread_ready || !mixer->wakeup_threads || + !mixer->thread_ready_m || !mixer->wakeup_threads_m) + { + goto error_recovery; + } + + if(fluid_rvoice_mixer_set_threads(mixer, extra_threads, prio) != FLUID_OK) + { + goto error_recovery; + } + +#endif + + return mixer; + +error_recovery: + delete_fluid_rvoice_mixer(mixer); + return NULL; +} + +static void +fluid_mixer_buffers_free(fluid_mixer_buffers_t *buffers) +{ + FLUID_FREE(buffers->finished_voices); + + /* free all the sample buffers */ + FLUID_FREE(buffers->local_buf); + FLUID_FREE(buffers->left_buf); + FLUID_FREE(buffers->right_buf); + FLUID_FREE(buffers->fx_left_buf); + FLUID_FREE(buffers->fx_right_buf); +} + +void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t *mixer) +{ + int i; + + fluid_return_if_fail(mixer != NULL); + +#if ENABLE_MIXER_THREADS + delete_rvoice_mixer_threads(mixer); + + if(mixer->thread_ready) + { + delete_fluid_cond(mixer->thread_ready); + } + + if(mixer->wakeup_threads) + { + delete_fluid_cond(mixer->wakeup_threads); + } + + if(mixer->thread_ready_m) + { + delete_fluid_cond_mutex(mixer->thread_ready_m); + } + + if(mixer->wakeup_threads_m) + { + delete_fluid_cond_mutex(mixer->wakeup_threads_m); + } + +#endif + fluid_mixer_buffers_free(&mixer->buffers); + + + for(i = 0; i < mixer->fx_units; i++) + { + if(mixer->fx[i].reverb) + { + delete_fluid_revmodel(mixer->fx[i].reverb); + } + + if(mixer->fx[i].chorus) + { + delete_fluid_chorus(mixer->fx[i].chorus); + } + } + + FLUID_FREE(mixer->fx); + FLUID_FREE(mixer->rvoices); + FLUID_FREE(mixer); +} + +#ifdef LADSPA +/** + * Set a LADSPS fx instance to be used by the mixer and assign the mixer buffers + * as LADSPA host buffers with sensible names */ +void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t *mixer, + fluid_ladspa_fx_t *ladspa_fx, int audio_groups) +{ + mixer->ladspa_fx = ladspa_fx; + + if(ladspa_fx == NULL) + { + return; + } + else + { + fluid_real_t *main_l = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); + fluid_real_t *main_r = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); + + fluid_real_t *rev = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + fluid_real_t *chor = rev; + + rev = &rev[SYNTH_REVERB_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; + chor = &chor[SYNTH_CHORUS_CHANNEL * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT]; + + fluid_ladspa_add_host_ports(ladspa_fx, "Main:L", audio_groups, + main_l, + FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); + + fluid_ladspa_add_host_ports(ladspa_fx, "Main:R", audio_groups, + main_r, + FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); + + fluid_ladspa_add_host_ports(ladspa_fx, "Reverb:Send", 1, + rev, + FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); + + fluid_ladspa_add_host_ports(ladspa_fx, "Chorus:Send", 1, + chor, + FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT); + } +} +#endif + +/** + * set one or more reverb shadow parameters for one fx group. + * These parameters will be returned if queried. + * (see fluid_rvoice_mixer_reverb_get_param()) + * + * @param mixer that contains all fx units. + * @param fx_group index of the fx group to which parameters must be set. + * must be in the range [-1..mixer->fx_units[. If -1 the changes are applied to + * all fx units. + * @param set Flags indicating which parameters should be set (#fluid_revmodel_set_t) + * @param values table of parameters values. + */ +void +fluid_rvoice_mixer_set_reverb_full(const fluid_rvoice_mixer_t *mixer, + int fx_group, int set, const double values[]) +{ + fluid_mixer_fx_t *fx = mixer->fx; + int nr_units = mixer->fx_units; + + if(fx_group >= 0) /* apply parameters to this fx group only */ + { + nr_units = fx_group + 1; + } + else /* apply parameters to all fx groups */ + { + fx_group = 0; + } + + for(; fx_group < nr_units; fx_group++) + { + int param; + + for(param = 0; param < FLUID_REVERB_PARAM_LAST; param++) + { + if(set & FLUID_REVPARAM_TO_SETFLAG(param)) + { + fx[fx_group].reverb_param[param] = values[param]; + } + } + } +} + +/** + * get one reverb shadow parameter for one fx group. + * (see fluid_rvoice_mixer_set_reverb_full()) + * + * @param mixer that contains all fx group units. + * @param fx_group index of the fx group to get parameter from. + * must be in the range [0..mixer->fx_units[. + * @param enum indicating the parameter to get. + * FLUID_REVERB_ROOMSIZE, reverb room size value. + * FLUID_REVERB_DAMP, reverb damping value. + * FLUID_REVERB_WIDTH, reverb width value. + * FLUID_REVERB_LEVEL, reverb level value. + * @return value. + */ +double +fluid_rvoice_mixer_reverb_get_param(const fluid_rvoice_mixer_t *mixer, + int fx_group, int param) +{ + return mixer->fx[fx_group].reverb_param[param]; +} + +/** + * set one or more chorus shadow parameters for one fx group. + * These parameters will be returned if queried. + * (see fluid_rvoice_mixer_chorus_get_param()) + * + * @param mixer that contains all fx units. + * @param fx_group index of the fx group to which parameters must be set. + * must be in the range [-1..mixer->fx_units[. If -1 the changes are applied + * to all fx group. + * Keep in mind, that the needed CPU time is proportional to 'nr'. + * @param set Flags indicating which parameters to set (#fluid_chorus_set_t) + * @param values table of pararameters. + */ +void +fluid_rvoice_mixer_set_chorus_full(const fluid_rvoice_mixer_t *mixer, + int fx_group, int set, const double values[]) +{ + fluid_mixer_fx_t *fx = mixer->fx; + int nr_units = mixer->fx_units; + + if(fx_group >= 0) /* apply parameters to this group fx only */ + { + nr_units = fx_group + 1; + } + else /* apply parameters to all fx units*/ + { + fx_group = 0; + } + + for(; fx_group < nr_units; fx_group++) + { + int param; + + for(param = 0; param < FLUID_CHORUS_PARAM_LAST; param++) + { + if(set & FLUID_CHORPARAM_TO_SETFLAG(param)) + { + fx[fx_group].chorus_param[param] = values[param]; + } + } + } +} + +/** + * get one chorus shadow parameter for one fx group. + * (see fluid_rvoice_mixer_set_chorus_full()) + * + * @param mixer that contains all fx groups units. + * @param fx_group index of the fx group to get parameter from. + * must be in the range [0..mixer->fx_units[. + * @param get Flags indicating which parameter to get (#fluid_chorus_set_t) + * @return the parameter value (0.0 is returned if error) + */ +double +fluid_rvoice_mixer_chorus_get_param(const fluid_rvoice_mixer_t *mixer, + int fx_group, int param) +{ + return mixer->fx[fx_group].chorus_param[param]; +} + +/* @deprecated: use fluid_rvoice_mixer_reverb_enable instead */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_enabled) +{ + fluid_rvoice_mixer_t *mixer = obj; + int on = param[0].i; + + mixer->with_reverb = on; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reverb_enable) +{ + fluid_rvoice_mixer_t *mixer = obj; + int fx_group = param[0].i; /* reverb fx group index */ + int on = param[1].i; /* on/off */ + + int nr_units = mixer->fx_units; + + /* does on/off must be applied only to fx group at index fx_group ? */ + if(fx_group >= 0) + { + mixer->fx[fx_group].reverb_on = on; + } + /* on/off must be applied to all fx groups */ + else + { + for(fx_group = 0; fx_group < nr_units; fx_group++) + { + mixer->fx[fx_group].reverb_on = on; + } + } + + /* set with_reverb if at least one reverb unit is on */ + for(fx_group = 0; fx_group < nr_units; fx_group++) + { + on = mixer->fx[fx_group].reverb_on; + + if(on) + { + break; + } + } + + mixer->with_reverb = on; +} + +/* @deprecated: use fluid_rvoice_mixer_chorus_enable instead */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_enabled) +{ + fluid_rvoice_mixer_t *mixer = obj; + int on = param[0].i; + mixer->with_chorus = on; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_chorus_enable) +{ + fluid_rvoice_mixer_t *mixer = obj; + int fx_group = param[0].i; /* chorus fx group index */ + int on = param[1].i; /* on/off */ + + int nr_units = mixer->fx_units; + + /* does on/off must be applied only to fx group at index fx_group ? */ + if(fx_group >= 0) + { + mixer->fx[fx_group].chorus_on = on; + } + /* on/off must be applied to all fx groups */ + else + { + for(fx_group = 0; fx_group < nr_units; fx_group++) + { + mixer->fx[fx_group].chorus_on = on; + } + } + + /* set with_chorus if at least one chorus unit is on */ + for(fx_group = 0; fx_group < nr_units; fx_group++) + { + on = mixer->fx[fx_group].chorus_on; + + if(on) + { + break; + } + } + + mixer->with_chorus = on; +} + +void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t *mixer, int on) +{ + mixer->mix_fx_to_out = on; +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_params) +{ + fluid_rvoice_mixer_t *mixer = obj; + int i = param[0].i; + int set = param[1].i; + int nr = param[2].i; + fluid_real_t level = param[3].real; + fluid_real_t speed = param[4].real; + fluid_real_t depth_ms = param[5].real; + int type = param[6].i; + + int nr_units = mixer->fx_units; + + /* does parameters must be applied only to fx group i ? */ + if(i >= 0) + { + nr_units = i + 1; + } + else + { + i = 0; /* parameters must be applied to all fx groups */ + } + + while(i < nr_units) + { + fluid_chorus_set(mixer->fx[i++].chorus, set, nr, level, speed, depth_ms, type); + } +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_params) +{ + fluid_rvoice_mixer_t *mixer = obj; + int i = param[0].i; /* fx group index */ + int set = param[1].i; + fluid_real_t roomsize = param[2].real; + fluid_real_t damping = param[3].real; + fluid_real_t width = param[4].real; + fluid_real_t level = param[5].real; + + int nr_units = mixer->fx_units; + + /* does parameters change should be applied only to fx group i ? */ + if(i >= 0) + { + nr_units = i + 1; /* parameters change must be applied to fx groups i */ + } + else + { + i = 0; /* parameters change must be applied to all fx groups */ + } + + while(i < nr_units) + { + fluid_revmodel_set(mixer->fx[i++].reverb, set, roomsize, damping, width, level); + } +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_reverb) +{ + fluid_rvoice_mixer_t *mixer = obj; + int i; + + for(i = 0; i < mixer->fx_units; i++) + { + fluid_revmodel_reset(mixer->fx[i].reverb); + } +} + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_chorus) +{ + fluid_rvoice_mixer_t *mixer = obj; + int i; + + for(i = 0; i < mixer->fx_units; i++) + { + fluid_chorus_reset(mixer->fx[i].chorus); + } +} + +int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t *mixer, + fluid_real_t **left, fluid_real_t **right) +{ + *left = fluid_align_ptr(mixer->buffers.left_buf, FLUID_DEFAULT_ALIGNMENT); + *right = fluid_align_ptr(mixer->buffers.right_buf, FLUID_DEFAULT_ALIGNMENT); + return mixer->buffers.buf_count; +} + +int fluid_rvoice_mixer_get_fx_bufs(fluid_rvoice_mixer_t *mixer, + fluid_real_t **fx_left, fluid_real_t **fx_right) +{ + *fx_left = fluid_align_ptr(mixer->buffers.fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + *fx_right = fluid_align_ptr(mixer->buffers.fx_right_buf, FLUID_DEFAULT_ALIGNMENT); + return mixer->buffers.fx_buf_count; +} + +int fluid_rvoice_mixer_get_bufcount(fluid_rvoice_mixer_t *mixer) +{ + return FLUID_MIXER_MAX_BUFFERS_DEFAULT; +} + +#if WITH_PROFILING +int fluid_rvoice_mixer_get_active_voices(fluid_rvoice_mixer_t *mixer) +{ + return mixer->active_voices; +} +#endif + +#if ENABLE_MIXER_THREADS + +static FLUID_INLINE fluid_rvoice_t * +fluid_mixer_get_mt_rvoice(fluid_rvoice_mixer_t *mixer) +{ + int i = fluid_atomic_int_exchange_and_add(&mixer->current_rvoice, 1); + + if(i >= mixer->active_voices) + { + return NULL; + } + + return mixer->rvoices[i]; +} + +#define THREAD_BUF_PROCESSING 0 +#define THREAD_BUF_VALID 1 +#define THREAD_BUF_NODATA 2 +#define THREAD_BUF_TERMINATE 3 + +/* Core thread function (processes voices in parallel to primary synthesis thread) */ +static fluid_thread_return_t +fluid_mixer_thread_func(void *data) +{ + fluid_mixer_buffers_t *buffers = data; + fluid_rvoice_mixer_t *mixer = buffers->mixer; + int hasValidData = 0; + FLUID_DECLARE_VLA(fluid_real_t *, bufs, buffers->buf_count * 2 + buffers->fx_buf_count * 2); + int bufcount = 0; + int current_blockcount = 0; + fluid_real_t *local_buf = fluid_align_ptr(buffers->local_buf, FLUID_DEFAULT_ALIGNMENT); + + while(!fluid_atomic_int_get(&mixer->threads_should_terminate)) + { + fluid_rvoice_t *rvoice = fluid_mixer_get_mt_rvoice(mixer); + + if(rvoice == NULL) + { + // if no voices: signal rendered buffers, sleep + fluid_atomic_int_set(&buffers->ready, hasValidData ? THREAD_BUF_VALID : THREAD_BUF_NODATA); + fluid_cond_mutex_lock(mixer->thread_ready_m); + fluid_cond_signal(mixer->thread_ready); + fluid_cond_mutex_unlock(mixer->thread_ready_m); + + fluid_cond_mutex_lock(mixer->wakeup_threads_m); + + while(1) + { + int j = fluid_atomic_int_get(&buffers->ready); + + if(j == THREAD_BUF_PROCESSING || j == THREAD_BUF_TERMINATE) + { + break; + } + + fluid_cond_wait(mixer->wakeup_threads, mixer->wakeup_threads_m); + } + + fluid_cond_mutex_unlock(mixer->wakeup_threads_m); + + hasValidData = 0; + } + else + { + // else: if buffer is not zeroed, zero buffers + if(!hasValidData) + { + // blockcount may have changed, since thread was put to sleep + current_blockcount = mixer->current_blockcount; + fluid_mixer_buffers_zero(buffers, current_blockcount); + bufcount = fluid_mixer_buffers_prepare(buffers, bufs); + hasValidData = 1; + } + + // then render voice to buffers + fluid_mixer_buffers_render_one(buffers, rvoice, bufs, bufcount, local_buf, current_blockcount); + } + } + + return FLUID_THREAD_RETURN_VALUE; +} + +static void +fluid_mixer_buffers_mix(fluid_mixer_buffers_t *dst, fluid_mixer_buffers_t *src, int current_blockcount) +{ + int i, j; + int scount = current_blockcount * FLUID_BUFSIZE; + int minbuf; + fluid_real_t *FLUID_RESTRICT base_src; + fluid_real_t *FLUID_RESTRICT base_dst; + + minbuf = dst->buf_count; + + if(minbuf > src->buf_count) + { + minbuf = src->buf_count; + } + + base_src = fluid_align_ptr(src->left_buf, FLUID_DEFAULT_ALIGNMENT); + base_dst = fluid_align_ptr(dst->left_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < minbuf; i++) + { + #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) + + for(j = 0; j < scount; j++) + { + int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; + base_dst[dsp_i] += base_src[dsp_i]; + } + } + + base_src = fluid_align_ptr(src->right_buf, FLUID_DEFAULT_ALIGNMENT); + base_dst = fluid_align_ptr(dst->right_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < minbuf; i++) + { + #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) + + for(j = 0; j < scount; j++) + { + int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; + base_dst[dsp_i] += base_src[dsp_i]; + } + } + + minbuf = dst->fx_buf_count; + + if(minbuf > src->fx_buf_count) + { + minbuf = src->fx_buf_count; + } + + base_src = fluid_align_ptr(src->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + base_dst = fluid_align_ptr(dst->fx_left_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < minbuf; i++) + { + #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) + + for(j = 0; j < scount; j++) + { + int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; + base_dst[dsp_i] += base_src[dsp_i]; + } + } + + base_src = fluid_align_ptr(src->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); + base_dst = fluid_align_ptr(dst->fx_right_buf, FLUID_DEFAULT_ALIGNMENT); + + for(i = 0; i < minbuf; i++) + { + #pragma omp simd aligned(base_dst,base_src:FLUID_DEFAULT_ALIGNMENT) + + for(j = 0; j < scount; j++) + { + int dsp_i = i * FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE + j; + base_dst[dsp_i] += base_src[dsp_i]; + } + } +} + + +/** + * Go through all threads and see if someone is finished for mixing + */ +static int +fluid_mixer_mix_in(fluid_rvoice_mixer_t *mixer, int extra_threads, int current_blockcount) +{ + int i, result, hasmixed; + + do + { + hasmixed = 0; + result = 0; + + for(i = 0; i < extra_threads; i++) + { + int j = fluid_atomic_int_get(&mixer->threads[i].ready); + + switch(j) + { + case THREAD_BUF_PROCESSING: + result = 1; + break; + + case THREAD_BUF_VALID: + fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_NODATA); + fluid_mixer_buffers_mix(&mixer->buffers, &mixer->threads[i], current_blockcount); + hasmixed = 1; + break; + } + } + } + while(hasmixed); + + return result; +} + +static void +fluid_render_loop_multithread(fluid_rvoice_mixer_t *mixer, int current_blockcount) +{ + int i, bufcount; + fluid_real_t *local_buf = fluid_align_ptr(mixer->buffers.local_buf, FLUID_DEFAULT_ALIGNMENT); + + FLUID_DECLARE_VLA(fluid_real_t *, bufs, + mixer->buffers.buf_count * 2 + mixer->buffers.fx_buf_count * 2); + // How many threads should we start this time? + int extra_threads = mixer->active_voices / VOICES_PER_THREAD; + + if(extra_threads > mixer->thread_count) + { + extra_threads = mixer->thread_count; + } + + if(extra_threads == 0) + { + // No extra threads? No thread overhead! + fluid_render_loop_singlethread(mixer, current_blockcount); + return; + } + + bufcount = fluid_mixer_buffers_prepare(&mixer->buffers, bufs); + + // Prepare voice list + fluid_cond_mutex_lock(mixer->wakeup_threads_m); + fluid_atomic_int_set(&mixer->current_rvoice, 0); + + for(i = 0; i < extra_threads; i++) + { + fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_PROCESSING); + } + + // Signal threads to wake up + fluid_cond_broadcast(mixer->wakeup_threads); + fluid_cond_mutex_unlock(mixer->wakeup_threads_m); + + // If thread is finished, mix it in + while(fluid_mixer_mix_in(mixer, extra_threads, current_blockcount)) + { + // Otherwise get a voice and render it + fluid_rvoice_t *rvoice = fluid_mixer_get_mt_rvoice(mixer); + + if(rvoice != NULL) + { + fluid_profile_ref_var(prof_ref); + fluid_mixer_buffers_render_one(&mixer->buffers, rvoice, bufs, bufcount, local_buf, current_blockcount); + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref, 1, + current_blockcount * FLUID_BUFSIZE); + //test++; + } + else + { + // If no voices, wait for mixes. Make sure one is still processing to avoid deadlock + int is_processing = 0; + //waits++; + fluid_cond_mutex_lock(mixer->thread_ready_m); + + for(i = 0; i < extra_threads; i++) + { + if(fluid_atomic_int_get(&mixer->threads[i].ready) == + THREAD_BUF_PROCESSING) + { + is_processing = 1; + } + } + + if(is_processing) + { + fluid_cond_wait(mixer->thread_ready, mixer->thread_ready_m); + } + + fluid_cond_mutex_unlock(mixer->thread_ready_m); + } + } + + //FLUID_LOG(FLUID_DBG, "Blockcount: %d, mixed %d of %d voices myself, waits = %d", + // current_blockcount, test, mixer->active_voices, waits); +} + +static void delete_rvoice_mixer_threads(fluid_rvoice_mixer_t *mixer) +{ + int i; + + // if no threads have been created yet (e.g. because a previous error prevented creation of threads + // mutexes and condition variables), skip terminating threads + if(mixer->thread_count != 0) + { + fluid_atomic_int_set(&mixer->threads_should_terminate, 1); + // Signal threads to wake up + fluid_cond_mutex_lock(mixer->wakeup_threads_m); + + for(i = 0; i < mixer->thread_count; i++) + { + fluid_atomic_int_set(&mixer->threads[i].ready, THREAD_BUF_TERMINATE); + } + + fluid_cond_broadcast(mixer->wakeup_threads); + fluid_cond_mutex_unlock(mixer->wakeup_threads_m); + + for(i = 0; i < mixer->thread_count; i++) + { + if(mixer->threads[i].thread) + { + fluid_thread_join(mixer->threads[i].thread); + delete_fluid_thread(mixer->threads[i].thread); + } + + fluid_mixer_buffers_free(&mixer->threads[i]); + } + } + + FLUID_FREE(mixer->threads); + mixer->thread_count = 0; + mixer->threads = NULL; +} + +/** + * Update amount of extra mixer threads. + * @param thread_count Number of extra mixer threads for multi-core rendering + * @param prio_level real-time prio level for the extra mixer threads + */ +static int fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t *mixer, int thread_count, int prio_level) +{ + char name[16]; + int i; + + // Kill all existing threads first + if(mixer->thread_count) + { + delete_rvoice_mixer_threads(mixer); + } + + if(thread_count == 0) + { + return FLUID_OK; + } + + // Now prepare the new threads + fluid_atomic_int_set(&mixer->threads_should_terminate, 0); + mixer->threads = FLUID_ARRAY(fluid_mixer_buffers_t, thread_count); + + if(mixer->threads == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + FLUID_MEMSET(mixer->threads, 0, thread_count * sizeof(fluid_mixer_buffers_t)); + mixer->thread_count = thread_count; + + for(i = 0; i < thread_count; i++) + { + fluid_mixer_buffers_t *b = &mixer->threads[i]; + + if(!fluid_mixer_buffers_init(b, mixer)) + { + return FLUID_FAILED; + } + + fluid_atomic_int_set(&b->ready, THREAD_BUF_NODATA); + FLUID_SNPRINTF(name, sizeof(name), "mixer%d", i); + b->thread = new_fluid_thread(name, fluid_mixer_thread_func, b, prio_level, 0); + + if(!b->thread) + { + return FLUID_FAILED; + } + } + + return FLUID_OK; +} +#endif + +/** + * Synthesize audio into buffers + * @param blockcount number of blocks to render, each having FLUID_BUFSIZE samples + * @return number of blocks rendered + */ +int +fluid_rvoice_mixer_render(fluid_rvoice_mixer_t *mixer, int blockcount) +{ + fluid_profile_ref_var(prof_ref); + + mixer->current_blockcount = blockcount; + + // Zero buffers + fluid_mixer_buffers_zero(&mixer->buffers, blockcount); + fluid_profile(FLUID_PROF_ONE_BLOCK_CLEAR, prof_ref, mixer->active_voices, + blockcount * FLUID_BUFSIZE); + +#if ENABLE_MIXER_THREADS + + if(mixer->thread_count > 0) + { + fluid_render_loop_multithread(mixer, blockcount); + } + else +#endif + { + fluid_render_loop_singlethread(mixer, blockcount); + } + + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICES, prof_ref, mixer->active_voices, + blockcount * FLUID_BUFSIZE); + + + // Process reverb & chorus + fluid_rvoice_mixer_process_fx(mixer, blockcount); + + // Call the callback and pack active voice array + fluid_rvoice_mixer_process_finished_voices(mixer); + + return blockcount; +} diff --git a/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.h b/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.h new file mode 100644 index 00000000000..63a456ce19e --- /dev/null +++ b/libs/fluidsynth/src/rvoice/fluid_rvoice_mixer.h @@ -0,0 +1,88 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_RVOICE_MIXER_H +#define _FLUID_RVOICE_MIXER_H + +#include "fluidsynth_priv.h" +#include "fluid_rvoice.h" +#include "fluid_ladspa.h" + +typedef struct _fluid_rvoice_mixer_t fluid_rvoice_mixer_t; + +int fluid_rvoice_mixer_render(fluid_rvoice_mixer_t *mixer, int blockcount); +int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t *mixer, + fluid_real_t **left, fluid_real_t **right); +int fluid_rvoice_mixer_get_fx_bufs(fluid_rvoice_mixer_t *mixer, + fluid_real_t **fx_left, fluid_real_t **fx_right); +int fluid_rvoice_mixer_get_bufcount(fluid_rvoice_mixer_t *mixer); +#if WITH_PROFILING +int fluid_rvoice_mixer_get_active_voices(fluid_rvoice_mixer_t *mixer); +#endif +fluid_rvoice_mixer_t *new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, int fx_units, + fluid_real_t sample_rate_max, fluid_real_t sample_rate, + fluid_rvoice_eventhandler_t *, int, int); + +void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t *); + +void +fluid_rvoice_mixer_set_reverb_full(const fluid_rvoice_mixer_t *mixer, + int fx_group, int set, const double values[]); + +double +fluid_rvoice_mixer_reverb_get_param(const fluid_rvoice_mixer_t *mixer, + int fx_group, int param); +void +fluid_rvoice_mixer_set_chorus_full(const fluid_rvoice_mixer_t *mixer, + int fx_group, int set, const double values[]); +double +fluid_rvoice_mixer_chorus_get_param(const fluid_rvoice_mixer_t *mixer, + int fx_group, int param); + + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_add_voice); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_samplerate); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_polyphony); + +/* @deprecated */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_enabled); +/* @deprecated */ +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_enabled); + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reverb_enable); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_chorus_enable); + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_chorus_params); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_set_reverb_params); + +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_reverb); +DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_mixer_reset_chorus); + + + +void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t *mixer, int on); +#ifdef LADSPA +void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t *mixer, + fluid_ladspa_fx_t *ladspa_fx, int audio_groups); +#endif + +#endif + diff --git a/libs/fluidsynth/src/sfloader/fluid_defsfont.c b/libs/fluidsynth/src/sfloader/fluid_defsfont.c new file mode 100644 index 00000000000..129867d0f0c --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_defsfont.c @@ -0,0 +1,2372 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * SoundFont file loading code borrowed from Smurf SoundFont Editor + * Copyright (C) 1999-2001 Josh Green + * + * 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#include "fluid_defsfont.h" +#include "fluid_sfont.h" +#include "fluid_sys.h" +#include "fluid_synth.h" +#include "fluid_samplecache.h" +#include "fluid_chan.h" + +/* EMU8k/10k hardware applies this factor to initial attenuation generator values set at preset and + * instrument level in a soundfont. We apply this factor when loading the generator values to stay + * compatible as most existing soundfonts expect exactly this (strange, non-standard) behaviour. */ +#define EMU_ATTENUATION_FACTOR (0.4f) + +/* Dynamic sample loading functions */ +static int pin_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); +static int unpin_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); +static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); +static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset); +static void unload_sample(fluid_sample_t *sample); +static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan); +static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason); +static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone); +static fluid_inst_t *find_inst_by_idx(fluid_defsfont_t *defsfont, int idx); + + +/*************************************************************** + * + * SFONT LOADER + */ + +/** + * Creates a default soundfont2 loader that can be used with fluid_synth_add_sfloader(). + * By default every synth instance has an initial default soundfont loader instance. + * Calling this function is usually only necessary to load a soundfont from memory, by providing custom callback functions via fluid_sfloader_set_callbacks(). + * + * @param settings A settings instance obtained by new_fluid_settings() + * @return A default soundfont2 loader struct + */ +fluid_sfloader_t *new_fluid_defsfloader(fluid_settings_t *settings) +{ + fluid_sfloader_t *loader; + fluid_return_val_if_fail(settings != NULL, NULL); + + loader = new_fluid_sfloader(fluid_defsfloader_load, delete_fluid_sfloader); + + if(loader == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + fluid_sfloader_set_data(loader, settings); + + return loader; +} + +fluid_sfont_t *fluid_defsfloader_load(fluid_sfloader_t *loader, const char *filename) +{ + fluid_defsfont_t *defsfont; + fluid_sfont_t *sfont; + + defsfont = new_fluid_defsfont(fluid_sfloader_get_data(loader)); + + if(defsfont == NULL) + { + return NULL; + } + + sfont = new_fluid_sfont(fluid_defsfont_sfont_get_name, + fluid_defsfont_sfont_get_preset, + fluid_defsfont_sfont_iteration_start, + fluid_defsfont_sfont_iteration_next, + fluid_defsfont_sfont_delete); + + if(sfont == NULL) + { + delete_fluid_defsfont(defsfont); + return NULL; + } + + fluid_sfont_set_data(sfont, defsfont); + + defsfont->sfont = sfont; + + if(fluid_defsfont_load(defsfont, &loader->file_callbacks, filename) == FLUID_FAILED) + { + fluid_defsfont_sfont_delete(sfont); + return NULL; + } + + return sfont; +} + + + +/*************************************************************** + * + * PUBLIC INTERFACE + */ + +int fluid_defsfont_sfont_delete(fluid_sfont_t *sfont) +{ + if(delete_fluid_defsfont(fluid_sfont_get_data(sfont)) != FLUID_OK) + { + return -1; + } + + delete_fluid_sfont(sfont); + return 0; +} + +const char *fluid_defsfont_sfont_get_name(fluid_sfont_t *sfont) +{ + return fluid_defsfont_get_name(fluid_sfont_get_data(sfont)); +} + +fluid_preset_t * +fluid_defsfont_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum) +{ + return fluid_defsfont_get_preset(fluid_sfont_get_data(sfont), bank, prenum); +} + +void fluid_defsfont_sfont_iteration_start(fluid_sfont_t *sfont) +{ + fluid_defsfont_iteration_start(fluid_sfont_get_data(sfont)); +} + +fluid_preset_t *fluid_defsfont_sfont_iteration_next(fluid_sfont_t *sfont) +{ + return fluid_defsfont_iteration_next(fluid_sfont_get_data(sfont)); +} + +void fluid_defpreset_preset_delete(fluid_preset_t *preset) +{ + fluid_defsfont_t *defsfont; + fluid_defpreset_t *defpreset; + + defsfont = fluid_sfont_get_data(preset->sfont); + defpreset = fluid_preset_get_data(preset); + + if(defsfont) + { + defsfont->preset = fluid_list_remove(defsfont->preset, defpreset); + } + + delete_fluid_defpreset(defpreset); + delete_fluid_preset(preset); +} + +const char *fluid_defpreset_preset_get_name(fluid_preset_t *preset) +{ + return fluid_defpreset_get_name(fluid_preset_get_data(preset)); +} + +int fluid_defpreset_preset_get_banknum(fluid_preset_t *preset) +{ + return fluid_defpreset_get_banknum(fluid_preset_get_data(preset)); +} + +int fluid_defpreset_preset_get_num(fluid_preset_t *preset) +{ + return fluid_defpreset_get_num(fluid_preset_get_data(preset)); +} + +int fluid_defpreset_preset_noteon(fluid_preset_t *preset, fluid_synth_t *synth, + int chan, int key, int vel) +{ + return fluid_defpreset_noteon(fluid_preset_get_data(preset), synth, chan, key, vel); +} + + +/*************************************************************** + * + * SFONT + */ + +/* + * new_fluid_defsfont + */ +fluid_defsfont_t *new_fluid_defsfont(fluid_settings_t *settings) +{ + fluid_defsfont_t *defsfont; + + defsfont = FLUID_NEW(fluid_defsfont_t); + + if(defsfont == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(defsfont, 0, sizeof(*defsfont)); + + fluid_settings_getint(settings, "synth.lock-memory", &defsfont->mlock); + fluid_settings_getint(settings, "synth.dynamic-sample-loading", &defsfont->dynamic_samples); + + return defsfont; +} + +/* + * delete_fluid_defsfont + */ +int delete_fluid_defsfont(fluid_defsfont_t *defsfont) +{ + fluid_list_t *list; + fluid_preset_t *preset; + fluid_sample_t *sample; + + fluid_return_val_if_fail(defsfont != NULL, FLUID_OK); + + /* If we use dynamic sample loading, make sure we unpin any + * pinned presets before removing this soundfont */ + if(defsfont->dynamic_samples) + { + for(list = defsfont->preset; list; list = fluid_list_next(list)) + { + preset = (fluid_preset_t *)fluid_list_get(list); + unpin_preset_samples(defsfont, preset); + } + } + + /* Check that no samples are currently used */ + for(list = defsfont->sample; list; list = fluid_list_next(list)) + { + sample = (fluid_sample_t *) fluid_list_get(list); + + if(sample->refcount != 0) + { + return FLUID_FAILED; + } + } + + if(defsfont->filename != NULL) + { + FLUID_FREE(defsfont->filename); + } + + for(list = defsfont->sample; list; list = fluid_list_next(list)) + { + sample = (fluid_sample_t *) fluid_list_get(list); + + /* If the sample data pointer is different to the sampledata chunk of + * the soundfont, then the sample has been loaded individually (SF3) + * and needs to be unloaded explicitly. This is safe even if using + * dynamic sample loading, as the sample_unload mechanism sets + * sample->data to NULL after unload. */ + if ((sample->data != NULL) && (sample->data != defsfont->sampledata)) + { + fluid_samplecache_unload(sample->data); + } + delete_fluid_sample(sample); + } + + if(defsfont->sample) + { + delete_fluid_list(defsfont->sample); + } + + if(defsfont->sampledata != NULL) + { + fluid_samplecache_unload(defsfont->sampledata); + } + + for(list = defsfont->preset; list; list = fluid_list_next(list)) + { + preset = (fluid_preset_t *)fluid_list_get(list); + fluid_defpreset_preset_delete(preset); + } + + delete_fluid_list(defsfont->preset); + + for(list = defsfont->inst; list; list = fluid_list_next(list)) + { + delete_fluid_inst(fluid_list_get(list)); + } + + delete_fluid_list(defsfont->inst); + + FLUID_FREE(defsfont); + return FLUID_OK; +} + +/* + * fluid_defsfont_get_name + */ +const char *fluid_defsfont_get_name(fluid_defsfont_t *defsfont) +{ + return defsfont->filename; +} + +/* Load sample data for a single sample from the Soundfont file. + * Returns FLUID_OK on error, otherwise FLUID_FAILED + */ +int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample) +{ + int num_samples; + unsigned int source_end = sample->source_end; + + /* For uncompressed samples we want to include the 46 zero sample word area following each sample + * in the Soundfont. Otherwise samples with loopend > end, which we have decided not to correct, would + * be corrected after all in fluid_sample_sanitize_loop */ + if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) + { + source_end += 46; /* Length of zero sample word after each sample, according to SF specs */ + + /* Safeguard against Soundfonts that are not quite valid and don't include 46 sample words after the + * last sample */ + if(source_end >= (defsfont->samplesize / sizeof(short))) + { + source_end = defsfont->samplesize / sizeof(short); + } + } + + num_samples = fluid_samplecache_load( + sfdata, sample->source_start, source_end, sample->sampletype, + defsfont->mlock, &sample->data, &sample->data24); + + if(num_samples < 0) + { + return FLUID_FAILED; + } + + if(num_samples == 0) + { + sample->start = sample->end = 0; + sample->loopstart = sample->loopend = 0; + return FLUID_OK; + } + + /* Ogg Vorbis samples already have loop pointers relative to the individual decompressed sample, + * but SF2 samples are relative to sample chunk start, so they need to be adjusted */ + if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) + { + sample->loopstart = sample->source_loopstart - sample->source_start; + sample->loopend = sample->source_loopend - sample->source_start; + } + + /* As we've just loaded an individual sample into it's own buffer, we need to adjust the start + * and end pointers */ + sample->start = 0; + sample->end = num_samples - 1; + + return FLUID_OK; +} + +/* Loads the sample data for all samples from the Soundfont file. For SF2 files, it loads the data in + * one large block. For SF3 files, each compressed sample gets loaded individually. + * Returns FLUID_OK on success, otherwise FLUID_FAILED + */ +int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata) +{ + fluid_list_t *list; + fluid_sample_t *sample; + int sf3_file = (sfdata->version.major == 3); + int sample_parsing_result = FLUID_OK; + int invalid_loops_were_sanitized = FALSE; + + /* For SF2 files, we load the sample data in one large block */ + if(!sf3_file) + { + int read_samples; + int num_samples = sfdata->samplesize / sizeof(short); + + read_samples = fluid_samplecache_load(sfdata, 0, num_samples - 1, 0, defsfont->mlock, + &defsfont->sampledata, &defsfont->sample24data); + + if(read_samples != num_samples) + { + FLUID_LOG(FLUID_ERR, "Attempted to read %d words of sample data, but got %d instead", + num_samples, read_samples); + return FLUID_FAILED; + } + } + + #pragma omp parallel + #pragma omp single + for(list = defsfont->sample; list; list = fluid_list_next(list)) + { + sample = fluid_list_get(list); + + if(sf3_file) + { + /* SF3 samples get loaded individually, as most (or all) of them are in Ogg Vorbis format + * anyway */ + #pragma omp task firstprivate(sample,sfdata,defsfont) shared(sample_parsing_result, invalid_loops_were_sanitized) default(none) + { + if(fluid_defsfont_load_sampledata(defsfont, sfdata, sample) == FLUID_FAILED) + { + #pragma omp critical + { + FLUID_LOG(FLUID_ERR, "Failed to load sample '%s'", sample->name); + sample_parsing_result = FLUID_FAILED; + } + } + else + { + int modified = fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short)); + if(modified) + { + #pragma omp critical + { + invalid_loops_were_sanitized = TRUE; + } + } + fluid_voice_optimize_sample(sample); + } + } + } + else + { + #pragma omp task firstprivate(sample, defsfont) shared(invalid_loops_were_sanitized) default(none) + { + int modified; + /* Data pointers of SF2 samples point to large sample data block loaded above */ + sample->data = defsfont->sampledata; + sample->data24 = defsfont->sample24data; + modified = fluid_sample_sanitize_loop(sample, defsfont->samplesize); + if(modified) + { + #pragma omp critical + { + invalid_loops_were_sanitized = TRUE; + } + } + fluid_voice_optimize_sample(sample); + } + } + } + + if(invalid_loops_were_sanitized) + { + FLUID_LOG(FLUID_WARN, + "Some invalid sample loops were sanitized! If you experience audible glitches, " + "start fluidsynth in verbose mode for detailed information."); + } + + return sample_parsing_result; +} + +/* + * fluid_defsfont_load + */ +int fluid_defsfont_load(fluid_defsfont_t *defsfont, const fluid_file_callbacks_t *fcbs, const char *file) +{ + SFData *sfdata; + fluid_list_t *p; + SFPreset *sfpreset; + SFSample *sfsample; + fluid_sample_t *sample; + fluid_defpreset_t *defpreset = NULL; + + defsfont->filename = FLUID_STRDUP(file); + + if(defsfont->filename == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + defsfont->fcbs = fcbs; + + /* The actual loading is done in the sfont and sffile files */ + sfdata = fluid_sffile_open(file, fcbs); + + if(sfdata == NULL) + { + /* error message already printed */ + return FLUID_FAILED; + } + + if(fluid_sffile_parse_presets(sfdata) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Couldn't parse presets from soundfont file"); + goto err_exit; + } + + /* Keep track of the position and size of the sample data because + it's loaded separately (and might be unoaded/reloaded in future) */ + defsfont->samplepos = sfdata->samplepos; + defsfont->samplesize = sfdata->samplesize; + defsfont->sample24pos = sfdata->sample24pos; + defsfont->sample24size = sfdata->sample24size; + + /* Create all samples from sample headers */ + p = sfdata->sample; + + while(p != NULL) + { + sfsample = (SFSample *)fluid_list_get(p); + + sample = new_fluid_sample(); + + if(sample == NULL) + { + goto err_exit; + } + + if(fluid_sample_import_sfont(sample, sfsample, defsfont) == FLUID_OK) + { + fluid_defsfont_add_sample(defsfont, sample); + } + else + { + delete_fluid_sample(sample); + sample = NULL; + } + + /* Store reference to FluidSynth sample in SFSample for later IZone fixups */ + sfsample->fluid_sample = sample; + + p = fluid_list_next(p); + } + + /* If dynamic sample loading is disabled, load all samples in the Soundfont */ + if(!defsfont->dynamic_samples) + { + if(fluid_defsfont_load_all_sampledata(defsfont, sfdata) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Unable to load all sample data"); + goto err_exit; + } + } + + /* Load all the presets */ + p = sfdata->preset; + + while(p != NULL) + { + sfpreset = (SFPreset *)fluid_list_get(p); + defpreset = new_fluid_defpreset(); + + if(defpreset == NULL) + { + goto err_exit; + } + + if(fluid_defpreset_import_sfont(defpreset, sfpreset, defsfont, sfdata) != FLUID_OK) + { + goto err_exit; + } + + if(fluid_defsfont_add_preset(defsfont, defpreset) == FLUID_FAILED) + { + goto err_exit; + } + + p = fluid_list_next(p); + } + + fluid_sffile_close(sfdata); + + return FLUID_OK; + +err_exit: + fluid_sffile_close(sfdata); + delete_fluid_defpreset(defpreset); + return FLUID_FAILED; +} + +/* fluid_defsfont_add_sample + * + * Add a sample to the SoundFont + */ +int fluid_defsfont_add_sample(fluid_defsfont_t *defsfont, fluid_sample_t *sample) +{ + defsfont->sample = fluid_list_prepend(defsfont->sample, sample); + return FLUID_OK; +} + +/* fluid_defsfont_add_preset + * + * Add a preset to the SoundFont + */ +int fluid_defsfont_add_preset(fluid_defsfont_t *defsfont, fluid_defpreset_t *defpreset) +{ + fluid_preset_t *preset; + + preset = new_fluid_preset(defsfont->sfont, + fluid_defpreset_preset_get_name, + fluid_defpreset_preset_get_banknum, + fluid_defpreset_preset_get_num, + fluid_defpreset_preset_noteon, + fluid_defpreset_preset_delete); + + if(preset == NULL) + { + return FLUID_FAILED; + } + + if(defsfont->dynamic_samples) + { + preset->notify = dynamic_samples_preset_notify; + } + + fluid_preset_set_data(preset, defpreset); + + defsfont->preset = fluid_list_append(defsfont->preset, preset); + + return FLUID_OK; +} + +/* + * fluid_defsfont_get_preset + */ +fluid_preset_t *fluid_defsfont_get_preset(fluid_defsfont_t *defsfont, int bank, int num) +{ + fluid_preset_t *preset; + fluid_list_t *list; + + for(list = defsfont->preset; list != NULL; list = fluid_list_next(list)) + { + preset = (fluid_preset_t *)fluid_list_get(list); + + if((fluid_preset_get_banknum(preset) == bank) && (fluid_preset_get_num(preset) == num)) + { + return preset; + } + } + + return NULL; +} + +/* + * fluid_defsfont_iteration_start + */ +void fluid_defsfont_iteration_start(fluid_defsfont_t *defsfont) +{ + defsfont->preset_iter_cur = defsfont->preset; +} + +/* + * fluid_defsfont_iteration_next + */ +fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t *defsfont) +{ + fluid_preset_t *preset = (fluid_preset_t *)fluid_list_get(defsfont->preset_iter_cur); + + defsfont->preset_iter_cur = fluid_list_next(defsfont->preset_iter_cur); + + return preset; +} + +/*************************************************************** + * + * PRESET + */ + +/* + * new_fluid_defpreset + */ +fluid_defpreset_t * +new_fluid_defpreset(void) +{ + fluid_defpreset_t *defpreset = FLUID_NEW(fluid_defpreset_t); + + if(defpreset == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + defpreset->next = NULL; + defpreset->name[0] = 0; + defpreset->bank = 0; + defpreset->num = 0; + defpreset->global_zone = NULL; + defpreset->zone = NULL; + defpreset->pinned = FALSE; + return defpreset; +} + +/* + * delete_fluid_defpreset + */ +void +delete_fluid_defpreset(fluid_defpreset_t *defpreset) +{ + fluid_preset_zone_t *zone; + + fluid_return_if_fail(defpreset != NULL); + + delete_fluid_preset_zone(defpreset->global_zone); + defpreset->global_zone = NULL; + + zone = defpreset->zone; + + while(zone != NULL) + { + defpreset->zone = zone->next; + delete_fluid_preset_zone(zone); + zone = defpreset->zone; + } + + FLUID_FREE(defpreset); +} + +int +fluid_defpreset_get_banknum(fluid_defpreset_t *defpreset) +{ + return defpreset->bank; +} + +int +fluid_defpreset_get_num(fluid_defpreset_t *defpreset) +{ + return defpreset->num; +} + +const char * +fluid_defpreset_get_name(fluid_defpreset_t *defpreset) +{ + return defpreset->name; +} + +/* + * fluid_defpreset_next + */ +fluid_defpreset_t * +fluid_defpreset_next(fluid_defpreset_t *defpreset) +{ + return defpreset->next; +} + +/* + * Adds global and local modulators list to the voice. This is done in 2 steps: + * - Step 1: Local modulators replace identical global modulators. + * - Step 2: global + local modulators are added to the voice using mode. + * + * Instrument zone list (local/global) must be added using FLUID_VOICE_OVERWRITE. + * Preset zone list (local/global) must be added using FLUID_VOICE_ADD. + * + * @param voice voice instance. + * @param global_mod global list of modulators. + * @param local_mod local list of modulators. + * @param mode Determines how to handle an existing identical modulator. + * #FLUID_VOICE_ADD to add (offset) the modulator amounts, + * #FLUID_VOICE_OVERWRITE to replace the modulator, +*/ +static void +fluid_defpreset_noteon_add_mod_to_voice(fluid_voice_t *voice, + fluid_mod_t *global_mod, fluid_mod_t *local_mod, + int mode) +{ + fluid_mod_t *mod; + /* list for 'sorting' global/local modulators */ + fluid_mod_t *mod_list[FLUID_NUM_MOD]; + int mod_list_count, i; + + /* identity_limit_count is the modulator upper limit number to handle with + * existing identical modulators. + * When identity_limit_count is below the actual number of modulators, this + * will restrict identity check to this upper limit, + * This is useful when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at + * noteon. + */ + int identity_limit_count; + + /* Step 1: Local modulators replace identical global modulators. */ + + /* local (instrument zone/preset zone), modulators: Put them all into a list. */ + mod_list_count = 0; + + while(local_mod) + { + /* As modulators number in local_mod list was limited to FLUID_NUM_MOD at + soundfont loading time (fluid_limit_mod_list()), here we don't need + to check if mod_list is full. + */ + mod_list[mod_list_count++] = local_mod; + local_mod = local_mod->next; + } + + /* global (instrument zone/preset zone), modulators. + * Replace modulators with the same definition in the global list: + * (Instrument zone: SF 2.01 page 69, 'bullet' 8) + * (Preset zone: SF 2.01 page 69, second-last bullet). + * + * mod_list contains local modulators. Now we know that there + * is no global modulator identical to another global modulator (this has + * been checked at soundfont loading time). So global modulators + * are only checked against local modulators number. + */ + + /* Restrict identity check to the number of local modulators */ + identity_limit_count = mod_list_count; + + while(global_mod) + { + /* 'Identical' global modulators are ignored. + * SF2.01 section 9.5.1 + * page 69, 'bullet' 3 defines 'identical'. */ + + for(i = 0; i < identity_limit_count; i++) + { + if(fluid_mod_test_identity(global_mod, mod_list[i])) + { + break; + } + } + + /* Finally add the new modulator to the list. */ + if(i >= identity_limit_count) + { + /* Although local_mod and global_mod lists was limited to + FLUID_NUM_MOD at soundfont loading time, it is possible that + local + global modulators exceeds FLUID_NUM_MOD. + So, checks if mod_list_count reaches the limit. + */ + if(mod_list_count >= FLUID_NUM_MOD) + { + /* mod_list is full, we silently forget this modulator and + next global modulators. When mod_list will be added to the + voice, a warning will be displayed if the voice list is full. + (see fluid_voice_add_mod_local()). + */ + break; + } + + mod_list[mod_list_count++] = global_mod; + } + + global_mod = global_mod->next; + } + + /* Step 2: global + local modulators are added to the voice using mode. */ + + /* + * mod_list contains local and global modulators, we know that: + * - there is no global modulator identical to another global modulator, + * - there is no local modulator identical to another local modulator, + * So these local/global modulators are only checked against + * actual number of voice modulators. + */ + + /* Restrict identity check to the actual number of voice modulators */ + /* Actual number of voice modulators : defaults + [instruments] */ + identity_limit_count = voice->mod_count; + + for(i = 0; i < mod_list_count; i++) + { + + mod = mod_list[i]; + /* in mode FLUID_VOICE_OVERWRITE disabled instruments modulators CANNOT be skipped. */ + /* in mode FLUID_VOICE_ADD disabled preset modulators can be skipped. */ + + if((mode == FLUID_VOICE_OVERWRITE) || (mod->amount != 0)) + { + /* Instrument modulators -supersede- existing (default) modulators. + SF 2.01 page 69, 'bullet' 6 */ + + /* Preset modulators -add- to existing instrument modulators. + SF2.01 page 70 first bullet on page */ + fluid_voice_add_mod_local(voice, mod, mode, identity_limit_count); + } + } +} + +/* + * fluid_defpreset_noteon + */ +int +fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int chan, int key, int vel) +{ + fluid_preset_zone_t *preset_zone, *global_preset_zone; + fluid_inst_t *inst; + fluid_inst_zone_t *inst_zone, *global_inst_zone; + fluid_voice_zone_t *voice_zone; + fluid_list_t *list; + fluid_voice_t *voice; + int tuned_key; + int i; + + /* For detuned channels it might be better to use another key for Soundfont sample selection + * giving better approximations for the pitch than the original key. + * Example: play key 60 on 6370 Hz => use tuned key 64 for sample selection + * + * This feature is only enabled for melodic channels. + * For drum channels we always select Soundfont samples by key numbers. + */ + + if(synth->channel[chan]->channel_type == CHANNEL_TYPE_MELODIC) + { + tuned_key = (int)(fluid_channel_get_key_pitch(synth->channel[chan], key) / 100.0f + 0.5f); + } + else + { + tuned_key = key; + } + + global_preset_zone = fluid_defpreset_get_global_zone(defpreset); + + /* run thru all the zones of this preset */ + preset_zone = fluid_defpreset_get_zone(defpreset); + + while(preset_zone != NULL) + { + + /* check if the note falls into the key and velocity range of this + preset */ + if(fluid_zone_inside_range(&preset_zone->range, tuned_key, vel)) + { + + inst = fluid_preset_zone_get_inst(preset_zone); + global_inst_zone = fluid_inst_get_global_zone(inst); + + /* run thru all the zones of this instrument that could start a voice */ + for(list = preset_zone->voice_zone; list != NULL; list = fluid_list_next(list)) + { + voice_zone = fluid_list_get(list); + + /* check if the instrument zone is ignored and the note falls into + the key and velocity range of this instrument zone. + An instrument zone must be ignored when its voice is already running + played by a legato passage (see fluid_synth_noteon_monopoly_legato()) */ + if(fluid_zone_inside_range(&voice_zone->range, tuned_key, vel)) + { + + inst_zone = voice_zone->inst_zone; + + /* this is a good zone. allocate a new synthesis process and initialize it */ + voice = fluid_synth_alloc_voice_LOCAL(synth, inst_zone->sample, chan, key, vel, &voice_zone->range); + + if(voice == NULL) + { + return FLUID_FAILED; + } + + + /* Instrument level, generators */ + + for(i = 0; i < GEN_LAST; i++) + { + + /* SF 2.01 section 9.4 'bullet' 4: + * + * A generator in a local instrument zone supersedes a + * global instrument zone generator. Both cases supersede + * the default generator -> voice_gen_set */ + + if(inst_zone->gen[i].flags) + { + fluid_voice_gen_set(voice, i, inst_zone->gen[i].val); + + } + else if((global_inst_zone != NULL) && (global_inst_zone->gen[i].flags)) + { + fluid_voice_gen_set(voice, i, global_inst_zone->gen[i].val); + + } + else + { + /* The generator has not been defined in this instrument. + * Do nothing, leave it at the default. + */ + } + + } /* for all generators */ + + /* Adds instrument zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global instrument modulators */ + global_inst_zone ? global_inst_zone->mod : NULL, + inst_zone->mod, /* local instrument modulators */ + FLUID_VOICE_OVERWRITE); /* mode */ + + /* Preset level, generators */ + + for(i = 0; i < GEN_LAST; i++) + { + + /* SF 2.01 section 8.5 page 58: If some generators are + encountered at preset level, they should be ignored. + However this check is not necessary when the soundfont + loader has ignored invalid preset generators. + Actually load_pgen()has ignored these invalid preset + generators: + GEN_STARTADDROFS, GEN_ENDADDROFS, + GEN_STARTLOOPADDROFS, GEN_ENDLOOPADDROFS, + GEN_STARTADDRCOARSEOFS,GEN_ENDADDRCOARSEOFS, + GEN_STARTLOOPADDRCOARSEOFS, + GEN_KEYNUM, GEN_VELOCITY, + GEN_ENDLOOPADDRCOARSEOFS, + GEN_SAMPLEMODE, GEN_EXCLUSIVECLASS,GEN_OVERRIDEROOTKEY + */ + + /* SF 2.01 section 9.4 'bullet' 9: A generator in a + * local preset zone supersedes a global preset zone + * generator. The effect is -added- to the destination + * summing node -> voice_gen_incr */ + + if(preset_zone->gen[i].flags) + { + fluid_voice_gen_incr(voice, i, preset_zone->gen[i].val); + } + else if((global_preset_zone != NULL) && global_preset_zone->gen[i].flags) + { + fluid_voice_gen_incr(voice, i, global_preset_zone->gen[i].val); + } + else + { + /* The generator has not been defined in this preset + * Do nothing, leave it unchanged. + */ + } + } /* for all generators */ + + /* Adds preset zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global preset modulators */ + global_preset_zone ? global_preset_zone->mod : NULL, + preset_zone->mod, /* local preset modulators */ + FLUID_VOICE_ADD); /* mode */ + + /* add the synthesis process to the synthesis loop. */ + fluid_synth_start_voice(synth, voice); + + /* Store the ID of the first voice that was created by this noteon event. + * Exclusive class may only terminate older voices. + * That avoids killing voices, which have just been created. + * (a noteon event can create several voice processes with the same exclusive + * class - for example when using stereo samples) + */ + } + } + } + + preset_zone = fluid_preset_zone_next(preset_zone); + } + + return FLUID_OK; +} + +/* + * fluid_defpreset_set_global_zone + */ +int +fluid_defpreset_set_global_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone) +{ + defpreset->global_zone = zone; + return FLUID_OK; +} + +/* + * fluid_defpreset_import_sfont + */ +int +fluid_defpreset_import_sfont(fluid_defpreset_t *defpreset, + SFPreset *sfpreset, + fluid_defsfont_t *defsfont, + SFData *sfdata) +{ + fluid_list_t *p; + SFZone *sfzone; + fluid_preset_zone_t *zone; + int count; + char zone_name[256]; + + if(FLUID_STRLEN(sfpreset->name) > 0) + { + FLUID_STRCPY(defpreset->name, sfpreset->name); + } + else + { + FLUID_SNPRINTF(defpreset->name, sizeof(defpreset->name), "Bank%d,Pre%d", sfpreset->bank, sfpreset->prenum); + } + + defpreset->bank = sfpreset->bank; + defpreset->num = sfpreset->prenum; + p = sfpreset->zone; + count = 0; + + while(p != NULL) + { + sfzone = (SFZone *)fluid_list_get(p); + FLUID_SNPRINTF(zone_name, sizeof(zone_name), "pz:%s/%d", defpreset->name, count); + zone = new_fluid_preset_zone(zone_name); + + if(zone == NULL) + { + return FLUID_FAILED; + } + + if(fluid_preset_zone_import_sfont(zone, defpreset->global_zone, sfzone, defsfont, sfdata) != FLUID_OK) + { + delete_fluid_preset_zone(zone); + return FLUID_FAILED; + } + + if((count == 0) && (fluid_preset_zone_get_inst(zone) == NULL)) + { + fluid_defpreset_set_global_zone(defpreset, zone); + } + else if(fluid_defpreset_add_zone(defpreset, zone) != FLUID_OK) + { + delete_fluid_preset_zone(zone); + return FLUID_FAILED; + } + + p = fluid_list_next(p); + count++; + } + + return FLUID_OK; +} + +/* + * fluid_defpreset_add_zone + */ +int +fluid_defpreset_add_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone) +{ + if(defpreset->zone == NULL) + { + zone->next = NULL; + defpreset->zone = zone; + } + else + { + zone->next = defpreset->zone; + defpreset->zone = zone; + } + + return FLUID_OK; +} + +/* + * fluid_defpreset_get_zone + */ +fluid_preset_zone_t * +fluid_defpreset_get_zone(fluid_defpreset_t *defpreset) +{ + return defpreset->zone; +} + +/* + * fluid_defpreset_get_global_zone + */ +fluid_preset_zone_t * +fluid_defpreset_get_global_zone(fluid_defpreset_t *defpreset) +{ + return defpreset->global_zone; +} + +/*************************************************************** + * + * PRESET_ZONE + */ + +/* + * fluid_preset_zone_next + */ +fluid_preset_zone_t * +fluid_preset_zone_next(fluid_preset_zone_t *zone) +{ + return zone->next; +} + +/* + * new_fluid_preset_zone + */ +fluid_preset_zone_t * +new_fluid_preset_zone(char *name) +{ + fluid_preset_zone_t *zone = NULL; + zone = FLUID_NEW(fluid_preset_zone_t); + + if(zone == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + zone->next = NULL; + zone->voice_zone = NULL; + zone->name = FLUID_STRDUP(name); + + if(zone->name == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(zone); + return NULL; + } + + zone->inst = NULL; + zone->range.keylo = 0; + zone->range.keyhi = 128; + zone->range.vello = 0; + zone->range.velhi = 128; + zone->range.ignore = FALSE; + + /* Flag all generators as unused (default, they will be set when they are found + * in the sound font). + * This also sets the generator values to default, but that is of no concern here.*/ + fluid_gen_init(&zone->gen[0], NULL); + zone->mod = NULL; /* list of modulators */ + return zone; +} + +/* + * delete list of modulators. + */ +void delete_fluid_list_mod(fluid_mod_t *mod) +{ + fluid_mod_t *tmp; + + while(mod) /* delete the modulators */ + { + tmp = mod; + mod = mod->next; + delete_fluid_mod(tmp); + } +} + +/* + * delete_fluid_preset_zone + */ +void +delete_fluid_preset_zone(fluid_preset_zone_t *zone) +{ + fluid_list_t *list; + + fluid_return_if_fail(zone != NULL); + + delete_fluid_list_mod(zone->mod); + + for(list = zone->voice_zone; list != NULL; list = fluid_list_next(list)) + { + FLUID_FREE(fluid_list_get(list)); + } + + delete_fluid_list(zone->voice_zone); + + FLUID_FREE(zone->name); + FLUID_FREE(zone); +} + +static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone) +{ + fluid_inst_zone_t *inst_zone; + fluid_sample_t *sample; + fluid_voice_zone_t *voice_zone; + fluid_zone_range_t *irange; + fluid_zone_range_t *prange = &preset_zone->range; + + fluid_return_val_if_fail(preset_zone->inst != NULL, FLUID_FAILED); + + inst_zone = fluid_inst_get_zone(preset_zone->inst); + + while(inst_zone != NULL) + { + + /* We only create voice ranges for zones that could actually start a voice, + * i.e. that have a sample and don't point to ROM */ + sample = fluid_inst_zone_get_sample(inst_zone); + + if((sample == NULL) || fluid_sample_in_rom(sample)) + { + inst_zone = fluid_inst_zone_next(inst_zone); + continue; + } + + voice_zone = FLUID_NEW(fluid_voice_zone_t); + + if(voice_zone == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + voice_zone->inst_zone = inst_zone; + + irange = &inst_zone->range; + + voice_zone->range.keylo = (prange->keylo > irange->keylo) ? prange->keylo : irange->keylo; + voice_zone->range.keyhi = (prange->keyhi < irange->keyhi) ? prange->keyhi : irange->keyhi; + voice_zone->range.vello = (prange->vello > irange->vello) ? prange->vello : irange->vello; + voice_zone->range.velhi = (prange->velhi < irange->velhi) ? prange->velhi : irange->velhi; + voice_zone->range.ignore = FALSE; + + preset_zone->voice_zone = fluid_list_append(preset_zone->voice_zone, voice_zone); + + inst_zone = fluid_inst_zone_next(inst_zone); + } + + return FLUID_OK; +} + +/** + * Checks if modulator mod is identical to another modulator in the list + * (specs SF 2.0X 7.4, 7.8). + * @param mod, modulator list. + * @param name, if not NULL, pointer on a string displayed as warning. + * @return TRUE if mod is identical to another modulator, FALSE otherwise. + */ +static int +fluid_zone_is_mod_identical(fluid_mod_t *mod, char *name) +{ + fluid_mod_t *next = mod->next; + + while(next) + { + /* is mod identical to next ? */ + if(fluid_mod_test_identity(mod, next)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, "Ignoring identical modulator %s", name); + } + + return TRUE; + } + + next = next->next; + } + + return FALSE; +} + +/** + * Limits the number of modulators in a modulator list. + * This is appropriate to internal synthesizer modulators tables + * which have a fixed size (FLUID_NUM_MOD). + * + * @param zone_name, zone name + * @param list_mod, address of pointer on modulator list. + */ +static void fluid_limit_mod_list(char *zone_name, fluid_mod_t **list_mod) +{ + int mod_idx = 0; /* modulator index */ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + + while(mod) + { + if((mod_idx + 1) > FLUID_NUM_MOD) + { + /* truncation of list_mod */ + if(mod_idx) + { + prev_mod->next = NULL; + } + else + { + *list_mod = NULL; + } + + delete_fluid_list_mod(mod); + FLUID_LOG(FLUID_WARN, "%s, modulators count limited to %d", zone_name, + FLUID_NUM_MOD); + break; + } + + mod_idx++; + prev_mod = mod; + mod = mod->next; + } +} + +/** + * Checks and remove invalid modulators from a zone modulators list. + * - checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1). + * - checks identical modulators in the list (specs SF 2.01 7.4, 7.8). + * @param zone_name, zone name. + * @param list_mod, address of pointer on modulators list. + */ +static void +fluid_zone_check_mod(char *zone_name, fluid_mod_t **list_mod) +{ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + int mod_idx = 0; /* modulator index */ + + while(mod) + { + char zone_mod_name[256]; + fluid_mod_t *next = mod->next; + + /* prepare modulator name: zonename/#modulator */ + FLUID_SNPRINTF(zone_mod_name, sizeof(zone_mod_name), "%s/mod%d", zone_name, mod_idx); + + /* has mod invalid sources ? */ + if(!fluid_mod_check_sources(mod, zone_mod_name) + /* or is mod identical to any following modulator ? */ + || fluid_zone_is_mod_identical(mod, zone_mod_name)) + { + /* the modulator is useless so we remove it */ + if(prev_mod) + { + prev_mod->next = next; + } + else + { + *list_mod = next; + } + + delete_fluid_mod(mod); /* freeing */ + } + else + { + prev_mod = mod; + } + + mod = next; + mod_idx++; + } + + /* limits the size of modulators list */ + fluid_limit_mod_list(zone_name, list_mod); +} + +/* + * fluid_zone_gen_import_sfont + * Imports generators from sfzone to gen and range. + * @param gen, pointer on destination generators table. + * @param range, pointer on destination range generators. + * @param sfzone, pointer on soundfont zone generators. + */ +static void +fluid_zone_gen_import_sfont(fluid_gen_t *gen, fluid_zone_range_t *range, fluid_zone_range_t *global_range, SFZone *sfzone) +{ + fluid_list_t *r; + SFGen *sfgen; + + if(global_range != NULL) + { + // All zones are initialized with the default range of 0-127. However, local zones should be superseded by + // the range of their global zone in case that local zone lacks a GEN_KEYRANGE or GEN_VELRANGE + // (see issue #1250). + range->keylo = global_range->keylo; + range->keyhi = global_range->keyhi; + range->vello = global_range->vello; + range->velhi = global_range->velhi; + } + + for(r = sfzone->gen; r != NULL;) + { + sfgen = (SFGen *)fluid_list_get(r); + + switch(sfgen->id) + { + case GEN_KEYRANGE: + range->keylo = sfgen->amount.range.lo; + range->keyhi = sfgen->amount.range.hi; + break; + + case GEN_VELRANGE: + range->vello = sfgen->amount.range.lo; + range->velhi = sfgen->amount.range.hi; + break; + + case GEN_ATTENUATION: + /* EMU8k/10k hardware applies a scale factor to initial attenuation generator values set at + * preset and instrument level */ + gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword * EMU_ATTENUATION_FACTOR; + gen[sfgen->id].flags = GEN_SET; + break; + + case GEN_INSTRUMENT: + case GEN_SAMPLEID: + gen[sfgen->id].val = (fluid_real_t) sfgen->amount.uword; + gen[sfgen->id].flags = GEN_SET; + break; + + default: + gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; + gen[sfgen->id].flags = GEN_SET; + break; + } + + r = fluid_list_next(r); + } +} + +/* + * fluid_zone_mod_source_import_sfont + * Imports source information from sf_source to src and flags. + * @param src, pointer on destination modulator source. + * @param flags, pointer on destination modulator flags. + * @param sf_source, soundfont modulator source. + * @return return TRUE if success, FALSE if source type is unknown. + */ +static int +fluid_zone_mod_source_import_sfont(unsigned char *src, unsigned char *flags, unsigned short sf_source) +{ + int type; + unsigned char flags_dest; /* destination flags */ + + /* sources */ + *src = sf_source & 127; /* index of source, seven-bit value, SF2.01 section 8.2, page 50 */ + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + flags_dest = 0; + + if(sf_source & (1 << 7)) + { + flags_dest |= FLUID_MOD_CC; + } + else + { + flags_dest |= FLUID_MOD_GC; + } + + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if(sf_source & (1 << 8)) + { + flags_dest |= FLUID_MOD_NEGATIVE; + } + else + { + flags_dest |= FLUID_MOD_POSITIVE; + } + + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if(sf_source & (1 << 9)) + { + flags_dest |= FLUID_MOD_BIPOLAR; + } + else + { + flags_dest |= FLUID_MOD_UNIPOLAR; + } + + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type = sf_source >> 10; + type &= 63; /* type is a 6-bit value */ + + if(type == 0) + { + flags_dest |= FLUID_MOD_LINEAR; + } + else if(type == 1) + { + flags_dest |= FLUID_MOD_CONCAVE; + } + else if(type == 2) + { + flags_dest |= FLUID_MOD_CONVEX; + } + else if(type == 3) + { + flags_dest |= FLUID_MOD_SWITCH; + } + else + { + *flags = flags_dest; + /* This shouldn't happen - unknown type! */ + return FALSE; + } + + *flags = flags_dest; + return TRUE; +} + +/* + * fluid_zone_mod_import_sfont + * Imports modulators from sfzone to modulators list mod. + * @param zone_name, zone name. + * @param mod, address of pointer on modulators list to return. + * @param sfzone, pointer on soundfont zone. + * @return FLUID_OK if success, FLUID_FAILED otherwise. + */ +static int +fluid_zone_mod_import_sfont(char *zone_name, fluid_mod_t **mod, SFZone *sfzone) +{ + fluid_list_t *r; + int count; + + /* Import the modulators (only SF2.1 and higher) */ + for(count = 0, r = sfzone->mod; r != NULL; count++) + { + + SFMod *mod_src = (SFMod *)fluid_list_get(r); + fluid_mod_t *mod_dest = new_fluid_mod(); + + if(mod_dest == NULL) + { + return FLUID_FAILED; + } + + mod_dest->next = NULL; /* pointer to next modulator, this is the end of the list now.*/ + + /* *** Amount *** */ + mod_dest->amount = mod_src->amount; + + /* *** Source *** */ + if(!fluid_zone_mod_source_import_sfont(&mod_dest->src1, &mod_dest->flags1, mod_src->src)) + { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount = 0; + } + + /* Note: When primary source input (src1) is set to General Controller 'No Controller', + output will be forced to 0.0 at synthesis time (see fluid_mod_get_value()). + That means that the minimum value of the modulator will be always 0.0. + We need to force amount value to 0 to ensure a correct evaluation of the minimum + value later (see fluid_voice_get_lower_boundary_for_attenuation()). + */ + if(((mod_dest->flags1 & FLUID_MOD_CC) == FLUID_MOD_GC) && + (mod_dest->src1 == FLUID_MOD_NONE)) + { + mod_dest->amount = 0; + } + + /* *** Dest *** */ + mod_dest->dest = mod_src->dest; /* index of controlled generator */ + + /* *** Amount source *** */ + if(!fluid_zone_mod_source_import_sfont(&mod_dest->src2, &mod_dest->flags2, mod_src->amtsrc)) + { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount = 0; + } + /* Note: When secondary source input (src2) is set to General Controller 'No Controller', + output will be forced to +1.0 at synthesis time (see fluid_mod_get_value()). + That means that this source will behave unipolar only. We need to force the + unipolar flag to ensure to ensure a correct evaluation of the minimum + value later (see fluid_voice_get_lower_boundary_for_attenuation()). + */ + if(((mod_dest->flags2 & FLUID_MOD_CC) == FLUID_MOD_GC) && + (mod_dest->src2 == FLUID_MOD_NONE)) + { + mod_dest->flags2 &= ~FLUID_MOD_BIPOLAR; + } + + /* *** Transform *** */ + /* SF2.01 only uses the 'linear' transform (0). + * Deactivate the modulator by setting the amount to 0 in any other case. + */ + if(mod_src->trans != 0) + { + mod_dest->amount = 0; + } + + /* Store the new modulator in the zone The order of modulators + * will make a difference, at least in an instrument context: The + * second modulator overwrites the first one, if they only differ + * in amount. */ + if(count == 0) + { + *mod = mod_dest; + } + else + { + fluid_mod_t *last_mod = *mod; + + /* Find the end of the list */ + while(last_mod->next != NULL) + { + last_mod = last_mod->next; + } + + last_mod->next = mod_dest; + } + + r = fluid_list_next(r); + } /* foreach modulator */ + + /* checks and removes invalid modulators in modulators list*/ + fluid_zone_check_mod(zone_name, mod); + return FLUID_OK; +} + +/* + * fluid_preset_zone_import_sfont + */ +int +fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, fluid_preset_zone_t *global_zone, SFZone *sfzone, fluid_defsfont_t *defsfont, SFData *sfdata) +{ + /* import the generators */ + fluid_zone_gen_import_sfont(zone->gen, &zone->range, global_zone ? &global_zone->range : NULL, sfzone); + + if(zone->gen[GEN_INSTRUMENT].flags == GEN_SET) + { + int inst_idx = (int) zone->gen[GEN_INSTRUMENT].val; + + zone->inst = find_inst_by_idx(defsfont, inst_idx); + + if(zone->inst == NULL) + { + zone->inst = fluid_inst_import_sfont(inst_idx, defsfont, sfdata); + } + + if(zone->inst == NULL) + { + + FLUID_LOG(FLUID_ERR, "Preset zone %s: Invalid instrument reference", + zone->name); + return FLUID_FAILED; + } + + if(fluid_preset_zone_create_voice_zones(zone) == FLUID_FAILED) + { + return FLUID_FAILED; + } + + /* We don't need this generator anymore */ + zone->gen[GEN_INSTRUMENT].flags = GEN_UNUSED; + } + + /* Import the modulators (only SF2.1 and higher) */ + return fluid_zone_mod_import_sfont(zone->name, &zone->mod, sfzone); +} + +/* + * fluid_preset_zone_get_inst + */ +fluid_inst_t * +fluid_preset_zone_get_inst(fluid_preset_zone_t *zone) +{ + return zone->inst; +} + + +/*************************************************************** + * + * INST + */ + +/* + * new_fluid_inst + */ +fluid_inst_t * +new_fluid_inst() +{ + fluid_inst_t *inst = FLUID_NEW(fluid_inst_t); + + if(inst == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + inst->name[0] = 0; + inst->global_zone = NULL; + inst->zone = NULL; + return inst; +} + +/* + * delete_fluid_inst + */ +void +delete_fluid_inst(fluid_inst_t *inst) +{ + fluid_inst_zone_t *zone; + + fluid_return_if_fail(inst != NULL); + + delete_fluid_inst_zone(inst->global_zone); + inst->global_zone = NULL; + + zone = inst->zone; + + while(zone != NULL) + { + inst->zone = zone->next; + delete_fluid_inst_zone(zone); + zone = inst->zone; + } + + FLUID_FREE(inst); +} + +/* + * fluid_inst_set_global_zone + */ +int +fluid_inst_set_global_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone) +{ + inst->global_zone = zone; + return FLUID_OK; +} + +/* + * fluid_inst_import_sfont + */ +fluid_inst_t * +fluid_inst_import_sfont(int inst_idx, fluid_defsfont_t *defsfont, SFData *sfdata) +{ + fluid_list_t *p; + fluid_list_t *inst_list; + fluid_inst_t *inst; + SFZone *sfzone; + SFInst *sfinst; + fluid_inst_zone_t *inst_zone; + char zone_name[256]; + int count; + + for (inst_list = sfdata->inst; inst_list; inst_list = fluid_list_next(inst_list)) + { + sfinst = fluid_list_get(inst_list); + if (sfinst->idx == inst_idx) + { + break; + } + } + if (inst_list == NULL) + { + return NULL; + } + + inst = (fluid_inst_t *) new_fluid_inst(); + + if(inst == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + inst->source_idx = sfinst->idx; + + p = sfinst->zone; + + if(FLUID_STRLEN(sfinst->name) > 0) + { + FLUID_STRCPY(inst->name, sfinst->name); + } + else + { + FLUID_STRCPY(inst->name, "<untitled>"); + } + + count = 0; + + while(p != NULL) + { + + sfzone = (SFZone *)fluid_list_get(p); + /* instrument zone name */ + FLUID_SNPRINTF(zone_name, sizeof(zone_name), "iz:%s/%d", inst->name, count); + + inst_zone = new_fluid_inst_zone(zone_name); + if(inst_zone == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error; + } + + if(fluid_inst_zone_import_sfont(inst_zone, inst->global_zone, sfzone, defsfont, sfdata) != FLUID_OK) + { + FLUID_LOG(FLUID_ERR, "fluid_inst_zone_import_sfont() failed for instrument %s", inst->name); + delete_fluid_inst_zone(inst_zone); + goto error; + } + + if((count == 0) && (fluid_inst_zone_get_sample(inst_zone) == NULL)) + { + fluid_inst_set_global_zone(inst, inst_zone); + + } + else if(fluid_inst_add_zone(inst, inst_zone) != FLUID_OK) + { + FLUID_LOG(FLUID_ERR, "fluid_inst_add_zone() failed for instrument %s", inst->name); + delete_fluid_inst_zone(inst_zone); + goto error; + } + + p = fluid_list_next(p); + count++; + } + + defsfont->inst = fluid_list_append(defsfont->inst, inst); + return inst; + +error: + delete_fluid_inst(inst); + return NULL; +} + +/* + * fluid_inst_add_zone + */ +int +fluid_inst_add_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone) +{ + if(inst->zone == NULL) + { + zone->next = NULL; + inst->zone = zone; + } + else + { + zone->next = inst->zone; + inst->zone = zone; + } + + return FLUID_OK; +} + +/* + * fluid_inst_get_zone + */ +fluid_inst_zone_t * +fluid_inst_get_zone(fluid_inst_t *inst) +{ + return inst->zone; +} + +/* + * fluid_inst_get_global_zone + */ +fluid_inst_zone_t * +fluid_inst_get_global_zone(fluid_inst_t *inst) +{ + return inst->global_zone; +} + +/*************************************************************** + * + * INST_ZONE + */ + +/* + * new_fluid_inst_zone + */ +fluid_inst_zone_t * +new_fluid_inst_zone(char *name) +{ + fluid_inst_zone_t *zone = NULL; + zone = FLUID_NEW(fluid_inst_zone_t); + + if(zone == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + zone->next = NULL; + zone->name = FLUID_STRDUP(name); + + if(zone->name == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(zone); + return NULL; + } + + zone->sample = NULL; + zone->range.keylo = 0; + zone->range.keyhi = 128; + zone->range.vello = 0; + zone->range.velhi = 128; + zone->range.ignore = FALSE; + /* Flag the generators as unused. + * This also sets the generator values to default, but they will be overwritten anyway, if used.*/ + fluid_gen_init(&zone->gen[0], NULL); + zone->mod = NULL; /* list of modulators */ + return zone; +} + +/* + * delete_fluid_inst_zone + */ +void +delete_fluid_inst_zone(fluid_inst_zone_t *zone) +{ + fluid_return_if_fail(zone != NULL); + + delete_fluid_list_mod(zone->mod); + + FLUID_FREE(zone->name); + FLUID_FREE(zone); +} + +/* + * fluid_inst_zone_next + */ +fluid_inst_zone_t * +fluid_inst_zone_next(fluid_inst_zone_t *zone) +{ + return zone->next; +} + +/* + * fluid_inst_zone_import_sfont + */ +int +fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, fluid_inst_zone_t *global_inst_zone, SFZone *sfzone, fluid_defsfont_t *defsfont, SFData *sfdata) +{ + /* import the generators */ + fluid_zone_gen_import_sfont(inst_zone->gen, &inst_zone->range, global_inst_zone ? &global_inst_zone->range : NULL, sfzone); + + /* FIXME */ + /* if (zone->gen[GEN_EXCLUSIVECLASS].flags == GEN_SET) { */ + /* FLUID_LOG(FLUID_DBG, "ExclusiveClass=%d\n", (int) zone->gen[GEN_EXCLUSIVECLASS].val); */ + /* } */ + + if (inst_zone->gen[GEN_SAMPLEID].flags == GEN_SET) + { + fluid_list_t *list; + SFSample *sfsample; + int sample_idx = (int) inst_zone->gen[GEN_SAMPLEID].val; + + /* find the SFSample by index */ + for(list = sfdata->sample; list; list = fluid_list_next(list)) + { + sfsample = fluid_list_get(list); + if (sfsample->idx == sample_idx) + { + break; + } + } + if (list == NULL) + { + FLUID_LOG(FLUID_ERR, "Instrument zone '%s': Invalid sample reference", + inst_zone->name); + return FLUID_FAILED; + } + + inst_zone->sample = sfsample->fluid_sample; + + /* we don't need this generator anymore, mark it as unused */ + inst_zone->gen[GEN_SAMPLEID].flags = GEN_UNUSED; + } + + /* Import the modulators (only SF2.1 and higher) */ + return fluid_zone_mod_import_sfont(inst_zone->name, &inst_zone->mod, sfzone); +} + +/* + * fluid_inst_zone_get_sample + */ +fluid_sample_t * +fluid_inst_zone_get_sample(fluid_inst_zone_t *zone) +{ + return zone->sample; +} + + +int +fluid_zone_inside_range(fluid_zone_range_t *range, int key, int vel) +{ + /* ignoreInstrumentZone is set in mono legato playing */ + int ignore_zone = range->ignore; + + /* Reset the 'ignore' request */ + range->ignore = FALSE; + + return !ignore_zone && ((range->keylo <= key) && + (range->keyhi >= key) && + (range->vello <= vel) && + (range->velhi >= vel)); +} + +/*************************************************************** + * + * SAMPLE + */ + +/* + * fluid_sample_in_rom + */ +int +fluid_sample_in_rom(fluid_sample_t *sample) +{ + return (sample->sampletype & FLUID_SAMPLETYPE_ROM); +} + + +/* + * fluid_sample_import_sfont + */ +int +fluid_sample_import_sfont(fluid_sample_t *sample, SFSample *sfsample, fluid_defsfont_t *defsfont) +{ + FLUID_STRCPY(sample->name, sfsample->name); + + sample->source_start = sfsample->start; + sample->source_end = (sfsample->end > 0) ? sfsample->end - 1 : 0; /* marks last sample, contrary to SF spec. */ + sample->source_loopstart = sfsample->loopstart; + sample->source_loopend = sfsample->loopend; + + sample->start = sample->source_start; + sample->end = sample->source_end; + sample->loopstart = sample->source_loopstart; + sample->loopend = sample->source_loopend; + sample->samplerate = sfsample->samplerate; + sample->origpitch = sfsample->origpitch; + sample->pitchadj = sfsample->pitchadj; + sample->sampletype = sfsample->sampletype; + + if(defsfont->dynamic_samples) + { + sample->notify = dynamic_samples_sample_notify; + } + + if(fluid_sample_validate(sample, defsfont->samplesize) == FLUID_FAILED) + { + return FLUID_FAILED; + } + + return FLUID_OK; +} + +/* Called if a sample is no longer used by a voice. Used by dynamic sample loading + * to unload a sample that is not used by any loaded presets anymore but couldn't + * be unloaded straight away because it was still in use by a voice. */ +static int dynamic_samples_sample_notify(fluid_sample_t *sample, int reason) +{ + if(reason == FLUID_SAMPLE_DONE && sample->preset_count == 0) + { + unload_sample(sample); + } + + return FLUID_OK; +} + +/* Called if a preset has been selected for or unselected from a channel. Used by + * dynamic sample loading to load and unload samples on demand. */ +static int dynamic_samples_preset_notify(fluid_preset_t *preset, int reason, int chan) +{ + fluid_defsfont_t *defsfont; + + if(reason == FLUID_PRESET_SELECTED) + { + FLUID_LOG(FLUID_DBG, "Selected preset '%s' on channel %d", fluid_preset_get_name(preset), chan); + defsfont = fluid_sfont_get_data(preset->sfont); + return load_preset_samples(defsfont, preset); + } + + if(reason == FLUID_PRESET_UNSELECTED) + { + FLUID_LOG(FLUID_DBG, "Deselected preset '%s' from channel %d", fluid_preset_get_name(preset), chan); + defsfont = fluid_sfont_get_data(preset->sfont); + return unload_preset_samples(defsfont, preset); + } + + if(reason == FLUID_PRESET_PIN) + { + defsfont = fluid_sfont_get_data(preset->sfont); + return pin_preset_samples(defsfont, preset); + } + + if(reason == FLUID_PRESET_UNPIN) + { + defsfont = fluid_sfont_get_data(preset->sfont); + return unpin_preset_samples(defsfont, preset); + } + + return FLUID_OK; +} + + +static int pin_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) +{ + fluid_defpreset_t *defpreset; + + defpreset = fluid_preset_get_data(preset); + if (defpreset->pinned) + { + return FLUID_OK; + } + + FLUID_LOG(FLUID_DBG, "Pinning preset '%s'", fluid_preset_get_name(preset)); + + if(load_preset_samples(defsfont, preset) == FLUID_FAILED) + { + return FLUID_FAILED; + } + + defpreset->pinned = TRUE; + + return FLUID_OK; +} + + +static int unpin_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) +{ + fluid_defpreset_t *defpreset; + + defpreset = fluid_preset_get_data(preset); + if (!defpreset->pinned) + { + return FLUID_OK; + } + + FLUID_LOG(FLUID_DBG, "Unpinning preset '%s'", fluid_preset_get_name(preset)); + + if(unload_preset_samples(defsfont, preset) == FLUID_FAILED) + { + return FLUID_FAILED; + } + + defpreset->pinned = FALSE; + + return FLUID_OK; +} + + +/* Walk through all samples used by the passed in preset and make sure that the + * sample data is loaded for each sample. Used by dynamic sample loading. */ +static int load_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) +{ + fluid_defpreset_t *defpreset; + fluid_preset_zone_t *preset_zone; + fluid_inst_t *inst; + fluid_inst_zone_t *inst_zone; + fluid_sample_t *sample; + SFData *sffile = NULL; + + defpreset = fluid_preset_get_data(preset); + preset_zone = fluid_defpreset_get_zone(defpreset); + + while(preset_zone != NULL) + { + inst = fluid_preset_zone_get_inst(preset_zone); + inst_zone = fluid_inst_get_zone(inst); + + while(inst_zone != NULL) + { + sample = fluid_inst_zone_get_sample(inst_zone); + + if((sample != NULL) && (sample->start != sample->end)) + { + sample->preset_count++; + + /* If this is the first time this sample has been selected, + * load the sampledata */ + if(sample->preset_count == 1) + { + /* Make sure we have an open Soundfont file. Do this here + * to avoid having to open the file if no loading is necessary + * for a preset */ + if(sffile == NULL) + { + sffile = fluid_sffile_open(defsfont->filename, defsfont->fcbs); + + if(sffile == NULL) + { + FLUID_LOG(FLUID_ERR, "Unable to open Soundfont file"); + return FLUID_FAILED; + } + } + + if(fluid_defsfont_load_sampledata(defsfont, sffile, sample) == FLUID_OK) + { + fluid_sample_sanitize_loop(sample, (sample->end + 1) * sizeof(short)); + fluid_voice_optimize_sample(sample); + } + else + { + FLUID_LOG(FLUID_ERR, "Unable to load sample '%s', disabling", sample->name); + sample->start = sample->end = 0; + } + } + } + + inst_zone = fluid_inst_zone_next(inst_zone); + } + + preset_zone = fluid_preset_zone_next(preset_zone); + } + + if(sffile != NULL) + { + fluid_sffile_close(sffile); + } + + return FLUID_OK; +} + +/* Walk through all samples used by the passed in preset and unload the sample data + * of each sample that is not used by any selected preset anymore. Used by dynamic + * sample loading. */ +static int unload_preset_samples(fluid_defsfont_t *defsfont, fluid_preset_t *preset) +{ + fluid_defpreset_t *defpreset; + fluid_preset_zone_t *preset_zone; + fluid_inst_t *inst; + fluid_inst_zone_t *inst_zone; + fluid_sample_t *sample; + + defpreset = fluid_preset_get_data(preset); + preset_zone = fluid_defpreset_get_zone(defpreset); + + while(preset_zone != NULL) + { + inst = fluid_preset_zone_get_inst(preset_zone); + inst_zone = fluid_inst_get_zone(inst); + + while(inst_zone != NULL) + { + sample = fluid_inst_zone_get_sample(inst_zone); + + if((sample != NULL) && (sample->preset_count > 0)) + { + sample->preset_count--; + + /* If the sample is not used by any preset or used by a + * sounding voice, unload it from the sample cache. If it's + * still in use by a voice, dynamic_samples_sample_notify will + * take care of unloading the sample as soon as the voice is + * finished with it (but only on the next API call). */ + if(sample->preset_count == 0 && sample->refcount == 0) + { + unload_sample(sample); + } + } + + inst_zone = fluid_inst_zone_next(inst_zone); + } + + preset_zone = fluid_preset_zone_next(preset_zone); + } + + return FLUID_OK; +} + +/* Unload an unused sample from the samplecache */ +static void unload_sample(fluid_sample_t *sample) +{ + fluid_return_if_fail(sample != NULL); + fluid_return_if_fail(sample->data != NULL); + fluid_return_if_fail(sample->preset_count == 0); + fluid_return_if_fail(sample->refcount == 0); + + FLUID_LOG(FLUID_DBG, "Unloading sample '%s'", sample->name); + + if(fluid_samplecache_unload(sample->data) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Unable to unload sample '%s'", sample->name); + } + else + { + sample->data = NULL; + sample->data24 = NULL; + } +} + +static fluid_inst_t *find_inst_by_idx(fluid_defsfont_t *defsfont, int idx) +{ + fluid_list_t *list; + fluid_inst_t *inst; + + for(list = defsfont->inst; list != NULL; list = fluid_list_next(list)) + { + inst = fluid_list_get(list); + + if(inst->source_idx == idx) + { + return inst; + } + } + + return NULL; +} diff --git a/libs/fluidsynth/src/sfloader/fluid_defsfont.h b/libs/fluidsynth/src/sfloader/fluid_defsfont.h new file mode 100644 index 00000000000..d67068955d7 --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_defsfont.h @@ -0,0 +1,232 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * SoundFont loading code borrowed from Smurf SoundFont Editor by Josh Green + * + * 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_DEFSFONT_H +#define _FLUID_DEFSFONT_H + + +#include "fluidsynth.h" +#include "fluidsynth_priv.h" +#include "fluid_sffile.h" +#include "fluid_list.h" +#include "fluid_mod.h" +#include "fluid_gen.h" + + + +/*-----------------------------------sfont.h----------------------------*/ + +#define SF_SAMPMODES_LOOP 1 +#define SF_SAMPMODES_UNROLL 2 + +#define SF_MIN_SAMPLERATE 400 +#define SF_MAX_SAMPLERATE 50000 + +#define SF_MIN_SAMPLE_LENGTH 32 + +/*************************************************************** + * + * FORWARD DECLARATIONS + */ +typedef struct _fluid_defsfont_t fluid_defsfont_t; +typedef struct _fluid_defpreset_t fluid_defpreset_t; +typedef struct _fluid_preset_zone_t fluid_preset_zone_t; +typedef struct _fluid_inst_t fluid_inst_t; +typedef struct _fluid_inst_zone_t fluid_inst_zone_t; /**< Soundfont Instrument Zone */ +typedef struct _fluid_voice_zone_t fluid_voice_zone_t; + +/* defines the velocity and key range for a zone */ +struct _fluid_zone_range_t +{ + int keylo; + int keyhi; + int vello; + int velhi; + unsigned char ignore; /* set to TRUE for legato playing to ignore this range zone */ +}; + +/* Stored on a preset zone to keep track of the inst zones that could start a voice + * and their combined preset zone/instument zone ranges */ +struct _fluid_voice_zone_t +{ + fluid_inst_zone_t *inst_zone; + fluid_zone_range_t range; +}; + +/* + + Public interface + + */ + +fluid_sfont_t *fluid_defsfloader_load(fluid_sfloader_t *loader, const char *filename); + + +int fluid_defsfont_sfont_delete(fluid_sfont_t *sfont); +const char *fluid_defsfont_sfont_get_name(fluid_sfont_t *sfont); +fluid_preset_t *fluid_defsfont_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum); +void fluid_defsfont_sfont_iteration_start(fluid_sfont_t *sfont); +fluid_preset_t *fluid_defsfont_sfont_iteration_next(fluid_sfont_t *sfont); + + +void fluid_defpreset_preset_delete(fluid_preset_t *preset); +const char *fluid_defpreset_preset_get_name(fluid_preset_t *preset); +int fluid_defpreset_preset_get_banknum(fluid_preset_t *preset); +int fluid_defpreset_preset_get_num(fluid_preset_t *preset); +int fluid_defpreset_preset_noteon(fluid_preset_t *preset, fluid_synth_t *synth, int chan, int key, int vel); + +int fluid_zone_inside_range(fluid_zone_range_t *zone_range, int key, int vel); + +/* + * fluid_defsfont_t + */ +struct _fluid_defsfont_t +{ + const fluid_file_callbacks_t *fcbs; /* the file callbacks used to load this Soundfont */ + char *filename; /* the filename of this soundfont */ + unsigned int samplepos; /* the position in the file at which the sample data starts */ + unsigned int samplesize; /* the size of the sample data in bytes */ + short *sampledata; /* the sample data, loaded in ram */ + + unsigned int sample24pos; /* position within sffd of the sm24 chunk, set to zero if no 24 bit sample support */ + unsigned int sample24size; /* length within sffd of the sm24 chunk */ + char *sample24data; /* if not NULL, the least significant byte of the 24bit sample data, loaded in ram */ + + fluid_sfont_t *sfont; /* pointer to parent sfont */ + fluid_list_t *sample; /* the samples in this soundfont */ + fluid_list_t *preset; /* the presets of this soundfont */ + fluid_list_t *inst; /* the instruments of this soundfont */ + int mlock; /* Should we try memlock (avoid swapping)? */ + int dynamic_samples; /* Enables dynamic sample loading if set */ + + fluid_list_t *preset_iter_cur; /* the current preset in the iteration */ +}; + + +fluid_defsfont_t *new_fluid_defsfont(fluid_settings_t *settings); +int delete_fluid_defsfont(fluid_defsfont_t *defsfont); +int fluid_defsfont_load(fluid_defsfont_t *defsfont, const fluid_file_callbacks_t *file_callbacks, const char *file); +const char *fluid_defsfont_get_name(fluid_defsfont_t *defsfont); +fluid_preset_t *fluid_defsfont_get_preset(fluid_defsfont_t *defsfont, int bank, int prenum); +void fluid_defsfont_iteration_start(fluid_defsfont_t *defsfont); +fluid_preset_t *fluid_defsfont_iteration_next(fluid_defsfont_t *defsfont); +int fluid_defsfont_load_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata, fluid_sample_t *sample); +int fluid_defsfont_load_all_sampledata(fluid_defsfont_t *defsfont, SFData *sfdata); + +int fluid_defsfont_add_sample(fluid_defsfont_t *defsfont, fluid_sample_t *sample); +int fluid_defsfont_add_preset(fluid_defsfont_t *defsfont, fluid_defpreset_t *defpreset); + + +/* + * fluid_preset_t + */ +struct _fluid_defpreset_t +{ + fluid_defpreset_t *next; + char name[21]; /* the name of the preset */ + unsigned int bank; /* the bank number */ + unsigned int num; /* the preset number */ + fluid_preset_zone_t *global_zone; /* the global zone of the preset */ + fluid_preset_zone_t *zone; /* the chained list of preset zones */ + int pinned; /* preset samples pinned to sample cache? */ +}; + +fluid_defpreset_t *new_fluid_defpreset(void); +void delete_fluid_defpreset(fluid_defpreset_t *defpreset); +fluid_defpreset_t *fluid_defpreset_next(fluid_defpreset_t *defpreset); +int fluid_defpreset_import_sfont(fluid_defpreset_t *defpreset, SFPreset *sfpreset, fluid_defsfont_t *defsfont, SFData *sfdata); +int fluid_defpreset_set_global_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone); +int fluid_defpreset_add_zone(fluid_defpreset_t *defpreset, fluid_preset_zone_t *zone); +fluid_preset_zone_t *fluid_defpreset_get_zone(fluid_defpreset_t *defpreset); +fluid_preset_zone_t *fluid_defpreset_get_global_zone(fluid_defpreset_t *defpreset); +int fluid_defpreset_get_banknum(fluid_defpreset_t *defpreset); +int fluid_defpreset_get_num(fluid_defpreset_t *defpreset); +const char *fluid_defpreset_get_name(fluid_defpreset_t *defpreset); +int fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int chan, int key, int vel); + +/* + * fluid_preset_zone + */ +struct _fluid_preset_zone_t +{ + fluid_preset_zone_t *next; + char *name; + fluid_inst_t *inst; + fluid_list_t *voice_zone; + fluid_zone_range_t range; + fluid_gen_t gen[GEN_LAST]; + fluid_mod_t *mod; /* List of modulators */ +}; + +fluid_preset_zone_t *new_fluid_preset_zone(char *name); +void delete_fluid_list_mod(fluid_mod_t *mod); +void delete_fluid_preset_zone(fluid_preset_zone_t *zone); +fluid_preset_zone_t *fluid_preset_zone_next(fluid_preset_zone_t *zone); +int fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, fluid_preset_zone_t *global_zone, SFZone *sfzone, fluid_defsfont_t *defssfont, SFData *sfdata); +fluid_inst_t *fluid_preset_zone_get_inst(fluid_preset_zone_t *zone); + +/* + * fluid_inst_t + */ +struct _fluid_inst_t +{ + char name[21]; + int source_idx; /* Index of instrument in source Soundfont */ + fluid_inst_zone_t *global_zone; + fluid_inst_zone_t *zone; +}; + +fluid_inst_t *new_fluid_inst(void); +fluid_inst_t *fluid_inst_import_sfont(int inst_idx, fluid_defsfont_t *defsfont, SFData *sfdata); +void delete_fluid_inst(fluid_inst_t *inst); +int fluid_inst_set_global_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone); +int fluid_inst_add_zone(fluid_inst_t *inst, fluid_inst_zone_t *zone); +fluid_inst_zone_t *fluid_inst_get_zone(fluid_inst_t *inst); +fluid_inst_zone_t *fluid_inst_get_global_zone(fluid_inst_t *inst); + +/* + * fluid_inst_zone_t + */ +struct _fluid_inst_zone_t +{ + fluid_inst_zone_t *next; + char *name; + fluid_sample_t *sample; + fluid_zone_range_t range; + fluid_gen_t gen[GEN_LAST]; + fluid_mod_t *mod; /* List of modulators */ +}; + + +fluid_inst_zone_t *new_fluid_inst_zone(char *name); +void delete_fluid_inst_zone(fluid_inst_zone_t *zone); +fluid_inst_zone_t *fluid_inst_zone_next(fluid_inst_zone_t *zone); +int fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, fluid_inst_zone_t *global_inst_zone, SFZone *sfzone, fluid_defsfont_t *defsfont, SFData *sfdata); +fluid_sample_t *fluid_inst_zone_get_sample(fluid_inst_zone_t *zone); + + +int fluid_sample_import_sfont(fluid_sample_t *sample, SFSample *sfsample, fluid_defsfont_t *defsfont); +int fluid_sample_in_rom(fluid_sample_t *sample); + + +#endif /* _FLUID_SFONT_H */ diff --git a/libs/fluidsynth/src/sfloader/fluid_instpatch.h b/libs/fluidsynth/src/sfloader/fluid_instpatch.h new file mode 100644 index 00000000000..c1838750ab7 --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_instpatch.h @@ -0,0 +1,14 @@ + +#ifndef _FLUID_INSTPATCH_H +#define _FLUID_INSTPATCH_H + +#include "fluid_sfont.h" +#include "fluid_settings.h" + +void fluid_instpatch_init(void); +void fluid_instpatch_deinit(void); +fluid_sfloader_t *new_fluid_instpatch_loader(fluid_settings_t *settings); + +int fluid_instpatch_supports_multi_init(void); + +#endif // _FLUID_INSTPATCH_H diff --git a/libs/fluidsynth/src/sfloader/fluid_samplecache.c b/libs/fluidsynth/src/sfloader/fluid_samplecache.c new file mode 100644 index 00000000000..64e9e9e7091 --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_samplecache.c @@ -0,0 +1,313 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * SoundFont file loading code borrowed from Smurf SoundFont Editor + * Copyright (C) 1999-2001 Josh Green + * + * 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +/* CACHED SAMPLE DATA LOADER + * + * This is a wrapper around fluid_sffile_read_sample_data that attempts to cache the read + * data across all FluidSynth instances in a global (process-wide) list. + */ + +#include "fluid_samplecache.h" +#include "fluid_sys.h" +#include "fluid_list.h" + + +typedef struct _fluid_samplecache_entry_t fluid_samplecache_entry_t; + +struct _fluid_samplecache_entry_t +{ + /* The following members all form the cache key */ + char *filename; + time_t modification_time; + unsigned int sf_samplepos; + unsigned int sf_samplesize; + unsigned int sf_sample24pos; + unsigned int sf_sample24size; + unsigned int sample_start; + unsigned int sample_end; + int sample_type; + /* End of cache key members */ + + short *sample_data; + char *sample_data24; + int sample_count; + + int num_references; + int mlocked; +}; + +static fluid_list_t *samplecache_list = NULL; +static fluid_mutex_t samplecache_mutex = FLUID_MUTEX_INIT; + +static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, unsigned int sample_start, + unsigned int sample_end, int sample_type, time_t mtime); +static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, unsigned int sample_start, + unsigned int sample_end, int sample_type, time_t mtime); +static void delete_samplecache_entry(fluid_samplecache_entry_t *entry); + +static int fluid_get_file_modification_time(char *filename, time_t *modification_time); + + +/* PUBLIC INTERFACE */ + +int fluid_samplecache_load(SFData *sf, + unsigned int sample_start, unsigned int sample_end, int sample_type, + int try_mlock, short **sample_data, char **sample_data24) +{ + fluid_samplecache_entry_t *entry; + int ret; + time_t mtime; + + fluid_mutex_lock(samplecache_mutex); + + if(fluid_get_file_modification_time(sf->fname, &mtime) == FLUID_FAILED) + { + mtime = 0; + } + + entry = get_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); + + if(entry == NULL) + { + fluid_mutex_unlock(samplecache_mutex); + entry = new_samplecache_entry(sf, sample_start, sample_end, sample_type, mtime); + + if(entry == NULL) + { + ret = -1; + goto unlock_exit; + } + + fluid_mutex_lock(samplecache_mutex); + samplecache_list = fluid_list_prepend(samplecache_list, entry); + } + fluid_mutex_unlock(samplecache_mutex); + + if(try_mlock && !entry->mlocked) + { + /* Lock the memory to disable paging. It's okay if this fails. It + * probably means that the user doesn't have the required permission. */ + if(fluid_mlock(entry->sample_data, entry->sample_count * sizeof(short)) == 0) + { + if(entry->sample_data24 != NULL) + { + entry->mlocked = (fluid_mlock(entry->sample_data24, entry->sample_count) == 0); + } + else + { + entry->mlocked = TRUE; + } + + if(!entry->mlocked) + { + fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short)); + FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible."); + } + } + } + + entry->num_references++; + *sample_data = entry->sample_data; + *sample_data24 = entry->sample_data24; + ret = entry->sample_count; + +unlock_exit: + return ret; +} + +int fluid_samplecache_unload(const short *sample_data) +{ + fluid_list_t *entry_list; + fluid_samplecache_entry_t *entry; + int ret; + + fluid_mutex_lock(samplecache_mutex); + + entry_list = samplecache_list; + + while(entry_list) + { + entry = (fluid_samplecache_entry_t *)fluid_list_get(entry_list); + + if(sample_data == entry->sample_data) + { + entry->num_references--; + + if(entry->num_references == 0) + { + if(entry->mlocked) + { + fluid_munlock(entry->sample_data, entry->sample_count * sizeof(short)); + + if(entry->sample_data24 != NULL) + { + fluid_munlock(entry->sample_data24, entry->sample_count); + } + } + + samplecache_list = fluid_list_remove(samplecache_list, entry); + delete_samplecache_entry(entry); + } + + ret = FLUID_OK; + goto unlock_exit; + } + + entry_list = fluid_list_next(entry_list); + } + + FLUID_LOG(FLUID_ERR, "Trying to free sample data not found in cache."); + ret = FLUID_FAILED; + +unlock_exit: + fluid_mutex_unlock(samplecache_mutex); + return ret; +} + + +/* Private functions */ +static fluid_samplecache_entry_t *new_samplecache_entry(SFData *sf, + unsigned int sample_start, + unsigned int sample_end, + int sample_type, + time_t mtime) +{ + fluid_samplecache_entry_t *entry; + + entry = FLUID_NEW(fluid_samplecache_entry_t); + + if(entry == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(entry, 0, sizeof(*entry)); + + entry->filename = FLUID_STRDUP(sf->fname); + + if(entry->filename == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_exit; + } + + entry->sf_samplepos = sf->samplepos; + entry->sf_samplesize = sf->samplesize; + entry->sf_sample24pos = sf->sample24pos; + entry->sf_sample24size = sf->sample24size; + entry->sample_start = sample_start; + entry->sample_end = sample_end; + entry->sample_type = sample_type; + entry->modification_time = mtime; + + entry->sample_count = fluid_sffile_read_sample_data(sf, sample_start, sample_end, sample_type, + &entry->sample_data, &entry->sample_data24); + + if(entry->sample_count < 0) + { + goto error_exit; + } + + return entry; + +error_exit: + delete_samplecache_entry(entry); + return NULL; +} + +static void delete_samplecache_entry(fluid_samplecache_entry_t *entry) +{ + fluid_return_if_fail(entry != NULL); + + FLUID_FREE(entry->filename); + FLUID_FREE(entry->sample_data); + FLUID_FREE(entry->sample_data24); + FLUID_FREE(entry); +} + +static fluid_samplecache_entry_t *get_samplecache_entry(SFData *sf, + unsigned int sample_start, + unsigned int sample_end, + int sample_type, + time_t mtime) +{ + fluid_list_t *entry_list; + fluid_samplecache_entry_t *entry; + + entry_list = samplecache_list; + + while(entry_list) + { + entry = (fluid_samplecache_entry_t *)fluid_list_get(entry_list); + + if((FLUID_STRCMP(sf->fname, entry->filename) == 0) && + (mtime == entry->modification_time) && + (sf->samplepos == entry->sf_samplepos) && + (sf->samplesize == entry->sf_samplesize) && + (sf->sample24pos == entry->sf_sample24pos) && + (sf->sample24size == entry->sf_sample24size) && + (sample_start == entry->sample_start) && + (sample_end == entry->sample_end) && + (sample_type == entry->sample_type)) + { + return entry; + } + + entry_list = fluid_list_next(entry_list); + } + + return NULL; +} + +static int fluid_get_file_modification_time(char *filename, time_t *modification_time) +{ + fluid_stat_buf_t buf; + + if(fluid_stat(filename, &buf)) + { + return FLUID_FAILED; + } + + *modification_time = buf.st_mtime; + return FLUID_OK; +} + + +/* Only used for tests */ +int fluid_samplecache_count_entries(void) +{ + fluid_list_t *entry; + int count = 0; + + fluid_mutex_lock(samplecache_mutex); + + for(entry = samplecache_list; entry != NULL; entry = fluid_list_next(entry)) + { + count++; + } + + fluid_mutex_unlock(samplecache_mutex); + + return count; +} diff --git a/libs/fluidsynth/src/sfloader/fluid_samplecache.h b/libs/fluidsynth/src/sfloader/fluid_samplecache.h new file mode 100644 index 00000000000..de6206ba7de --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_samplecache.h @@ -0,0 +1,37 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_SAMPLECACHE_H +#define _FLUID_SAMPLECACHE_H + +#include "fluid_sfont.h" +#include "fluid_sffile.h" + +int fluid_samplecache_load(SFData *sf, + unsigned int sample_start, unsigned int sample_end, int sample_type, + int try_mlock, short **data, char **data24); + +int fluid_samplecache_unload(const short *sample_data); + +/* Only used for tests */ +int fluid_samplecache_count_entries(void); + +#endif /* _FLUID_SAMPLECACHE_H */ diff --git a/libs/fluidsynth/src/sfloader/fluid_sffile.c b/libs/fluidsynth/src/sfloader/fluid_sffile.c new file mode 100644 index 00000000000..96d06ceae6c --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_sffile.c @@ -0,0 +1,2527 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * SoundFont file loading code borrowed from Smurf SoundFont Editor + * Copyright (C) 1999-2001 Josh Green + * + * 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#include "fluid_sffile.h" +#include "fluid_sfont.h" +#include "fluid_sys.h" + +#if LIBSNDFILE_SUPPORT +#include <sndfile.h> +#endif + +#if LIBINSTPATCH_SUPPORT +#include <libinstpatch/libinstpatch.h> +#endif + +/*=================================sfload.c======================== + Borrowed from Smurf SoundFont Editor by Josh Green + =================================================================*/ + +/* FOURCC definitions */ +#define RIFF_FCC FLUID_FOURCC('R','I','F','F') +#define LIST_FCC FLUID_FOURCC('L','I','S','T') +#define SFBK_FCC FLUID_FOURCC('s','f','b','k') +#define INFO_FCC FLUID_FOURCC('I','N','F','O') +#define SDTA_FCC FLUID_FOURCC('s','d','t','a') +#define PDTA_FCC FLUID_FOURCC('p','d','t','a') /* info/sample/preset */ + +#define IFIL_FCC FLUID_FOURCC('i','f','i','l') +#define ISNG_FCC FLUID_FOURCC('i','s','n','g') +#define INAM_FCC FLUID_FOURCC('I','N','A','M') +#define IROM_FCC FLUID_FOURCC('i','r','o','m') /* info ids (1st byte of info strings) */ +#define IVER_FCC FLUID_FOURCC('i','v','e','r') +#define ICRD_FCC FLUID_FOURCC('I','C','R','D') +#define IENG_FCC FLUID_FOURCC('I','E','N','G') +#define IPRD_FCC FLUID_FOURCC('I','P','R','D') /* more info ids */ +#define ICOP_FCC FLUID_FOURCC('I','C','O','P') +#define ICMT_FCC FLUID_FOURCC('I','C','M','T') +#define ISFT_FCC FLUID_FOURCC('I','S','F','T') /* and yet more info ids */ + +#define SNAM_FCC FLUID_FOURCC('s','n','a','m') +#define SMPL_FCC FLUID_FOURCC('s','m','p','l') /* sample ids */ +#define PHDR_FCC FLUID_FOURCC('p','h','d','r') +#define PBAG_FCC FLUID_FOURCC('p','b','a','g') +#define PMOD_FCC FLUID_FOURCC('p','m','o','d') +#define PGEN_FCC FLUID_FOURCC('p','g','e','n') /* preset ids */ +#define IHDR_FCC FLUID_FOURCC('i','n','s','t') +#define IBAG_FCC FLUID_FOURCC('i','b','a','g') +#define IMOD_FCC FLUID_FOURCC('i','m','o','d') +#define IGEN_FCC FLUID_FOURCC('i','g','e','n') /* instrument ids */ +#define SHDR_FCC FLUID_FOURCC('s','h','d','r') /* sample info */ +#define SM24_FCC FLUID_FOURCC('s','m','2','4') + +/* Set when the FCC code is unknown */ +#define UNKN_ID FLUID_N_ELEMENTS(idlist) + +/* + * This declares a uint32_t array containing the SF2 chunk identifiers. + */ +static const uint32_t idlist[] = +{ + RIFF_FCC, + LIST_FCC, + SFBK_FCC, + INFO_FCC, + SDTA_FCC, + PDTA_FCC, + + IFIL_FCC, + ISNG_FCC, + INAM_FCC, + IROM_FCC, + IVER_FCC, + ICRD_FCC, + IENG_FCC, + IPRD_FCC, + ICOP_FCC, + ICMT_FCC, + ISFT_FCC, + + SNAM_FCC, + SMPL_FCC, + PHDR_FCC, + PBAG_FCC, + PMOD_FCC, + PGEN_FCC, + IHDR_FCC, + IBAG_FCC, + IMOD_FCC, + IGEN_FCC, + SHDR_FCC, + SM24_FCC +}; + +static const unsigned short invalid_inst_gen[] = +{ + GEN_UNUSED1, + GEN_UNUSED2, + GEN_UNUSED3, + GEN_UNUSED4, + GEN_RESERVED1, + GEN_RESERVED2, + GEN_RESERVED3, + GEN_INSTRUMENT, +}; + +static const unsigned short invalid_preset_gen[] = +{ + GEN_STARTADDROFS, + GEN_ENDADDROFS, + GEN_STARTLOOPADDROFS, + GEN_ENDLOOPADDROFS, + GEN_STARTADDRCOARSEOFS, + GEN_ENDADDRCOARSEOFS, + GEN_STARTLOOPADDRCOARSEOFS, + GEN_KEYNUM, + GEN_VELOCITY, + GEN_ENDLOOPADDRCOARSEOFS, + GEN_SAMPLEMODE, + GEN_EXCLUSIVECLASS, + GEN_OVERRIDEROOTKEY, + GEN_SAMPLEID, +}; + + +/* sfont file chunk sizes */ +#define SF_PHDR_SIZE (38) +#define SF_BAG_SIZE (4) +#define SF_MOD_SIZE (10) +#define SF_GEN_SIZE (4) +#define SF_IHDR_SIZE (22) +#define SF_SHDR_SIZE (46) + + +#define READCHUNK(sf, var) \ + do \ + { \ + if (sf->fcbs->fread(var, 8, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + ((SFChunk *)(var))->size = FLUID_LE32TOH(((SFChunk *)(var))->size); \ + } while (0) + +#define READD(sf, var) \ + do \ + { \ + uint32_t _temp; \ + if (sf->fcbs->fread(&_temp, 4, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + var = FLUID_LE32TOH(_temp); \ + } while (0) + +#define READW(sf, var) \ + do \ + { \ + uint16_t _temp; \ + if (sf->fcbs->fread(&_temp, 2, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + var = FLUID_LE16TOH(_temp); \ + } while (0) + +#define READID(sf, var) \ + do \ + { \ + if (sf->fcbs->fread(var, 4, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + } while (0) + +#define READSTR(sf, var) \ + do \ + { \ + if (sf->fcbs->fread(var, 20, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + (*var)[20] = '\0'; \ + } while (0) + +#define READB(sf, var) \ + do \ + { \ + if (sf->fcbs->fread(&var, 1, sf->sffd) == FLUID_FAILED) \ + return FALSE; \ + } while (0) + +#define FSKIP(sf, size) \ + do \ + { \ + if (sf->fcbs->fseek(sf->sffd, size, SEEK_CUR) == FLUID_FAILED) \ + return FALSE; \ + } while (0) + +#define FSKIPW(sf) \ + do \ + { \ + if (sf->fcbs->fseek(sf->sffd, 2, SEEK_CUR) == FLUID_FAILED) \ + return FALSE; \ + } while (0) + +/* removes and advances a fluid_list_t pointer */ +#define SLADVREM(list, item) \ + do \ + { \ + fluid_list_t *_temp = item; \ + item = fluid_list_next(item); \ + list = fluid_list_remove_link(list, _temp); \ + delete1_fluid_list(_temp); \ + } while (0) + + +static int load_header(SFData *sf); +static int load_body(SFData *sf); +static int process_info(SFData *sf, int size); +static int process_sdta(SFData *sf, unsigned int size); +static int process_pdta(SFData *sf, int size); +static int load_phdr(SFData *sf, unsigned int size); +static int load_pbag(SFData *sf, int size); +static int load_pmod(SFData *sf, int size); +static int load_ihdr(SFData *sf, unsigned int size); +static int load_ibag(SFData *sf, int size); +static int load_imod(SFData *sf, int size); +static int load_shdr(SFData *sf, unsigned int size); + +static int chunkid(uint32_t id); +static int read_listchunk(SFData *sf, SFChunk *chunk); +static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size); +static int preset_compare_func(const void *a, const void *b); +static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist); +static int valid_inst_genid(unsigned short genid); +static int valid_preset_genid(unsigned short genid); + +static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data); +static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24); + +/** + * Check if a file is a SoundFont file. + * + * @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. + */ +int fluid_is_soundfont(const char *filename) +{ + FILE *fp; + uint32_t fcc; + int retcode = FALSE; + const char* err_msg; + + do + { + if((fp = fluid_file_open(filename, &err_msg)) == NULL) + { + FLUID_LOG(FLUID_ERR, "fluid_is_soundfont(): fopen() failed: '%s'", err_msg); + return retcode; + } + + if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) + { + FLUID_LOG(FLUID_ERR, "fluid_is_soundfont(): failed to read RIFF chunk id."); + break; + } + + if(fcc != RIFF_FCC) + { + FLUID_LOG(FLUID_ERR, "fluid_is_soundfont(): expected RIFF chunk id '0x%04X' but got '0x%04X'.", (unsigned int) RIFF_FCC, (unsigned int)fcc); + break; + } + + if(FLUID_FSEEK(fp, 4, SEEK_CUR)) + { + FLUID_LOG(FLUID_ERR, "fluid_is_soundfont(): cannot seek +4 bytes."); + break; + } + + if(FLUID_FREAD(&fcc, sizeof(fcc), 1, fp) != 1) + { + FLUID_LOG(FLUID_ERR, "fluid_is_soundfont(): failed to read SFBK chunk id."); + break; + } + + retcode = (fcc == SFBK_FCC); + if(retcode) + { + break; // seems to be SF2, stop here + } +#ifdef LIBINSTPATCH_SUPPORT + else + { + IpatchFileHandle *fhandle = ipatch_file_identify_open(filename, NULL); + if(fhandle != NULL) + { + retcode = (ipatch_file_identify(fhandle->file, NULL) == IPATCH_TYPE_DLS_FILE); + ipatch_file_close(fhandle); + } + } +#endif + } + while(0); + + FLUID_FCLOSE(fp); + + return retcode; +} + +/* + * Open a SoundFont file and parse it's contents into a SFData structure. + * + * @param fname filename + * @param fcbs file callback structure + * @return the partially parsed SoundFont as SFData structure or NULL on error + */ +SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs) +{ + SFData *sf; + fluid_long_long_t fsize = 0; + + if(!(sf = FLUID_NEW(SFData))) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(sf, 0, sizeof(SFData)); + + fluid_rec_mutex_init(sf->mtx); + sf->fcbs = fcbs; + + if((sf->sffd = fcbs->fopen(fname)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Unable to open file '%s'", fname); + goto error_exit; + } + + sf->fname = FLUID_STRDUP(fname); + + if(sf->fname == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_exit; + } + + /* get size of file by seeking to end */ + if(fcbs->fseek(sf->sffd, 0L, SEEK_END) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Seek to end of file failed"); + goto error_exit; + } + + if((fsize = fcbs->ftell(sf->sffd)) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Get end of file position failed"); + goto error_exit; + } + + sf->filesize = fsize; + + if(fcbs->fseek(sf->sffd, 0, SEEK_SET) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Rewind to start of file failed"); + goto error_exit; + } + + if(!load_header(sf)) + { + goto error_exit; + } + + return sf; + +error_exit: + fluid_sffile_close(sf); + return NULL; +} + +/* + * Parse all preset information from the soundfont + * + * @return FLUID_OK on success, otherwise FLUID_FAILED + */ +int fluid_sffile_parse_presets(SFData *sf) +{ + if(!load_body(sf)) + { + return FLUID_FAILED; + } + + return FLUID_OK; +} + +/* Load sample data from the soundfont file + * + * This function will always return the sample data in WAV format. If the sample_type specifies an + * Ogg Vorbis compressed sample, it will be decompressed automatically before returning. + * + * @param sf SFData instance + * @param sample_start index of first sample point in Soundfont sample chunk + * @param sample_end index of last sample point in Soundfont sample chunk + * @param sample_type type of the sample in Soundfont + * @param data pointer to sample data pointer, will point to loaded sample data on success + * @param data24 pointer to 24-bit sample data pointer if 24-bit data present, will point to loaded + * 24-bit sample data on success or NULL if no 24-bit data is present in file + * + * @return The number of sample words in returned buffers or -1 on failure + */ +int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end, + int sample_type, short **data, char **data24) +{ + int num_samples; + + if(sample_type & FLUID_SAMPLETYPE_OGG_VORBIS) + { + num_samples = fluid_sffile_read_vorbis(sf, sample_start, sample_end, data); + } + else + { + num_samples = fluid_sffile_read_wav(sf, sample_start, sample_end, data, data24); + } + + return num_samples; +} + +/* + * Close a SoundFont file and free the SFData structure. + * + * @param sf pointer to SFData structure + * @param fcbs file callback structure + */ +void fluid_sffile_close(SFData *sf) +{ + fluid_list_t *entry; + SFPreset *preset; + SFInst *inst; + + fluid_rec_mutex_destroy(sf->mtx); + if(sf->sffd) + { + sf->fcbs->fclose(sf->sffd); + } + + FLUID_FREE(sf->fname); + + entry = sf->info; + + while(entry) + { + FLUID_FREE(fluid_list_get(entry)); + entry = fluid_list_next(entry); + } + + delete_fluid_list(sf->info); + + entry = sf->preset; + + while(entry) + { + preset = (SFPreset *)fluid_list_get(entry); + delete_preset(preset); + entry = fluid_list_next(entry); + } + + delete_fluid_list(sf->preset); + + entry = sf->inst; + + while(entry) + { + inst = (SFInst *)fluid_list_get(entry); + delete_inst(inst); + entry = fluid_list_next(entry); + } + + delete_fluid_list(sf->inst); + + entry = sf->sample; + + while(entry) + { + FLUID_FREE(fluid_list_get(entry)); + entry = fluid_list_next(entry); + } + + delete_fluid_list(sf->sample); + + FLUID_FREE(sf); +} + + +/* + * Private functions + */ + +/* sound font file load functions */ +static int chunkid(uint32_t id) +{ + unsigned int i; + + for(i = 0; i < FLUID_N_ELEMENTS(idlist); i++) + { + if(idlist[i] == id) + { + break; + } + } + + /* Return chunk id or UNKN_ID if not found */ + return i; +} + +static int load_header(SFData *sf) +{ + SFChunk chunk; + + READCHUNK(sf, &chunk); /* load RIFF chunk */ + + if(chunk.id != RIFF_FCC) + { + /* error if not RIFF */ + FLUID_LOG(FLUID_ERR, "Not a RIFF file"); + return FALSE; + } + + READID(sf, &chunk.id); /* load file ID */ + + if(chunk.id != SFBK_FCC) + { + /* error if not SFBK_ID */ + FLUID_LOG(FLUID_ERR, "Not a SoundFont file"); + return FALSE; + } + + if(chunk.size != sf->filesize - 8) + { + FLUID_LOG(FLUID_ERR, "SoundFont file size mismatch"); + return FALSE; + } + + /* Process INFO block */ + if(!read_listchunk(sf, &chunk)) + { + return FALSE; + } + + if(chunk.id != INFO_FCC) + { + FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting INFO chunk"); + return FALSE; + } + + if(!process_info(sf, chunk.size)) + { + return FALSE; + } + + /* Process sample chunk */ + if(!read_listchunk(sf, &chunk)) + { + return FALSE; + } + + if(chunk.id != SDTA_FCC) + { + FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting SAMPLE chunk"); + return FALSE; + } + + if(!process_sdta(sf, chunk.size)) + { + return FALSE; + } + + /* process HYDRA chunk */ + if(!read_listchunk(sf, &chunk)) + { + return FALSE; + } + + if(chunk.id != PDTA_FCC) + { + FLUID_LOG(FLUID_ERR, "Invalid ID found when expecting HYDRA chunk"); + return FALSE; + } + + sf->hydrapos = sf->fcbs->ftell(sf->sffd); + sf->hydrasize = chunk.size; + + return TRUE; +} + +static int load_body(SFData *sf) +{ + if(sf->fcbs->fseek(sf->sffd, sf->hydrapos, SEEK_SET) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Failed to seek to HYDRA position"); + return FALSE; + } + + if(!process_pdta(sf, sf->hydrasize)) + { + return FALSE; + } + + /* sort preset list by bank, preset # */ + sf->preset = fluid_list_sort(sf->preset, preset_compare_func); + + return TRUE; +} + +static int read_listchunk(SFData *sf, SFChunk *chunk) +{ + READCHUNK(sf, chunk); /* read list chunk */ + + if(chunk->id != LIST_FCC) /* error if ! list chunk */ + { + FLUID_LOG(FLUID_ERR, "Invalid chunk id in level 0 parse"); + return FALSE; + } + + READID(sf, &chunk->id); /* read id string */ + chunk->size -= 4; + return TRUE; +} + +static int process_info(SFData *sf, int size) +{ + SFChunk chunk; + union + { + char *chr; + uint32_t *fcc; + } item; + unsigned short ver; + + while(size > 0) + { + READCHUNK(sf, &chunk); + size -= 8; + + if(chunk.id == IFIL_FCC) + { + /* sound font version chunk? */ + if(chunk.size != 4) + { + FLUID_LOG(FLUID_ERR, "Sound font version info chunk has invalid size"); + return FALSE; + } + + READW(sf, ver); + sf->version.major = ver; + READW(sf, ver); + sf->version.minor = ver; + + if(sf->version.major < 2) + { + FLUID_LOG(FLUID_ERR, "Sound font version is %d.%d which is not" + " supported, convert to version 2.0x", + sf->version.major, sf->version.minor); + return FALSE; + } + + if(sf->version.major == 3) + { +#if !LIBSNDFILE_SUPPORT + FLUID_LOG(FLUID_WARN, + "Sound font version is %d.%d but fluidsynth was compiled without" + " support for (v3.x)", + sf->version.major, sf->version.minor); + return FALSE; +#endif + } + else if(sf->version.major > 2) + { + FLUID_LOG(FLUID_WARN, + "Sound font version is %d.%d which is newer than" + " what this version of fluidsynth was designed for (v2.0x)", + sf->version.major, sf->version.minor); + return FALSE; + } + } + else if(chunk.id == IVER_FCC) + { + /* ROM version chunk? */ + if(chunk.size != 4) + { + FLUID_LOG(FLUID_ERR, "ROM version info chunk has invalid size"); + return FALSE; + } + + READW(sf, ver); + sf->romver.major = ver; + READW(sf, ver); + sf->romver.minor = ver; + } + else if(chunkid(chunk.id) != UNKN_ID) + { + if((chunk.id != ICMT_FCC && chunk.size > 256) || (chunk.size > 65536) || (chunk.size % 2)) + { + FLUID_LOG(FLUID_ERR, "INFO sub chunk %.4s has invalid chunk size of %d bytes", + (char*)&chunk.id, chunk.size); + return FALSE; + } + + /* alloc for chunk fcc and da chunk */ + if(!(item.fcc = FLUID_MALLOC(chunk.size + sizeof(uint32_t) + 1))) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + /* attach to INFO list, fluid_sffile_close will cleanup if FAIL occurs */ + sf->info = fluid_list_append(sf->info, item.fcc); + + /* save chunk fcc and update pointer to data value */ + *item.fcc++ = chunk.id; + + if(sf->fcbs->fread(item.chr, chunk.size, sf->sffd) == FLUID_FAILED) + { + return FALSE; + } + + /* force terminate info item */ + item.chr[chunk.size] = '\0'; + } + else + { + FLUID_LOG(FLUID_ERR, "Invalid chunk id in INFO chunk"); + return FALSE; + } + + size -= chunk.size; + } + + if(size < 0) + { + FLUID_LOG(FLUID_ERR, "INFO chunk size mismatch"); + return FALSE; + } + + return TRUE; +} + +static int process_sdta(SFData *sf, unsigned int size) +{ + SFChunk chunk; + + if(size == 0) + { + return TRUE; /* no sample data? */ + } + + /* read sub chunk */ + READCHUNK(sf, &chunk); + size -= 8; + + if(chunk.id != SMPL_FCC) + { + FLUID_LOG(FLUID_ERR, "Expected SMPL chunk found invalid id instead"); + return FALSE; + } + + /* SDTA chunk may also contain sm24 chunk for 24 bit samples + * (not yet supported), only an error if SMPL chunk size is + * greater than SDTA. */ + if(chunk.size > size) + { + FLUID_LOG(FLUID_ERR, "SDTA chunk size mismatch"); + return FALSE; + } + + /* sample data follows */ + sf->samplepos = sf->fcbs->ftell(sf->sffd); + + /* used to check validity of sample headers */ + sf->samplesize = chunk.size; + + FSKIP(sf, chunk.size); + size -= chunk.size; + + if(sf->version.major >= 2 && sf->version.minor >= 4) + { + /* any chance to find another chunk here? */ + if(size > 8) + { + /* read sub chunk */ + READCHUNK(sf, &chunk); + size -= 8; + + if(chunk.id == SM24_FCC) + { + int sm24size, sdtahalfsize; + + FLUID_LOG(FLUID_DBG, "Found SM24 chunk"); + + if(chunk.size > size) + { + FLUID_LOG(FLUID_WARN, "SM24 exceeds SDTA chunk, ignoring SM24"); + goto ret; // no error + } + + sdtahalfsize = sf->samplesize / 2; + /* + 1 byte in the case that half the size of smpl chunk is an odd value */ + sdtahalfsize += sdtahalfsize % 2; + sm24size = chunk.size; + + if(sdtahalfsize != sm24size) + { + FLUID_LOG(FLUID_WARN, "SM24 not equal to half the size of SMPL chunk (0x%X != " + "0x%X), ignoring SM24", + sm24size, sdtahalfsize); + goto ret; // no error + } + + /* sample data24 follows */ + sf->sample24pos = sf->fcbs->ftell(sf->sffd); + sf->sample24size = sm24size; + } + } + } + +ret: + FSKIP(sf, size); + + return TRUE; +} + +static int pdtahelper(SFData *sf, unsigned int expid, unsigned int reclen, SFChunk *chunk, int *size) +{ + READCHUNK(sf, chunk); + *size -= 8; + + if(chunk->id != expid) + { + FLUID_LOG(FLUID_ERR, "Expected PDTA sub-chunk '%.4s' found invalid id instead", (char*)&expid); + return FALSE; + } + + if(chunk->size % reclen) /* valid chunk size? */ + { + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size is not a multiple of %d bytes", (char*)&expid, reclen); + return FALSE; + } + + if((*size -= chunk->size) < 0) + { + FLUID_LOG(FLUID_ERR, "'%.4s' chunk size exceeds remaining PDTA chunk size", (char*)&expid); + return FALSE; + } + + return TRUE; +} + +static int process_pdta(SFData *sf, int size) +{ + SFChunk chunk; + + if(!pdtahelper(sf, PHDR_FCC, SF_PHDR_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_phdr(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, PBAG_FCC, SF_BAG_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_pbag(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, PMOD_FCC, SF_MOD_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_pmod(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, PGEN_FCC, SF_GEN_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_pgen(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, IHDR_FCC, SF_IHDR_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_ihdr(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, IBAG_FCC, SF_BAG_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_ibag(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, IMOD_FCC, SF_MOD_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_imod(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, IGEN_FCC, SF_GEN_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_igen(sf, chunk.size)) + { + return FALSE; + } + + if(!pdtahelper(sf, SHDR_FCC, SF_SHDR_SIZE, &chunk, &size)) + { + return FALSE; + } + + if(!load_shdr(sf, chunk.size)) + { + return FALSE; + } + + return TRUE; +} + +/* preset header loader */ +static int load_phdr(SFData *sf, unsigned int size) +{ + unsigned int i; + int i2; + SFPreset *preset, *prev_preset = NULL; + unsigned short pbag_idx, prev_pbag_idx = 0; + + if(size % SF_PHDR_SIZE || size == 0) + { + FLUID_LOG(FLUID_ERR, "Preset header chunk size is invalid"); + return FALSE; + } + + i = size / SF_PHDR_SIZE - 1; + + if(i == 0) + { + /* at least one preset + term record */ + FLUID_LOG(FLUID_WARN, "File contains no presets"); + FSKIP(sf, SF_PHDR_SIZE); + return TRUE; + } + + for(; i > 0; i--) + { + /* load all preset headers */ + if((preset = FLUID_NEW(SFPreset)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + sf->preset = fluid_list_append(sf->preset, preset); + preset->zone = NULL; /* In case of failure, fluid_sffile_close can cleanup */ + READSTR(sf, &preset->name); /* possible read failure ^ */ + READW(sf, preset->prenum); + READW(sf, preset->bank); + READW(sf, pbag_idx); + FSKIP(sf, 4); /* library ignored */ + FSKIP(sf, 4); /* genre ignored */ + FSKIP(sf, 4); /* morphology ignored */ + + if(prev_preset) + { + /* not first preset? */ + if(pbag_idx < prev_pbag_idx) + { + FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic"); + return FALSE; + } + + i2 = pbag_idx - prev_pbag_idx; + + while(i2--) + { + prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL); + } + } + else if(pbag_idx > 0) /* 1st preset, warn if ofs >0 */ + { + FLUID_LOG(FLUID_WARN, "%d preset zones not referenced, discarding", pbag_idx); + } + + prev_preset = preset; /* update preset ptr */ + prev_pbag_idx = pbag_idx; + } + + FSKIP(sf, 24); + READW(sf, pbag_idx); /* Read terminal generator index */ + FSKIP(sf, 12); + + if(pbag_idx < prev_pbag_idx) + { + FLUID_LOG(FLUID_ERR, "Preset header indices not monotonic"); + return FALSE; + } + + i2 = pbag_idx - prev_pbag_idx; + + while(i2--) + { + prev_preset->zone = fluid_list_prepend(prev_preset->zone, NULL); + } + + return TRUE; +} + +/* preset bag loader */ +static int load_pbag(SFData *sf, int size) +{ + fluid_list_t *preset_list; + fluid_list_t *zone_list; + SFZone *z, *pz = NULL; + unsigned short genndx, modndx; + unsigned short pgenndx = 0, pmodndx = 0; + unsigned short i; + + if(size % SF_BAG_SIZE || size == 0) /* size is multiple of SF_BAG_SIZE? */ + { + FLUID_LOG(FLUID_ERR, "Preset bag chunk size is invalid"); + return FALSE; + } + + preset_list = sf->preset; + + /* traverse through presets */ + while(preset_list) + { + zone_list = ((SFPreset *)(preset_list->data))->zone; + + /* traverse preset's zones */ + while(zone_list) + { + if((size -= SF_BAG_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch"); + return FALSE; + } + + if((z = FLUID_NEW(SFZone)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + zone_list->data = z; + z->gen = NULL; /* Init gen and mod before possible failure, */ + z->mod = NULL; /* to ensure proper cleanup (fluid_sffile_close) */ + READW(sf, genndx); /* possible read failure ^ */ + READW(sf, modndx); + + if(pz) + { + /* if not first zone */ + if(genndx < pgenndx) + { + FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic"); + return FALSE; + } + + if(modndx < pmodndx) + { + FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic"); + return FALSE; + } + + i = genndx - pgenndx; + + while(i--) + { + pz->gen = fluid_list_prepend(pz->gen, NULL); + } + + i = modndx - pmodndx; + + while(i--) + { + pz->mod = fluid_list_prepend(pz->mod, NULL); + } + } + + pz = z; /* update previous zone ptr */ + pgenndx = genndx; /* update previous zone gen index */ + pmodndx = modndx; /* update previous zone mod index */ + zone_list = fluid_list_next(zone_list); + } + + preset_list = fluid_list_next(preset_list); + } + + size -= SF_BAG_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "Preset bag chunk size mismatch"); + return FALSE; + } + + READW(sf, genndx); + READW(sf, modndx); + + if(!pz) + { + if(genndx > 0) + { + FLUID_LOG(FLUID_WARN, "No preset generators and terminal index not 0"); + } + + if(modndx > 0) + { + FLUID_LOG(FLUID_WARN, "No preset modulators and terminal index not 0"); + } + + return TRUE; + } + + if(genndx < pgenndx) + { + FLUID_LOG(FLUID_ERR, "Preset bag generator indices not monotonic"); + return FALSE; + } + + if(modndx < pmodndx) + { + FLUID_LOG(FLUID_ERR, "Preset bag modulator indices not monotonic"); + return FALSE; + } + + i = genndx - pgenndx; + + while(i--) + { + pz->gen = fluid_list_prepend(pz->gen, NULL); + } + + i = modndx - pmodndx; + + while(i--) + { + pz->mod = fluid_list_prepend(pz->mod, NULL); + } + + return TRUE; +} + +/* preset modulator loader */ +static int load_pmod(SFData *sf, int size) +{ + fluid_list_t *preset_list; + fluid_list_t *zone_list; + fluid_list_t *mod_list; + SFMod *m; + + preset_list = sf->preset; + + while(preset_list) + { + /* traverse through all presets */ + zone_list = ((SFPreset *)(preset_list->data))->zone; + + while(zone_list) + { + /* traverse this preset's zones */ + mod_list = ((SFZone *)(zone_list->data))->mod; + + while(mod_list) + { + /* load zone's modulators */ + if((size -= SF_MOD_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch"); + return FALSE; + } + + if((m = FLUID_NEW(SFMod)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + mod_list->data = m; + READW(sf, m->src); + READW(sf, m->dest); + READW(sf, m->amount); + READW(sf, m->amtsrc); + READW(sf, m->trans); + mod_list = fluid_list_next(mod_list); + } + + zone_list = fluid_list_next(zone_list); + } + + preset_list = fluid_list_next(preset_list); + } + + /* + If there isn't even a terminal record + Hmmm, the specs say there should be one, but.. + */ + if(size == 0) + { + return TRUE; + } + + size -= SF_MOD_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "Preset modulator chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_MOD_SIZE); /* terminal mod */ + + return TRUE; +} + +/* ------------------------------------------------------------------- + * preset generator loader + * generator (per preset) loading rules: + * Zones with no generators or modulators shall be annihilated + * Global zone must be 1st zone, discard additional ones (instrumentless zones) + * + * generator (per zone) loading rules (in order of decreasing precedence): + * KeyRange is 1st in list (if exists), else discard + * if a VelRange exists only preceded by a KeyRange, else discard + * if a generator follows an instrument discard it + * if a duplicate generator exists replace previous one + * ------------------------------------------------------------------- */ +int load_pgen(SFData *sf, int size) +{ + fluid_list_t *dup; + fluid_list_t *preset_list; + fluid_list_t *zone_list; + fluid_list_t *gen_list; + SFZone *zone; + SFGen *g; + SFPreset *preset; + SFGenAmount genval; + unsigned short genid; + int level, skip, drop, discarded; + + preset_list = sf->preset; + + while(preset_list) + { + preset = fluid_list_get(preset_list); + + /* traverse through all presets */ + discarded = FALSE; + zone_list = preset->zone; + + /* traverse preset's zones */ + while(zone_list) + { + zone = fluid_list_get(zone_list); + level = 0; + gen_list = zone->gen; + + while(gen_list) + { + /* load zone's generators */ + dup = NULL; + skip = FALSE; + drop = FALSE; + + if((size -= SF_GEN_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); + return FALSE; + } + + READW(sf, genid); + + if(genid == GEN_KEYRANGE) + { + /* nothing precedes */ + if(level == 0) + { + level = 1; + READB(sf, genval.range.lo); + READB(sf, genval.range.hi); + } + else + { + skip = TRUE; + } + } + else if(genid == GEN_VELRANGE) + { + /* only KeyRange precedes */ + if(level <= 1) + { + level = 2; + READB(sf, genval.range.lo); + READB(sf, genval.range.hi); + } + else + { + skip = TRUE; + } + } + else if(genid == GEN_INSTRUMENT) + { + /* inst is last gen */ + level = 3; + READW(sf, genval.uword); + } + else + { + level = 2; + + if(valid_preset_genid(genid)) + { + /* generator valid? */ + READW(sf, genval.sword); + dup = find_gen_by_id(genid, zone->gen); + } + else + { + skip = TRUE; + } + } + + if(!skip) + { + if(!dup) + { + /* if gen ! dup alloc new */ + if((g = FLUID_NEW(SFGen)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + gen_list->data = g; + g->id = genid; + } + else + { + g = (SFGen *)(dup->data); /* ptr to orig gen */ + drop = TRUE; + } + + g->amount = genval; + } + else + { + /* Skip this generator */ + discarded = TRUE; + drop = TRUE; + FSKIPW(sf); + } + + if(!drop) + { + gen_list = fluid_list_next(gen_list); /* next gen */ + } + else + { + SLADVREM(zone->gen, gen_list); /* drop place holder */ + } + + /* GEN_INSTRUMENT should be the last generator */ + if (level == 3) + { + break; + } + + } /* generator loop */ + + /* Anything below level 3 means it's a global zone. The global zone + * should always be the first zone in the list, so discard any + * other global zones we encounter */ + if(level < 3 && (zone_list != preset->zone)) + { + /* advance to next zone before deleting the current list element */ + zone_list = fluid_list_next(zone_list); + + FLUID_LOG(FLUID_WARN, "Preset '%s': Discarding invalid global zone", + preset->name); + preset->zone = fluid_list_remove(preset->zone, zone); + delete_zone(zone); + + /* we have already advanced the zone_list pointer, so continue with next zone */ + continue; + } + + /* All remaining generators are invalid and should be discarded + * (because they come after an instrument generator) */ + while(gen_list) + { + discarded = TRUE; + + if((size -= SF_GEN_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_GEN_SIZE); + SLADVREM(zone->gen, gen_list); + } + + zone_list = fluid_list_next(zone_list); + } + + if(discarded) + { + FLUID_LOG(FLUID_WARN, + "Preset '%s': Some invalid generators were discarded", + preset->name); + } + + preset_list = fluid_list_next(preset_list); + } + + /* in case there isn't a terminal record */ + if(size == 0) + { + return TRUE; + } + + size -= SF_GEN_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "Preset generator chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_GEN_SIZE); /* terminal gen */ + + return TRUE; +} + +/* instrument header loader */ +static int load_ihdr(SFData *sf, unsigned int size) +{ + unsigned int i; + int i2; + SFInst *inst, *prev_inst = NULL; /* ptr to current & previous instrument */ + unsigned short zndx, pzndx = 0; + + if(size % SF_IHDR_SIZE || size == 0) /* chunk size is valid? */ + { + FLUID_LOG(FLUID_ERR, "Instrument header has invalid size"); + return FALSE; + } + + size = size / SF_IHDR_SIZE - 1; + + if(size == 0) + { + /* at least one preset + term record */ + FLUID_LOG(FLUID_WARN, "File contains no instruments"); + FSKIP(sf, SF_IHDR_SIZE); + return TRUE; + } + + for(i = 0; i < size; i++) + { + /* load all instrument headers */ + if((inst = FLUID_NEW(SFInst)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + sf->inst = fluid_list_append(sf->inst, inst); + inst->zone = NULL; /* For proper cleanup if fail (fluid_sffile_close) */ + inst->idx = i; + READSTR(sf, &inst->name); /* Possible read failure ^ */ + READW(sf, zndx); + + if(prev_inst) + { + /* not first instrument? */ + if(zndx < pzndx) + { + FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic"); + return FALSE; + } + + i2 = zndx - pzndx; + + while(i2--) + { + prev_inst->zone = fluid_list_prepend(prev_inst->zone, NULL); + } + } + else if(zndx > 0) /* 1st inst, warn if ofs >0 */ + { + FLUID_LOG(FLUID_WARN, "%d instrument zones not referenced, discarding", zndx); + } + + pzndx = zndx; + prev_inst = inst; /* update instrument ptr */ + } + + FSKIP(sf, 20); + READW(sf, zndx); + + if(zndx < pzndx) + { + FLUID_LOG(FLUID_ERR, "Instrument header indices not monotonic"); + return FALSE; + } + + i2 = zndx - pzndx; + + while(i2--) + { + prev_inst->zone = fluid_list_prepend(prev_inst->zone, NULL); + } + + return TRUE; +} + +/* instrument bag loader */ +static int load_ibag(SFData *sf, int size) +{ + fluid_list_t *inst_list; + fluid_list_t *zone_list; + SFZone *z, *pz = NULL; + unsigned short genndx, modndx, pgenndx = 0, pmodndx = 0; + int i; + + if(size % SF_BAG_SIZE || size == 0) /* size is multiple of SF_BAG_SIZE? */ + { + FLUID_LOG(FLUID_ERR, "Instrument bag chunk size is invalid"); + return FALSE; + } + + inst_list = sf->inst; + + while(inst_list) + { + /* traverse through inst */ + zone_list = ((SFInst *)(inst_list->data))->zone; + + while(zone_list) + { + /* load this inst's zones */ + if((size -= SF_BAG_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Instrument bag chunk size mismatch"); + return FALSE; + } + + if((z = FLUID_NEW(SFZone)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + zone_list->data = z; + z->gen = NULL; /* In case of failure, */ + z->mod = NULL; /* fluid_sffile_close can clean up */ + READW(sf, genndx); /* READW = possible read failure */ + READW(sf, modndx); + + if(pz) + { + /* if not first zone */ + if(genndx < pgenndx) + { + FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic"); + return FALSE; + } + + if(modndx < pmodndx) + { + FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic"); + return FALSE; + } + + i = genndx - pgenndx; + + while(i--) + { + pz->gen = fluid_list_prepend(pz->gen, NULL); + } + + i = modndx - pmodndx; + + while(i--) + { + pz->mod = fluid_list_prepend(pz->mod, NULL); + } + } + + pz = z; /* update previous zone ptr */ + pgenndx = genndx; + pmodndx = modndx; + zone_list = fluid_list_next(zone_list); + } + + inst_list = fluid_list_next(inst_list); + } + + size -= SF_BAG_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "Instrument chunk size mismatch"); + return FALSE; + } + + READW(sf, genndx); + READW(sf, modndx); + + if(!pz) + { + /* in case that all are no zoners */ + if(genndx > 0) + { + FLUID_LOG(FLUID_WARN, "No instrument generators and terminal index not 0"); + } + + if(modndx > 0) + { + FLUID_LOG(FLUID_WARN, "No instrument modulators and terminal index not 0"); + } + + return TRUE; + } + + if(genndx < pgenndx) + { + FLUID_LOG(FLUID_ERR, "Instrument generator indices not monotonic"); + return FALSE; + } + + if(modndx < pmodndx) + { + FLUID_LOG(FLUID_ERR, "Instrument modulator indices not monotonic"); + return FALSE; + } + + i = genndx - pgenndx; + + while(i--) + { + pz->gen = fluid_list_prepend(pz->gen, NULL); + } + + i = modndx - pmodndx; + + while(i--) + { + pz->mod = fluid_list_prepend(pz->mod, NULL); + } + + return TRUE; +} + +/* instrument modulator loader */ +static int load_imod(SFData *sf, int size) +{ + fluid_list_t *inst_list; + fluid_list_t *zone_list; + fluid_list_t *mod_list; + SFMod *m; + + inst_list = sf->inst; + + while(inst_list) + { + /* traverse through all inst */ + zone_list = ((SFInst *)(inst_list->data))->zone; + + while(zone_list) + { + /* traverse this inst's zones */ + mod_list = ((SFZone *)(zone_list->data))->mod; + + while(mod_list) + { + /* load zone's modulators */ + if((size -= SF_MOD_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch"); + return FALSE; + } + + if((m = FLUID_NEW(SFMod)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + mod_list->data = m; + READW(sf, m->src); + READW(sf, m->dest); + READW(sf, m->amount); + READW(sf, m->amtsrc); + READW(sf, m->trans); + mod_list = fluid_list_next(mod_list); + } + + zone_list = fluid_list_next(zone_list); + } + + inst_list = fluid_list_next(inst_list); + } + + /* + If there isn't even a terminal record + Hmmm, the specs say there should be one, but.. + */ + if(size == 0) + { + return TRUE; + } + + size -= SF_MOD_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "Instrument modulator chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_MOD_SIZE); /* terminal mod */ + + return TRUE; +} + +/* load instrument generators (see load_pgen for loading rules) */ +int load_igen(SFData *sf, int size) +{ + fluid_list_t *dup; + fluid_list_t *inst_list; + fluid_list_t *zone_list; + fluid_list_t *gen_list; + SFZone *zone; + SFGen *g; + SFInst *inst; + SFGenAmount genval; + unsigned short genid; + int level, skip, drop, discarded; + + inst_list = sf->inst; + + /* traverse through all instruments */ + while(inst_list) + { + inst = fluid_list_get(inst_list); + + discarded = FALSE; + zone_list = inst->zone; + + /* traverse this instrument's zones */ + while(zone_list) + { + zone = fluid_list_get(zone_list); + + level = 0; + gen_list = zone->gen; + + while(gen_list) + { + /* load zone's generators */ + dup = NULL; + skip = FALSE; + drop = FALSE; + + if((size -= SF_GEN_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch"); + return FALSE; + } + + READW(sf, genid); + + if(genid == GEN_KEYRANGE) + { + /* nothing precedes */ + if(level == 0) + { + level = 1; + READB(sf, genval.range.lo); + READB(sf, genval.range.hi); + } + else + { + skip = TRUE; + } + } + else if(genid == GEN_VELRANGE) + { + /* only KeyRange precedes */ + if(level <= 1) + { + level = 2; + READB(sf, genval.range.lo); + READB(sf, genval.range.hi); + } + else + { + skip = TRUE; + } + } + else if(genid == GEN_SAMPLEID) + { + /* sample is last gen */ + level = 3; + READW(sf, genval.uword); + } + else + { + level = 2; + + if(valid_inst_genid(genid)) + { + /* gen valid? */ + READW(sf, genval.sword); + dup = find_gen_by_id(genid, zone->gen); + } + else + { + skip = TRUE; + } + } + + if(!skip) + { + if(!dup) + { + /* if gen ! dup alloc new */ + if((g = FLUID_NEW(SFGen)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + + gen_list->data = g; + g->id = genid; + } + else + { + g = (SFGen *)(dup->data); + drop = TRUE; + } + + g->amount = genval; + } + else + { + /* skip this generator */ + discarded = TRUE; + drop = TRUE; + FSKIPW(sf); + } + + if(!drop) + { + gen_list = fluid_list_next(gen_list); /* next gen */ + } + else + { + SLADVREM(zone->gen, gen_list); + } + + /* GEN_SAMPLEID should be last generator */ + if (level == 3) + { + break; + } + + } /* generator loop */ + + /* Anything below level 3 means it's a global zone. The global zone + * should always be the first zone in the list, so discard any + * other global zones we encounter */ + if(level < 3 && (zone_list != inst->zone)) + { + /* advance to next zone before deleting the current list element */ + zone_list = fluid_list_next(zone_list); + + FLUID_LOG(FLUID_WARN, "Instrument '%s': Discarding invalid global zone", + inst->name); + inst->zone = fluid_list_remove(inst->zone, zone); + delete_zone(zone); + + /* we have already advanced the zone_list pointer, so continue with next zone */ + continue; + } + + /* All remaining generators must be invalid and should be discarded + * (because they come after a sampleid generator) */ + while(gen_list) + { + discarded = TRUE; + + if((size -= SF_GEN_SIZE) < 0) + { + FLUID_LOG(FLUID_ERR, "Instrument generator chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_GEN_SIZE); + SLADVREM(zone->gen, gen_list); + } + + zone_list = fluid_list_next(zone_list); /* next zone */ + } + + if(discarded) + { + FLUID_LOG(FLUID_WARN, + "Instrument '%s': Some invalid generators were discarded", + inst->name); + } + + inst_list = fluid_list_next(inst_list); + } + + /* for those non-terminal record cases, grr! */ + if(size == 0) + { + return TRUE; + } + + size -= SF_GEN_SIZE; + + if(size != 0) + { + FLUID_LOG(FLUID_ERR, "IGEN chunk size mismatch"); + return FALSE; + } + + FSKIP(sf, SF_GEN_SIZE); /* terminal gen */ + + return TRUE; +} + +/* sample header loader */ +static int load_shdr(SFData *sf, unsigned int size) +{ + unsigned int i; + SFSample *p; + + if(size % SF_SHDR_SIZE || size == 0) /* size is multiple of SHDR size? */ + { + FLUID_LOG(FLUID_ERR, "Sample header has invalid size"); + return FALSE; + } + + size = size / SF_SHDR_SIZE - 1; + + if(size == 0) + { + /* at least one sample + term record? */ + FLUID_LOG(FLUID_WARN, "File contains no samples"); + FSKIP(sf, SF_SHDR_SIZE); + return TRUE; + } + + /* load all sample headers */ + for(i = 0; i < size; i++) + { + if((p = FLUID_NEW(SFSample)) == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FALSE; + } + p->idx = i; + + sf->sample = fluid_list_prepend(sf->sample, p); + READSTR(sf, &p->name); + READD(sf, p->start); + READD(sf, p->end); + READD(sf, p->loopstart); + READD(sf, p->loopend); + READD(sf, p->samplerate); + READB(sf, p->origpitch); + READB(sf, p->pitchadj); + FSKIPW(sf); /* skip sample link */ + READW(sf, p->sampletype); + } + + FSKIP(sf, SF_SHDR_SIZE); /* skip terminal shdr */ + + return TRUE; +} + +void delete_preset(SFPreset *preset) +{ + fluid_list_t *entry; + SFZone *zone; + + if(!preset) + { + return; + } + + entry = preset->zone; + + while(entry) + { + zone = (SFZone *)fluid_list_get(entry); + delete_zone(zone); + entry = fluid_list_next(entry); + } + + delete_fluid_list(preset->zone); + + FLUID_FREE(preset); +} + +void delete_inst(SFInst *inst) +{ + fluid_list_t *entry; + SFZone *zone; + + if(!inst) + { + return; + } + + entry = inst->zone; + + while(entry) + { + zone = (SFZone *)fluid_list_get(entry); + delete_zone(zone); + entry = fluid_list_next(entry); + } + + delete_fluid_list(inst->zone); + + FLUID_FREE(inst); +} + + +/* Free all elements of a zone (Preset or Instrument) */ +void delete_zone(SFZone *zone) +{ + fluid_list_t *entry; + + if(!zone) + { + return; + } + + entry = zone->gen; + + while(entry) + { + FLUID_FREE(fluid_list_get(entry)); + entry = fluid_list_next(entry); + } + + delete_fluid_list(zone->gen); + + entry = zone->mod; + + while(entry) + { + FLUID_FREE(fluid_list_get(entry)); + entry = fluid_list_next(entry); + } + + delete_fluid_list(zone->mod); + + FLUID_FREE(zone); +} + +/* preset sort function, first by bank, then by preset # */ +static int preset_compare_func(const void *a, const void *b) +{ + int aval, bval; + + aval = (int)(((const SFPreset *)a)->bank) << 16 | ((const SFPreset *)a)->prenum; + bval = (int)(((const SFPreset *)b)->bank) << 16 | ((const SFPreset *)b)->prenum; + + return (aval - bval); +} + +/* Find a generator by its id in the passed in list. + * + * @return pointer to SFGen if found, otherwise NULL + */ +static fluid_list_t *find_gen_by_id(int gen, fluid_list_t *genlist) +{ + /* is generator in gen list? */ + fluid_list_t *p; + + p = genlist; + + while(p) + { + if(p->data == NULL) + { + return NULL; + } + + if(gen == ((SFGen *)p->data)->id) + { + break; + } + + p = fluid_list_next(p); + } + + return p; +} + +/* check validity of instrument generator */ +static int valid_inst_genid(unsigned short genid) +{ + size_t i; + + /* OVERRIDEROOTKEY is the last official generator, everything + * following it are generators internal to FluidSynth and will + * never appear in a SoundFont file. */ + if(genid > GEN_OVERRIDEROOTKEY) + { + return FALSE; + } + + for(i = 0; i < FLUID_N_ELEMENTS(invalid_inst_gen); i++) + { + if (invalid_inst_gen[i] == genid) + { + return FALSE; + } + } + + return TRUE; +} + +/* check validity of preset generator */ +static int valid_preset_genid(unsigned short genid) +{ + size_t i; + + if(!valid_inst_genid(genid)) + { + return FALSE; + } + + for(i = 0; i < FLUID_N_ELEMENTS(invalid_preset_gen); i++) + { + if (invalid_preset_gen[i] == genid) + { + return FALSE; + } + } + + return TRUE; +} + + +static int fluid_sffile_read_wav(SFData *sf, unsigned int start, unsigned int end, short **data, char **data24) +{ + short *loaded_data = NULL; + char *loaded_data24 = NULL; + unsigned int num_samples; + + fluid_return_val_if_fail((end + 1) > start , -1); + + num_samples = (end + 1) - start; + + if((start * sizeof(short) > sf->samplesize) || (end * sizeof(short) > sf->samplesize)) + { + FLUID_LOG(FLUID_ERR, "Sample offsets exceed sample data chunk"); + goto error_exit; + } + + /* Load 16-bit sample data */ + if(sf->fcbs->fseek(sf->sffd, sf->samplepos + (start * sizeof(short)), SEEK_SET) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Failed to seek to sample position"); + goto error_exit; + } + + loaded_data = FLUID_ARRAY(short, num_samples); + + if(loaded_data == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_exit; + } + + if(sf->fcbs->fread(loaded_data, num_samples * sizeof(short), sf->sffd) == FLUID_FAILED) + { +#if FLUID_VERSION_CHECK(FLUIDSYNTH_VERSION_MAJOR, FLUIDSYNTH_VERSION_MINOR, FLUIDSYNTH_VERSION_MICRO) < FLUID_VERSION_CHECK(2,2,0) + if((int)(num_samples * sizeof(short)) < 0) + { + FLUID_LOG(FLUID_INFO, + "This SoundFont seems to be bigger than 2GB, which is not supported in this version of fluidsynth. " + "You need to use at least fluidsynth 2.2.0"); + } +#endif + FLUID_LOG(FLUID_ERR, "Failed to read sample data"); + goto error_exit; + } + + /* If this machine is big endian, byte swap the 16 bit samples */ + if(FLUID_IS_BIG_ENDIAN) + { + unsigned int i; + + for(i = 0; i < num_samples; i++) + { + loaded_data[i] = FLUID_LE16TOH(loaded_data[i]); + } + } + + *data = loaded_data; + + /* Optionally load additional 8 bit sample data for 24-bit support. Any failures while loading + * the 24-bit sample data will be logged as errors but won't prevent the sample reading to + * fail, as sound output is still possible with the 16-bit sample data. */ + if(sf->sample24pos) + { + if((start > sf->sample24size) || (end > sf->sample24size)) + { + FLUID_LOG(FLUID_ERR, "Sample offsets exceed 24-bit sample data chunk"); + goto error24_exit; + } + + if(sf->fcbs->fseek(sf->sffd, sf->sample24pos + start, SEEK_SET) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Failed to seek position for 24-bit sample data in data file"); + goto error24_exit; + } + + loaded_data24 = FLUID_ARRAY(char, num_samples); + + if(loaded_data24 == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory reading 24-bit sample data"); + goto error24_exit; + } + + if(sf->fcbs->fread(loaded_data24, num_samples, sf->sffd) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Failed to read 24-bit sample data"); + goto error24_exit; + } + } + + *data24 = loaded_data24; + + return num_samples; + +error24_exit: + FLUID_LOG(FLUID_WARN, "Ignoring 24-bit sample data, sound quality might suffer"); + FLUID_FREE(loaded_data24); + *data24 = NULL; + return num_samples; + +error_exit: + FLUID_FREE(loaded_data); + FLUID_FREE(loaded_data24); + return -1; +} + + +/* Ogg Vorbis loading and decompression */ +#if LIBSNDFILE_SUPPORT + +/* Virtual file access routines to allow loading individually compressed + * samples from the Soundfont sample data chunk using the file callbacks + * passing in during opening of the file */ +typedef struct _sfvio_data_t +{ + SFData *sffile; + sf_count_t start; /* start byte offset of compressed data */ + sf_count_t end; /* end byte offset of compressed data */ + sf_count_t offset; /* current virtual file offset from start byte offset */ + +} sfvio_data_t; + +static sf_count_t sfvio_get_filelen(void *user_data) +{ + sfvio_data_t *data = user_data; + + return (data->end + 1) - data->start; +} + +static sf_count_t sfvio_seek(sf_count_t offset, int whence, void *user_data) +{ + sfvio_data_t *data = user_data; + SFData *sf = data->sffile; + sf_count_t new_offset; + + switch(whence) + { + case SEEK_SET: + new_offset = offset; + break; + + case SEEK_CUR: + new_offset = data->offset + offset; + break; + + case SEEK_END: + new_offset = sfvio_get_filelen(user_data) + offset; + break; + + default: + goto fail; /* proper error handling not possible?? */ + } + + new_offset += data->start; + fluid_rec_mutex_lock(sf->mtx); + if (data->start <= new_offset && new_offset <= data->end && + sf->fcbs->fseek(sf->sffd, new_offset, SEEK_SET) != FLUID_FAILED) + { + data->offset = new_offset - data->start; + } + fluid_rec_mutex_unlock(sf->mtx); + +fail: + return data->offset; +} + +static sf_count_t sfvio_read(void *ptr, sf_count_t count, void *user_data) +{ + sfvio_data_t *data = user_data; + SFData *sf = data->sffile; + sf_count_t remain; + + remain = sfvio_get_filelen(user_data) - data->offset; + + if(count > remain) + { + count = remain; + } + + if(count == 0) + { + return count; + } + + fluid_rec_mutex_lock(sf->mtx); + if (sf->fcbs->fseek(sf->sffd, data->start + data->offset, SEEK_SET) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "This should never happen: fseek failed in sfvoid_read()"); + count = 0; + } + else + { + if (sf->fcbs->fread(ptr, count, sf->sffd) == FLUID_FAILED) + { + FLUID_LOG(FLUID_ERR, "Failed to read compressed sample data"); + count = 0; + } + } + fluid_rec_mutex_unlock(sf->mtx); + + data->offset += count; + + return count; +} + +static sf_count_t sfvio_tell(void *user_data) +{ + sfvio_data_t *data = user_data; + + return data->offset; +} + +/** + * Read Ogg Vorbis compressed data from the Soundfont and decompress it, returning the number of samples + * in the decompressed WAV. Only 16-bit mono samples are supported. + * + * Note that this function takes byte indices for start and end source data. The sample headers in SF3 + * files use byte indices, so those pointers can be passed directly to this function. + * + * This function uses a virtual file structure in order to read the Ogg Vorbis + * data from arbitrary locations in the source file. + */ +static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data) +{ + SNDFILE *sndfile; + SF_INFO sfinfo; + SF_VIRTUAL_IO sfvio = + { + sfvio_get_filelen, + sfvio_seek, + sfvio_read, + NULL, + sfvio_tell + }; + sfvio_data_t sfdata; + short *wav_data = NULL; + + if((start_byte > sf->samplesize) || (end_byte > sf->samplesize)) + { + FLUID_LOG(FLUID_ERR, "Ogg Vorbis data offsets exceed sample data chunk"); + return -1; + } + + // Initialize file position indicator and SF_INFO structure + sfdata.sffile = sf; + sfdata.start = sf->samplepos + start_byte; + sfdata.end = sf->samplepos + end_byte; + sfdata.offset = -1; + + /* Seek to sfdata.start, the beginning of Ogg Vorbis data in Soundfont */ + sfvio_seek(0, SEEK_SET, &sfdata); + if (sfdata.offset != 0) + { + FLUID_LOG(FLUID_ERR, "Failed to seek to compressed sample position"); + return -1; + } + + FLUID_MEMSET(&sfinfo, 0, sizeof(sfinfo)); + + // Open sample as a virtual file + sndfile = sf_open_virtual(&sfvio, SFM_READ, &sfinfo, &sfdata); + + if(!sndfile) + { + FLUID_LOG(FLUID_ERR, "sf_open_virtual(): %s", sf_strerror(sndfile)); + return -1; + } + + // Empty sample + if(sfinfo.frames <= 0 || sfinfo.channels <= 0) + { + FLUID_LOG(FLUID_DBG, "Empty decompressed sample"); + *data = NULL; + sf_close(sndfile); + return 0; + } + + // Mono sample + if(sfinfo.channels != 1) + { + FLUID_LOG(FLUID_DBG, "Unsupported channel count %d in ogg sample", sfinfo.channels); + goto error_exit; + } + + if((sfinfo.format & SF_FORMAT_OGG) == 0) + { + FLUID_LOG(FLUID_WARN, "OGG sample is not OGG compressed, this is not officially supported"); + } + + wav_data = FLUID_ARRAY(short, sfinfo.frames * sfinfo.channels); + + if(!wav_data) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_exit; + } + + /* Automatically decompresses the Ogg Vorbis data to 16-bit PCM */ + if(sf_readf_short(sndfile, wav_data, sfinfo.frames) < sfinfo.frames) + { + FLUID_LOG(FLUID_DBG, "Decompression failed!"); + FLUID_LOG(FLUID_ERR, "sf_readf_short(): %s", sf_strerror(sndfile)); + goto error_exit; + } + + sf_close(sndfile); + + *data = wav_data; + + return sfinfo.frames; + +error_exit: + FLUID_FREE(wav_data); + sf_close(sndfile); + return -1; +} +#else +static int fluid_sffile_read_vorbis(SFData *sf, unsigned int start_byte, unsigned int end_byte, short **data) +{ + return -1; +} +#endif diff --git a/libs/fluidsynth/src/sfloader/fluid_sffile.h b/libs/fluidsynth/src/sfloader/fluid_sffile.h new file mode 100644 index 00000000000..5275c6252bc --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_sffile.h @@ -0,0 +1,194 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * SoundFont loading code borrowed from Smurf SoundFont Editor by Josh Green + * + * 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_SFFILE_H +#define _FLUID_SFFILE_H + + +#include "fluid_gen.h" +#include "fluid_list.h" +#include "fluid_mod.h" +#include "fluidsynth.h" +#include "fluid_sys.h" + + +/* Sound Font structure defines */ + +/* Forward declarations */ +typedef union _SFGenAmount SFGenAmount; +typedef struct _SFVersion SFVersion; +typedef struct _SFMod SFMod; +typedef struct _SFGen SFGen; +typedef struct _SFZone SFZone; +typedef struct _SFSample SFSample; +typedef struct _SFInst SFInst; +typedef struct _SFPreset SFPreset; +typedef struct _SFData SFData; +typedef struct _SFChunk SFChunk; + + +struct _SFVersion +{ + /* version structure */ + unsigned short major; + unsigned short minor; +}; + +struct _SFMod +{ + /* Modulator structure */ + unsigned short src; /* source modulator */ + unsigned short dest; /* destination generator */ + signed short amount; /* signed, degree of modulation */ + unsigned short amtsrc; /* second source controls amnt of first */ + unsigned short trans; /* transform applied to source */ +}; + +union _SFGenAmount /* Generator amount structure */ +{ + signed short sword; /* signed 16 bit value */ + unsigned short uword; /* unsigned 16 bit value */ + struct + { + unsigned char lo; /* low value for ranges */ + unsigned char hi; /* high value for ranges */ + } range; +}; + +struct _SFGen +{ + /* Generator structure */ + unsigned short id; /* generator ID */ + SFGenAmount amount; /* generator value */ +}; + +struct _SFZone +{ + /* Sample/instrument zone structure */ + fluid_list_t *gen; /* list of generators */ + fluid_list_t *mod; /* list of modulators */ +}; + +struct _SFSample +{ + /* Sample structure */ + char name[21]; /* Name of sample */ + int idx; /* Index of this instrument in the Soundfont */ + unsigned int start; /* Offset in sample area to start of sample */ + unsigned int end; /* Offset from start to end of sample, + this is the last point of the + sample, the SF spec has this as the + 1st point after, corrected on + load/save */ + unsigned int loopstart; /* Offset from start to start of loop */ + unsigned int loopend; /* Offset from start to end of loop, + marks the first point after loop, + whose sample value is ideally + equivalent to loopstart */ + unsigned int samplerate; /* Sample rate recorded at */ + unsigned char origpitch; /* root midi key number */ + signed char pitchadj; /* pitch correction in cents */ + unsigned short sampletype; /* 1 mono,2 right,4 left,linked 8,0x8000=ROM */ + fluid_sample_t *fluid_sample; /* Imported sample (fixed up in fluid_defsfont_load) */ +}; + +struct _SFInst +{ + /* Instrument structure */ + char name[21]; /* Name of instrument */ + int idx; /* Index of this instrument in the Soundfont */ + fluid_list_t *zone; /* list of instrument zones */ +}; + +struct _SFPreset +{ + /* Preset structure */ + char name[21]; /* preset name */ + unsigned short prenum; /* preset number */ + unsigned short bank; /* bank number */ + fluid_list_t *zone; /* list of preset zones */ +}; + +/* NOTE: sffd is also used to determine if sound font is new (NULL) */ +struct _SFData +{ + /* Sound font data structure */ + SFVersion version; /* sound font version */ + SFVersion romver; /* ROM version */ + + unsigned int filesize; + + unsigned int samplepos; /* position within sffd of the sample chunk */ + unsigned int samplesize; /* length within sffd of the sample chunk */ + + unsigned int sample24pos; /* position within sffd of the sm24 chunk, set to zero if no 24 bit + sample support */ + unsigned int sample24size; /* length within sffd of the sm24 chunk */ + + unsigned int hydrapos; + unsigned int hydrasize; + + char *fname; /* file name */ + FILE *sffd; /* loaded sfont file descriptor */ + const fluid_file_callbacks_t *fcbs; /* file callbacks used to read this file */ + + fluid_rec_mutex_t mtx; /* this mutex can be used to synchronize calls to fcbs when using multiple threads (e.g. SF3 loading) */ + + fluid_list_t *info; /* linked list of info strings (1st byte is ID) */ + fluid_list_t *preset; /* linked list of preset info */ + fluid_list_t *inst; /* linked list of instrument info */ + fluid_list_t *sample; /* linked list of sample info */ +}; + +/* functions */ + + +/*-----------------------------------sffile.h----------------------------*/ +/* + File structures and routines (used to be in sffile.h) +*/ + +/* sfont file data structures */ +struct _SFChunk +{ + /* RIFF file chunk structure */ + unsigned int id; /* chunk id */ + unsigned int size; /* size of the following chunk */ +}; + +/* Public functions */ +SFData *fluid_sffile_open(const char *fname, const fluid_file_callbacks_t *fcbs); +void fluid_sffile_close(SFData *sf); +int fluid_sffile_parse_presets(SFData *sf); +int fluid_sffile_read_sample_data(SFData *sf, unsigned int sample_start, unsigned int sample_end, + int sample_type, short **data, char **data24); + + +/* extern only for unit test purposes */ +int load_igen(SFData *sf, int size); +int load_pgen(SFData *sf, int size); +void delete_preset(SFPreset *preset); +void delete_inst(SFInst *inst); +void delete_zone(SFZone *zone); + +#endif /* _FLUID_SFFILE_H */ diff --git a/libs/fluidsynth/src/sfloader/fluid_sfont.c b/libs/fluidsynth/src/sfloader/fluid_sfont.c new file mode 100644 index 00000000000..00423c00356 --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_sfont.c @@ -0,0 +1,850 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_sfont.h" +#include "fluid_sys.h" + + +void *default_fopen(const char *path) +{ + const char* msg; + FILE* handle = fluid_file_open(path, &msg); + + if(handle == NULL) + { + FLUID_LOG(FLUID_ERR, "fluid_sfloader_load(): Failed to open '%s': %s", path, msg); + } + + return handle; +} + +int default_fclose(void *handle) +{ + return FLUID_FCLOSE((FILE *)handle) == 0 ? FLUID_OK : FLUID_FAILED; +} + +fluid_long_long_t default_ftell(void *handle) +{ + return FLUID_FTELL((FILE *)handle); +} + +#ifdef _WIN32 +#define FLUID_PRIi64 "I64d" +#else +#define FLUID_PRIi64 "lld" +#endif + +int safe_fread(void *buf, fluid_long_long_t count, void *fd) +{ + if(FLUID_FREAD(buf, (size_t)count, 1, (FILE *)fd) != 1) + { + if(feof((FILE *)fd)) + { + FLUID_LOG(FLUID_ERR, "EOF while attempting to read %" FLUID_PRIi64 " bytes", count); + } + else + { + FLUID_LOG(FLUID_ERR, "File read failed"); + } + + return FLUID_FAILED; + } + + return FLUID_OK; +} + +int safe_fseek(void *fd, fluid_long_long_t ofs, int whence) +{ + if(FLUID_FSEEK((FILE *)fd, ofs, whence) != 0) + { + FLUID_LOG(FLUID_ERR, "File seek failed with offset = %" FLUID_PRIi64 " and whence = %d", ofs, whence); + return FLUID_FAILED; + } + + return FLUID_OK; +} + +#undef FLUID_PRIi64 + +/** + * Creates a new SoundFont loader. + * + * @param load Pointer to a function that provides a #fluid_sfont_t (see #fluid_sfloader_load_t). + * @param free Pointer to a function that destroys this instance (see #fluid_sfloader_free_t). + * Unless any private data needs to be freed it is sufficient to set this to delete_fluid_sfloader(). + * + * @return the SoundFont loader instance on success, NULL otherwise. + */ +fluid_sfloader_t *new_fluid_sfloader(fluid_sfloader_load_t load, fluid_sfloader_free_t free) +{ + fluid_sfloader_t *loader; + + fluid_return_val_if_fail(load != NULL, NULL); + fluid_return_val_if_fail(free != NULL, NULL); + + loader = FLUID_NEW(fluid_sfloader_t); + + if(loader == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(loader, 0, sizeof(*loader)); + + loader->load = load; + loader->free = free; + fluid_sfloader_set_callbacks(loader, + default_fopen, + safe_fread, + safe_fseek, + default_ftell, + default_fclose); + + return loader; +} + +/** + * Frees a SoundFont loader created with new_fluid_sfloader(). + * + * @param loader The SoundFont loader instance to free. + */ +void delete_fluid_sfloader(fluid_sfloader_t *loader) +{ + fluid_return_if_fail(loader != NULL); + + FLUID_FREE(loader); +} + +/** + * Specify private data to be used by #fluid_sfloader_load_t. + * + * @param loader The SoundFont loader instance. + * @param data The private data to store. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_sfloader_set_data(fluid_sfloader_t *loader, void *data) +{ + fluid_return_val_if_fail(loader != NULL, FLUID_FAILED); + + loader->data = data; + return FLUID_OK; +} + +/** + * Obtain private data previously set with fluid_sfloader_set_data(). + * + * @param loader The SoundFont loader instance. + * @return The private data or NULL if none explicitly set before. + */ +void *fluid_sfloader_get_data(fluid_sfloader_t *loader) +{ + fluid_return_val_if_fail(loader != NULL, NULL); + + return loader->data; +} + +/** + * Set custom callbacks to be used upon soundfont loading. + * + * @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. + * @param seek A function implementing #fluid_sfloader_callback_seek_t. + * @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, + fluid_sfloader_callback_read_t read, + fluid_sfloader_callback_seek_t seek, + fluid_sfloader_callback_tell_t tell, + fluid_sfloader_callback_close_t close) +{ + fluid_file_callbacks_t *cb; + + fluid_return_val_if_fail(loader != NULL, FLUID_FAILED); + fluid_return_val_if_fail(open != NULL, FLUID_FAILED); + fluid_return_val_if_fail(read != NULL, FLUID_FAILED); + fluid_return_val_if_fail(seek != NULL, FLUID_FAILED); + fluid_return_val_if_fail(tell != NULL, FLUID_FAILED); + fluid_return_val_if_fail(close != NULL, FLUID_FAILED); + + cb = &loader->file_callbacks; + + cb->fopen = open; + cb->fread = read; + cb->fseek = seek; + cb->ftell = tell; + cb->fclose = close; + + // NOTE: if we ever make the instpatch loader public, this may return FLUID_FAILED + return FLUID_OK; +} + +/** + * 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. + * @param iter_next A function implementing #fluid_sfont_iteration_next_t, or NULL if preset iteration not needed. + * @param free A function implementing #fluid_sfont_free_t. + * @return The soundfont instance on success or NULL otherwise. + */ +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, + fluid_sfont_iteration_next_t iter_next, + fluid_sfont_free_t free) +{ + fluid_sfont_t *sfont; + + fluid_return_val_if_fail(get_name != NULL, NULL); + fluid_return_val_if_fail(get_preset != NULL, NULL); + fluid_return_val_if_fail(free != NULL, NULL); + + sfont = FLUID_NEW(fluid_sfont_t); + + if(sfont == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(sfont, 0, sizeof(*sfont)); + + sfont->get_name = get_name; + sfont->get_preset = get_preset; + sfont->iteration_start = iter_start; + sfont->iteration_next = iter_next; + sfont->free = free; + + return sfont; +} + +/** + * Set private data to use with a SoundFont instance. + * + * @param sfont The SoundFont instance. + * @param data The private data to store. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_sfont_set_data(fluid_sfont_t *sfont, void *data) +{ + fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); + + sfont->data = data; + return FLUID_OK; +} + +/** + * Retrieve the private data of a SoundFont instance. + * + * @param sfont The SoundFont instance. + * @return The private data or NULL if none explicitly set before. + */ +void *fluid_sfont_get_data(fluid_sfont_t *sfont) +{ + fluid_return_val_if_fail(sfont != NULL, NULL); + + return sfont->data; +} + +/** + * Retrieve the unique ID of a SoundFont instance. + * + * @param sfont The SoundFont instance. + * @return The SoundFont ID. + */ +int fluid_sfont_get_id(fluid_sfont_t *sfont) +{ + return sfont->id; +} + +/** + * Retrieve the name of a SoundFont instance. + * + * @param sfont The SoundFont instance. + * @return The name of the SoundFont. + */ +const char *fluid_sfont_get_name(fluid_sfont_t *sfont) +{ + return sfont->get_name(sfont); +} + +/** + * 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 + * @return The preset instance or NULL if none found. + */ +fluid_preset_t *fluid_sfont_get_preset(fluid_sfont_t *sfont, int bank, int prenum) +{ + return sfont->get_preset(sfont, bank, prenum); +} + + +/** + * Starts / re-starts virtual preset iteration in a SoundFont. + * + * @param sfont Virtual SoundFont instance + */ +void fluid_sfont_iteration_start(fluid_sfont_t *sfont) +{ + fluid_return_if_fail(sfont != NULL); + fluid_return_if_fail(sfont->iteration_start != NULL); + + sfont->iteration_start(sfont); +} + +/** + * Virtual SoundFont preset iteration function. + * + * Returns preset information to the caller and advances the + * internal iteration state to the next preset for subsequent calls. + * @param sfont The SoundFont instance. + * @return NULL when no more presets are available, otherwise the a pointer to the current preset + */ +fluid_preset_t *fluid_sfont_iteration_next(fluid_sfont_t *sfont) +{ + fluid_return_val_if_fail(sfont != NULL, NULL); + fluid_return_val_if_fail(sfont->iteration_next != NULL, NULL); + + return sfont->iteration_next(sfont); +} + +/** + * Destroys a SoundFont instance created with new_fluid_sfont(). + * + * @param sfont The SoundFont instance to destroy. + * @return Always returns 0. + * + * Implements #fluid_sfont_free_t. + * + */ +int delete_fluid_sfont(fluid_sfont_t *sfont) +{ + fluid_return_val_if_fail(sfont != NULL, 0); + + FLUID_FREE(sfont); + return 0; +} + +/** + * Create a virtual SoundFont preset instance. + * + * @param parent_sfont The SoundFont instance this preset shall belong to + * @param get_name A function implementing #fluid_preset_get_name_t + * @param get_bank A function implementing #fluid_preset_get_banknum_t + * @param get_num A function implementing #fluid_preset_get_num_t + * @param noteon A function implementing #fluid_preset_noteon_t + * @param free A function implementing #fluid_preset_free_t + * @return The preset instance on success, NULL otherwise. + */ +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, + fluid_preset_get_num_t get_num, + fluid_preset_noteon_t noteon, + fluid_preset_free_t free) +{ + fluid_preset_t *preset; + + fluid_return_val_if_fail(parent_sfont != NULL, NULL); + fluid_return_val_if_fail(get_name != NULL, NULL); + fluid_return_val_if_fail(get_bank != NULL, NULL); + fluid_return_val_if_fail(get_num != NULL, NULL); + fluid_return_val_if_fail(noteon != NULL, NULL); + fluid_return_val_if_fail(free != NULL, NULL); + + preset = FLUID_NEW(fluid_preset_t); + + if(preset == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(preset, 0, sizeof(*preset)); + + preset->sfont = parent_sfont; + preset->get_name = get_name; + preset->get_banknum = get_bank; + preset->get_num = get_num; + preset->noteon = noteon; + preset->free = free; + + return preset; +} + +/** + * Set private data to use with a SoundFont preset instance. + * + * @param preset The SoundFont preset instance. + * @param data The private data to store. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_preset_set_data(fluid_preset_t *preset, void *data) +{ + fluid_return_val_if_fail(preset != NULL, FLUID_FAILED); + + preset->data = data; + return FLUID_OK; +} + +/** + * Retrieve the private data of a SoundFont preset instance. + * + * @param preset The SoundFont preset instance. + * @return The private data or NULL if none explicitly set before. + */ +void *fluid_preset_get_data(fluid_preset_t *preset) +{ + fluid_return_val_if_fail(preset != NULL, NULL); + + return preset->data; +} + +/** + * Retrieves the presets name by executing the \p get_name function + * provided on its creation. + * + * @param preset The SoundFont preset instance. + * @return Pointer to a NULL-terminated string containing the presets name. + */ +const char *fluid_preset_get_name(fluid_preset_t *preset) +{ + return preset->get_name(preset); +} + +/** + * Retrieves the presets bank number by executing the \p get_bank function + * provided on its creation. + * + * @param preset The SoundFont preset instance. + * @return The bank number of \p preset. + */ +int fluid_preset_get_banknum(fluid_preset_t *preset) +{ + return preset->get_banknum(preset); +} + +/** + * Retrieves the presets (instrument) number by executing the \p get_num function + * provided on its creation. + * + * @param preset The SoundFont preset instance. + * @return The number of \p preset. + */ +int fluid_preset_get_num(fluid_preset_t *preset) +{ + return preset->get_num(preset); +} + +/** + * Retrieves the presets parent SoundFont instance. + * + * @param preset The SoundFont preset instance. + * @return The parent SoundFont of \p preset. + */ +fluid_sfont_t *fluid_preset_get_sfont(fluid_preset_t *preset) +{ + return preset->sfont; +} + +/** + * Destroys a SoundFont preset instance created with new_fluid_preset(). + * + * @param preset The SoundFont preset instance to destroy. + * + * Implements #fluid_preset_free_t. + * + */ +void delete_fluid_preset(fluid_preset_t *preset) +{ + fluid_return_if_fail(preset != NULL); + + FLUID_FREE(preset); +} + +/** + * Create a new sample instance. + * + * @return The sample on success, NULL otherwise. + */ +fluid_sample_t * +new_fluid_sample() +{ + fluid_sample_t *sample = NULL; + + sample = FLUID_NEW(fluid_sample_t); + + if(sample == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(sample, 0, sizeof(*sample)); + + return sample; +} + +/** + * Destroy a sample instance previously created with new_fluid_sample(). + * + * @param sample The sample to destroy. + */ +void +delete_fluid_sample(fluid_sample_t *sample) +{ + fluid_return_if_fail(sample != NULL); + + if(sample->auto_free) + { + FLUID_FREE(sample->data); + FLUID_FREE(sample->data24); + } + + FLUID_FREE(sample); +} + +/** + * Returns the size of the fluid_sample_t structure. + * + * @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! + */ +size_t fluid_sample_sizeof() +{ + return sizeof(fluid_sample_t); +} + +/** + * 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 + */ +int fluid_sample_set_name(fluid_sample_t *sample, const char *name) +{ + fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + + FLUID_STRNCPY(sample->name, name, sizeof(sample->name)); + return FLUID_OK; +} + +/** + * 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 + * @param nbframes Number of samples in \a data + * @param sample_rate Sampling rate of the sample data + * @param copy_data TRUE to copy the sample data (and automatically free it upon delete_fluid_sample()), FALSE to use it directly (and not free it) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note If \a copy_data is FALSE, data should have 8 unused frames at start + * and 8 unused frames at the end and \a nbframes should be >=48 + */ +int +fluid_sample_set_sound_data(fluid_sample_t *sample, + short *data, + char *data24, + unsigned int nbframes, + unsigned int sample_rate, + short copy_data + ) +{ + /* the number of samples before the start and after the end */ +#define SAMPLE_LOOP_MARGIN 8U + + fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); + fluid_return_val_if_fail(data != NULL, FLUID_FAILED); + fluid_return_val_if_fail(nbframes != 0, FLUID_FAILED); + + /* in case we already have some data */ + if((sample->data != NULL || sample->data24 != NULL) && sample->auto_free) + { + FLUID_FREE(sample->data); + FLUID_FREE(sample->data24); + } + + sample->data = NULL; + sample->data24 = NULL; + + if(copy_data) + { + unsigned int storedNbFrames; + + /* nbframes should be >= 48 (SoundFont specs) */ + storedNbFrames = nbframes; + + if(storedNbFrames < 48) + { + storedNbFrames = 48; + } + + storedNbFrames += 2 * SAMPLE_LOOP_MARGIN; + + sample->data = FLUID_ARRAY(short, storedNbFrames); + + if(sample->data == NULL) + { + goto error_rec; + } + + FLUID_MEMSET(sample->data, 0, storedNbFrames * sizeof(short)); + FLUID_MEMCPY(sample->data + SAMPLE_LOOP_MARGIN, data, nbframes * sizeof(short)); + + if(data24 != NULL) + { + sample->data24 = FLUID_ARRAY(char, storedNbFrames); + + if(sample->data24 == NULL) + { + goto error_rec; + } + + FLUID_MEMSET(sample->data24, 0, storedNbFrames); + FLUID_MEMCPY(sample->data24 + SAMPLE_LOOP_MARGIN, data24, nbframes * sizeof(char)); + } + + /* pointers */ + /* all from the start of data */ + sample->start = SAMPLE_LOOP_MARGIN; + sample->end = SAMPLE_LOOP_MARGIN + nbframes - 1; + } + else + { + /* we cannot assure the SAMPLE_LOOP_MARGIN */ + sample->data = data; + sample->data24 = data24; + sample->start = 0; + sample->end = nbframes - 1; + } + + sample->samplerate = sample_rate; + sample->sampletype = FLUID_SAMPLETYPE_MONO; + sample->auto_free = copy_data; + + return FLUID_OK; + +error_rec: + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(sample->data); + FLUID_FREE(sample->data24); + sample->data = NULL; + sample->data24 = NULL; + return FLUID_FAILED; + +#undef SAMPLE_LOOP_MARGIN +} + +/** + * Set the loop of a sample. + * + * @param sample SoundFont sample + * @param loop_start Start sample index of the loop. + * @param loop_end End index of the loop (must be a valid sample as it marks the last sample to be played). + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_sample_set_loop(fluid_sample_t *sample, unsigned int loop_start, unsigned int loop_end) +{ + fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); + + sample->loopstart = loop_start; + sample->loopend = loop_end; + + return FLUID_OK; +} + +/** + * Set the pitch of a sample. + * + * @param sample SoundFont sample + * @param root_key Root MIDI note of sample (0-127) + * @param fine_tune Fine tune in cents + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_sample_set_pitch(fluid_sample_t *sample, int root_key, int fine_tune) +{ + fluid_return_val_if_fail(sample != NULL, FLUID_FAILED); + fluid_return_val_if_fail(0 <= root_key && root_key <= 127, FLUID_FAILED); + + sample->origpitch = root_key; + sample->pitchadj = fine_tune; + + return FLUID_OK; +} + + +/** + * Validate parameters of a sample + * + */ +int fluid_sample_validate(fluid_sample_t *sample, unsigned int buffer_size) +{ +#define EXCLUSIVE_FLAGS (FLUID_SAMPLETYPE_MONO | FLUID_SAMPLETYPE_RIGHT | FLUID_SAMPLETYPE_LEFT) + static const unsigned int supported_flags = EXCLUSIVE_FLAGS | FLUID_SAMPLETYPE_LINKED | FLUID_SAMPLETYPE_OGG_VORBIS | FLUID_SAMPLETYPE_ROM; + + /* ROM samples are unusable for us by definition */ + if(sample->sampletype & FLUID_SAMPLETYPE_ROM) + { + FLUID_LOG(FLUID_WARN, "Sample '%s': ROM sample ignored", sample->name); + return FLUID_FAILED; + } + + if(sample->sampletype & ~supported_flags) + { + FLUID_LOG(FLUID_WARN, "Sample '%s' has unknown flags, possibly using an unsupported compression; sample ignored", sample->name); + return FLUID_FAILED; + } + + if((sample->sampletype & EXCLUSIVE_FLAGS) & ((sample->sampletype & EXCLUSIVE_FLAGS) - 1)) + { + FLUID_LOG(FLUID_INFO, "Sample '%s' should be either mono or left or right; using it anyway", sample->name); + } + + if((sample->sampletype & FLUID_SAMPLETYPE_LINKED) && (sample->sampletype & EXCLUSIVE_FLAGS)) + { + FLUID_LOG(FLUID_INFO, "Linked sample '%s' should not be mono, left or right at the same time; using it anyway", sample->name); + } + + if((sample->sampletype & EXCLUSIVE_FLAGS) == 0) + { + FLUID_LOG(FLUID_INFO, "Sample '%s' has no flags set, assuming mono", sample->name); + sample->sampletype = FLUID_SAMPLETYPE_MONO; + } + + /* Ogg vorbis compressed samples in the SF3 format use byte indices for + * sample start and end pointers before decompression. Standard SF2 samples + * use sample word indices for all pointers, so use half the buffer_size + * for validation. */ + if(!(sample->sampletype & FLUID_SAMPLETYPE_OGG_VORBIS)) + { + if(buffer_size % 2) + { + FLUID_LOG(FLUID_WARN, "Sample '%s': invalid buffer size", sample->name); + return FLUID_FAILED; + } + + buffer_size /= 2; + } + + if((sample->end > buffer_size) || (sample->start >= sample->end)) + { + FLUID_LOG(FLUID_WARN, "Sample '%s': invalid start/end file positions", sample->name); + return FLUID_FAILED; + } + + return FLUID_OK; +#undef EXCLUSIVE_FLAGS +} + +/* Check the sample loop pointers and optionally convert them to something + * usable in case they are broken. Return a boolean indicating if the pointers + * have been modified, so the user can be notified of possible audio glitches. + */ +int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int buffer_size) +{ + int modified = FALSE; + unsigned int max_end = buffer_size / 2; + /* In fluid_sample_t the sample end pointer points to the last sample, not + * to the data word after the last sample. FIXME: why? */ + unsigned int sample_end = sample->end + 1; + + if(sample->loopstart == sample->loopend) + { + /* Some SoundFonts disable loops by setting loopstart = loopend. While + * technically invalid, we decided to accept those samples anyway. + * Before fluidsynth 2.2.5 we've set those indices to zero, as this + * change was believed to be inaudible. This turned out to be an + * incorrect assumption, as the loop points may still be modified by + * loop offset modulators afterwards. + */ + if(sample->loopstart != sample->start) + { + // Many soundfonts set loopstart == loopend == sample->start to disabled to loop. + // Only report cases where it's not equal to the sample->start, to avoid spam. + FLUID_LOG(FLUID_DBG, "Sample '%s': zero length loop detected: loopstart == loopend == '%d', sample start '%d', using it anyway", + sample->name, sample->loopstart, sample->start); + } + } + else if(sample->loopstart > sample->loopend) + { + unsigned int tmp; + + /* If loop start and end are reversed, try to swap them around and + * continue validation */ + FLUID_LOG(FLUID_DBG, "Sample '%s': reversed loop pointers '%d' - '%d', trying to fix", + sample->name, sample->loopstart, sample->loopend); + tmp = sample->loopstart; + sample->loopstart = sample->loopend; + sample->loopend = tmp; + modified = TRUE; + } + + /* The SoundFont 2.4 spec defines the loopstart index as the first sample + * point of the loop while loopend is the first point AFTER the last sample + * of the loop. However we cannot be sure whether any of loopend or end is + * correct. Hours of thinking through this have concluded that it would be + * best practice to mangle with loops as little as necessary by only making + * sure the pointers are within sample->start to max_end. Incorrect + * soundfont shall preferably fail loudly. */ + if((sample->loopstart < sample->start) || (sample->loopstart > max_end)) + { + FLUID_LOG(FLUID_DBG, "Sample '%s': invalid loop start '%d', setting to sample start '%d'", + sample->name, sample->loopstart, sample->start); + sample->loopstart = sample->start; + modified = TRUE; + } + + if((sample->loopend < sample->start) || (sample->loopend > max_end)) + { + FLUID_LOG(FLUID_DBG, "Sample '%s': invalid loop end '%d', setting to sample end '%d'", + sample->name, sample->loopend, sample_end); + sample->loopend = sample_end; + modified = TRUE; + } + + if((sample->loopstart > sample_end) || (sample->loopend > sample_end)) + { + FLUID_LOG(FLUID_DBG, "Sample '%s': loop range '%d - %d' after sample end '%d', using it anyway", + sample->name, sample->loopstart, sample->loopend, sample_end); + } + + return modified; +} diff --git a/libs/fluidsynth/src/sfloader/fluid_sfont.h b/libs/fluidsynth/src/sfloader/fluid_sfont.h new file mode 100644 index 00000000000..9a42c02eb19 --- /dev/null +++ b/libs/fluidsynth/src/sfloader/fluid_sfont.h @@ -0,0 +1,189 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _PRIV_FLUID_SFONT_H +#define _PRIV_FLUID_SFONT_H + +#include "fluidsynth.h" + +int fluid_sample_validate(fluid_sample_t *sample, unsigned int max_end); +int fluid_sample_sanitize_loop(fluid_sample_t *sample, unsigned int max_end); + +/* + * Utility macros to access soundfonts, presets, and samples + */ + +#define fluid_sfloader_delete(_loader) { if ((_loader) && (_loader)->free) (*(_loader)->free)(_loader); } +#define fluid_sfloader_load(_loader, _filename) (*(_loader)->load)(_loader, _filename) + + +#define fluid_sfont_delete_internal(_sf) ( ((_sf) && (_sf)->free)? (*(_sf)->free)(_sf) : 0) + + +#define fluid_preset_delete_internal(_preset) \ + { if ((_preset) && (_preset)->free) { (*(_preset)->free)(_preset); }} + +#define fluid_preset_noteon(_preset,_synth,_ch,_key,_vel) \ + (*(_preset)->noteon)(_preset,_synth,_ch,_key,_vel) + +#define fluid_preset_notify(_preset,_reason,_chan) \ + ( ((_preset) && (_preset)->notify) ? (*(_preset)->notify)(_preset,_reason,_chan) : FLUID_OK ) + + +#define fluid_sample_incr_ref(_sample) { (_sample)->refcount++; } + +#define fluid_sample_decr_ref(_sample) \ + (_sample)->refcount--; \ + if (((_sample)->refcount == 0) && ((_sample)->notify)) \ + (*(_sample)->notify)(_sample, FLUID_SAMPLE_DONE); + + + +/** + * File callback structure to enable custom soundfont loading (e.g. from memory). + */ +struct _fluid_file_callbacks_t +{ + fluid_sfloader_callback_open_t fopen; + fluid_sfloader_callback_read_t fread; + fluid_sfloader_callback_seek_t fseek; + fluid_sfloader_callback_close_t fclose; + fluid_sfloader_callback_tell_t ftell; +}; + +/** + * SoundFont loader structure. + */ +struct _fluid_sfloader_t +{ + void *data; /**< User defined data pointer used by _fluid_sfloader_t::load() */ + + /** Callback structure specifying file operations used during soundfont loading to allow custom loading, such as from memory */ + fluid_file_callbacks_t file_callbacks; + + fluid_sfloader_free_t free; + + fluid_sfloader_load_t load; +}; + +/** + * Virtual SoundFont instance structure. + */ +struct _fluid_sfont_t +{ + void *data; /**< User defined data */ + int id; /**< SoundFont ID */ + int refcount; /**< SoundFont reference count (1 if no presets referencing it) */ + int bankofs; /**< Bank offset */ + + fluid_sfont_free_t free; + + fluid_sfont_get_name_t get_name; + + fluid_sfont_get_preset_t get_preset; + + fluid_sfont_iteration_start_t iteration_start; + + fluid_sfont_iteration_next_t iteration_next; +}; + +/** + * Virtual SoundFont preset. + */ +struct _fluid_preset_t +{ + void *data; /**< User supplied data */ + fluid_sfont_t *sfont; /**< Parent virtual SoundFont */ + + fluid_preset_free_t free; + + fluid_preset_get_name_t get_name; + + fluid_preset_get_banknum_t get_banknum; + + fluid_preset_get_num_t get_num; + + fluid_preset_noteon_t noteon; + + /** + * Virtual SoundFont preset notify method. + * @param preset Virtual SoundFont preset + * @param reason #FLUID_PRESET_SELECTED or #FLUID_PRESET_UNSELECTED + * @param chan MIDI channel number + * @return Should return #FLUID_OK + * + * Implement this optional method if the preset needs to be notified about + * preset select and unselect events. + * + * This method may be called from within synthesis context and therefore + * should be as efficient as possible and not perform any operations considered + * bad for realtime audio output (memory allocations and other OS calls). + */ + int (*notify)(fluid_preset_t *preset, int reason, int chan); +}; + +/** + * Virtual SoundFont sample. + */ +struct _fluid_sample_t +{ + char name[21]; /**< Sample name */ + + /* The following four sample pointers store the original pointers from the Soundfont + * file. They are never changed after loading and are used to re-create the + * actual sample pointers after a sample has been unloaded and loaded again. The + * actual sample pointers get modified during loading for SF3 (compressed) samples + * and individually loaded SF2 samples. */ + unsigned int source_start; + unsigned int source_end; + unsigned int source_loopstart; + unsigned int source_loopend; + + unsigned int start; /**< Start index */ + unsigned int end; /**< End index, index of last valid sample point (contrary to SF spec) */ + unsigned int loopstart; /**< Loop start index */ + unsigned int loopend; /**< Loop end index, first point following the loop (superimposed on loopstart) */ + + unsigned int samplerate; /**< Sample rate */ + int origpitch; /**< Original pitch (MIDI note number, 0-127) */ + int pitchadj; /**< Fine pitch adjustment (+/- 99 cents) */ + int sampletype; /**< Specifies the type of this sample as indicated by the #fluid_sample_type enum */ + int auto_free; /**< TRUE if _fluid_sample_t::data and _fluid_sample_t::data24 should be freed upon sample destruction */ + short *data; /**< Pointer to the sample's 16 bit PCM data */ + char *data24; /**< If not NULL, pointer to the least significant byte counterparts of each sample data point in order to create 24 bit audio samples */ + + int amplitude_that_reaches_noise_floor_is_valid; /**< Indicates if \a amplitude_that_reaches_noise_floor is valid (TRUE), set to FALSE initially to calculate. */ + double amplitude_that_reaches_noise_floor; /**< The amplitude at which the sample's loop will be below the noise floor. For voice off optimization, calculated automatically. */ + + unsigned int refcount; /**< Count of voices using this sample */ + int preset_count; /**< Count of selected presets using this sample (used for dynamic sample loading) */ + + /** + * Implement this function to receive notification when sample is no longer used. + * @param sample Virtual SoundFont sample + * @param reason #FLUID_SAMPLE_DONE only currently + * @return Should return #FLUID_OK + */ + int (*notify)(fluid_sample_t *sample, int reason); +}; + + +#endif /* _PRIV_FLUID_SFONT_H */ diff --git a/libs/fluidsynth/src/synth/fluid_chan.c b/libs/fluidsynth/src/synth/fluid_chan.c new file mode 100644 index 00000000000..0f2eecb44e0 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_chan.c @@ -0,0 +1,732 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_chan.h" +#include "fluid_mod.h" +#include "fluid_synth.h" +#include "fluid_sfont.h" + +/* Field shift amounts for sfont_bank_prog bit field integer */ +#define PROG_SHIFTVAL 0 +#define BANK_SHIFTVAL 8 +#define SFONT_SHIFTVAL 22 + +/* Field mask values for sfont_bank_prog bit field integer */ +#define PROG_MASKVAL 0x000000FF /* Bit 7 is used to indicate unset state */ +#define BANK_MASKVAL 0x003FFF00 +#define BANKLSB_MASKVAL 0x00007F00 +#define BANKMSB_MASKVAL 0x003F8000 +#define SFONT_MASKVAL 0xFFC00000 + + +static void fluid_channel_init(fluid_channel_t *chan); + + +fluid_channel_t * +new_fluid_channel(fluid_synth_t *synth, int num) +{ + fluid_channel_t *chan; + + chan = FLUID_NEW(fluid_channel_t); + + if(chan == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + chan->synth = synth; + chan->channum = num; + chan->preset = NULL; + chan->tuning = NULL; + + fluid_channel_init(chan); + fluid_channel_init_ctrl(chan, 0); + + return chan; +} + +static void +fluid_channel_init(fluid_channel_t *chan) +{ + fluid_preset_t *newpreset; + int i, prognum, banknum; + + chan->sostenuto_orderid = 0; + /*--- Init poly/mono modes variables --------------------------------------*/ + chan->mode = 0; + chan->mode_val = 0; + + /* monophonic list initialization */ + for(i = 0; i < FLUID_CHANNEL_SIZE_MONOLIST; i++) + { + chan->monolist[i].next = i + 1; + } + + chan->monolist[FLUID_CHANNEL_SIZE_MONOLIST - 1].next = 0; /* ending element chained to the 1st */ + chan->i_last = chan->n_notes = 0; /* clears the list */ + chan->i_first = chan->monolist[chan->i_last].next; /* first note index in the list */ + fluid_channel_clear_prev_note(chan); /* Mark previous note invalid */ + /*---*/ + chan->key_mono_sustained = INVALID_NOTE; /* No previous mono note sustained */ + chan->legatomode = FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER; /* Default mode */ + chan->portamentomode = FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY; /* Default mode */ + /*--- End of poly/mono initialization --------------------------------------*/ + + chan->channel_type = (chan->channum == 9) ? CHANNEL_TYPE_DRUM : CHANNEL_TYPE_MELODIC; + prognum = 0; + banknum = (chan->channel_type == CHANNEL_TYPE_DRUM) ? DRUM_INST_BANK : 0; + + chan->sfont_bank_prog = 0 << SFONT_SHIFTVAL | banknum << BANK_SHIFTVAL + | prognum << PROG_SHIFTVAL; + + newpreset = fluid_synth_find_preset(chan->synth, banknum, prognum); + fluid_channel_set_preset(chan, newpreset); + + chan->interp_method = FLUID_INTERP_DEFAULT; + chan->tuning_bank = 0; + chan->tuning_prog = 0; + chan->nrpn_select = 0; + chan->nrpn_active = 0; + + if(chan->tuning) + { + fluid_tuning_unref(chan->tuning, 1); + chan->tuning = NULL; + } +} + +/* + @param is_all_ctrl_off if nonzero, only resets some controllers, according to + https://www.midi.org/techspecs/rp15.php +*/ +void +fluid_channel_init_ctrl(fluid_channel_t *chan, int is_all_ctrl_off) +{ + int i; + + chan->channel_pressure = 0; + chan->pitch_bend = 0x2000; /* Range is 0x4000, pitch bend wheel starts in centered position */ + + for(i = 0; i < GEN_LAST; i++) + { + chan->gen[i] = 0.0f; + } + + if(is_all_ctrl_off) + { + for(i = 0; i < ALL_SOUND_OFF; i++) + { + if(i >= EFFECTS_DEPTH1 && i <= EFFECTS_DEPTH5) + { + continue; + } + + if(i >= SOUND_CTRL1 && i <= SOUND_CTRL10) + { + continue; + } + + if(i == BANK_SELECT_MSB || i == BANK_SELECT_LSB || i == VOLUME_MSB || + i == VOLUME_LSB || i == PAN_MSB || i == PAN_LSB || + i == BALANCE_MSB || i == BALANCE_LSB + ) + { + continue; + } + + fluid_channel_set_cc(chan, i, 0); + } + } + else + { + for(i = 0; i < 128; i++) + { + fluid_channel_set_cc(chan, i, 0); + } + + chan->previous_cc_breath = 0;/* Reset previous breath */ + } + /* Unconditionally clear PTC receive (issue #1050) */ + fluid_channel_clear_portamento(chan); + + /* Reset polyphonic key pressure on all voices */ + for(i = 0; i < 128; i++) + { + fluid_channel_set_key_pressure(chan, i, 0); + } + + /* Set RPN controllers to NULL state */ + fluid_channel_set_cc(chan, RPN_LSB, 127); + fluid_channel_set_cc(chan, RPN_MSB, 127); + + /* Set NRPN controllers to NULL state */ + fluid_channel_set_cc(chan, NRPN_LSB, 127); + fluid_channel_set_cc(chan, NRPN_MSB, 127); + + /* Expression (MSB & LSB) */ + fluid_channel_set_cc(chan, EXPRESSION_MSB, 127); + fluid_channel_set_cc(chan, EXPRESSION_LSB, 127); + + if(!is_all_ctrl_off) + { + + chan->pitch_wheel_sensitivity = 2; /* two semi-tones */ + + /* Just like panning, a value of 64 indicates no change for sound ctrls */ + for(i = SOUND_CTRL1; i <= SOUND_CTRL10; i++) + { + fluid_channel_set_cc(chan, i, 64); + } + + /* Volume / initial attenuation (MSB & LSB) */ + fluid_channel_set_cc(chan, VOLUME_MSB, 100); + fluid_channel_set_cc(chan, VOLUME_LSB, 0); + + /* Pan (MSB & LSB) */ + fluid_channel_set_cc(chan, PAN_MSB, 64); + fluid_channel_set_cc(chan, PAN_LSB, 0); + + /* Balance (MSB & LSB) */ + fluid_channel_set_cc(chan, BALANCE_MSB, 64); + fluid_channel_set_cc(chan, BALANCE_LSB, 0); + + /* Reverb */ + /* fluid_channel_set_cc (chan, EFFECTS_DEPTH1, 40); */ + /* Note: although XG standard specifies the default amount of reverb to + be 40, most people preferred having it at zero. + See https://lists.gnu.org/archive/html/fluid-dev/2009-07/msg00016.html */ + } +} + +/* Only called by delete_fluid_synth(), so no need to queue a preset free event */ +void +delete_fluid_channel(fluid_channel_t *chan) +{ + fluid_return_if_fail(chan != NULL); + + FLUID_FREE(chan); +} + +void +fluid_channel_reset(fluid_channel_t *chan) +{ + fluid_channel_init(chan); + fluid_channel_init_ctrl(chan, 0); +} + +/* Should only be called from synthesis context */ +int +fluid_channel_set_preset(fluid_channel_t *chan, fluid_preset_t *preset) +{ + fluid_sfont_t *sfont; + + if(chan->preset == preset) + { + return FLUID_OK; + } + + if(chan->preset) + { + sfont = chan->preset->sfont; + sfont->refcount--; + } + + fluid_preset_notify(chan->preset, FLUID_PRESET_UNSELECTED, chan->channum); + + chan->preset = preset; + + if(preset) + { + sfont = preset->sfont; + sfont->refcount++; + } + + fluid_preset_notify(preset, FLUID_PRESET_SELECTED, chan->channum); + + return FLUID_OK; +} + +/* Set SoundFont ID, MIDI bank and/or program. Use -1 to use current value. */ +void +fluid_channel_set_sfont_bank_prog(fluid_channel_t *chan, int sfontnum, + int banknum, int prognum) +{ + int oldval, newval, oldmask; + + newval = ((sfontnum != -1) ? sfontnum << SFONT_SHIFTVAL : 0) + | ((banknum != -1) ? banknum << BANK_SHIFTVAL : 0) + | ((prognum != -1) ? prognum << PROG_SHIFTVAL : 0); + + oldmask = ((sfontnum != -1) ? 0 : SFONT_MASKVAL) + | ((banknum != -1) ? 0 : BANK_MASKVAL) + | ((prognum != -1) ? 0 : PROG_MASKVAL); + + oldval = chan->sfont_bank_prog; + newval = (newval & ~oldmask) | (oldval & oldmask); + chan->sfont_bank_prog = newval; +} + +/* Set bank LSB 7 bits */ +void +fluid_channel_set_bank_lsb(fluid_channel_t *chan, int banklsb) +{ + int oldval, newval, style; + + style = chan->synth->bank_select; + + if(style == FLUID_BANK_STYLE_GM || + style == FLUID_BANK_STYLE_GS) + { + return; /* ignored */ + } + + oldval = chan->sfont_bank_prog; + + if(style == FLUID_BANK_STYLE_XG) + { + newval = (oldval & ~BANK_MASKVAL) | (banklsb << BANK_SHIFTVAL); + } + else /* style == FLUID_BANK_STYLE_MMA */ + { + newval = (oldval & ~BANKLSB_MASKVAL) | (banklsb << BANK_SHIFTVAL); + } + + chan->sfont_bank_prog = newval; +} + +/* Set bank MSB 7 bits */ +void +fluid_channel_set_bank_msb(fluid_channel_t *chan, int bankmsb) +{ + int oldval, newval, style; + + style = chan->synth->bank_select; + + if(style == FLUID_BANK_STYLE_XG) + { + /* XG bank, do drum-channel auto-switch */ + /* The number "120" was based on several keyboards having drums at 120 - 127, + reference: https://lists.nongnu.org/archive/html/fluid-dev/2011-02/msg00003.html */ + chan->channel_type = (120 <= bankmsb) ? CHANNEL_TYPE_DRUM : CHANNEL_TYPE_MELODIC; + return; + } + + if(style == FLUID_BANK_STYLE_GM || + chan->channel_type == CHANNEL_TYPE_DRUM) + { + return; /* ignored */ + } + + oldval = chan->sfont_bank_prog; + + if(style == FLUID_BANK_STYLE_GS) + { + newval = (oldval & ~BANK_MASKVAL) | (bankmsb << BANK_SHIFTVAL); + } + else /* style == FLUID_BANK_STYLE_MMA */ + { + newval = (oldval & ~BANKMSB_MASKVAL) | (bankmsb << (BANK_SHIFTVAL + 7)); + } + + chan->sfont_bank_prog = newval; + +} + +/* Get SoundFont ID, MIDI bank and/or program. Use NULL to ignore a value. */ +void +fluid_channel_get_sfont_bank_prog(fluid_channel_t *chan, int *sfont, + int *bank, int *prog) +{ + int sfont_bank_prog; + + sfont_bank_prog = chan->sfont_bank_prog; + + if(sfont) + { + *sfont = (sfont_bank_prog & SFONT_MASKVAL) >> SFONT_SHIFTVAL; + } + + if(bank) + { + *bank = (sfont_bank_prog & BANK_MASKVAL) >> BANK_SHIFTVAL; + } + + if(prog) + { + *prog = (sfont_bank_prog & PROG_MASKVAL) >> PROG_SHIFTVAL; + } +} + +/** + * Compute the pitch for a key after applying Fluidsynth's tuning functionality + * and channel coarse/fine tunings. + * @param chan fluid_channel_t + * @param key MIDI note number (0-127) + * @return the pitch of the key + */ +fluid_real_t fluid_channel_get_key_pitch(fluid_channel_t *chan, int key) +{ + if(chan->tuning) + { + return fluid_tuning_get_pitch(chan->tuning, key) + + 100.0f * fluid_channel_get_gen(chan, GEN_COARSETUNE) + + fluid_channel_get_gen(chan, GEN_FINETUNE); + } + else + { + return key * 100.0f; + } +} + +/** + * Updates legato/ staccato playing state + * The function is called: + * - on noteon before adding a note into the monolist. + * - on noteoff after removing a note out of the monolist. + * @param chan fluid_channel_t. +*/ +static void +fluid_channel_update_legato_staccato_state(fluid_channel_t *chan) +{ + /* Updates legato/ staccato playing state */ + if(chan->n_notes) + { + chan->mode |= FLUID_CHANNEL_LEGATO_PLAYING; /* Legato state */ + } + else + { + chan->mode &= ~ FLUID_CHANNEL_LEGATO_PLAYING; /* Staccato state */ + } +} + +/** + * Adds a note into the monophonic list. The function is part of the legato + * detector. fluid_channel_add_monolist() is intended to be called by + * fluid_synth_noteon_mono_LOCAL(). + * + * When a note is added at noteOn each element is use in the forward direction + * and indexed by i_last variable. + * + * @param chan fluid_channel_t. + * @param key MIDI note number (0-127). + * @param vel MIDI velocity (0-127, 0=noteoff). + * @param onenote. When 1 the function adds the note but the monophonic list + * keeps only one note (used on noteOn poly). + * Note: i_last index keeps a trace of the most recent note added. + * prev_note keeps a trace of the note prior i_last note. + * FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing state. + * + * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). +*/ +void +fluid_channel_add_monolist(fluid_channel_t *chan, unsigned char key, + unsigned char vel, unsigned char onenote) +{ + unsigned char i_last = chan->i_last; + /* Updates legato/ staccato playing state */ + fluid_channel_update_legato_staccato_state(chan); + + if(chan->n_notes) + { + /* keeps trace of the note prior last note */ + chan->prev_note = chan->monolist[i_last].note; + } + + /* moves i_last forward before writing new note */ + i_last = chan->monolist[i_last].next; + chan->i_last = i_last; /* now ilast indexes the last note */ + chan->monolist[i_last].note = key; /* we save note and velocity */ + chan->monolist[i_last].vel = vel; + + if(onenote) + { + /* clears monolist before one note addition */ + chan->i_first = i_last; + chan->n_notes = 0; + } + + if(chan->n_notes < FLUID_CHANNEL_SIZE_MONOLIST) + { + chan->n_notes++; /* updates n_notes */ + } + else + { + /* The end of buffer is reach. So circular motion for i_first */ + /* i_first index is moved forward */ + chan->i_first = chan->monolist[i_last].next; + } +} + +/** + * Searching a note in the monophonic list. The function is part of the legato + * detector. fluid_channel_search_monolist() is intended to be called by + * fluid_synth_noteoff_mono_LOCAL(). + * + * The search starts from the first note in the list indexed by i_first + + * @param chan fluid_channel_t. + * @param key MIDI note number (0-127) to search. + * @param i_prev pointer on returned index of the note prior the note to search. + * @return index of the note if find, FLUID_FAILED otherwise. + * + */ +int +fluid_channel_search_monolist(fluid_channel_t *chan, unsigned char key, int *i_prev) +{ + short n = chan->n_notes; /* number of notes in monophonic list */ + short j, i = chan->i_first; /* searching starts from i_first included */ + + for(j = 0 ; j < n ; j++) + { + if(chan->monolist[i].note == key) + { + if(i == chan->i_first) + { + /* tracking index of the previous note (i_prev) */ + for(j = chan->i_last ; n < FLUID_CHANNEL_SIZE_MONOLIST; n++) + { + j = chan->monolist[j].next; + } + + * i_prev = j; /* returns index of the previous note */ + } + + return i; /* returns index of the note to search */ + } + + * i_prev = i; /* tracking index of the previous note (i_prev) */ + i = chan->monolist[i].next; /* next element */ + } + + return FLUID_FAILED; /* not found */ +} + +/** + * removes a note from the monophonic list. The function is part of + * the legato detector. + * fluid_channel_remove_monolist() is intended to be called by + * fluid_synth_noteoff_mono_LOCAL(). + * + * When a note is removed at noteOff the element concerned is fast unlinked + * and relinked after the i_last element. + * + * @param chan fluid_channel_t. + * @param + * i, index of the note to remove. If i is invalid or the list is + * empty, the function do nothing and returns FLUID_FAILED. + * @param + * On input, i_prev is a pointer on index of the note previous i. + * On output i_prev is a pointer on index of the note previous i if i is the last note + * in the list,FLUID_FAILED otherwise. When the returned index is valid it means + * a legato detection on noteoff. + * + * Note: the following variables in Channel keeps trace of the situation. + * - i_last index keeps a trace of the most recent note played even if + * the list is empty. + * - prev_note keeps a trace of the note removed if it is i_last. + * - FLUID_CHANNEL_LEGATO_PLAYING bit keeps a trace of legato/staccato playing state. + * + * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). + */ +void +fluid_channel_remove_monolist(fluid_channel_t *chan, int i, int *i_prev) +{ + unsigned char i_last = chan->i_last; + + /* checks if index is valid */ + if(i < 0 || i >= FLUID_CHANNEL_SIZE_MONOLIST || !chan->n_notes) + { + * i_prev = FLUID_FAILED; + } + + /* The element is about to be removed and inserted between i_last and next */ + /* Note: when i is egal to i_last or egal to i_first, removing/inserting + isn't necessary */ + if(i == i_last) + { + /* Removing/Inserting isn't necessary */ + /* keeps trace of the note prior last note */ + chan->prev_note = chan->monolist[i_last].note; + /* moves i_last backward to the previous */ + chan->i_last = *i_prev; /* i_last index is moved backward */ + } + else + { + /* i is before i_last */ + if(i == chan->i_first) + { + /* Removing/inserting isn't necessary */ + /* i_first index is moved forward to the next element*/ + chan->i_first = chan->monolist[i].next; + } + else + { + /* i is between i_first and i_last */ + /* Unlinks element i and inserts after i_last */ + chan->monolist[* i_prev].next = chan->monolist[i].next; /* unlinks i */ + /*inserts i after i_last */ + chan->monolist[i].next = chan->monolist[i_last].next; + chan->monolist[i_last].next = i; + } + + * i_prev = FLUID_FAILED; + } + + chan->n_notes--; /* updates the number of note in the list */ + /* Updates legato/ staccato playing state */ + fluid_channel_update_legato_staccato_state(chan); +} + +/** + * On noteOff on a polyphonic channel,the monophonic list is fully flushed. + * + * @param chan fluid_channel_t. + * Note: i_last index keeps a trace of the most recent note played even if + * the list is empty. + * prev_note keeps a trace of the note i_last . + * FLUID_CHANNEL_LEGATO_PLAYING bit keeps a trace of legato/staccato playing. + */ +void fluid_channel_clear_monolist(fluid_channel_t *chan) +{ + /* keeps trace off the most recent note played */ + chan->prev_note = chan->monolist[chan->i_last].note; + + /* flushes the monolist */ + chan->i_first = chan->monolist[chan->i_last].next; + chan->n_notes = 0; + /* Update legato/ sataccato playing state */ + chan->mode &= ~ FLUID_CHANNEL_LEGATO_PLAYING; /* Staccato state */ +} + +/** + * On noteOn on a polyphonic channel,adds the note into the monophonic list + * keeping only this note. + * @param + * chan fluid_channel_t. + * key, vel, note and velocity added in the monolist + * Note: i_last index keeps a trace of the most recent note inserted. + * prev_note keeps a trace of the note prior i_last note. + * FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing. + */ +void fluid_channel_set_onenote_monolist(fluid_channel_t *chan, unsigned char key, + unsigned char vel) +{ + fluid_channel_add_monolist(chan, key, vel, 1); +} + +/** + * The function changes the state (Valid/Invalid) of the previous note played in + * a staccato manner (fluid_channel_prev_note()). + * When potamento mode 'each note' or 'staccato only' is selected, on next + * noteOn a portamento will be started from the most recent note played + * staccato. + * It will be possible that it isn't appropriate. To give the musician the + * possibility to choose a portamento from this note , prev_note will be forced + * to invalid state on noteOff if portamento pedal is Off. + * + * The function is intended to be called when the following event occurs: + * - On noteOff (in poly or mono mode), to mark prev_note invalid. + * - On Portamento Off(in poly or mono mode), to mark prev_note invalid. + * @param chan fluid_channel_t. + */ +void fluid_channel_invalid_prev_note_staccato(fluid_channel_t *chan) +{ + /* checks if the playing is staccato */ + if(!(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) + { + + /* checks if portamento pedal is off */ + if(! fluid_channel_portamento(chan)) + { + /* forces prev_note invalid */ + fluid_channel_clear_prev_note(chan); + } + } + + /* else prev_note still remains valid for next fromkey portamento */ +} + +/** + * The function handles poly/mono commutation on legato pedal On/Off. + * @param chan fluid_channel_t. + * @param value, value of the CC legato. + */ +void fluid_channel_cc_legato(fluid_channel_t *chan, int value) +{ + /* Special handling of the monophonic list */ + if(!(chan->mode & FLUID_CHANNEL_POLY_OFF) && chan->n_notes) /* The monophonic list have notes */ + { + if(value < 64) /* legato is released */ + { + /* returns from monophonic to polyphonic with notes in the monophonic list */ + + /* The monophonic list is flushed keeping last note only + Note: i_last index keeps a trace of the most recent note played. + prev_note keeps a trace of the note i_last. + FLUID_CHANNEL_LEGATO_PLAYING bit keeps trace of legato/staccato playing. + */ + chan->i_first = chan->i_last; + chan->n_notes = 1; + } + else /* legato is depressed */ + { + /* Inters in monophonic from polyphonic with note in monophonic list */ + /* Stops the running note to remain coherent with Breath Sync mode */ + if((chan->mode & FLUID_CHANNEL_BREATH_SYNC) && !fluid_channel_breath_msb(chan)) + { + fluid_synth_noteoff_monopoly(chan->synth, chan->channum, + fluid_channel_last_note(chan), 1); + } + } + } +} + +/** + * The function handles CC Breath On/Off detection. When a channel is in + * Breath Sync mode and in monophonic playing, the breath controller allows + * to trigger noteon/noteoff note when the musician starts to breath (noteon) and + * stops to breath (noteoff). + * @param chan fluid_channel_t. + * @param value, value of the CC Breath.. + */ +void fluid_channel_cc_breath_note_on_off(fluid_channel_t *chan, int value) +{ + if((chan->mode & FLUID_CHANNEL_BREATH_SYNC) && fluid_channel_is_playing_mono(chan) && + (chan->n_notes)) + { + /* The monophonic list isn't empty */ + if((value > 0) && (chan->previous_cc_breath == 0)) + { + /* CC Breath On detection */ + fluid_synth_noteon_mono_staccato(chan->synth, chan->channum, + fluid_channel_last_note(chan), + fluid_channel_last_vel(chan)); + } + else if((value == 0) && (chan->previous_cc_breath > 0)) + { + /* CC Breath Off detection */ + fluid_synth_noteoff_monopoly(chan->synth, chan->channum, + fluid_channel_last_note(chan), 1); + } + } + + chan->previous_cc_breath = value; +} diff --git a/libs/fluidsynth/src/synth/fluid_chan.h b/libs/fluidsynth/src/synth/fluid_chan.h new file mode 100644 index 00000000000..96eb02e37f5 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_chan.h @@ -0,0 +1,276 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_CHAN_H +#define _FLUID_CHAN_H + +#include "fluidsynth_priv.h" +#include "fluid_midi.h" +#include "fluid_tuning.h" + +/* The mononophonic list is part of the legato detector for monophonic mode */ +/* see fluid_synth_monopoly.c about a description of the legato detector device */ +/* Size of the monophonic list + - 1 is the minimum. it allows playing legato passage of any number + of notes on noteon only. + - Size above 1 allows playing legato on noteon but also on noteOff. + This allows the musician to play fast trills. + This feature is particularly usful when the MIDI input device is a keyboard. + Choosing a size of 10 is sufficient (because most musicians have only 10 + fingers when playing a monophonic instrument). +*/ +#define FLUID_CHANNEL_SIZE_MONOLIST 10 + +/* + + The monophonic list + +------------------------------------------------+ + | +----+ +----+ +----+ +----+ | + | |note| |note| |note| |note| | + +--->|vel |-->|vel |-->....-->|vel |-->|vel |----+ + +----+ +----+ +----+ +----+ + /|\ /|\ + | | + i_first i_last + + The monophonic list is a circular buffer of FLUID_CHANNEL_SIZE_MONOLIST elements. + Each element is linked forward at initialisation time. + - when a note is added at noteOn (see fluid_channel_add_monolist()) each + element is use in the forward direction and indexed by i_last variable. + - when a note is removed at noteOff (see fluid_channel_remove_monolist()), + the element concerned is fast unlinked and relinked after the i_last element. + + The most recent note added is indexed by i_last. + The most ancient note added is the first note indexed by i_first. i_first is + moving in the forward direction in a circular manner. + +*/ +struct mononote +{ + unsigned char next; /* next note */ + unsigned char note; /* note */ + unsigned char vel; /* velocity */ +}; + +/* + * fluid_channel_t + * + * Mutual exclusion notes (as of 1.1.2): + * None - everything should have been synchronized by the synth. + */ +struct _fluid_channel_t +{ + fluid_synth_t *synth; /**< Parent synthesizer instance */ + int channum; /**< MIDI channel number */ + + /* Poly Mono variables see macro access description */ + int mode; /**< Poly Mono mode */ + int mode_val; /**< number of channel in basic channel group */ + + /* monophonic list - legato detector */ + unsigned char i_first; /**< First note index */ + unsigned char i_last; /**< most recent note index since the most recent add */ + unsigned char prev_note; /**< previous note of the most recent add/remove */ + unsigned char n_notes; /**< actual number of notes in the list */ + struct mononote monolist[FLUID_CHANNEL_SIZE_MONOLIST]; /**< monophonic list */ + + unsigned char key_mono_sustained; /**< previous sustained monophonic note */ + unsigned char previous_cc_breath; /**< Previous Breath */ + enum fluid_channel_legato_mode legatomode; /**< legato mode */ + enum fluid_channel_portamento_mode portamentomode; /**< portamento mode */ + /*- End of Poly/mono variables description */ + + unsigned char cc[128]; /**< MIDI controller values from [0;127] */ + unsigned char key_pressure[128]; /**< MIDI polyphonic key pressure from [0;127] */ + + /* Drum channel flag, CHANNEL_TYPE_MELODIC, or CHANNEL_TYPE_DRUM. */ + enum fluid_midi_channel_type channel_type; + enum fluid_interp interp_method; /**< Interpolation method (enum fluid_interp) */ + + unsigned char channel_pressure; /**< MIDI channel pressure from [0;127] */ + unsigned char pitch_wheel_sensitivity; /**< Current pitch wheel sensitivity */ + short pitch_bend; /**< Current pitch bend value */ + /* Sostenuto order id gives the order of SostenutoOn event. + * This value is useful to known when the sostenuto pedal is depressed + * (before or after a key note). We need to compare SostenutoOrderId with voice id. + */ + unsigned int sostenuto_orderid; + + int tuning_bank; /**< Current tuning bank number */ + int tuning_prog; /**< Current tuning program number */ + fluid_tuning_t *tuning; /**< Micro tuning */ + + fluid_preset_t *preset; /**< Selected preset */ + int sfont_bank_prog; /**< SoundFont ID (bit 21-31), bank (bit 7-20), program (bit 0-6) */ + + /* NRPN system */ + enum fluid_gen_type nrpn_select; /* Generator ID of SoundFont NRPN message */ + char nrpn_active; /* 1 if data entry CCs are for NRPN, 0 if RPN */ + + /* The values of the generators, set by NRPN messages, or by + * fluid_synth_set_gen(), are cached in the channel so they can be + * applied to future notes. They are copied to a voice's generators + * in fluid_voice_init(), which calls fluid_gen_init(). */ + fluid_real_t gen[GEN_LAST]; +}; + +fluid_channel_t *new_fluid_channel(fluid_synth_t *synth, int num); +void fluid_channel_init_ctrl(fluid_channel_t *chan, int is_all_ctrl_off); +void delete_fluid_channel(fluid_channel_t *chan); +void fluid_channel_reset(fluid_channel_t *chan); +int fluid_channel_set_preset(fluid_channel_t *chan, fluid_preset_t *preset); +void fluid_channel_set_sfont_bank_prog(fluid_channel_t *chan, int sfont, + int bank, int prog); +void fluid_channel_set_bank_lsb(fluid_channel_t *chan, int banklsb); +void fluid_channel_set_bank_msb(fluid_channel_t *chan, int bankmsb); +void fluid_channel_get_sfont_bank_prog(fluid_channel_t *chan, int *sfont, + int *bank, int *prog); +fluid_real_t fluid_channel_get_key_pitch(fluid_channel_t *chan, int key); + +#define fluid_channel_get_preset(chan) ((chan)->preset) +#define fluid_channel_set_cc(chan, num, val) \ + ((chan)->cc[num] = (val)) +#define fluid_channel_get_cc(chan, num) \ + ((chan)->cc[num]) +#define fluid_channel_get_key_pressure(chan, key) \ + ((chan)->key_pressure[key]) +#define fluid_channel_set_key_pressure(chan, key, val) \ + ((chan)->key_pressure[key] = (val)) +#define fluid_channel_get_channel_pressure(chan) \ + ((chan)->channel_pressure) +#define fluid_channel_set_channel_pressure(chan, val) \ + ((chan)->channel_pressure = (val)) +#define fluid_channel_get_pitch_bend(chan) \ + ((chan)->pitch_bend) +#define fluid_channel_set_pitch_bend(chan, val) \ + ((chan)->pitch_bend = (val)) +#define fluid_channel_get_pitch_wheel_sensitivity(chan) \ + ((chan)->pitch_wheel_sensitivity) +#define fluid_channel_set_pitch_wheel_sensitivity(chan, val) \ + ((chan)->pitch_wheel_sensitivity = (val)) +#define fluid_channel_get_num(chan) ((chan)->channum) +#define fluid_channel_set_interp_method(chan, new_method) \ + ((chan)->interp_method = (new_method)) +#define fluid_channel_get_interp_method(chan) \ + ((chan)->interp_method); +#define fluid_channel_set_tuning(_c, _t) { (_c)->tuning = _t; } +#define fluid_channel_has_tuning(_c) ((_c)->tuning != NULL) +#define fluid_channel_get_tuning(_c) ((_c)->tuning) +#define fluid_channel_get_tuning_bank(chan) \ + ((chan)->tuning_bank) +#define fluid_channel_set_tuning_bank(chan, bank) \ + ((chan)->tuning_bank = (bank)) +#define fluid_channel_get_tuning_prog(chan) \ + ((chan)->tuning_prog) +#define fluid_channel_set_tuning_prog(chan, prog) \ + ((chan)->tuning_prog = (prog)) +#define fluid_channel_portamentotime(_c) \ + ((_c)->cc[PORTAMENTO_TIME_MSB] * 128 + (_c)->cc[PORTAMENTO_TIME_LSB]) +#define fluid_channel_portamento(_c) ((_c)->cc[PORTAMENTO_SWITCH] >= 64) +#define fluid_channel_breath_msb(_c) ((_c)->cc[BREATH_MSB] > 0) +#define fluid_channel_clear_portamento(_c) ((_c)->cc[PORTAMENTO_CTRL] = INVALID_NOTE) +#define fluid_channel_legato(_c) ((_c)->cc[LEGATO_SWITCH] >= 64) +#define fluid_channel_sustained(_c) ((_c)->cc[SUSTAIN_SWITCH] >= 64) +#define fluid_channel_sostenuto(_c) ((_c)->cc[SOSTENUTO_SWITCH] >= 64) +#define fluid_channel_set_gen(_c, _n, _v) { (_c)->gen[_n] = _v; } +#define fluid_channel_get_gen(_c, _n) ((_c)->gen[_n]) +#define fluid_channel_get_min_note_length_ticks(chan) \ + ((chan)->synth->min_note_length_ticks) + +/* Macros interface to poly/mono mode variables */ +#define MASK_BASICCHANINFOS (FLUID_CHANNEL_MODE_MASK|FLUID_CHANNEL_BASIC|FLUID_CHANNEL_ENABLED) +/* Set the basic channel infos for a MIDI basic channel */ +#define fluid_channel_set_basic_channel_info(chan,Infos) \ + (chan->mode = (chan->mode & ~MASK_BASICCHANINFOS) | (Infos & MASK_BASICCHANINFOS)) +/* Reset the basic channel infos for a MIDI basic channel */ +#define fluid_channel_reset_basic_channel_info(chan) (chan->mode &= ~MASK_BASICCHANINFOS) + +/* Macros interface to breath variables */ +#define FLUID_CHANNEL_BREATH_MASK (FLUID_CHANNEL_BREATH_POLY|FLUID_CHANNEL_BREATH_MONO|FLUID_CHANNEL_BREATH_SYNC) +/* Set the breath infos for a MIDI channel */ +#define fluid_channel_set_breath_info(chan,BreathInfos) \ +(chan->mode = (chan->mode & ~FLUID_CHANNEL_BREATH_MASK) | (BreathInfos & FLUID_CHANNEL_BREATH_MASK)) +/* Get the breath infos for a MIDI channel */ +#define fluid_channel_get_breath_info(chan) (chan->mode & FLUID_CHANNEL_BREATH_MASK) + +/* Returns true when channel is mono or legato is on */ +#define fluid_channel_is_playing_mono(chan) ((chan->mode & FLUID_CHANNEL_POLY_OFF) ||\ + fluid_channel_legato(chan)) + +/* Macros interface to monophonic list variables */ +#define INVALID_NOTE (255) +/* Returns true when a note is a valid note */ +#define fluid_channel_is_valid_note(n) (n != INVALID_NOTE) +/* Marks prev_note as invalid. */ +#define fluid_channel_clear_prev_note(chan) (chan->prev_note = INVALID_NOTE) + +/* Returns the most recent note from i_last entry of the monophonic list */ +#define fluid_channel_last_note(chan) (chan->monolist[chan->i_last].note) + +/* Returns the most recent velocity from i_last entry of the monophonic list */ +#define fluid_channel_last_vel(chan) (chan->monolist[chan->i_last].vel) + +/* + prev_note is used to determine fromkey_portamento as well as + fromkey_legato (see fluid_synth_get_fromkey_portamento_legato()). + + prev_note is updated on noteOn/noteOff mono by the legato detector as this: + - On noteOn mono, before adding a new note into the monolist,the most + recent note in the list (i.e at i_last position) is kept in prev_note. + - Similarly, on noteOff mono , before removing a note out of the monolist, + the most recent note (i.e those at i_last position) is kept in prev_note. +*/ +#define fluid_channel_prev_note(chan) (chan->prev_note) + +/* Interface to poly/mono mode variables */ +enum fluid_channel_mode_flags_internal +{ + FLUID_CHANNEL_BASIC = 0x04, /**< if flag set the corresponding midi channel is a basic channel */ + FLUID_CHANNEL_ENABLED = 0x08, /**< if flag set the corresponding midi channel is enabled, else disabled, i.e. channel ignores any MIDI messages */ + + /* + FLUID_CHANNEL_LEGATO_PLAYING bit of channel mode keeps trace of the legato /staccato + state playing. + FLUID_CHANNEL_LEGATO_PLAYING bit is updated on noteOn/noteOff mono by the legato detector: + - On noteOn, before inserting a new note into the monolist. + - On noteOff, after removing a note out of the monolist. + + - On noteOn, this state is used by fluid_synth_noteon_mono_LOCAL() + to play the current note legato or staccato. + - On noteOff, this state is used by fluid_synth_noteoff_mono_LOCAL() + to play the current noteOff legato with the most recent note. + */ + /* bit7, 1: means legato playing , 0: means staccato playing */ + FLUID_CHANNEL_LEGATO_PLAYING = 0x80 +}; + +/* End of interface to monophonic list variables */ + +void fluid_channel_add_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel, unsigned char onenote); +int fluid_channel_search_monolist(fluid_channel_t *chan, unsigned char key, int *i_prev); +void fluid_channel_remove_monolist(fluid_channel_t *chan, int i, int *i_prev); +void fluid_channel_clear_monolist(fluid_channel_t *chan); +void fluid_channel_set_onenote_monolist(fluid_channel_t *chan, unsigned char key, unsigned char vel); +void fluid_channel_invalid_prev_note_staccato(fluid_channel_t *chan); +void fluid_channel_cc_legato(fluid_channel_t *chan, int value); +void fluid_channel_cc_breath_note_on_off(fluid_channel_t *chan, int value); + + +#endif /* _FLUID_CHAN_H */ diff --git a/libs/fluidsynth/src/synth/fluid_event.c b/libs/fluidsynth/src/synth/fluid_event.c new file mode 100644 index 00000000000..48b781bd7bc --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_event.c @@ -0,0 +1,863 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +/* + 2002 : API design by Peter Hanappe and Antoine Schmitt + August 2002 : Implementation by Antoine Schmitt as@gratin.org + as part of the infiniteCD author project + http://www.infiniteCD.org/ + Oct4.2002 : AS : corrected bug in heap allocation, that caused a crash during sequencer free. +*/ + + +#include "fluid_event.h" +#include "fluidsynth_priv.h" +#include "fluid_midi.h" + +/*************************************************************** + * + * SEQUENCER EVENTS + */ + +/* Event alloc/free */ + +void +fluid_event_clear(fluid_event_t *evt) +{ + FLUID_MEMSET(evt, 0, sizeof(fluid_event_t)); + + // by default, no type + evt->dest = -1; + evt->src = -1; + evt->type = -1; + evt->id = -1; +} + +/** + * Create a new sequencer event structure. + * @return New sequencer event structure or NULL if out of memory + */ +fluid_event_t * +new_fluid_event() +{ + fluid_event_t *evt; + + evt = FLUID_NEW(fluid_event_t); + + if(evt == NULL) + { + FLUID_LOG(FLUID_PANIC, "event: Out of memory\n"); + return NULL; + } + + fluid_event_clear(evt); + + return(evt); +} + +/** + * Delete a sequencer event structure. + * @param evt Sequencer event structure created by new_fluid_event(). + */ +void +delete_fluid_event(fluid_event_t *evt) +{ + fluid_return_if_fail(evt != NULL); + + FLUID_FREE(evt); +} + +/** + * Set the time field of a sequencer event. + * @internal + * @param evt Sequencer event structure + * @param time Time value to assign + */ +void +fluid_event_set_time(fluid_event_t *evt, unsigned int time) +{ + evt->time = time; +} + +void +fluid_event_set_id(fluid_event_t *evt, fluid_note_id_t id) +{ + evt->id = id; +} + +/** + * Set source of a sequencer event. \c src must be a unique sequencer ID or -1 if not set. + * @param evt Sequencer event structure + * @param src Unique sequencer ID + */ +void +fluid_event_set_source(fluid_event_t *evt, fluid_seq_id_t src) +{ + evt->src = src; +} + +/** + * Set destination of this sequencer event, i.e. the sequencer client this event will be sent to. \c dest must be a unique sequencer ID. + * @param evt Sequencer event structure + * @param dest The destination unique sequencer ID + */ +void +fluid_event_set_dest(fluid_event_t *evt, fluid_seq_id_t dest) +{ + evt->dest = dest; +} + +/** + * Set a sequencer event to be a timer event. + * @param evt Sequencer event structure + * @param data User supplied data pointer + */ +void +fluid_event_timer(fluid_event_t *evt, void *data) +{ + evt->type = FLUID_SEQ_TIMER; + evt->data = data; +} + +/** + * Set a sequencer event to be a note on event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param key MIDI note number (0-127) + * @param vel MIDI velocity value (0-127) + * @note Since fluidsynth 2.2.2, this function will give you a #FLUID_SEQ_NOTEOFF when + * called with @p vel being zero. + */ +void +fluid_event_noteon(fluid_event_t *evt, int channel, short key, short vel) +{ + if(vel == 0) + { + fluid_event_noteoff(evt, channel, key); + return; + } + + evt->type = FLUID_SEQ_NOTEON; + evt->channel = channel; + evt->key = key; + evt->vel = vel; +} + +/** + * Set a sequencer event to be a note off event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param key MIDI note number (0-127) + */ +void +fluid_event_noteoff(fluid_event_t *evt, int channel, short key) +{ + evt->type = FLUID_SEQ_NOTEOFF; + evt->channel = channel; + evt->key = key; +} + +/** + * Set a sequencer event to be a note duration event. + * + * Before fluidsynth 2.2.0, this event type was naively implemented when used in conjunction with fluid_sequencer_register_fluidsynth(), + * because it simply enqueued a fluid_event_noteon() and fluid_event_noteoff(). + * A handling for overlapping notes was not implemented. Starting with 2.2.0, this changes: If a fluid_event_note() is already playing, + * while another fluid_event_note() arrives on the same @c channel and @c key, the earlier event will be canceled. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param key MIDI note number (0-127) + * @param vel MIDI velocity value (1-127) + * @param duration Duration of note in the time scale used by the sequencer + * + * @note The application should decide whether to use only Notes with duration, or separate NoteOn and NoteOff events. + * @warning Calling this function with @p vel or @p duration being zero results in undefined behavior! + */ +void +fluid_event_note(fluid_event_t *evt, int channel, short key, short vel, unsigned int duration) +{ + evt->type = FLUID_SEQ_NOTE; + evt->channel = channel; + evt->key = key; + evt->vel = vel; + evt->duration = duration; +} + +/** + * Set a sequencer event to be an all sounds off event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + */ +void +fluid_event_all_sounds_off(fluid_event_t *evt, int channel) +{ + evt->type = FLUID_SEQ_ALLSOUNDSOFF; + evt->channel = channel; +} + +/** + * Set a sequencer event to be a all notes off event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + */ +void +fluid_event_all_notes_off(fluid_event_t *evt, int channel) +{ + evt->type = FLUID_SEQ_ALLNOTESOFF; + evt->channel = channel; +} + +/** + * Set a sequencer event to be a bank select event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param bank_num MIDI bank number (0-16383) + */ +void +fluid_event_bank_select(fluid_event_t *evt, int channel, short bank_num) +{ + evt->type = FLUID_SEQ_BANKSELECT; + evt->channel = channel; + evt->control = bank_num; +} + +/** + * Set a sequencer event to be a program change event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val MIDI program number (0-127) + */ +void +fluid_event_program_change(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_PROGRAMCHANGE; + evt->channel = channel; + evt->value = val; +} + +/** + * Set a sequencer event to be a program select event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param sfont_id SoundFont ID number + * @param bank_num MIDI bank number (0-16383) + * @param preset_num MIDI preset number (0-127) + */ +void +fluid_event_program_select(fluid_event_t *evt, int channel, + unsigned int sfont_id, short bank_num, short preset_num) +{ + evt->type = FLUID_SEQ_PROGRAMSELECT; + evt->channel = channel; + evt->duration = sfont_id; + evt->value = preset_num; + evt->control = bank_num; +} + +/** + * Set a sequencer event to be a pitch bend event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param pitch MIDI pitch bend value (0-16383, 8192 = no bend) + */ +void +fluid_event_pitch_bend(fluid_event_t *evt, int channel, int pitch) +{ + evt->type = FLUID_SEQ_PITCHBEND; + evt->channel = channel; + + if(pitch < 0) + { + pitch = 0; + } + + if(pitch > 16383) + { + pitch = 16383; + } + + evt->pitch = pitch; +} + +/** + * Set a sequencer event to be a pitch wheel sensitivity event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param value MIDI pitch wheel sensitivity value in semitones + */ +void +fluid_event_pitch_wheelsens(fluid_event_t *evt, int channel, int value) +{ + evt->type = FLUID_SEQ_PITCHWHEELSENS; + evt->channel = channel; + evt->value = value; +} + +/** + * Set a sequencer event to be a modulation event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val MIDI modulation value (0-127) + */ +void +fluid_event_modulation(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_MODULATION; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a MIDI sustain event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val MIDI sustain value (0-127) + */ +void +fluid_event_sustain(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_SUSTAIN; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a MIDI control change event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param control MIDI control number (0-127) + * @param val MIDI control value (0-127) + */ +void +fluid_event_control_change(fluid_event_t *evt, int channel, short control, int val) +{ + evt->type = FLUID_SEQ_CONTROLCHANGE; + evt->channel = channel; + evt->control = control; + evt->value = val; +} + +/** + * Set a sequencer event to be a stereo pan event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val MIDI panning value (0-127, 0=left, 64 = middle, 127 = right) + */ +void +fluid_event_pan(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_PAN; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a volume event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val Volume value (0-127) + */ +void +fluid_event_volume(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_VOLUME; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a reverb send event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val Reverb amount (0-127) + */ +void +fluid_event_reverb_send(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_REVERBSEND; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a chorus send event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val Chorus amount (0-127) + */ +void +fluid_event_chorus_send(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_CHORUSSEND; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + + +/** + * Set a sequencer event to be an unregistering event. + * @param evt Sequencer event structure + * @since 1.1.0 + */ +void +fluid_event_unregistering(fluid_event_t *evt) +{ + evt->type = FLUID_SEQ_UNREGISTERING; +} + +/** + * Set a sequencer event to be a scale change event. + * Useful for scheduling tempo changes. + * @param evt Sequencer event structure + * @param new_scale The new time scale to apply to the sequencer, see fluid_sequencer_set_time_scale() + * @since 2.2.0 + */ +void +fluid_event_scale(fluid_event_t *evt, double new_scale) +{ + evt->type = FLUID_SEQ_SCALE; + evt->scale = new_scale; +} + +/** + * Set a sequencer event to be a channel-wide aftertouch event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param val Aftertouch amount (0-127) + * @since 1.1.0 + */ +void +fluid_event_channel_pressure(fluid_event_t *evt, int channel, int val) +{ + evt->type = FLUID_SEQ_CHANNELPRESSURE; + evt->channel = channel; + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->value = val; +} + +/** + * Set a sequencer event to be a polyphonic aftertouch event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param key MIDI note number (0-127) + * @param val Aftertouch amount (0-127) + * @since 2.0.0 + */ +void +fluid_event_key_pressure(fluid_event_t *evt, int channel, short key, int val) +{ + evt->type = FLUID_SEQ_KEYPRESSURE; + evt->channel = channel; + + if(key < 0) + { + key = 0; + } + + if(key > 127) + { + key = 127; + } + + if(val < 0) + { + val = 0; + } + + if(val > 127) + { + val = 127; + } + + evt->key = key; + evt->value = val; +} + +/** + * Set a sequencer event to be a midi system reset event. + * @param evt Sequencer event structure + * @since 1.1.0 + */ +void +fluid_event_system_reset(fluid_event_t *evt) +{ + evt->type = FLUID_SEQ_SYSTEMRESET; +} + +/** + * Transforms an incoming MIDI event (from a MIDI driver or MIDI router) to a + * sequencer event. + * + * @param evt Sequencer event structure + * @param event MIDI event + * @return #FLUID_OK or #FLUID_FAILED + * + * @note This function copies the fields of the MIDI event into the provided + * sequencer event. Calling applications must create the sequencer event and set + * additional fields such as the source and destination of the sequencer event. + * + * @code{.cpp} + * // ... get MIDI event, e.g. using player_callback() + * + * // Send MIDI event to sequencer to play + * fluid_event_t *evt = new_fluid_event(); + * fluid_event_set_source(evt, -1); + * fluid_event_set_dest(evt, seqid); + * fluid_event_from_midi_event(evt, event); + * fluid_sequencer_send_at(sequencer, evt, 50, 0); // relative time + * delete_fluid_event(evt); + * @endcode + * + * @since 2.2.7 + */ +int fluid_event_from_midi_event(fluid_event_t *evt, const fluid_midi_event_t *event) +{ + int chan; + fluid_return_val_if_fail(event != NULL, FLUID_FAILED); + + chan = fluid_midi_event_get_channel(event); + + switch (fluid_midi_event_get_type(event)) + { + case NOTE_OFF: + fluid_event_noteoff(evt, chan, (short)fluid_midi_event_get_key(event)); + break; + + case NOTE_ON: + fluid_event_noteon(evt, + fluid_midi_event_get_channel(event), + (short)fluid_midi_event_get_key(event), + (short)fluid_midi_event_get_velocity(event)); + break; + + case CONTROL_CHANGE: + fluid_event_control_change(evt, + chan, + (short)fluid_midi_event_get_control(event), + (short)fluid_midi_event_get_value(event)); + break; + + case PROGRAM_CHANGE: + fluid_event_program_change(evt, chan, (short)fluid_midi_event_get_program(event)); + break; + + case PITCH_BEND: + fluid_event_pitch_bend(evt, chan, fluid_midi_event_get_pitch(event)); + break; + + case CHANNEL_PRESSURE: + fluid_event_channel_pressure(evt, chan, (short)fluid_midi_event_get_program(event)); + break; + + case KEY_PRESSURE: + fluid_event_key_pressure(evt, + chan, + (short)fluid_midi_event_get_key(event), + (short)fluid_midi_event_get_value(event)); + break; + + case MIDI_SYSTEM_RESET: + fluid_event_system_reset(evt); + break; + + default: /* Not yet implemented */ + return FLUID_FAILED; + } + + return FLUID_OK; +} + +/* + * Accessing event data + */ + +/** + * Get the event type (#fluid_seq_event_type) field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Event type (#fluid_seq_event_type). + */ +int fluid_event_get_type(fluid_event_t *evt) +{ + return evt->type; +} + +/** + * @internal + * Get the time field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Time value + */ +unsigned int fluid_event_get_time(fluid_event_t *evt) +{ + return evt->time; +} + +/** + * @internal + * Get the time field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Time value + */ +fluid_note_id_t fluid_event_get_id(fluid_event_t *evt) +{ + return evt->id; +} + +/** + * Get the source sequencer client from a sequencer event structure. + * @param evt Sequencer event structure + * @return source field of the sequencer event + */ +fluid_seq_id_t fluid_event_get_source(fluid_event_t *evt) +{ + return evt->src; +} + +/** + * Get the dest sequencer client from a sequencer event structure. + * @param evt Sequencer event structure + * @return dest field of the sequencer event + */ +fluid_seq_id_t fluid_event_get_dest(fluid_event_t *evt) +{ + return evt->dest; +} + +/** + * Get the MIDI channel field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI zero-based channel number + */ +int fluid_event_get_channel(fluid_event_t *evt) +{ + return evt->channel; +} + +/** + * Get the MIDI note field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI note number (0-127) + */ +short fluid_event_get_key(fluid_event_t *evt) +{ + return evt->key; +} + +/** + * Get the MIDI velocity field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI velocity value (0-127) + */ +short fluid_event_get_velocity(fluid_event_t *evt) + +{ + return evt->vel; +} + +/** + * Get the MIDI control number field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI control number (0-127) + */ +short fluid_event_get_control(fluid_event_t *evt) +{ + return evt->control; +} + +/** + * Get the value field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Value field of event. + * + * The Value field is used by the following event types: + * #FLUID_SEQ_PROGRAMCHANGE, #FLUID_SEQ_PROGRAMSELECT (preset_num), + * #FLUID_SEQ_PITCHWHEELSENS, #FLUID_SEQ_MODULATION, #FLUID_SEQ_SUSTAIN, + * #FLUID_SEQ_CONTROLCHANGE, #FLUID_SEQ_PAN, #FLUID_SEQ_VOLUME, + * #FLUID_SEQ_REVERBSEND, #FLUID_SEQ_CHORUSSEND. + */ +int fluid_event_get_value(fluid_event_t *evt) +{ + return evt->value; +} + +/** + * Get the data field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Data field of event. + * + * Used by the #FLUID_SEQ_TIMER event type. + */ +void *fluid_event_get_data(fluid_event_t *evt) +{ + return evt->data; +} + +/** + * Get the duration field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Note duration value in the time scale used by the sequencer (by default milliseconds) + * + * Used by the #FLUID_SEQ_NOTE event type. + */ +unsigned int fluid_event_get_duration(fluid_event_t *evt) +{ + return evt->duration; +} + +/** + * Get the MIDI bank field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI bank number (0-16383) + * + * Used by the #FLUID_SEQ_BANKSELECT and #FLUID_SEQ_PROGRAMSELECT + * event types. + */ +short fluid_event_get_bank(fluid_event_t *evt) +{ + return evt->control; +} + +/** + * Get the pitch field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI pitch bend pitch value (0-16383, 8192 = no bend) + * + * Used by the #FLUID_SEQ_PITCHBEND event type. + */ +int fluid_event_get_pitch(fluid_event_t *evt) +{ + return evt->pitch; +} + +/** + * Get the MIDI program field from a sequencer event structure. + * @param evt Sequencer event structure + * @return MIDI program number (0-127) + * + * Used by the #FLUID_SEQ_PROGRAMCHANGE and #FLUID_SEQ_PROGRAMSELECT + * event types. + */ +int +fluid_event_get_program(fluid_event_t *evt) +{ + return evt->value; +} + +/** + * Get the SoundFont ID field from a sequencer event structure. + * @param evt Sequencer event structure + * @return SoundFont identifier value. + * + * Used by the #FLUID_SEQ_PROGRAMSELECT event type. + */ +unsigned int +fluid_event_get_sfont_id(fluid_event_t *evt) +{ + return evt->duration; +} + +/** + * Gets time scale field from a sequencer event structure. + * @param evt Sequencer event structure + * @return SoundFont identifier value. + * + * Used by the #FLUID_SEQ_SCALE event type. + */ +double fluid_event_get_scale(fluid_event_t *evt) +{ + return evt->scale; +} diff --git a/libs/fluidsynth/src/synth/fluid_event.h b/libs/fluidsynth/src/synth/fluid_event.h new file mode 100644 index 00000000000..4a0cca5d93d --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_event.h @@ -0,0 +1,65 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_EVENT_PRIV_H +#define _FLUID_EVENT_PRIV_H + +#include "fluidsynth.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int fluid_note_id_t; + +/* Private data for event */ +/* ?? should be optimized in size, using unions */ +struct _fluid_event_t +{ + unsigned int time; + int type; + fluid_seq_id_t src; + fluid_seq_id_t dest; + int channel; + short key; + short vel; + short control; + int value; + fluid_note_id_t id; + int pitch; + unsigned int duration; + double scale; + void *data; +}; + +unsigned int fluid_event_get_time(fluid_event_t *evt); +void fluid_event_set_time(fluid_event_t *evt, unsigned int time); + +fluid_note_id_t fluid_event_get_id(fluid_event_t *evt); +void fluid_event_set_id(fluid_event_t *evt, fluid_note_id_t id); + +void fluid_event_clear(fluid_event_t *evt); + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUID_EVENT_PRIV_H */ diff --git a/libs/fluidsynth/src/synth/fluid_gen.c b/libs/fluidsynth/src/synth/fluid_gen.c new file mode 100644 index 00000000000..2ce2b0f74b5 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_gen.c @@ -0,0 +1,133 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#include "fluid_gen.h" +#include "fluid_chan.h" + + +#define _GEN(_name) GEN_ ## _name, #_name + + +/* See SFSpec21 $8.1.3 */ +static const fluid_gen_info_t fluid_gen_info[] = +{ + /* number/name init nrpn-scale min max def */ + { _GEN(STARTADDROFS), 1, 1, 0.0f, 1e10f, 0.0f }, + { _GEN(ENDADDROFS), 1, 1, -1e10f, 0.0f, 0.0f }, + { _GEN(STARTLOOPADDROFS), 1, 1, -1e10f, 1e10f, 0.0f }, + { _GEN(ENDLOOPADDROFS), 1, 1, -1e10f, 1e10f, 0.0f }, + { _GEN(STARTADDRCOARSEOFS), 0, 1, 0.0f, 1e10f, 0.0f }, + { _GEN(MODLFOTOPITCH), 1, 2, -12000.0f, 12000.0f, 0.0f }, + { _GEN(VIBLFOTOPITCH), 1, 2, -12000.0f, 12000.0f, 0.0f }, + { _GEN(MODENVTOPITCH), 1, 2, -12000.0f, 12000.0f, 0.0f }, + { _GEN(FILTERFC), 1, 2, 1500.0f, 13500.0f, 13500.0f }, + { _GEN(FILTERQ), 1, 1, 0.0f, 960.0f, 0.0f }, + { _GEN(MODLFOTOFILTERFC), 1, 2, -12000.0f, 12000.0f, 0.0f }, + { _GEN(MODENVTOFILTERFC), 1, 2, -12000.0f, 12000.0f, 0.0f }, + { _GEN(ENDADDRCOARSEOFS), 0, 1, -1e10f, 0.0f, 0.0f }, + { _GEN(MODLFOTOVOL), 1, 1, -960.0f, 960.0f, 0.0f }, + { _GEN(UNUSED1), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(CHORUSSEND), 1, 1, 0.0f, 1000.0f, 0.0f }, + { _GEN(REVERBSEND), 1, 1, 0.0f, 1000.0f, 0.0f }, + { _GEN(PAN), 1, 1, -500.0f, 500.0f, 0.0f }, + { _GEN(UNUSED2), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(UNUSED3), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(UNUSED4), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(MODLFODELAY), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(MODLFOFREQ), 1, 4, -16000.0f, 4500.0f, 0.0f }, + { _GEN(VIBLFODELAY), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(VIBLFOFREQ), 1, 4, -16000.0f, 4500.0f, 0.0f }, + { _GEN(MODENVDELAY), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(MODENVATTACK), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(MODENVHOLD), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(MODENVDECAY), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(MODENVSUSTAIN), 0, 1, 0.0f, 1000.0f, 0.0f }, + { _GEN(MODENVRELEASE), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(KEYTOMODENVHOLD), 0, 1, -1200.0f, 1200.0f, 0.0f }, + { _GEN(KEYTOMODENVDECAY), 0, 1, -1200.0f, 1200.0f, 0.0f }, + { _GEN(VOLENVDELAY), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(VOLENVATTACK), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(VOLENVHOLD), 1, 2, -12000.0f, 5000.0f, -12000.0f }, + { _GEN(VOLENVDECAY), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(VOLENVSUSTAIN), 0, 1, 0.0f, 1440.0f, 0.0f }, + { _GEN(VOLENVRELEASE), 1, 2, -12000.0f, 8000.0f, -12000.0f }, + { _GEN(KEYTOVOLENVHOLD), 0, 1, -1200.0f, 1200.0f, 0.0f }, + { _GEN(KEYTOVOLENVDECAY), 0, 1, -1200.0f, 1200.0f, 0.0f }, + { _GEN(INSTRUMENT), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(RESERVED1), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(KEYRANGE), 0, 0, 0.0f, 127.0f, 0.0f }, + { _GEN(VELRANGE), 0, 0, 0.0f, 127.0f, 0.0f }, + { _GEN(STARTLOOPADDRCOARSEOFS), 0, 1, -1e10f, 1e10f, 0.0f }, + { _GEN(KEYNUM), 1, 0, 0.0f, 127.0f, -1.0f }, + { _GEN(VELOCITY), 1, 1, 0.0f, 127.0f, -1.0f }, + { _GEN(ATTENUATION), 1, 1, 0.0f, 1440.0f, 0.0f }, + { _GEN(RESERVED2), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(ENDLOOPADDRCOARSEOFS), 0, 1, -1e10f, 1e10f, 0.0f }, + { _GEN(COARSETUNE), 0, 1, -120.0f, 120.0f, 0.0f }, + { _GEN(FINETUNE), 0, 1, -99.0f, 99.0f, 0.0f }, + { _GEN(SAMPLEID), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(SAMPLEMODE), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(RESERVED3), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(SCALETUNE), 0, 1, 0.0f, 1200.0f, 100.0f }, + { _GEN(EXCLUSIVECLASS), 0, 0, 0.0f, 0.0f, 0.0f }, + { _GEN(OVERRIDEROOTKEY), 1, 0, 0.0f, 127.0f, -1.0f }, + { _GEN(PITCH), 1, 0, 0.0f, 127.0f, 0.0f }, + { _GEN(CUSTOM_BALANCE), 1, 0, -960.0f, 960.0f, 0.0f }, + { _GEN(CUSTOM_FILTERFC), 1, 2, 0.0f, 22050.0f, 0.0f }, + { _GEN(CUSTOM_FILTERQ), 1, 1, 0.0f, 960.0f, 0.0f } +}; + +/* fluid_gen_init + * + * Set an array of generators to their initial value + */ +void +fluid_gen_init(fluid_gen_t *gen, fluid_channel_t *channel) +{ + int i; + + for(i = 0; i < GEN_LAST; i++) + { + gen[i].flags = GEN_UNUSED; + gen[i].mod = 0.0; + gen[i].nrpn = (channel == NULL) ? 0.0 : fluid_channel_get_gen(channel, i); + gen[i].val = fluid_gen_info[i].def; + } +} + +fluid_real_t fluid_gen_scale(int gen, float value) +{ + return (fluid_gen_info[gen].min + + value * (fluid_gen_info[gen].max - fluid_gen_info[gen].min)); +} + +fluid_real_t fluid_gen_scale_nrpn(int gen, int data) +{ + data = data - 8192; + fluid_clip(data, -8192, 8192); + return (fluid_real_t)(data * fluid_gen_info[gen].nrpn_scale); +} + + +const char *fluid_gen_name(int gen) +{ + return fluid_gen_info[gen].name; +} diff --git a/libs/fluidsynth/src/synth/fluid_gen.h b/libs/fluidsynth/src/synth/fluid_gen.h new file mode 100644 index 00000000000..b87e8d8a8c6 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_gen.h @@ -0,0 +1,67 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_GEN_H +#define _FLUID_GEN_H + +#include "fluidsynth_priv.h" + +typedef struct _fluid_gen_info_t +{ + char num; /* Generator number */ + char *name; + char init; /* Does the generator need to be initialized (not used) */ + char nrpn_scale; /* The scale to convert from NRPN (cfr. fluid_gen_map_nrpn()) */ + float min; /* The minimum value */ + float max; /* The maximum value */ + float def; /* The default value (cfr. fluid_gen_init()) */ +} fluid_gen_info_t; + +/* + * SoundFont generator structure. + */ +typedef struct _fluid_gen_t +{ + unsigned char flags; /**< Is the generator set or not (#fluid_gen_flags) */ + double val; /**< The nominal value */ + double mod; /**< Change by modulators */ + double nrpn; /**< Change by NRPN messages */ +} fluid_gen_t; + +/* + * Enum value for 'flags' field of #fluid_gen_t (not really flags). + */ +enum fluid_gen_flags +{ + GEN_UNUSED, /**< Generator value is not set */ + GEN_SET, /**< Generator value is set */ +}; + +#define fluid_gen_set_mod(_gen, _val) { (_gen)->mod = (double) (_val); } +#define fluid_gen_set_nrpn(_gen, _val) { (_gen)->nrpn = (double) (_val); } + +fluid_real_t fluid_gen_scale(int gen, float value); +fluid_real_t fluid_gen_scale_nrpn(int gen, int nrpn); +void fluid_gen_init(fluid_gen_t *gen, fluid_channel_t *channel); +const char *fluid_gen_name(int gen); + + +#endif /* _FLUID_GEN_H */ diff --git a/libs/fluidsynth/src/synth/fluid_mod.c b/libs/fluidsynth/src/synth/fluid_mod.c new file mode 100644 index 00000000000..95996720812 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_mod.c @@ -0,0 +1,881 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_mod.h" +#include "fluid_chan.h" +#include "fluid_voice.h" + +/** + * 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 +fluid_mod_clone(fluid_mod_t *mod, const fluid_mod_t *src) +{ + mod->dest = src->dest; + mod->src1 = src->src1; + mod->flags1 = src->flags1; + mod->src2 = src->src2; + mod->flags2 = src->flags2; + mod->amount = src->amount; +} + +/** + * 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 + * controller is a general controller (#FLUID_MOD_GC) or a MIDI CC controller + * (#FLUID_MOD_CC), see #fluid_mod_flags. + */ +void +fluid_mod_set_source1(fluid_mod_t *mod, int src, int flags) +{ + mod->src1 = src; + mod->flags1 = 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 + * controller is a general controller (#FLUID_MOD_GC) or a MIDI CC controller + * (#FLUID_MOD_CC), see #fluid_mod_flags. + */ +void +fluid_mod_set_source2(fluid_mod_t *mod, int src, int flags) +{ + mod->src2 = src; + mod->flags2 = flags; +} + +/** + * Set the destination effect of a modulator. + * + * @param mod The modulator instance + * @param dest Destination generator (#fluid_gen_type) + */ +void +fluid_mod_set_dest(fluid_mod_t *mod, int dest) +{ + mod->dest = dest; +} + +/** + * Set the scale amount of a modulator. + * + * @param mod The modulator instance + * @param amount Scale amount to assign + */ +void +fluid_mod_set_amount(fluid_mod_t *mod, double amount) +{ + mod->amount = (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). + */ +int +fluid_mod_get_source1(const fluid_mod_t *mod) +{ + return mod->src1; +} + +/** + * Get primary source flags from a modulator. + * + * @param mod The modulator instance + * @return The primary source flags (#fluid_mod_flags). + */ +int +fluid_mod_get_flags1(const fluid_mod_t *mod) +{ + return mod->flags1; +} + +/** + * 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). + */ +int +fluid_mod_get_source2(const fluid_mod_t *mod) +{ + return mod->src2; +} + +/** + * Get secondary source flags from a modulator. + * + * @param mod The modulator instance + * @return The secondary source flags (#fluid_mod_flags). + */ +int +fluid_mod_get_flags2(const fluid_mod_t *mod) +{ + return mod->flags2; +} + +/** + * Get destination effect from a modulator. + * + * @param mod The modulator instance + * @return Destination generator (#fluid_gen_type) + */ +int +fluid_mod_get_dest(const fluid_mod_t *mod) +{ + return mod->dest; +} + +/** + * Get the scale amount from a modulator. + * + * @param mod The modulator instance + * @return Scale amount + */ +double +fluid_mod_get_amount(const fluid_mod_t *mod) +{ + return (double) mod->amount; +} + +/* + * retrieves the initial value from the given source of the modulator + */ +static fluid_real_t +fluid_mod_get_source_value(const unsigned char mod_src, + const unsigned char mod_flags, + fluid_real_t *range, + const fluid_voice_t *voice + ) +{ + const fluid_channel_t *chan = voice->channel; + fluid_real_t val; + + if(mod_flags & FLUID_MOD_CC) + { + val = fluid_channel_get_cc(chan, mod_src); + + if(mod_src == PORTAMENTO_CTRL) + { + // an invalid portamento fromkey should be treated as 0 when it's actually used for moulating + if(!fluid_channel_is_valid_note(val)) + { + val = 0; + } + } + } + else + { + switch(mod_src) + { + case FLUID_MOD_NONE: /* SF 2.01 8.2.1 item 0: src enum=0 => value is 1 */ + val = *range; + break; + + case FLUID_MOD_VELOCITY: + val = fluid_voice_get_actual_velocity(voice); + break; + + case FLUID_MOD_KEY: + val = fluid_voice_get_actual_key(voice); + break; + + case FLUID_MOD_KEYPRESSURE: + val = fluid_channel_get_key_pressure(chan, voice->key); + break; + + case FLUID_MOD_CHANNELPRESSURE: + val = fluid_channel_get_channel_pressure(chan); + break; + + case FLUID_MOD_PITCHWHEEL: + val = fluid_channel_get_pitch_bend(chan); + *range = 0x4000; + break; + + case FLUID_MOD_PITCHWHEELSENS: + val = fluid_channel_get_pitch_wheel_sensitivity(chan); + break; + + default: + FLUID_LOG(FLUID_ERR, "Unknown modulator source '%d', disabling modulator.", mod_src); + val = 0.0; + } + } + + return val; +} + +/** + * transforms the initial value retrieved by \c fluid_mod_get_source_value into [0.0;1.0] + */ +static fluid_real_t +fluid_mod_transform_source_value(fluid_real_t val, unsigned char mod_flags, const fluid_real_t range) +{ + /* normalized value, i.e. usually in the range [0;1] */ + const fluid_real_t val_norm = val / range; + + /* we could also only switch case the lower nibble of mod_flags, however + * this would keep us from adding further mod types in the future + * + * instead just remove the flag(s) we already took care of + */ + mod_flags &= ~FLUID_MOD_CC; + + switch(mod_flags/* & 0x0f*/) + { + case FLUID_MOD_LINEAR | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =0 */ + val = val_norm; + break; + + case FLUID_MOD_LINEAR | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =1 */ + val = 1.0f - val_norm; + break; + + case FLUID_MOD_LINEAR | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =2 */ + val = -1.0f + 2.0f * val_norm; + break; + + case FLUID_MOD_LINEAR | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =3 */ + val = 1.0f - 2.0f * val_norm; + break; + + case FLUID_MOD_CONCAVE | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =4 */ + val = fluid_concave(127 * (val_norm)); + break; + + case FLUID_MOD_CONCAVE | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =5 */ + val = fluid_concave(127 * (1.0f - val_norm)); + break; + + case FLUID_MOD_CONCAVE | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =6 */ + val = (val_norm > 0.5f) ? fluid_concave(127 * 2 * (val_norm - 0.5f)) + : -fluid_concave(127 * 2 * (0.5f - val_norm)); + break; + + case FLUID_MOD_CONCAVE | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =7 */ + val = (val_norm > 0.5f) ? -fluid_concave(127 * 2 * (val_norm - 0.5f)) + : fluid_concave(127 * 2 * (0.5f - val_norm)); + break; + + case FLUID_MOD_CONVEX | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =8 */ + val = fluid_convex(127 * (val_norm)); + break; + + case FLUID_MOD_CONVEX | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =9 */ + val = fluid_convex(127 * (1.0f - val_norm)); + break; + + case FLUID_MOD_CONVEX | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =10 */ + val = (val_norm > 0.5f) ? fluid_convex(127 * 2 * (val_norm - 0.5f)) + : -fluid_convex(127 * 2 * (0.5f - val_norm)); + break; + + case FLUID_MOD_CONVEX | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =11 */ + val = (val_norm > 0.5f) ? -fluid_convex(127 * 2 * (val_norm - 0.5f)) + : fluid_convex(127 * 2 * (0.5f - val_norm)); + break; + + case FLUID_MOD_SWITCH | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* =12 */ + val = (val_norm >= 0.5f) ? 1.0f : 0.0f; + break; + + case FLUID_MOD_SWITCH | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* =13 */ + val = (val_norm >= 0.5f) ? 0.0f : 1.0f; + break; + + case FLUID_MOD_SWITCH | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* =14 */ + val = (val_norm >= 0.5f) ? 1.0f : -1.0f; + break; + + case FLUID_MOD_SWITCH | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* =15 */ + val = (val_norm >= 0.5f) ? -1.0f : 1.0f; + break; + + /* + * MIDI CCs only have a resolution of 7 bits. The closer val_norm gets to 1, + * the less will be the resulting change of the sinus. When using this sin() + * for scaling the cutoff frequency, there will be no audible difference between + * MIDI CCs 118 to 127. To avoid this waste of CCs multiply with 0.87 + * (at least for unipolar) which makes sin() never get to 1.0 but to 0.98 which + * is close enough. + */ + case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE: /* custom sin(x) */ + val = FLUID_SIN((FLUID_M_PI / 2.0f * 0.87f) * val_norm); + break; + + case FLUID_MOD_SIN | FLUID_MOD_UNIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ + val = FLUID_SIN((FLUID_M_PI / 2.0f * 0.87f) * (1.0f - val_norm)); + break; + + case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_POSITIVE: /* custom */ + val = (val_norm > 0.5f) ? FLUID_SIN(FLUID_M_PI * (val_norm - 0.5f)) + : -FLUID_SIN(FLUID_M_PI * (0.5f - val_norm)); + break; + + case FLUID_MOD_SIN | FLUID_MOD_BIPOLAR | FLUID_MOD_NEGATIVE: /* custom */ + val = (val_norm > 0.5f) ? -FLUID_SIN(FLUID_M_PI * (val_norm - 0.5f)) + : FLUID_SIN(FLUID_M_PI * (0.5f - val_norm)); + break; + + default: + FLUID_LOG(FLUID_ERR, "Unknown modulator type '%d', disabling modulator.", mod_flags); + val = 0.0f; + break; + } + + return val; +} + +/* + * fluid_mod_get_value. + * Computes and return modulator output following SF2.01 + * (See SoundFont Modulator Controller Model Chapter 9.5). + * + * Output = Transform(Amount * Map(primary source input) * Map(secondary source input)) + * + * Notes: + * 1)fluid_mod_get_value, ignores the Transform operator. The result is: + * + * Output = Amount * Map(primary source input) * Map(secondary source input) + * + * 2)When primary source input (src1) is set to General Controller 'No Controller', + * output is forced to 0. + * + * 3)When secondary source input (src2) is set to General Controller 'No Controller', + * output is forced to +1.0 + */ +fluid_real_t +fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice) +{ + extern fluid_mod_t default_vel2filter_mod; + + fluid_real_t v1 = 0.0, v2 = 1.0; + /* The wording of the default modulators refers to a range of 127/128. + * And the table in section 9.5.3 suggests, that this mapping should be applied + * to all unipolar and bipolar mappings respectively. + * + * Thinking about this further, this is actually pretty clever, as this is properly + * addresses MIDI Recommended Practice (RP-036) Default Pan Formula + * "Since MIDI controller values range from 0 to 127, the exact center + * of the range, 63.5, cannot be represented." + * + * When changing the overall range to 127/128 however, the "middle pan" value of 64 + * can be correctly represented. + */ + fluid_real_t range1 = 128.0, range2 = 128.0; + + /* 'special treatment' for default controller + * + * Reference: SF2.01 section 8.4.2 + * + * The GM default controller 'vel-to-filter cut off' is not clearly + * defined: If implemented according to the specs, the filter + * frequency jumps between vel=63 and vel=64. To maintain + * compatibility with existing sound fonts, the implementation is + * 'hardcoded', it is impossible to implement using only one + * modulator otherwise. + * + * I assume here, that the 'intention' of the paragraph is one + * octave (1200 cents) filter frequency shift between vel=127 and + * vel=64. 'amount' is (-2400), at least as long as the controller + * is set to default. + * + * Further, the 'appearance' of the modulator (source enumerator, + * destination enumerator, flags etc) is different from that + * described in section 8.4.2, but it matches the definition used in + * several SF2.1 sound fonts (where it is used only to turn it off). + * */ + if(fluid_mod_test_identity(mod, &default_vel2filter_mod)) + { +// S. Christian Collins' mod, to stop forcing velocity based filtering + /* + if (voice->vel < 64){ + return (fluid_real_t) mod->amount / 2.0; + } else { + return (fluid_real_t) mod->amount * (127 - voice->vel) / 127; + } + */ + return 0; // (fluid_real_t) mod->amount / 2.0; + } + +// end S. Christian Collins' mod + + /* get the initial value of the first source */ + if(mod->src1 > 0) + { + v1 = fluid_mod_get_source_value(mod->src1, mod->flags1, &range1, voice); + + /* transform the input value */ + v1 = fluid_mod_transform_source_value(v1, mod->flags1, range1); + } + /* When primary source input (src1) is set to General Controller 'No Controller', + output is forced to 0.0 + */ + else + { + return 0.0; + } + + /* no need to go further */ + if(v1 == 0.0f) + { + return 0.0f; + } + + /* get the second input source */ + if(mod->src2 > 0) + { + v2 = fluid_mod_get_source_value(mod->src2, mod->flags2, &range2, voice); + + /* transform the second input value */ + v2 = fluid_mod_transform_source_value(v2, mod->flags2, range2); + } + /* When secondary source input (src2) is set to General Controller 'No Controller', + output is forced to +1.0 + */ + else + { + v2 = 1.0f; + } + + /* it's as simple as that: */ + return (fluid_real_t) mod->amount * v1 * v2; +} + +/** + * Create a new uninitialized modulator structure. + * + * @return New allocated modulator or NULL if out of memory + */ +fluid_mod_t * +new_fluid_mod() +{ + fluid_mod_t *mod = FLUID_NEW(fluid_mod_t); + + if(mod == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + return mod; +} + +/** + * Free a modulator structure. + * + * @param mod Modulator to free + */ +void +delete_fluid_mod(fluid_mod_t *mod) +{ + FLUID_FREE(mod); +} + +/** + * Returns the size of the fluid_mod_t structure. + * + * @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() +{ + return sizeof(fluid_mod_t); +} + +/** + * Checks if modulator with source 1 other than CC is FLUID_MOD_NONE. + * + * @param mod, modulator. + * @return TRUE if modulator source 1 other than cc is FLUID_MOD_NONE, FALSE otherwise. + */ +static int +fluid_mod_is_src1_none(const fluid_mod_t *mod) +{ + return(((mod->flags1 & FLUID_MOD_CC) == 0) && (mod->src1 == FLUID_MOD_NONE)); +} + +/** + * Checks if modulators source other than CC source is invalid. + * + * @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) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) != 0) /* src is a CC */ + /* SF2.01 section 8.2.1: Constant value */ + || ((src == FLUID_MOD_NONE) + || (src == FLUID_MOD_VELOCITY) /* Note-on velocity */ + || (src == FLUID_MOD_KEY) /* Note-on key number */ + || (src == FLUID_MOD_KEYPRESSURE) /* Poly pressure */ + || (src == FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ + || (src == FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ + || (src == FLUID_MOD_PITCHWHEELSENS) /* Pitch wheel sensitivity */ + )); +} + +/** + * 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 + * 0 to check src2 source. + * @return FALSE if selected modulator's source CC is invalid, TRUE otherwise. + */ +static int +fluid_mod_check_cc_source(const fluid_mod_t *mod, unsigned char src1_select) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) == 0) /* src is non CC */ + || ((src != BANK_SELECT_MSB) + && (src != BANK_SELECT_LSB) + && (src != DATA_ENTRY_MSB) + && (src != DATA_ENTRY_LSB) + /* is src not NRPN_LSB, NRPN_MSB, RPN_LSB, RPN_MSB */ + && ((src < NRPN_LSB) || (RPN_MSB < src)) + /* is src not ALL_SOUND_OFF, ALL_CTRL_OFF, LOCAL_CONTROL, ALL_NOTES_OFF ? */ + /* is src not OMNI_OFF, OMNI_ON, POLY_OFF, POLY_ON ? */ + && (src < ALL_SOUND_OFF) + /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) + However, as long fluidsynth will use only CC 7 bits resolution, + it is safe to ignore these SF recommendations on CC receive. + See explanations in fluid_synth_cc_LOCAL() */ + /* uncomment next line to forbid CC lsb */ + /* && ((src < 32) || (63 < src)) */ + )); +} + +/** + * 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. + */ +int fluid_mod_check_sources(const fluid_mod_t *mod, char *name) +{ + static const char invalid_non_cc_src[] = + "Invalid modulator, using non-CC source %s.src%d=%d"; + static const char invalid_cc_src[] = + "Invalid modulator, using CC source %s.src%d=%d"; + static const char src1_is_none[] = + "Modulator with source 1 none %s.src1=%d"; + + /* checks valid non cc sources */ + if(!fluid_mod_check_non_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + /* + When src1 is non CC source FLUID_MOD_NONE, the modulator is valid but + the output of this modulator will be forced to 0 at synthesis time. + Also this modulator cannot be used to overwrite a default modulator (as + there is no default modulator with src1 source equal to FLUID_MOD_NONE). + Consequently it is useful to return FALSE to indicate this modulator + being useless. It will be removed later with others invalid modulators. + */ + if(fluid_mod_is_src1_none(mod)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, src1_is_none, name, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_non_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + /* checks valid cc sources */ + if(!fluid_mod_check_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + return TRUE; +} + +/** + * 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 + * + * SF2.01 section 9.5.1 page 69, 'bullet' 3 defines 'identical'. + */ +int +fluid_mod_test_identity(const fluid_mod_t *mod1, const fluid_mod_t *mod2) +{ + return mod1->dest == mod2->dest + && mod1->src1 == mod2->src1 + && mod1->src2 == mod2->src2 + && mod1->flags1 == mod2->flags1 + && mod1->flags2 == mod2->flags2; +} + +/** + * Check if the modulator has the given source. + * + * @param mod The modulator instance + * @param cc Boolean value indicating if ctrl is a CC controller or not + * @param ctrl The source to check for (if \c cc == FALSE : a value of type #fluid_mod_src, else the value of the MIDI CC to check for) + * + * @return TRUE if the modulator has the given source, FALSE otherwise. + */ +int fluid_mod_has_source(const fluid_mod_t *mod, int cc, int ctrl) +{ + return + ( + ( + ((mod->src1 == ctrl) && ((mod->flags1 & FLUID_MOD_CC) != 0) && (cc != 0)) + || ((mod->src1 == ctrl) && ((mod->flags1 & FLUID_MOD_CC) == 0) && (cc == 0)) + ) + || + ( + ((mod->src2 == ctrl) && ((mod->flags2 & FLUID_MOD_CC) != 0) && (cc != 0)) + || ((mod->src2 == ctrl) && ((mod->flags2 & FLUID_MOD_CC) == 0) && (cc == 0)) + ) + ); +} + +/** + * 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. + */ +int fluid_mod_has_dest(const fluid_mod_t *mod, int gen) +{ + return mod->dest == gen; +} + + +/* debug function: Prints the contents of a modulator */ +#ifdef DEBUG +void fluid_dump_modulator(fluid_mod_t *mod) +{ + int src1 = mod->src1; + int dest = mod->dest; + int src2 = mod->src2; + int flags1 = mod->flags1; + int flags2 = mod->flags2; + fluid_real_t amount = (fluid_real_t)mod->amount; + + printf("Src: "); + + if(flags1 & FLUID_MOD_CC) + { + printf("MIDI CC=%i", src1); + } + else + { + switch(src1) + { + case FLUID_MOD_NONE: + printf("None"); + break; + + case FLUID_MOD_VELOCITY: + printf("note-on velocity"); + break; + + case FLUID_MOD_KEY: + printf("Key nr"); + break; + + case FLUID_MOD_KEYPRESSURE: + printf("Poly pressure"); + break; + + case FLUID_MOD_CHANNELPRESSURE: + printf("Chan pressure"); + break; + + case FLUID_MOD_PITCHWHEEL: + printf("Pitch Wheel"); + break; + + case FLUID_MOD_PITCHWHEELSENS: + printf("Pitch Wheel sens"); + break; + + default: + printf("(unknown: %i)", src1); + }; /* switch src1 */ + }; /* if not CC */ + + if(flags1 & FLUID_MOD_NEGATIVE) + { + printf("- "); + } + else + { + printf("+ "); + }; + + if(flags1 & FLUID_MOD_BIPOLAR) + { + printf("bip "); + } + else + { + printf("unip "); + }; + + printf("-> "); + + switch(dest) + { + case GEN_FILTERQ: + printf("Q"); + break; + + case GEN_FILTERFC: + printf("fc"); + break; + + case GEN_CUSTOM_FILTERQ: + printf("custom-Q"); + break; + + case GEN_CUSTOM_FILTERFC: + printf("custom-fc"); + break; + + case GEN_VIBLFOTOPITCH: + printf("VibLFO-to-pitch"); + break; + + case GEN_MODENVTOPITCH: + printf("ModEnv-to-pitch"); + break; + + case GEN_MODLFOTOPITCH: + printf("ModLFO-to-pitch"); + break; + + case GEN_CHORUSSEND: + printf("Chorus send"); + break; + + case GEN_REVERBSEND: + printf("Reverb send"); + break; + + case GEN_PAN: + printf("pan"); + break; + + case GEN_CUSTOM_BALANCE: + printf("balance"); + break; + + case GEN_ATTENUATION: + printf("att"); + break; + + default: + printf("dest %i", dest); + }; /* switch dest */ + + printf(", amount %f flags %i src2 %i flags2 %i\n", amount, flags1, src2, flags2); +}; +#endif + diff --git a/libs/fluidsynth/src/synth/fluid_mod.h b/libs/fluidsynth/src/synth/fluid_mod.h new file mode 100644 index 00000000000..3e7661741f9 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_mod.h @@ -0,0 +1,54 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_MOD_H +#define _FLUID_MOD_H + +#include "fluidsynth_priv.h" +#include "fluid_conv.h" + +/* + * Modulator structure. See SoundFont 2.04 PDF section 8.2. + */ +struct _fluid_mod_t +{ + unsigned char dest; /**< Destination generator to control */ + unsigned char src1; /**< Source controller 1 */ + unsigned char flags1; /**< Source controller 1 flags */ + unsigned char src2; /**< Source controller 2 */ + unsigned char flags2; /**< Source controller 2 flags */ + double amount; /**< Multiplier amount */ + /* The 'next' field allows to link modulators into a list. It is + * not used in fluid_voice.c, there each voice allocates memory for a + * fixed number of modulators. Since there may be a huge number of + * different zones, this is more efficient. + */ + fluid_mod_t *next; +}; + +fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice); +int fluid_mod_check_sources(const fluid_mod_t *mod, char *name); + +#ifdef DEBUG +void fluid_dump_modulator(fluid_mod_t *mod); +#endif + + +#endif /* _FLUID_MOD_H */ diff --git a/libs/fluidsynth/src/synth/fluid_synth.c b/libs/fluidsynth/src/synth/fluid_synth.c new file mode 100644 index 00000000000..07cca126e39 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_synth.c @@ -0,0 +1,8445 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_synth.h" +#include "fluid_sys.h" +#include "fluid_chan.h" +#include "fluid_tuning.h" +#include "fluid_settings.h" +#include "fluid_sfont.h" +#include "fluid_defsfont.h" +#include "fluid_instpatch.h" + +#ifdef TRAP_ON_FPE +#define _GNU_SOURCE +#include <fenv.h> + +/* seems to not be declared in fenv.h */ +extern int feenableexcept(int excepts); +#endif + +#define FLUID_API_RETURN(return_value) \ + do { fluid_synth_api_exit(synth); \ + return return_value; } while (0) + +#define FLUID_API_RETURN_IF_CHAN_DISABLED(return_value) \ + do { if (FLUID_LIKELY(synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED)) \ + {} \ + else \ + { FLUID_API_RETURN(return_value); } \ + } while (0) + +#define FLUID_API_ENTRY_CHAN(fail_value) \ + fluid_return_val_if_fail (synth != NULL, fail_value); \ + fluid_return_val_if_fail (chan >= 0, fail_value); \ + fluid_synth_api_enter(synth); \ + if (chan >= synth->midi_channels) { \ + FLUID_API_RETURN(fail_value); \ + } \ + +static void fluid_synth_init(void); +static void fluid_synth_api_enter(fluid_synth_t *synth); +static void fluid_synth_api_exit(fluid_synth_t *synth); + +static int fluid_synth_noteon_LOCAL(fluid_synth_t *synth, int chan, int key, + int vel); +static int fluid_synth_noteoff_LOCAL(fluid_synth_t *synth, int chan, int key); +static int fluid_synth_cc_LOCAL(fluid_synth_t *synth, int channum, int num); +static int fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, + int len, char *response, + int *response_len, int avail_response, + int *handled, int dryrun); +static int fluid_synth_sysex_gs_dt1(fluid_synth_t *synth, const char *data, + int len, char *response, + int *response_len, int avail_response, + int *handled, int dryrun); +static int fluid_synth_sysex_xg(fluid_synth_t *synth, const char *data, + int len, char *response, + int *response_len, int avail_response, + int *handled, int dryrun); +int fluid_synth_all_notes_off_LOCAL(fluid_synth_t *synth, int chan); +static int fluid_synth_all_sounds_off_LOCAL(fluid_synth_t *synth, int chan); +static int fluid_synth_system_reset_LOCAL(fluid_synth_t *synth); +static int fluid_synth_modulate_voices_LOCAL(fluid_synth_t *synth, int chan, + int is_cc, int ctrl); +static int fluid_synth_modulate_voices_all_LOCAL(fluid_synth_t *synth, int chan); +static int fluid_synth_update_channel_pressure_LOCAL(fluid_synth_t *synth, int channum); +static int fluid_synth_update_key_pressure_LOCAL(fluid_synth_t *synth, int chan, int key); +static int fluid_synth_update_pitch_bend_LOCAL(fluid_synth_t *synth, int chan); +static int fluid_synth_update_pitch_wheel_sens_LOCAL(fluid_synth_t *synth, int chan); +static int fluid_synth_set_preset(fluid_synth_t *synth, int chan, + fluid_preset_t *preset); +static int fluid_synth_reverb_get_param(fluid_synth_t *synth, int fx_group, + int param, double *value); +static int fluid_synth_chorus_get_param(fluid_synth_t *synth, int fx_group, + int param, double *value); + +static fluid_preset_t * +fluid_synth_get_preset(fluid_synth_t *synth, int sfontnum, + int banknum, int prognum); +static fluid_preset_t * +fluid_synth_get_preset_by_sfont_name(fluid_synth_t *synth, const char *sfontname, + int banknum, int prognum); + +static void fluid_synth_update_presets(fluid_synth_t *synth); +static void fluid_synth_update_gain_LOCAL(fluid_synth_t *synth); +static int fluid_synth_update_polyphony_LOCAL(fluid_synth_t *synth, int new_polyphony); +static void init_dither(void); +static FLUID_INLINE int16_t round_clip_to_i16(float x); +static int fluid_synth_render_blocks(fluid_synth_t *synth, int blockcount); + +static fluid_voice_t *fluid_synth_free_voice_by_kill_LOCAL(fluid_synth_t *synth); +static void fluid_synth_kill_by_exclusive_class_LOCAL(fluid_synth_t *synth, + fluid_voice_t *new_voice); +static int fluid_synth_sfunload_callback(void *data, unsigned int msec); +static fluid_tuning_t *fluid_synth_get_tuning(fluid_synth_t *synth, + int bank, int prog); +static int fluid_synth_replace_tuning_LOCK(fluid_synth_t *synth, + fluid_tuning_t *tuning, + int bank, int prog, int apply); +static void fluid_synth_replace_tuning_LOCAL(fluid_synth_t *synth, + fluid_tuning_t *old_tuning, + fluid_tuning_t *new_tuning, + int apply, int unref_new); +static void fluid_synth_update_voice_tuning_LOCAL(fluid_synth_t *synth, + fluid_channel_t *channel); +static int fluid_synth_set_tuning_LOCAL(fluid_synth_t *synth, int chan, + fluid_tuning_t *tuning, int apply); +static void fluid_synth_set_gen_LOCAL(fluid_synth_t *synth, int chan, + int param, float value); +static void fluid_synth_stop_LOCAL(fluid_synth_t *synth, unsigned int id); + + +static int fluid_synth_set_important_channels(fluid_synth_t *synth, const char *channels); + + +/* Callback handlers for real-time settings */ +static void fluid_synth_handle_gain(void *data, const char *name, double value); +static void fluid_synth_handle_polyphony(void *data, const char *name, int value); +static void fluid_synth_handle_device_id(void *data, const char *name, int value); +static void fluid_synth_handle_overflow(void *data, const char *name, double value); +static void fluid_synth_handle_important_channels(void *data, const char *name, + const char *value); +static void fluid_synth_handle_reverb_chorus_num(void *data, const char *name, double value); +static void fluid_synth_handle_reverb_chorus_int(void *data, const char *name, int value); + + +static void fluid_synth_reset_basic_channel_LOCAL(fluid_synth_t *synth, int chan, int nbr_chan); +static int fluid_synth_check_next_basic_channel(fluid_synth_t *synth, int basicchan, int mode, int val); +static void fluid_synth_set_basic_channel_LOCAL(fluid_synth_t *synth, int basicchan, int mode, int val); + +/*************************************************************** + * + * GLOBAL + */ + +/* has the synth module been initialized? */ +/* fluid_atomic_int_t may be anything, so init with {0} to catch most cases */ +static fluid_atomic_int_t fluid_synth_initialized = {0}; + +/* default modulators + * SF2.01 page 52 ff: + * + * There is a set of predefined default modulators. They have to be + * explicitly overridden by the sound font in order to turn them off. + */ + +static fluid_mod_t default_vel2att_mod; /* SF2.01 section 8.4.1 */ +/*not static */ fluid_mod_t default_vel2filter_mod; /* SF2.01 section 8.4.2 */ +static fluid_mod_t default_at2viblfo_mod; /* SF2.01 section 8.4.3 */ +static fluid_mod_t default_mod2viblfo_mod; /* SF2.01 section 8.4.4 */ +static fluid_mod_t default_att_mod; /* SF2.01 section 8.4.5 */ +static fluid_mod_t default_pan_mod; /* SF2.01 section 8.4.6 */ +static fluid_mod_t default_expr_mod; /* SF2.01 section 8.4.7 */ +static fluid_mod_t default_reverb_mod; /* SF2.01 section 8.4.8 */ +static fluid_mod_t default_chorus_mod; /* SF2.01 section 8.4.9 */ +static fluid_mod_t default_pitch_bend_mod; /* SF2.01 section 8.4.10 */ +static fluid_mod_t custom_balance_mod; /* Non-standard modulator */ + + +/* custom_breath2att_modulator is not a default modulator specified in SF +it is intended to replace default_vel2att_mod on demand using +API fluid_set_breath_mode() or command shell setbreathmode. +*/ +static fluid_mod_t custom_breath2att_mod; + +/* reverb presets */ +static const fluid_revmodel_presets_t revmodel_preset[] = +{ + /* name */ /* roomsize */ /* damp */ /* width */ /* level */ + { "Test 1", 0.2f, 0.0f, 0.5f, 0.9f }, + { "Test 2", 0.4f, 0.2f, 0.5f, 0.8f }, + { "Test 3", 0.6f, 0.4f, 0.5f, 0.7f }, + { "Test 4", 0.8f, 0.7f, 0.5f, 0.6f }, + { "Test 5", 0.8f, 1.0f, 0.5f, 0.5f }, +}; + + +/*************************************************************** + * + * INITIALIZATION & UTILITIES + */ + +void fluid_synth_settings(fluid_settings_t *settings) +{ + fluid_settings_register_int(settings, "synth.verbose", 0, 0, 1, FLUID_HINT_TOGGLED); + + fluid_settings_register_int(settings, "synth.reverb.active", 1, 0, 1, FLUID_HINT_TOGGLED); + fluid_settings_register_num(settings, "synth.reverb.room-size", FLUID_REVERB_DEFAULT_ROOMSIZE, 0.0f, 1.0f, 0); + fluid_settings_register_num(settings, "synth.reverb.damp", FLUID_REVERB_DEFAULT_DAMP, 0.0f, 1.0f, 0); + fluid_settings_register_num(settings, "synth.reverb.width", FLUID_REVERB_DEFAULT_WIDTH, 0.0f, 100.0f, 0); + fluid_settings_register_num(settings, "synth.reverb.level", FLUID_REVERB_DEFAULT_LEVEL, 0.0f, 1.0f, 0); + + fluid_settings_register_int(settings, "synth.chorus.active", 1, 0, 1, FLUID_HINT_TOGGLED); + fluid_settings_register_int(settings, "synth.chorus.nr", FLUID_CHORUS_DEFAULT_N, 0, 99, 0); + fluid_settings_register_num(settings, "synth.chorus.level", FLUID_CHORUS_DEFAULT_LEVEL, 0.0f, 10.0f, 0); + fluid_settings_register_num(settings, "synth.chorus.speed", FLUID_CHORUS_DEFAULT_SPEED, 0.1f, 5.0f, 0); + fluid_settings_register_num(settings, "synth.chorus.depth", FLUID_CHORUS_DEFAULT_DEPTH, 0.0f, 256.0f, 0); + + fluid_settings_register_int(settings, "synth.ladspa.active", 0, 0, 1, FLUID_HINT_TOGGLED); + fluid_settings_register_int(settings, "synth.lock-memory", 1, 0, 1, FLUID_HINT_TOGGLED); + fluid_settings_register_str(settings, "midi.portname", "", 0); + +#ifdef DEFAULT_SOUNDFONT + fluid_settings_register_str(settings, "synth.default-soundfont", DEFAULT_SOUNDFONT, 0); +#endif + + fluid_settings_register_int(settings, "synth.polyphony", 256, 1, 65535, 0); + fluid_settings_register_int(settings, "synth.midi-channels", 16, 16, 256, 0); + fluid_settings_register_num(settings, "synth.gain", 0.2f, 0.0f, 10.0f, 0); + fluid_settings_register_int(settings, "synth.audio-channels", 1, 1, 128, 0); + fluid_settings_register_int(settings, "synth.audio-groups", 1, 1, 128, 0); + fluid_settings_register_int(settings, "synth.effects-channels", 2, 2, 2, 0); + fluid_settings_register_int(settings, "synth.effects-groups", 1, 1, 128, 0); + fluid_settings_register_num(settings, "synth.sample-rate", 44100.0f, 8000.0f, 96000.0f, 0); + fluid_settings_register_int(settings, "synth.device-id", 0, 0, 127, 0); +#ifdef ENABLE_MIXER_THREADS + fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 256, 0); +#else + fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 1, 0); +#endif + + fluid_settings_register_int(settings, "synth.min-note-length", 10, 0, 65535, 0); + + fluid_settings_register_int(settings, "synth.threadsafe-api", 1, 0, 1, FLUID_HINT_TOGGLED); + + fluid_settings_register_num(settings, "synth.overflow.percussion", 4000, -10000, 10000, 0); + fluid_settings_register_num(settings, "synth.overflow.sustained", -1000, -10000, 10000, 0); + fluid_settings_register_num(settings, "synth.overflow.released", -2000, -10000, 10000, 0); + fluid_settings_register_num(settings, "synth.overflow.age", 1000, -10000, 10000, 0); + fluid_settings_register_num(settings, "synth.overflow.volume", 500, -10000, 10000, 0); + fluid_settings_register_num(settings, "synth.overflow.important", 5000, -50000, 50000, 0); + fluid_settings_register_str(settings, "synth.overflow.important-channels", "", 0); + + fluid_settings_register_str(settings, "synth.midi-bank-select", "gs", 0); + fluid_settings_add_option(settings, "synth.midi-bank-select", "gm"); + fluid_settings_add_option(settings, "synth.midi-bank-select", "gs"); + fluid_settings_add_option(settings, "synth.midi-bank-select", "xg"); + fluid_settings_add_option(settings, "synth.midi-bank-select", "mma"); + + fluid_settings_register_int(settings, "synth.dynamic-sample-loading", 0, 0, 1, FLUID_HINT_TOGGLED); +} + +/** + * Get FluidSynth runtime version. + * @param major Location to store major number + * @param minor Location to store minor number + * @param micro Location to store micro number + */ +void fluid_version(int *major, int *minor, int *micro) +{ + *major = FLUIDSYNTH_VERSION_MAJOR; + *minor = FLUIDSYNTH_VERSION_MINOR; + *micro = FLUIDSYNTH_VERSION_MICRO; +} + +/** + * Get FluidSynth runtime version as a string. + * @return FluidSynth version string, which is internal and should not be + * modified or freed. + */ +char * +fluid_version_str(void) +{ + return FLUIDSYNTH_VERSION; +} + +/* + * void fluid_synth_init + * + * Does all the initialization for this module. + */ +static void +fluid_synth_init(void) +{ +#ifdef TRAP_ON_FPE + #if !defined(__GLIBC__) && defined(__linux__) + #warning "Trap on FPE is only supported when using glibc!" + #else + /* Turn on floating point exception traps */ + feenableexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_INVALID); + #endif +#endif + + init_dither(); + + /* custom_breath2att_mod is not a default modulator specified in SF2.01. + it is intended to replace default_vel2att_mod on demand using + API fluid_set_breath_mode() or command shell setbreathmode. + */ + fluid_mod_set_source1(&custom_breath2att_mod, /* The modulator we are programming here */ + BREATH_MSB, /* Source. breath MSB corresponds to 2. */ + FLUID_MOD_CC /* MIDI continuous controller */ + | FLUID_MOD_CONCAVE /* Curve shape. Corresponds to 'type=1' */ + | FLUID_MOD_UNIPOLAR /* Polarity. Corresponds to 'P=0' */ + | FLUID_MOD_NEGATIVE /* Direction. Corresponds to 'D=1' */ + ); + fluid_mod_set_source2(&custom_breath2att_mod, 0, 0); /* No 2nd source */ + fluid_mod_set_dest(&custom_breath2att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ + fluid_mod_set_amount(&custom_breath2att_mod, FLUID_PEAK_ATTENUATION); /* Modulation amount: 960 */ + + /* SF2.01 page 53 section 8.4.1: MIDI Note-On Velocity to Initial Attenuation */ + fluid_mod_set_source1(&default_vel2att_mod, /* The modulator we are programming here */ + FLUID_MOD_VELOCITY, /* Source. VELOCITY corresponds to 'index=2'. */ + FLUID_MOD_GC /* Not a MIDI continuous controller */ + | FLUID_MOD_CONCAVE /* Curve shape. Corresponds to 'type=1' */ + | FLUID_MOD_UNIPOLAR /* Polarity. Corresponds to 'P=0' */ + | FLUID_MOD_NEGATIVE /* Direction. Corresponds to 'D=1' */ + ); + fluid_mod_set_source2(&default_vel2att_mod, 0, 0); /* No 2nd source */ + fluid_mod_set_dest(&default_vel2att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ + fluid_mod_set_amount(&default_vel2att_mod, FLUID_PEAK_ATTENUATION); /* Modulation amount: 960 */ + + + + /* SF2.01 page 53 section 8.4.2: MIDI Note-On Velocity to Filter Cutoff + * Have to make a design decision here. The specs don't make any sense this way or another. + * One sound font, 'Kingston Piano', which has been praised for its quality, tries to + * override this modulator with an amount of 0 and positive polarity (instead of what + * the specs say, D=1) for the secondary source. + * So if we change the polarity to 'positive', one of the best free sound fonts works... + */ + fluid_mod_set_source1(&default_vel2filter_mod, FLUID_MOD_VELOCITY, /* Index=2 */ + FLUID_MOD_GC /* CC=0 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_NEGATIVE /* D=1 */ + ); + fluid_mod_set_source2(&default_vel2filter_mod, FLUID_MOD_VELOCITY, /* Index=2 */ + FLUID_MOD_GC /* CC=0 */ + | FLUID_MOD_SWITCH /* type=3 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + // do not remove | FLUID_MOD_NEGATIVE /* D=1 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_dest(&default_vel2filter_mod, GEN_FILTERFC); /* Target: Initial filter cutoff */ + fluid_mod_set_amount(&default_vel2filter_mod, -2400); + + + + /* SF2.01 page 53 section 8.4.3: MIDI Channel pressure to Vibrato LFO pitch depth */ + fluid_mod_set_source1(&default_at2viblfo_mod, FLUID_MOD_CHANNELPRESSURE, /* Index=13 */ + FLUID_MOD_GC /* CC=0 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_at2viblfo_mod, 0, 0); /* no second source */ + fluid_mod_set_dest(&default_at2viblfo_mod, GEN_VIBLFOTOPITCH); /* Target: Vib. LFO => pitch */ + fluid_mod_set_amount(&default_at2viblfo_mod, 50); + + + + /* SF2.01 page 53 section 8.4.4: Mod wheel (Controller 1) to Vibrato LFO pitch depth */ + fluid_mod_set_source1(&default_mod2viblfo_mod, MODULATION_MSB, /* Index=1 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_mod2viblfo_mod, 0, 0); /* no second source */ + fluid_mod_set_dest(&default_mod2viblfo_mod, GEN_VIBLFOTOPITCH); /* Target: Vib. LFO => pitch */ + fluid_mod_set_amount(&default_mod2viblfo_mod, 50); + + + + /* SF2.01 page 55 section 8.4.5: MIDI continuous controller 7 to initial attenuation*/ + fluid_mod_set_source1(&default_att_mod, VOLUME_MSB, /* index=7 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_CONCAVE /* type=1 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_NEGATIVE /* D=1 */ + ); + fluid_mod_set_source2(&default_att_mod, 0, 0); /* No second source */ + fluid_mod_set_dest(&default_att_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ + fluid_mod_set_amount(&default_att_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ + + + + /* SF2.01 page 55 section 8.4.6 MIDI continuous controller 10 to Pan Position */ + fluid_mod_set_source1(&default_pan_mod, PAN_MSB, /* index=10 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_BIPOLAR /* P=1 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_pan_mod, 0, 0); /* No second source */ + fluid_mod_set_dest(&default_pan_mod, GEN_PAN); /* Target: pan */ + /* Amount: 500. The SF specs $8.4.6, p. 55 says: "Amount = 1000 + tenths of a percent". The center value (64) corresponds to 50%, + so it follows that amount = 50% x 1000/% = 500. */ + fluid_mod_set_amount(&default_pan_mod, 500.0); + + + /* SF2.01 page 55 section 8.4.7: MIDI continuous controller 11 to initial attenuation*/ + fluid_mod_set_source1(&default_expr_mod, EXPRESSION_MSB, /* index=11 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_CONCAVE /* type=1 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_NEGATIVE /* D=1 */ + ); + fluid_mod_set_source2(&default_expr_mod, 0, 0); /* No second source */ + fluid_mod_set_dest(&default_expr_mod, GEN_ATTENUATION); /* Target: Initial attenuation */ + fluid_mod_set_amount(&default_expr_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ + + + + /* SF2.01 page 55 section 8.4.8: MIDI continuous controller 91 to Reverb send */ + fluid_mod_set_source1(&default_reverb_mod, EFFECTS_DEPTH1, /* index=91 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_reverb_mod, 0, 0); /* No second source */ + fluid_mod_set_dest(&default_reverb_mod, GEN_REVERBSEND); /* Target: Reverb send */ + fluid_mod_set_amount(&default_reverb_mod, 200); /* Amount: 200 ('tenths of a percent') */ + + + + /* SF2.01 page 55 section 8.4.9: MIDI continuous controller 93 to Chorus send */ + fluid_mod_set_source1(&default_chorus_mod, EFFECTS_DEPTH3, /* index=93 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_chorus_mod, 0, 0); /* No second source */ + fluid_mod_set_dest(&default_chorus_mod, GEN_CHORUSSEND); /* Target: Chorus */ + fluid_mod_set_amount(&default_chorus_mod, 200); /* Amount: 200 ('tenths of a percent') */ + + + + /* SF2.01 page 57 section 8.4.10 MIDI Pitch Wheel to Initial Pitch ... */ + /* Initial Pitch is not a "standard" generator, because it isn't mentioned in the + list of generators in the SF2 specifications. That's why destination Initial Pitch + is replaced here by fine tune generator. + */ + fluid_mod_set_source1(&default_pitch_bend_mod, FLUID_MOD_PITCHWHEEL, /* Index=14 */ + FLUID_MOD_GC /* CC =0 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_BIPOLAR /* P=1 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&default_pitch_bend_mod, FLUID_MOD_PITCHWHEELSENS, /* Index = 16 */ + FLUID_MOD_GC /* CC=0 */ + | FLUID_MOD_LINEAR /* type=0 */ + | FLUID_MOD_UNIPOLAR /* P=0 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + /* Also see the comment in gen.h about GEN_PITCH */ + fluid_mod_set_dest(&default_pitch_bend_mod, GEN_FINETUNE); /* Destination: Fine Tune */ + fluid_mod_set_amount(&default_pitch_bend_mod, 12700.0); /* Amount: 12700 cents */ + + + /* Non-standard MIDI continuous controller 8 to channel stereo balance */ + fluid_mod_set_source1(&custom_balance_mod, BALANCE_MSB, /* Index=8 */ + FLUID_MOD_CC /* CC=1 */ + | FLUID_MOD_CONCAVE /* type=1 */ + | FLUID_MOD_BIPOLAR /* P=1 */ + | FLUID_MOD_POSITIVE /* D=0 */ + ); + fluid_mod_set_source2(&custom_balance_mod, 0, 0); + fluid_mod_set_dest(&custom_balance_mod, GEN_CUSTOM_BALANCE); /* Destination: stereo balance */ + /* Amount: 96 dB of attenuation (on the opposite channel) */ + fluid_mod_set_amount(&custom_balance_mod, FLUID_PEAK_ATTENUATION); /* Amount: 960 */ + +#if defined(LIBINSTPATCH_SUPPORT) + /* defer libinstpatch init to fluid_instpatch.c to avoid #include "libinstpatch.h" */ + if(!fluid_instpatch_supports_multi_init()) + { + fluid_instpatch_init(); + } +#endif +} + +static FLUID_INLINE unsigned int fluid_synth_get_ticks(fluid_synth_t *synth) +{ + return fluid_atomic_int_get(&synth->ticks_since_start); +} + +static FLUID_INLINE void fluid_synth_add_ticks(fluid_synth_t *synth, int val) +{ + fluid_atomic_int_add(&synth->ticks_since_start, val); +} + + +/*************************************************************** + * FLUID SAMPLE TIMERS + * Timers that use written audio data as timing reference + */ +struct _fluid_sample_timer_t +{ + fluid_sample_timer_t *next; /* Single linked list of timers */ + unsigned long starttick; + fluid_timer_callback_t callback; + void *data; + int isfinished; +}; + +/* + * fluid_sample_timer_process - called when synth->ticks is updated + */ +static void fluid_sample_timer_process(fluid_synth_t *synth) +{ + fluid_sample_timer_t *st; + long msec; + int cont; + unsigned int ticks = fluid_synth_get_ticks(synth); + + for(st = synth->sample_timers; st; st = st->next) + { + if(st->isfinished) + { + continue; + } + + msec = (long)(1000.0 * ((double)(ticks - st->starttick)) / synth->sample_rate); + cont = (*st->callback)(st->data, msec); + + if(cont == 0) + { + st->isfinished = 1; + } + } +} + +fluid_sample_timer_t *new_fluid_sample_timer(fluid_synth_t *synth, fluid_timer_callback_t callback, void *data) +{ + fluid_sample_timer_t *result = FLUID_NEW(fluid_sample_timer_t); + + if(result == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + fluid_sample_timer_reset(synth, result); + result->data = data; + result->callback = callback; + result->next = synth->sample_timers; + synth->sample_timers = result; + return result; +} + +void delete_fluid_sample_timer(fluid_synth_t *synth, fluid_sample_timer_t *timer) +{ + fluid_sample_timer_t **ptr; + fluid_return_if_fail(synth != NULL); + fluid_return_if_fail(timer != NULL); + + ptr = &synth->sample_timers; + + while(*ptr) + { + if(*ptr == timer) + { + *ptr = timer->next; + FLUID_FREE(timer); + return; + } + + ptr = &((*ptr)->next); + } +} + +void fluid_sample_timer_reset(fluid_synth_t *synth, fluid_sample_timer_t *timer) +{ + timer->starttick = fluid_synth_get_ticks(synth); + timer->isfinished = 0; +} + +/*************************************************************** + * + * FLUID SYNTH + */ + +static FLUID_INLINE void +fluid_synth_update_mixer(fluid_synth_t *synth, fluid_rvoice_function_t method, int intparam, + fluid_real_t realparam) +{ + fluid_return_if_fail(synth != NULL && synth->eventhandler != NULL); + fluid_return_if_fail(synth->eventhandler->mixer != NULL); + fluid_rvoice_eventhandler_push_int_real(synth->eventhandler, method, + synth->eventhandler->mixer, + intparam, realparam); +} + +static FLUID_INLINE unsigned int fluid_synth_get_min_note_length_LOCAL(fluid_synth_t *synth) +{ + int i; + fluid_settings_getint(synth->settings, "synth.min-note-length", &i); + return (unsigned int)(i * synth->sample_rate / 1000.0f); +} + +/** + * Create new FluidSynth instance. + * @param settings Configuration parameters to use (used directly). + * @return New FluidSynth instance or NULL on error + * + * @note The @p settings parameter is used directly, but the synth does not take ownership of it. + * Hence, the caller is responsible for freeing it, when no longer needed. + * Further note that you may modify FluidSettings of the + * @p settings instance. However, only those FluidSettings marked as 'realtime' will + * affect the synth immediately. See the \ref fluidsettings for more details. + * + * @warning The @p settings object should only be used by a single synth at a time. I.e. creating + * multiple synth instances with a single @p settings object causes undefined behavior. Once the + * "single synth" has been deleted, you may use the @p settings object again for another synth. + */ +fluid_synth_t * +new_fluid_synth(fluid_settings_t *settings) +{ + fluid_synth_t *synth; + fluid_sfloader_t *loader; + char *important_channels; + int i, prio_level = 0; + int with_ladspa = 0; + double sample_rate_min, sample_rate_max; + + /* initialize all the conversion tables and other stuff */ + if(fluid_atomic_int_compare_and_exchange(&fluid_synth_initialized, 0, 1)) + { + fluid_synth_init(); + } + + /* allocate a new synthesizer object */ + synth = FLUID_NEW(fluid_synth_t); + + if(synth == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(synth, 0, sizeof(fluid_synth_t)); + +#if defined(LIBINSTPATCH_SUPPORT) + if(fluid_instpatch_supports_multi_init()) + { + fluid_instpatch_init(); + } +#endif + + fluid_rec_mutex_init(synth->mutex); + fluid_settings_getint(settings, "synth.threadsafe-api", &synth->use_mutex); + synth->public_api_count = 0; + + synth->settings = settings; + + fluid_settings_getint(settings, "synth.reverb.active", &synth->with_reverb); + fluid_settings_getint(settings, "synth.chorus.active", &synth->with_chorus); + fluid_settings_getint(settings, "synth.verbose", &synth->verbose); + + fluid_settings_getint(settings, "synth.polyphony", &synth->polyphony); + fluid_settings_getnum(settings, "synth.sample-rate", &synth->sample_rate); + fluid_settings_getnum_range(settings, "synth.sample-rate", &sample_rate_min, &sample_rate_max); + fluid_settings_getint(settings, "synth.midi-channels", &synth->midi_channels); + fluid_settings_getint(settings, "synth.audio-channels", &synth->audio_channels); + fluid_settings_getint(settings, "synth.audio-groups", &synth->audio_groups); + fluid_settings_getint(settings, "synth.effects-channels", &synth->effects_channels); + fluid_settings_getint(settings, "synth.effects-groups", &synth->effects_groups); + fluid_settings_getnum_float(settings, "synth.gain", &synth->gain); + fluid_settings_getint(settings, "synth.device-id", &synth->device_id); + fluid_settings_getint(settings, "synth.cpu-cores", &synth->cores); + + fluid_settings_getnum_float(settings, "synth.overflow.percussion", &synth->overflow.percussion); + fluid_settings_getnum_float(settings, "synth.overflow.released", &synth->overflow.released); + fluid_settings_getnum_float(settings, "synth.overflow.sustained", &synth->overflow.sustained); + fluid_settings_getnum_float(settings, "synth.overflow.volume", &synth->overflow.volume); + fluid_settings_getnum_float(settings, "synth.overflow.age", &synth->overflow.age); + fluid_settings_getnum_float(settings, "synth.overflow.important", &synth->overflow.important); + + /* register the callbacks */ + fluid_settings_callback_num(settings, "synth.gain", + fluid_synth_handle_gain, synth); + fluid_settings_callback_int(settings, "synth.polyphony", + fluid_synth_handle_polyphony, synth); + fluid_settings_callback_int(settings, "synth.device-id", + fluid_synth_handle_device_id, synth); + fluid_settings_callback_num(settings, "synth.overflow.percussion", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_num(settings, "synth.overflow.sustained", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_num(settings, "synth.overflow.released", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_num(settings, "synth.overflow.age", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_num(settings, "synth.overflow.volume", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_num(settings, "synth.overflow.important", + fluid_synth_handle_overflow, synth); + fluid_settings_callback_str(settings, "synth.overflow.important-channels", + fluid_synth_handle_important_channels, synth); + fluid_settings_callback_num(settings, "synth.reverb.room-size", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_num(settings, "synth.reverb.damp", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_num(settings, "synth.reverb.width", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_num(settings, "synth.reverb.level", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_int(settings, "synth.reverb.active", + fluid_synth_handle_reverb_chorus_int, synth); + fluid_settings_callback_int(settings, "synth.chorus.active", + fluid_synth_handle_reverb_chorus_int, synth); + fluid_settings_callback_int(settings, "synth.chorus.nr", + fluid_synth_handle_reverb_chorus_int, synth); + fluid_settings_callback_num(settings, "synth.chorus.level", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_num(settings, "synth.chorus.depth", + fluid_synth_handle_reverb_chorus_num, synth); + fluid_settings_callback_num(settings, "synth.chorus.speed", + fluid_synth_handle_reverb_chorus_num, synth); + + /* do some basic sanity checking on the settings */ + + if(synth->midi_channels % 16 != 0) + { + int n = synth->midi_channels / 16; + synth->midi_channels = (n + 1) * 16; + fluid_settings_setint(settings, "synth.midi-channels", synth->midi_channels); + FLUID_LOG(FLUID_WARN, "Requested number of MIDI channels is not a multiple of 16. " + "I'll increase the number of channels to the next multiple."); + } + + if(synth->audio_channels < 1) + { + FLUID_LOG(FLUID_WARN, "Requested number of audio channels is smaller than 1. " + "Changing this setting to 1."); + synth->audio_channels = 1; + } + else if(synth->audio_channels > 128) + { + FLUID_LOG(FLUID_WARN, "Requested number of audio channels is too big (%d). " + "Limiting this setting to 128.", synth->audio_channels); + synth->audio_channels = 128; + } + + if(synth->audio_groups < 1) + { + FLUID_LOG(FLUID_WARN, "Requested number of audio groups is smaller than 1. " + "Changing this setting to 1."); + synth->audio_groups = 1; + } + else if(synth->audio_groups > 128) + { + FLUID_LOG(FLUID_WARN, "Requested number of audio groups is too big (%d). " + "Limiting this setting to 128.", synth->audio_groups); + synth->audio_groups = 128; + } + + if(synth->effects_channels < 2) + { + FLUID_LOG(FLUID_WARN, "Invalid number of effects channels (%d)." + "Setting effects channels to 2.", synth->effects_channels); + synth->effects_channels = 2; + } + + /* + number of buffers rendered by the mixer is determined by synth->audio_groups. + audio from MIDI channel is rendered, mapped and mixed in these buffers. + + Typically synth->audio_channels is only used by audio driver and should be set + to the same value that synth->audio_groups. In some situation using LADSPA, + it is best to diminish audio-channels so that the driver will be able to pass + the audio to audio devices in the case these devices have a limited number of + audio channels. + + audio-channels must not be greater then audio-groups, otherwise these + audio output above audio-groups will not be rendered by the mixeur. + */ + if(synth->audio_channels > synth->audio_groups) + { + synth->audio_channels = synth->audio_groups; + fluid_settings_setint(settings, "synth.audio-channels", synth->audio_channels); + FLUID_LOG(FLUID_WARN, "Requested audio-channels to high. " + "Limiting this setting to audio-groups."); + } + + if(fluid_settings_dupstr(settings, "synth.overflow.important-channels", + &important_channels) == FLUID_OK) + { + if(fluid_synth_set_important_channels(synth, important_channels) != FLUID_OK) + { + FLUID_LOG(FLUID_WARN, "Failed to set overflow important channels"); + } + + FLUID_FREE(important_channels); + } + + /* as soon as the synth is created it starts playing. */ + synth->state = FLUID_SYNTH_PLAYING; + + synth->fromkey_portamento = INVALID_NOTE; /* disable portamento */ + + fluid_atomic_int_set(&synth->ticks_since_start, 0); + synth->tuning = NULL; + fluid_private_init(synth->tuning_iter); + + /* Initialize multi-core variables if multiple cores enabled */ + if(synth->cores > 1) + { + fluid_settings_getint(synth->settings, "audio.realtime-prio", &prio_level); + } + + /* Allocate event queue for rvoice mixer */ + /* In an overflow situation, a new voice takes about 50 spaces in the queue! */ + synth->eventhandler = new_fluid_rvoice_eventhandler(synth->polyphony * 64, + synth->polyphony, synth->audio_groups, + synth->effects_channels, synth->effects_groups, + (fluid_real_t)sample_rate_max, synth->sample_rate, + synth->cores - 1, prio_level); + + if(synth->eventhandler == NULL) + { + goto error_recovery; + } + + /* Setup the list of default modulators. + * Needs to happen after eventhandler has been set up, as fluid_synth_enter_api is called in the process */ + synth->default_mod = NULL; + fluid_synth_add_default_mod(synth, &default_vel2att_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_vel2filter_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_at2viblfo_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_mod2viblfo_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_att_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_pan_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_expr_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_reverb_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_chorus_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &default_pitch_bend_mod, FLUID_SYNTH_ADD); + fluid_synth_add_default_mod(synth, &custom_balance_mod, FLUID_SYNTH_ADD); + + /* Create and initialize the Fx unit.*/ + fluid_settings_getint(settings, "synth.ladspa.active", &with_ladspa); + + if(with_ladspa) + { +#ifdef LADSPA + synth->ladspa_fx = new_fluid_ladspa_fx(synth->sample_rate, + FLUID_MIXER_MAX_BUFFERS_DEFAULT * FLUID_BUFSIZE); + + if(synth->ladspa_fx == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + + fluid_rvoice_mixer_set_ladspa(synth->eventhandler->mixer, synth->ladspa_fx, + synth->audio_groups); +#else /* LADSPA */ + FLUID_LOG(FLUID_WARN, "FluidSynth has not been compiled with LADSPA support"); +#endif /* LADSPA */ + } + + /* allocate and add the dls sfont loader */ +#ifdef LIBINSTPATCH_SUPPORT + loader = new_fluid_instpatch_loader(settings); + + if(loader == NULL) + { + FLUID_LOG(FLUID_WARN, "Failed to create the instpatch SoundFont loader"); + } + else + { + fluid_synth_add_sfloader(synth, loader); + } +#endif + + /* allocate and add the default sfont loader */ + loader = new_fluid_defsfloader(settings); + + if(loader == NULL) + { + FLUID_LOG(FLUID_WARN, "Failed to create the default SoundFont loader"); + } + else + { + fluid_synth_add_sfloader(synth, loader); + } + + /* allocate all channel objects */ + synth->channel = FLUID_ARRAY(fluid_channel_t *, synth->midi_channels); + + if(synth->channel == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + + FLUID_MEMSET(synth->channel, 0, synth->midi_channels * sizeof(*synth->channel)); + for(i = 0; i < synth->midi_channels; i++) + { + synth->channel[i] = new_fluid_channel(synth, i); + + if(synth->channel[i] == NULL) + { + goto error_recovery; + } + } + + /* allocate all synthesis processes */ + synth->nvoice = synth->polyphony; + synth->voice = FLUID_ARRAY(fluid_voice_t *, synth->nvoice); + + if(synth->voice == NULL) + { + goto error_recovery; + } + + FLUID_MEMSET(synth->voice, 0, synth->nvoice * sizeof(*synth->voice)); + for(i = 0; i < synth->nvoice; i++) + { + synth->voice[i] = new_fluid_voice(synth->eventhandler, synth->sample_rate); + + if(synth->voice[i] == NULL) + { + goto error_recovery; + } + } + + /* sets a default basic channel */ + /* Sets one basic channel: basic channel 0, mode 0 (Omni On - Poly) */ + /* (i.e all channels are polyphonic) */ + /* Must be called after channel objects allocation */ + fluid_synth_set_basic_channel_LOCAL(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY, + synth->midi_channels); + + synth->min_note_length_ticks = fluid_synth_get_min_note_length_LOCAL(synth); + + + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, + synth->polyphony, 0.0f); + fluid_synth_reverb_on(synth, -1, synth->with_reverb); + fluid_synth_chorus_on(synth, -1, synth->with_chorus); + + synth->cur = FLUID_BUFSIZE; + synth->curmax = 0; + synth->dither_index = 0; + + { + double values[FLUID_REVERB_PARAM_LAST]; + + fluid_settings_getnum(settings, "synth.reverb.room-size", &values[FLUID_REVERB_ROOMSIZE]); + fluid_settings_getnum(settings, "synth.reverb.damp", &values[FLUID_REVERB_DAMP]); + fluid_settings_getnum(settings, "synth.reverb.width", &values[FLUID_REVERB_WIDTH]); + fluid_settings_getnum(settings, "synth.reverb.level", &values[FLUID_REVERB_LEVEL]); + + fluid_synth_set_reverb_full(synth, -1, FLUID_REVMODEL_SET_ALL, values); + } + + { + double values[FLUID_CHORUS_PARAM_LAST]; + + fluid_settings_getint(settings, "synth.chorus.nr", &i); + values[FLUID_CHORUS_NR] = (double)i; + fluid_settings_getnum(settings, "synth.chorus.level", &values[FLUID_CHORUS_LEVEL]); + fluid_settings_getnum(settings, "synth.chorus.speed", &values[FLUID_CHORUS_SPEED]); + fluid_settings_getnum(settings, "synth.chorus.depth", &values[FLUID_CHORUS_DEPTH]); + values[FLUID_CHORUS_TYPE] = (double)FLUID_CHORUS_DEFAULT_TYPE; + + fluid_synth_set_chorus_full(synth, -1, FLUID_CHORUS_SET_ALL, values); + } + + + synth->bank_select = FLUID_BANK_STYLE_GS; + + if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "gm")) + { + synth->bank_select = FLUID_BANK_STYLE_GM; + } + else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "gs")) + { + synth->bank_select = FLUID_BANK_STYLE_GS; + } + else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "xg")) + { + synth->bank_select = FLUID_BANK_STYLE_XG; + } + else if(fluid_settings_str_equal(settings, "synth.midi-bank-select", "mma")) + { + synth->bank_select = FLUID_BANK_STYLE_MMA; + } + + fluid_synth_process_event_queue(synth); + + /* FIXME */ + synth->start = fluid_curtime(); + + return synth; + +error_recovery: + delete_fluid_synth(synth); + return NULL; +} + + +/** + * Delete a FluidSynth instance. + * @param synth FluidSynth instance to delete + * + * @note Other users of a synthesizer instance, such as audio and MIDI drivers, + * should be deleted prior to freeing the FluidSynth instance. + */ +void +delete_fluid_synth(fluid_synth_t *synth) +{ + int i, k; + fluid_list_t *list; + fluid_sfont_t *sfont; + fluid_sfloader_t *loader; + + fluid_return_if_fail(synth != NULL); + + fluid_profiling_print(); + + /* unregister all real-time settings callback, to avoid a use-after-free when changing those settings after + * this synth has been deleted*/ + + fluid_settings_callback_num(synth->settings, "synth.gain", + NULL, NULL); + fluid_settings_callback_int(synth->settings, "synth.polyphony", + NULL, NULL); + fluid_settings_callback_int(synth->settings, "synth.device-id", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.percussion", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.sustained", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.released", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.age", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.volume", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.overflow.important", + NULL, NULL); + fluid_settings_callback_str(synth->settings, "synth.overflow.important-channels", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.reverb.room-size", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.reverb.damp", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.reverb.width", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.reverb.level", + NULL, NULL); + fluid_settings_callback_int(synth->settings, "synth.reverb.active", + NULL, NULL); + fluid_settings_callback_int(synth->settings, "synth.chorus.active", + NULL, NULL); + fluid_settings_callback_int(synth->settings, "synth.chorus.nr", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.chorus.level", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.chorus.depth", + NULL, NULL); + fluid_settings_callback_num(synth->settings, "synth.chorus.speed", + NULL, NULL); + + /* turn off all voices, needed to unload SoundFont data */ + if(synth->voice != NULL) + { + for(i = 0; i < synth->nvoice; i++) + { + fluid_voice_t *voice = synth->voice[i]; + + if(!voice) + { + continue; + } + + /* WARNING: A this point we must ensure that the reference counter + of any soundfont sample owned by any rvoice belonging to the voice + are correctly decremented. This is the contrary part to + to fluid_voice_init() where the sample's reference counter is + incremented. + */ + fluid_voice_unlock_rvoice(voice); + fluid_voice_overflow_rvoice_finished(voice); + + if(fluid_voice_is_playing(voice)) + { + fluid_voice_off(voice); + /* If we only use fluid_voice_off(voice) it will trigger a delayed + * fluid_voice_stop(voice) via fluid_synth_check_finished_voices(). + * But here, we are deleting the fluid_synth_t instance so + * fluid_voice_stop() will be never triggered resulting in + * SoundFont data never unloaded (i.e a serious memory leak). + * So, fluid_voice_stop() must be explicitly called to insure + * unloading SoundFont data + */ + fluid_voice_stop(voice); + } + } + } + + /* also unset all presets for clean SoundFont unload */ + if(synth->channel != NULL) + { + for(i = 0; i < synth->midi_channels; i++) + { + if(synth->channel[i] != NULL) + { + fluid_channel_set_preset(synth->channel[i], NULL); + } + } + } + + delete_fluid_rvoice_eventhandler(synth->eventhandler); + + /* delete all the SoundFonts */ + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + fluid_sfont_delete_internal(sfont); + } + + delete_fluid_list(synth->sfont); + + /* delete all the SoundFont loaders */ + + for(list = synth->loaders; list; list = fluid_list_next(list)) + { + loader = (fluid_sfloader_t *) fluid_list_get(list); + fluid_sfloader_delete(loader); + } + + delete_fluid_list(synth->loaders); + + /* wait for and delete all the lazy sfont unloading timers */ + + for(list = synth->fonts_to_be_unloaded; list; list = fluid_list_next(list)) + { + fluid_timer_t* timer = fluid_list_get(list); + // explicitly join to wait for the unload really to happen + fluid_timer_join(timer); + // delete_fluid_timer alone would stop the timer, even if it had not unloaded the soundfont yet + delete_fluid_timer(timer); + } + + delete_fluid_list(synth->fonts_to_be_unloaded); + + if(synth->channel != NULL) + { + for(i = 0; i < synth->midi_channels; i++) + { + delete_fluid_channel(synth->channel[i]); + } + + FLUID_FREE(synth->channel); + } + + if(synth->voice != NULL) + { + for(i = 0; i < synth->nvoice; i++) + { + delete_fluid_voice(synth->voice[i]); + } + + FLUID_FREE(synth->voice); + } + + + /* free the tunings, if any */ + if(synth->tuning != NULL) + { + for(i = 0; i < 128; i++) + { + if(synth->tuning[i] != NULL) + { + for(k = 0; k < 128; k++) + { + delete_fluid_tuning(synth->tuning[i][k]); + } + + FLUID_FREE(synth->tuning[i]); + } + } + + FLUID_FREE(synth->tuning); + } + + fluid_private_free(synth->tuning_iter); + +#ifdef LADSPA + /* Release the LADSPA effects unit */ + delete_fluid_ladspa_fx(synth->ladspa_fx); +#endif + + /* delete all default modulators */ + delete_fluid_list_mod(synth->default_mod); + + FLUID_FREE(synth->overflow.important_channels); + + fluid_rec_mutex_destroy(synth->mutex); + + FLUID_FREE(synth); + +#if defined(LIBINSTPATCH_SUPPORT) + if(fluid_instpatch_supports_multi_init()) + { + fluid_instpatch_deinit(); + } +#endif +} + +/** + * Get a textual representation of the last error + * @param synth FluidSynth instance + * @return Pointer to string of last error message. Valid until the same + * calling thread calls another FluidSynth function which fails. String is + * internal and should not be modified or freed. + * @deprecated This function is not thread-safe and does not work with multiple synths. + * It has been deprecated. It may return "" in a future release and will eventually be removed. + */ +const char * +fluid_synth_error(fluid_synth_t *synth) +{ + return ""; +} + +/** + * Send a note-on event to a FluidSynth object. + * + * This function will take care of proper legato playing. If a note on channel @p chan is + * already playing at the given key @p key, it will be released (even if it is sustained). + * In other words, overlapping notes are not allowed. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param key MIDI note number (0-127) + * @param vel MIDI velocity (0-127, 0=noteoff) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_noteon(fluid_synth_t *synth, int chan, int key, int vel) +{ + int result; + fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); + fluid_return_val_if_fail(vel >= 0 && vel <= 127, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + result = fluid_synth_noteon_LOCAL(synth, chan, key, vel); + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of fluid_synth_noteon */ +static int +fluid_synth_noteon_LOCAL(fluid_synth_t *synth, int chan, int key, int vel) +{ + fluid_channel_t *channel ; + + /* notes with velocity zero go to noteoff */ + if(vel == 0) + { + return fluid_synth_noteoff_LOCAL(synth, chan, key); + } + + channel = synth->channel[chan]; + + /* makes sure this channel has a preset */ + if(channel->preset == NULL) + { + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "noteon\t%d\t%d\t%d\t%05d\t%.3f\t%.3f\t%.3f\t%d\t%s", + chan, key, vel, 0, + fluid_synth_get_ticks(synth) / 44100.0f, + (fluid_curtime() - synth->start) / 1000.0f, + 0.0f, 0, "channel has no preset"); + } + + return FLUID_FAILED; + } + + if(fluid_channel_is_playing_mono(channel)) /* channel is mono or legato CC is On) */ + { + /* play the noteOn in monophonic */ + return fluid_synth_noteon_mono_LOCAL(synth, chan, key, vel); + } + else + { + /* channel is poly and legato CC is Off) */ + + /* plays the noteOn in polyphonic */ + /* Sets the note at first position in monophonic list */ + /* In the case where the musician intends to inter the channel in monophonic + (by depressing the CC legato on), the next noteOn mono could be played legato + with the previous note poly (if the musician choose this). + */ + fluid_channel_set_onenote_monolist(channel, (unsigned char) key, + (unsigned char) vel); + + /* If there is another voice process on the same channel and key, + advance it to the release phase. */ + fluid_synth_release_voice_on_same_note_LOCAL(synth, chan, key); + + /* a noteon poly is passed to fluid_synth_noteon_monopoly_legato(). + This allows an opportunity to get this note played legato with a previous + note if a CC PTC have been received before this noteon. This behavior is + a MIDI specification (see FluidPolymono-0004.pdf chapter 4.3-a ,3.4.11 + for details). + */ + return fluid_synth_noteon_monopoly_legato(synth, chan, INVALID_NOTE, key, vel); + } +} + +/** + * Sends a note-off event to a FluidSynth object. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param key MIDI note number (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise (may just mean that no + * voices matched the note off event) + */ +int +fluid_synth_noteoff(fluid_synth_t *synth, int chan, int key) +{ + int result; + fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + result = fluid_synth_noteoff_LOCAL(synth, chan, key); + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of fluid_synth_noteoff */ +static int +fluid_synth_noteoff_LOCAL(fluid_synth_t *synth, int chan, int key) +{ + int status; + fluid_channel_t *channel = synth->channel[chan]; + + if(fluid_channel_is_playing_mono(channel)) /* channel is mono or legato CC is On) */ + { + /* play the noteOff in monophonic */ + status = fluid_synth_noteoff_mono_LOCAL(synth, chan, key); + } + else + { + /* channel is poly and legato CC is Off) */ + /* removes the note from the monophonic list */ + if(channel->n_notes && key == fluid_channel_last_note(channel)) + { + fluid_channel_clear_monolist(channel); + } + + status = fluid_synth_noteoff_monopoly(synth, chan, key, 0); + } + + /* Changes the state (Valid/Invalid) of the most recent note played in a + staccato manner */ + fluid_channel_invalid_prev_note_staccato(channel); + return status; +} + +/* Damps voices on a channel (turn notes off), if they're sustained by + sustain pedal */ +static int +fluid_synth_damp_voices_by_sustain_LOCAL(fluid_synth_t *synth, int chan) +{ + fluid_channel_t *channel = synth->channel[chan]; + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if((fluid_voice_get_channel(voice) == chan) && fluid_voice_is_sustained(voice)) + { + if(voice->key == channel->key_mono_sustained) + { + /* key_mono_sustained is a possible mono note sustainted + (by sustain or sostenuto pedal). It must be marked released + (INVALID_NOTE) here because it is released only by sustain pedal */ + channel->key_mono_sustained = INVALID_NOTE; + } + + fluid_voice_release(voice); + } + } + + return FLUID_OK; +} + +/* Damps voices on a channel (turn notes off), if they're sustained by + sostenuto pedal */ +static int +fluid_synth_damp_voices_by_sostenuto_LOCAL(fluid_synth_t *synth, int chan) +{ + fluid_channel_t *channel = synth->channel[chan]; + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if((fluid_voice_get_channel(voice) == chan) && fluid_voice_is_sostenuto(voice)) + { + if(voice->key == channel->key_mono_sustained) + { + /* key_mono_sustained is a possible mono note sustainted + (by sustain or sostenuto pedal). It must be marked released + (INVALID_NOTE) here because it is released only by sostenuto pedal */ + channel->key_mono_sustained = INVALID_NOTE; + } + + fluid_voice_release(voice); + } + } + + return FLUID_OK; +} + +/** + * Adds the specified modulator \c mod as default modulator to the synth. \c mod will + * take effect for any subsequently created voice. + * @param synth FluidSynth instance + * @param mod Modulator info (values copied, passed in object can be freed immediately afterwards) + * @param mode Determines how to handle an existing identical modulator (#fluid_synth_add_mod) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note Not realtime safe (due to internal memory allocation) and therefore should not be called + * from synthesis context at the risk of stalling audio output. + */ +int +fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mode) +{ + fluid_mod_t *default_mod; + fluid_mod_t *last_mod = NULL; + fluid_mod_t *new_mod; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); + fluid_return_val_if_fail((mode == FLUID_SYNTH_ADD) || (mode == FLUID_SYNTH_OVERWRITE) , FLUID_FAILED); + + /* Checks if modulators sources are valid */ + if(!fluid_mod_check_sources(mod, "api fluid_synth_add_default_mod mod")) + { + return FLUID_FAILED; + } + + fluid_synth_api_enter(synth); + + default_mod = synth->default_mod; + + while(default_mod != NULL) + { + if(fluid_mod_test_identity(default_mod, mod)) + { + if(mode == FLUID_SYNTH_ADD) + { + default_mod->amount += mod->amount; + } + else // mode == FLUID_SYNTH_OVERWRITE + { + default_mod->amount = mod->amount; + } + + FLUID_API_RETURN(FLUID_OK); + } + + last_mod = default_mod; + default_mod = default_mod->next; + } + + /* Add a new modulator (no existing modulator to add / overwrite). */ + new_mod = new_fluid_mod(); + + if(new_mod == NULL) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + fluid_mod_clone(new_mod, mod); + new_mod->next = NULL; + + if(last_mod == NULL) + { + synth->default_mod = new_mod; + } + else + { + last_mod->next = new_mod; + } + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Removes the specified modulator \c mod from the synth's default modulator list. + * fluid_mod_test_identity() will be used to test modulator matching. + * @param synth synth instance + * @param mod The modulator to remove + * @return #FLUID_OK if a matching modulator was found and successfully removed, #FLUID_FAILED otherwise + * + * @note Not realtime safe (due to internal memory freeing) and therefore should not be called + * from synthesis context at the risk of stalling audio output. + */ +int +fluid_synth_remove_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod) +{ + fluid_mod_t *default_mod; + fluid_mod_t *last_mod; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + last_mod = default_mod = synth->default_mod; + + while(default_mod != NULL) + { + if(fluid_mod_test_identity(default_mod, mod)) + { + if(synth->default_mod == default_mod) + { + synth->default_mod = default_mod->next; + } + else + { + last_mod->next = default_mod->next; + } + + delete_fluid_mod(default_mod); + FLUID_API_RETURN(FLUID_OK); + } + + last_mod = default_mod; + default_mod = default_mod->next; + } + + FLUID_API_RETURN(FLUID_FAILED); +} + + +/** + * Send a MIDI controller event on a MIDI channel. + * + * Most CCs are 7-bits wide in FluidSynth. There are a few exceptions which may be 14-bits wide as are documented here: + * https://github.com/FluidSynth/fluidsynth/wiki/FluidFeatures#midi-control-cha... + * + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param num MIDI controller number (0-127) + * @param val MIDI controller value (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @note This function supports MIDI Global Controllers which will be sent to + * all channels of the basic channel if this basic channel is in mode OmniOff/Mono. + * This is accomplished by sending the CC one MIDI channel below the basic + * channel of the receiver. + * Examples: let a synthesizer with 16 MIDI channels: + * - Let a basic channel 7 in mode 3 (Omni Off, Mono). If MIDI channel 6 is disabled it + * could be used as CC global for all channels belonging to basic channel 7. + * - Let a basic channel 0 in mode 3. If MIDI channel 15 is disabled it could be used + * as CC global for all channels belonging to basic channel 0. + * @warning Contrary to the MIDI Standard, this function does not clear LSB controllers, + * when MSB controllers are received. + */ +int +fluid_synth_cc(fluid_synth_t *synth, int chan, int num, int val) +{ + int result = FLUID_FAILED; + fluid_channel_t *channel; + fluid_return_val_if_fail(num >= 0 && num <= 127, FLUID_FAILED); + fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + channel = synth->channel[chan]; + + if(channel->mode & FLUID_CHANNEL_ENABLED) + { + /* chan is enabled */ + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "cc\t%d\t%d\t%d", chan, num, val); + } + + fluid_channel_set_cc(channel, num, val); + result = fluid_synth_cc_LOCAL(synth, chan, num); + } + else /* chan is disabled so it is a candidate for global channel */ + { + /* looks for next basic channel */ + int n_chan = synth->midi_channels; /* MIDI Channels number */ + int basicchan ; + + if(chan < n_chan - 1) + { + basicchan = chan + 1; /* next channel */ + } + else + { + basicchan = 0; /* wrap to 0 */ + } + + channel = synth->channel[basicchan]; + + /* Channel must be a basicchan in mode OMNIOFF_MONO */ + if((channel->mode & FLUID_CHANNEL_BASIC) && + ((channel->mode & FLUID_CHANNEL_MODE_MASK) == FLUID_CHANNEL_MODE_OMNIOFF_MONO)) + { + /* sends cc to all channels in this basic channel */ + int i, nbr = channel->mode_val; + + for(i = basicchan; i < basicchan + nbr; i++) + { + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "cc\t%d\t%d\t%d", i, num, val); + } + + fluid_channel_set_cc(synth->channel[i], num, val); + result = fluid_synth_cc_LOCAL(synth, i, num); + } + } + /* The channel chan is not a valid 'global channel' */ + else + { + result = FLUID_FAILED; + } + } + + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of MIDI CC set function. + Most of CC are allowed to modulate but not all. A comment describes if CC num + isn't allowed to modulate. + Following explanations should help to understand both MIDI specifications and + Soundfont specifications in regard to MIDI specs. + + MIDI specs: + CC LSB (32 to 63) are LSB contributions to CC MSB (0 to 31). + It's up to the synthesizer to decide to take LSB values into account or not. + Actually Fluidsynth doesn't use CC LSB value inside fluid_voice_update_param() + (once fluid_voice_modulate() has been triggered). This is because actually + fluidsynth needs only 7 bits resolution (and not 14 bits) from these CCs. + So fluidsynth is using only 7 bit MSB (except for portamento time). + In regard to MIDI specs Fluidsynth behaves correctly. + + Soundfont specs 2.01 - 8.2.1: + To deal correctly with MIDI CC (regardless if any synth will use CC MSB alone (7 bit) + or both CCs MSB,LSB (14 bits) during synthesis), SF specs recommend not making use of + CC LSB (i.e only CC MSB) in modulator sources to trigger modulation (i.e modulators + with CC LSB connected to sources inputs should be ignored). + These specifics are particularly suited for synths that use 14 bits CCs. In this case, + the MIDI transmitter sends CC LSB first followed by CC MSB. The MIDI synth receives + both CC LSB and CC MSB but only CC MSB will trigger the modulation. + This will produce correct synthesis parameters update from a correct 14 bits CC. + If in SF specs, modulator sources with CC LSB had been accepted, both CC LSB and + CC MSB will triggers 2 modulations. This leads to incorrect synthesis parameters + update followed by correct synthesis parameters update. + + However, as long as fluidsynth will use only CC 7 bits resolution, it is safe to ignore + these SF recommendations on CC receive. +*/ +static int +fluid_synth_cc_LOCAL(fluid_synth_t *synth, int channum, int num) +{ + fluid_channel_t *chan = synth->channel[channum]; + int nrpn_select; + int value; + + value = fluid_channel_get_cc(chan, num); + + switch(num) + { + case LOCAL_CONTROL: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + break; + + /* CC omnioff, omnion, mono, poly */ + /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + case POLY_OFF: + case POLY_ON: + case OMNI_OFF: + case OMNI_ON: + + /* allowed only if channum is a basic channel */ + if(chan->mode & FLUID_CHANNEL_BASIC) + { + /* Construction of new_mode from current channel mode and this CC mode */ + int new_mode = chan->mode & FLUID_CHANNEL_MODE_MASK; + + switch(num) + { + case POLY_OFF: + new_mode |= FLUID_CHANNEL_POLY_OFF; + break; + + case POLY_ON: + new_mode &= ~FLUID_CHANNEL_POLY_OFF; + break; + + case OMNI_OFF: + new_mode |= FLUID_CHANNEL_OMNI_OFF; + break; + + case OMNI_ON: + new_mode &= ~FLUID_CHANNEL_OMNI_OFF; + break; + + default: /* should never happen */ + return FLUID_FAILED; + } + + /* MIDI specs: if value is 0 it means all channels from channum to next + basic channel minus 1 (if any) or to MIDI channel count minus 1. + However, if value is > 0 (e.g. 4), the group of channels will be be + limited to 4. + value is ignored for #FLUID_CHANNEL_MODE_OMNIOFF_POLY as this mode + implies a group of only one channel. + */ + /* Checks value range and changes this existing basic channel group */ + value = fluid_synth_check_next_basic_channel(synth, channum, new_mode, value); + + if(value != FLUID_FAILED) + { + /* reset the current basic channel before changing it */ + fluid_synth_reset_basic_channel_LOCAL(synth, channum, chan->mode_val); + fluid_synth_set_basic_channel_LOCAL(synth, channum, new_mode, value); + break; /* FLUID_OK */ + } + } + + return FLUID_FAILED; + + case LEGATO_SWITCH: /* not allowed to modulate */ + /* handles Poly/mono commutation on Legato pedal On/Off.*/ + fluid_channel_cc_legato(chan, value); + break; + + case PORTAMENTO_SWITCH: /* not allowed to modulate */ + /* Special handling of the monophonic list */ + /* Invalids the most recent note played in a staccato manner */ + fluid_channel_invalid_prev_note_staccato(chan); + break; + + case SUSTAIN_SWITCH: /* not allowed to modulate */ + + /* Release voices if Sustain switch is released */ + if(value < 64) /* Sustain is released */ + { + fluid_synth_damp_voices_by_sustain_LOCAL(synth, channum); + } + + break; + + case SOSTENUTO_SWITCH: /* not allowed to modulate */ + + /* Release voices if Sostetuno switch is released */ + if(value < 64) /* Sostenuto is released */ + { + fluid_synth_damp_voices_by_sostenuto_LOCAL(synth, channum); + } + else /* Sostenuto is depressed */ + /* Update sostenuto order id when pedaling on Sostenuto */ + { + chan->sostenuto_orderid = synth->noteid; /* future voice id value */ + } + + break; + + case BANK_SELECT_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_channel_set_bank_msb(chan, value & 0x7F); + break; + + case BANK_SELECT_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_channel_set_bank_lsb(chan, value & 0x7F); + break; + + case ALL_NOTES_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_synth_all_notes_off_LOCAL(synth, channum); + break; + + case ALL_SOUND_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_synth_all_sounds_off_LOCAL(synth, channum); + break; + + case ALL_CTRL_OFF: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_channel_init_ctrl(chan, 1); + // the hold pedals have been reset, we maybe need to release voices + fluid_synth_damp_voices_by_sustain_LOCAL(synth, channum); + fluid_synth_damp_voices_by_sostenuto_LOCAL(synth, channum); + fluid_synth_modulate_voices_all_LOCAL(synth, channum); + break; + + case DATA_ENTRY_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + break; + + case DATA_ENTRY_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + { + int data = (value << 7) + fluid_channel_get_cc(chan, DATA_ENTRY_LSB); + + if(chan->nrpn_active) /* NRPN is active? */ + { + /* SontFont 2.01 NRPN Message (Sect. 9.6, p. 74) */ + if((fluid_channel_get_cc(chan, NRPN_MSB) == 120) + && (fluid_channel_get_cc(chan, NRPN_LSB) < 100)) + { + nrpn_select = chan->nrpn_select; + + if(nrpn_select < GEN_LAST) + { + float val = fluid_gen_scale_nrpn(nrpn_select, data); + fluid_synth_set_gen_LOCAL(synth, channum, nrpn_select, val); + } + + chan->nrpn_select = 0; /* Reset to 0 */ + } + } + else if(fluid_channel_get_cc(chan, RPN_MSB) == 0) /* RPN is active: MSB = 0? */ + { + switch(fluid_channel_get_cc(chan, RPN_LSB)) + { + case RPN_PITCH_BEND_RANGE: /* Set bend range in semitones */ + fluid_channel_set_pitch_wheel_sensitivity(synth->channel[channum], value); + fluid_synth_update_pitch_wheel_sens_LOCAL(synth, channum); /* Update bend range */ + /* FIXME - Handle LSB? (Fine bend range in cents) */ + break; + + case RPN_CHANNEL_FINE_TUNE: /* Fine tune is 14 bit over +/-1 semitone (+/- 100 cents, 8192 = center) */ + fluid_synth_set_gen_LOCAL(synth, channum, GEN_FINETUNE, + (float)(data - 8192) * (100.0f / 8192.0f)); + break; + + case RPN_CHANNEL_COARSE_TUNE: /* Coarse tune is 7 bit and in semitones (64 is center) */ + fluid_synth_set_gen_LOCAL(synth, channum, GEN_COARSETUNE, + value - 64); + break; + + case RPN_TUNING_PROGRAM_CHANGE: + fluid_channel_set_tuning_prog(chan, value); + fluid_synth_activate_tuning(synth, channum, + fluid_channel_get_tuning_bank(chan), + value, TRUE); + break; + + case RPN_TUNING_BANK_SELECT: + fluid_channel_set_tuning_bank(chan, value); + break; + + case RPN_MODULATION_DEPTH_RANGE: + break; + } + } + + break; + } + + case NRPN_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + fluid_channel_set_cc(chan, NRPN_LSB, 0); + chan->nrpn_select = 0; + chan->nrpn_active = 1; + break; + + case NRPN_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + + /* SontFont 2.01 NRPN Message (Sect. 9.6, p. 74) */ + if(fluid_channel_get_cc(chan, NRPN_MSB) == 120) + { + if(value == 100) + { + chan->nrpn_select += 100; + } + else if(value == 101) + { + chan->nrpn_select += 1000; + } + else if(value == 102) + { + chan->nrpn_select += 10000; + } + else if(value < 100) + { + chan->nrpn_select += value; + } + } + + chan->nrpn_active = 1; + break; + + case RPN_MSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + case RPN_LSB: /* not allowed to modulate (spec SF 2.01 - 8.2.1) */ + chan->nrpn_active = 0; + break; + + case BREATH_MSB: + /* handles CC Breath On/Off noteOn/noteOff mode */ + fluid_channel_cc_breath_note_on_off(chan, value); + + /* fall-through */ + default: + /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) */ + /* However, as long fluidsynth will use only CC 7 bits resolution, it + is safe to ignore these SF recommendations on CC receive. See + explanations above */ + /* if (! (32 <= num && num <= 63)) */ + { + return fluid_synth_modulate_voices_LOCAL(synth, channum, 1, num); + } + } + + return FLUID_OK; +} + +/** + * Get current MIDI controller value on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param num MIDI controller number (0-127) + * @param pval Location to store MIDI controller value (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_get_cc(fluid_synth_t *synth, int chan, int num, int *pval) +{ + fluid_return_val_if_fail(num >= 0 && num < 128, FLUID_FAILED); + fluid_return_val_if_fail(pval != NULL, FLUID_FAILED); + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + *pval = fluid_channel_get_cc(synth->channel[chan], num); + FLUID_API_RETURN(FLUID_OK); +} + +/* + * Handler for synth.device-id setting. + */ +static void +fluid_synth_handle_device_id(void *data, const char *name, int value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_return_if_fail(synth != NULL); + + fluid_synth_api_enter(synth); + synth->device_id = value; + fluid_synth_api_exit(synth); +} + +/** + * Process a MIDI SYSEX (system exclusive) message. + * @param synth FluidSynth instance + * @param data Buffer containing SYSEX data (not including 0xF0 and 0xF7) + * @param len Length of data in buffer + * @param response Buffer to store response to or NULL to ignore + * @param response_len IN/OUT parameter, in: size of response buffer, out: + * amount of data written to response buffer (if #FLUID_FAILED is returned and + * this value is non-zero, it indicates the response buffer is too small) + * @param handled Optional location to store boolean value if message was + * recognized and handled or not (set to TRUE if it was handled) + * @param dryrun TRUE to just do a dry run but not actually execute the SYSEX + * command (useful for checking if a SYSEX message would be handled) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + * @note When Fluidsynth receives an XG System Mode ON message, it compares the @p synth 's deviceID + * directly with the deviceID of the SysEx message. This is contrary to the XG spec (page 42), which + * requires to only compare the lower nibble. However, following the XG spec seems to break drum channels + * for a lot of MIDI files out there and therefore we've decided for this customization. If you rely on + * XG System Mode ON messages, make sure to set the setting \ref settings_synth_device-id to match the + * deviceID provided in the SysEx message (in most cases, this will be <code>deviceID=16</code>). + * + * @code + * SYSEX format (0xF0 and 0xF7 bytes shall not be passed to this function): + * Non-realtime: 0xF0 0x7E <DeviceId> [BODY] 0xF7 + * Realtime: 0xF0 0x7F <DeviceId> [BODY] 0xF7 + * Tuning messages: 0xF0 0x7E/0x7F <DeviceId> 0x08 <sub ID2> [BODY] <ChkSum> 0xF7 + * GS DT1 messages: 0xF0 0x41 <DeviceId> 0x42 0x12 [ADDRESS (3 bytes)] [DATA] <ChkSum> 0xF7 + * @endcode + */ +int +fluid_synth_sysex(fluid_synth_t *synth, const char *data, int len, + char *response, int *response_len, int *handled, int dryrun) +{ + int avail_response = 0; + + if(handled) + { + *handled = FALSE; + } + + if(response_len) + { + avail_response = *response_len; + *response_len = 0; + } + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(data != NULL, FLUID_FAILED); + fluid_return_val_if_fail(len > 0, FLUID_FAILED); + fluid_return_val_if_fail(!response || response_len, FLUID_FAILED); + + if(len < 4) + { + return FLUID_OK; + } + + /* MIDI tuning SYSEX message? */ + if((data[0] == MIDI_SYSEX_UNIV_NON_REALTIME || data[0] == MIDI_SYSEX_UNIV_REALTIME) + && (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL || synth->device_id == MIDI_SYSEX_DEVICE_ID_ALL) + && data[2] == MIDI_SYSEX_MIDI_TUNING_ID) + { + int result; + fluid_synth_api_enter(synth); + result = fluid_synth_sysex_midi_tuning(synth, data, len, response, + response_len, avail_response, + handled, dryrun); + + FLUID_API_RETURN(result); + } + + /* GM or GM2 system on */ + if(data[0] == MIDI_SYSEX_UNIV_NON_REALTIME + && (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL || synth->device_id == MIDI_SYSEX_DEVICE_ID_ALL) + && data[2] == MIDI_SYSEX_GM_ID) + { + if(handled) + { + *handled = TRUE; + } + if(!dryrun && (data[3] == MIDI_SYSEX_GM_ON + || data[3] == MIDI_SYSEX_GM2_ON)) + { + int result; + synth->bank_select = FLUID_BANK_STYLE_GM; + fluid_synth_api_enter(synth); + result = fluid_synth_system_reset_LOCAL(synth); + FLUID_API_RETURN(result); + } + return FLUID_OK; + } + + /* GS DT1 message */ + if(data[0] == MIDI_SYSEX_MANUF_ROLAND + && (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL || synth->device_id == MIDI_SYSEX_DEVICE_ID_ALL) + && data[2] == MIDI_SYSEX_GS_ID + && data[3] == MIDI_SYSEX_GS_DT1) + { + int result; + fluid_synth_api_enter(synth); + result = fluid_synth_sysex_gs_dt1(synth, data, len, response, + response_len, avail_response, + handled, dryrun); + FLUID_API_RETURN(result); + } + + /* XG message */ + if(data[0] == MIDI_SYSEX_MANUF_YAMAHA + && (data[1] == synth->device_id || data[1] == MIDI_SYSEX_DEVICE_ID_ALL || synth->device_id == MIDI_SYSEX_DEVICE_ID_ALL) + && data[2] == MIDI_SYSEX_XG_ID) + { + int result; + fluid_synth_api_enter(synth); + result = fluid_synth_sysex_xg(synth, data, len, response, + response_len, avail_response, + handled, dryrun); + FLUID_API_RETURN(result); + } + + return FLUID_OK; +} + +/* Handler for MIDI tuning SYSEX messages */ +static int +fluid_synth_sysex_midi_tuning(fluid_synth_t *synth, const char *data, int len, + char *response, int *response_len, int avail_response, + int *handled, int dryrun) +{ + int realtime, msgid; + int bank = 0, prog, channels; + double tunedata[128]; + int keys[128]; + char name[17]={0}; + int note, frac, frac2; + uint8_t chksum; + int i, count, index; + const char *dataptr; + char *resptr;; + + realtime = data[0] == MIDI_SYSEX_UNIV_REALTIME; + msgid = data[3]; + + switch(msgid) + { + case MIDI_SYSEX_TUNING_BULK_DUMP_REQ: + case MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK: + if(data[3] == MIDI_SYSEX_TUNING_BULK_DUMP_REQ) + { + if(len != 5 || data[4] & 0x80 || !response) + { + return FLUID_OK; + } + + *response_len = 406; + prog = data[4]; + } + else + { + if(len != 6 || data[4] & 0x80 || data[5] & 0x80 || !response) + { + return FLUID_OK; + } + + *response_len = 407; + bank = data[4]; + prog = data[5]; + } + + if(dryrun) + { + if(handled) + { + *handled = TRUE; + } + + return FLUID_OK; + } + + if(avail_response < *response_len) + { + return FLUID_FAILED; + } + + /* Get tuning data, return if tuning not found */ + if(fluid_synth_tuning_dump(synth, bank, prog, name, 17, tunedata) == FLUID_FAILED) + { + *response_len = 0; + return FLUID_OK; + } + + resptr = response; + + *resptr++ = MIDI_SYSEX_UNIV_NON_REALTIME; + *resptr++ = synth->device_id; + *resptr++ = MIDI_SYSEX_MIDI_TUNING_ID; + *resptr++ = MIDI_SYSEX_TUNING_BULK_DUMP; + + if(msgid == MIDI_SYSEX_TUNING_BULK_DUMP_REQ_BANK) + { + *resptr++ = bank; + } + + *resptr++ = prog; + /* copy 16 ASCII characters (potentially not null terminated) to the sysex buffer */ + FLUID_MEMCPY(resptr, name, 16); + resptr += 16; + + for(i = 0; i < 128; i++) + { + note = tunedata[i] / 100.0; + fluid_clip(note, 0, 127); + + frac = ((tunedata[i] - note * 100.0) * 16384.0 + 50.0) / 100.0; + fluid_clip(frac, 0, 16383); + + *resptr++ = note; + *resptr++ = frac >> 7; + *resptr++ = frac & 0x7F; + } + + if(msgid == MIDI_SYSEX_TUNING_BULK_DUMP_REQ) + { + /* NOTE: Checksum is not as straight forward as the bank based messages */ + chksum = MIDI_SYSEX_UNIV_NON_REALTIME ^ MIDI_SYSEX_MIDI_TUNING_ID + ^ MIDI_SYSEX_TUNING_BULK_DUMP ^ prog; + + for(i = 21; i < 128 * 3 + 21; i++) + { + chksum ^= response[i]; + } + } + else + { + for(i = 1, chksum = 0; i < 406; i++) + { + chksum ^= response[i]; + } + } + + *resptr++ = chksum & 0x7F; + + if(handled) + { + *handled = TRUE; + } + + break; + + case MIDI_SYSEX_TUNING_NOTE_TUNE: + case MIDI_SYSEX_TUNING_NOTE_TUNE_BANK: + dataptr = data + 4; + + if(msgid == MIDI_SYSEX_TUNING_NOTE_TUNE) + { + if(len < 10 || data[4] & 0x80 || data[5] & 0x80 || len != data[5] * 4 + 6) + { + return FLUID_OK; + } + } + else + { + if(len < 11 || data[4] & 0x80 || data[5] & 0x80 || data[6] & 0x80 + || len != data[6] * 4 + 7) + { + return FLUID_OK; + } + + bank = *dataptr++; + } + + if(dryrun) + { + if(handled) + { + *handled = TRUE; + } + + return FLUID_OK; + } + + prog = *dataptr++; + count = *dataptr++; + + for(i = 0, index = 0; i < count; i++) + { + note = *dataptr++; + + if(note & 0x80) + { + return FLUID_OK; + } + + keys[index] = note; + + note = *dataptr++; + frac = *dataptr++; + frac2 = *dataptr++; + + if(note & 0x80 || frac & 0x80 || frac2 & 0x80) + { + return FLUID_OK; + } + + frac = frac << 7 | frac2; + + /* No change pitch value? Doesn't really make sense to send that, but.. */ + if(note == 0x7F && frac == 16383) + { + continue; + } + + tunedata[index] = note * 100.0 + (frac * 100.0 / 16384.0); + index++; + } + + if(index > 0) + { + if(fluid_synth_tune_notes(synth, bank, prog, index, keys, tunedata, + realtime) == FLUID_FAILED) + { + return FLUID_FAILED; + } + } + + if(handled) + { + *handled = TRUE; + } + + break; + + case MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE: + case MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE: + if((msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE && len != 19) + || (msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_2BYTE && len != 31)) + { + return FLUID_OK; + } + + if(data[4] & 0x80 || data[5] & 0x80 || data[6] & 0x80) + { + return FLUID_OK; + } + + if(dryrun) + { + if(handled) + { + *handled = TRUE; + } + + return FLUID_OK; + } + + channels = (data[4] & 0x03) << 14 | data[5] << 7 | data[6]; + + if(msgid == MIDI_SYSEX_TUNING_OCTAVE_TUNE_1BYTE) + { + for(i = 0; i < 12; i++) + { + frac = data[i + 7]; + + if(frac & 0x80) + { + return FLUID_OK; + } + + tunedata[i] = (int)frac - 64; + } + } + else + { + for(i = 0; i < 12; i++) + { + frac = data[i * 2 + 7]; + frac2 = data[i * 2 + 8]; + + if(frac & 0x80 || frac2 & 0x80) + { + return FLUID_OK; + } + + tunedata[i] = (((int)frac << 7 | (int)frac2) - 8192) * (200.0 / 16384.0); + } + } + + if(fluid_synth_activate_octave_tuning(synth, 0, 0, "SYSEX", + tunedata, realtime) == FLUID_FAILED) + { + return FLUID_FAILED; + } + + if(channels) + { + for(i = 0; i < 16; i++) + { + if(channels & (1 << i)) + { + fluid_synth_activate_tuning(synth, i, 0, 0, realtime); + } + } + } + + if(handled) + { + *handled = TRUE; + } + + break; + } + + return FLUID_OK; +} + +/* Handler for GS DT1 messages */ +static int +fluid_synth_sysex_gs_dt1(fluid_synth_t *synth, const char *data, int len, + char *response, int *response_len, int avail_response, + int *handled, int dryrun) +{ + int addr; + int len_data; + int checksum = 0, i; + + if(len < 9) // at least one byte of data should be transmitted + { + FLUID_LOG(FLUID_INFO, "SysEx DT1: message too short, dropping it."); + return FLUID_FAILED; + } + len_data = len - 8; + addr = (data[4] << 16) | (data[5] << 8) | data[6]; + + for (i = 4; i < len - 1; ++i) + { + checksum += data[i]; + } + checksum = 0x80 - (checksum & 0x7F); + if (checksum != data[len - 1]) + { + FLUID_LOG(FLUID_INFO, "SysEx DT1: dropping message on addr 0x%x due to incorrect checksum 0x%x. Correct checksum: 0x%x", addr, (int)data[len - 1], checksum); + return FLUID_FAILED; + } + + if (addr == 0x40007F) // Mode set + { + if (len_data > 1 || (data[7] != 0 && data[7] != 0x7f)) + { + FLUID_LOG(FLUID_INFO, "SysEx DT1: dropping invalid mode set message"); + return FLUID_FAILED; + } + if (handled) + { + *handled = TRUE; + } + if (!dryrun) + { + if (data[7] == 0) + { + synth->bank_select = FLUID_BANK_STYLE_GS; + } + else + { + synth->bank_select = FLUID_BANK_STYLE_GM; + } + return fluid_synth_system_reset_LOCAL(synth); + } + return FLUID_OK; + } + + if (synth->bank_select != FLUID_BANK_STYLE_GS) + { + return FLUID_OK; // Silently ignore all other messages + } + + if ((addr & 0xFFF0FF) == 0x401015) // Use for rhythm part + { + if (len_data > 1 || data[7] > 0x02) + { + FLUID_LOG(FLUID_INFO, "SysEx DT1: dropping invalid rhythm part message"); + return FLUID_FAILED; + } + if (handled) + { + *handled = TRUE; + } + if (!dryrun) + { + int chan = (addr >> 8) & 0x0F; + //See the Patch Part parameters section in SC-88Pro/8850 owner's manual + chan = chan >= 0x0a ? chan : (chan == 0 ? 9 : chan - 1); + synth->channel[chan]->channel_type = + data[7] == 0x00 ? CHANNEL_TYPE_MELODIC : CHANNEL_TYPE_DRUM; + + FLUID_LOG(FLUID_DBG, "SysEx DT1: setting MIDI channel %d to type %d", chan, (int)synth->channel[chan]->channel_type); + //Roland synths seem to "remember" the last instrument a channel + //used in the selected mode. This behavior is not replicated here. + fluid_synth_program_change(synth, chan, 0); + } + return FLUID_OK; + } + + //silently ignore + return FLUID_OK; +} + +/* Handler for XG messages */ +static int +fluid_synth_sysex_xg(fluid_synth_t *synth, const char *data, int len, + char *response, int *response_len, int avail_response, + int *handled, int dryrun) +{ + int addr; + int len_data; + + if(len < 7) // at least one byte of data should be transmitted + { + return FLUID_FAILED; + } + len_data = len - 6; + addr = (data[3] << 16) | (data[4] << 8) | data[5]; + + if (addr == 0x00007E // Reset + || addr == 0x00007F) // Reset to factory + { + if (len_data > 1 || data[6] != 0) + { + return FLUID_FAILED; + } + if (handled) + { + *handled = TRUE; + } + if (!dryrun) + { + synth->bank_select = FLUID_BANK_STYLE_XG; + return fluid_synth_system_reset_LOCAL(synth); + } + return FLUID_OK; + } + + /* No other messages handled yet + if (synth->bank_select != FLUID_BANK_STYLE_XG) + { + return FLUID_OK; // Silently ignore all other messages + }*/ + + //silently ignore + return FLUID_OK; +} + +/** + * Turn off all voices that are playing on the given MIDI channel, by putting them into release phase. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1), (chan=-1 selects all channels) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.4 + */ +int +fluid_synth_all_notes_off(fluid_synth_t *synth, int chan) +{ + int result; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(chan >= -1, FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(chan >= synth->midi_channels) + { + result = FLUID_FAILED; + } + else + { + /* Allowed (even for channel disabled) as chan = -1 selects all channels */ + result = fluid_synth_all_notes_off_LOCAL(synth, chan); + } + + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of all notes off, (chan=-1 selects all channels) */ +//static int +int +fluid_synth_all_notes_off_LOCAL(fluid_synth_t *synth, int chan) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice) && ((-1 == chan) || (chan == fluid_voice_get_channel(voice)))) + { + fluid_voice_noteoff(voice); + } + } + + return FLUID_OK; +} + +/** + * Immediately stop all voices on the given MIDI channel (skips release phase). + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1), (chan=-1 selects all channels) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.4 + */ +int +fluid_synth_all_sounds_off(fluid_synth_t *synth, int chan) +{ + int result; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(chan >= -1, FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(chan >= synth->midi_channels) + { + result = FLUID_FAILED; + } + else + { + /* Allowed (even for channel disabled) as chan = -1 selects all channels */ + result = fluid_synth_all_sounds_off_LOCAL(synth, chan); + } + + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of all sounds off, (chan=-1 selects all channels) */ +static int +fluid_synth_all_sounds_off_LOCAL(fluid_synth_t *synth, int chan) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice) && ((-1 == chan) || (chan == fluid_voice_get_channel(voice)))) + { + fluid_voice_off(voice); + } + } + + return FLUID_OK; +} + +/** + * Reset reverb engine + * @param synth FluidSynth instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_reset_reverb(fluid_synth_t *synth) +{ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_reverb, 0, 0.0f); + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Reset chorus engine + * @param synth FluidSynth instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_reset_chorus(fluid_synth_t *synth) +{ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_chorus, 0, 0.0f); + FLUID_API_RETURN(FLUID_OK); +} + + +/** + * Send MIDI system reset command (big red 'panic' button), turns off notes, resets + * controllers and restores initial basic channel configuration. + * @param synth FluidSynth instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_system_reset(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + result = fluid_synth_system_reset_LOCAL(synth); + FLUID_API_RETURN(result); +} + +/* Local variant of the system reset command */ +static int +fluid_synth_system_reset_LOCAL(fluid_synth_t *synth) +{ + int i; + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "=== systemreset ==="); + } + + fluid_synth_all_sounds_off_LOCAL(synth, -1); + + for(i = 0; i < synth->midi_channels; i++) + { + fluid_channel_reset(synth->channel[i]); + } + + /* Basic channel 0, Mode Omni On Poly */ + fluid_synth_set_basic_channel(synth, 0, FLUID_CHANNEL_MODE_OMNION_POLY, + synth->midi_channels); + + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_reverb, 0, 0.0f); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_chorus, 0, 0.0f); + + return FLUID_OK; +} + +/** + * Update voices on a MIDI channel after a MIDI control change. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param is_cc Boolean value indicating if ctrl is a CC controller or not + * @param ctrl MIDI controller value + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +static int +fluid_synth_modulate_voices_LOCAL(fluid_synth_t *synth, int chan, int is_cc, int ctrl) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_get_channel(voice) == chan) + { + fluid_voice_modulate(voice, is_cc, ctrl); + } + } + + return FLUID_OK; +} + +/** + * Update voices on a MIDI channel after all MIDI controllers have been changed. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +static int +fluid_synth_modulate_voices_all_LOCAL(fluid_synth_t *synth, int chan) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_get_channel(voice) == chan) + { + fluid_voice_modulate_all(voice); + } + } + + return FLUID_OK; +} + +/** + * Set the MIDI channel pressure controller value. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param val MIDI channel pressure value (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_channel_pressure(fluid_synth_t *synth, int chan, int val) +{ + int result; + fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "channelpressure\t%d\t%d", chan, val); + } + + fluid_channel_set_channel_pressure(synth->channel[chan], val); + result = fluid_synth_update_channel_pressure_LOCAL(synth, chan); + + FLUID_API_RETURN(result); +} + +/* Updates channel pressure from within synthesis thread */ +static int +fluid_synth_update_channel_pressure_LOCAL(fluid_synth_t *synth, int chan) +{ + return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_CHANNELPRESSURE); +} + +/** + * Set the MIDI polyphonic key pressure controller value. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param key MIDI key number (0-127) + * @param val MIDI key pressure value (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 2.0.0 + */ +int +fluid_synth_key_pressure(fluid_synth_t *synth, int chan, int key, int val) +{ + int result; + fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); + fluid_return_val_if_fail(val >= 0 && val <= 127, FLUID_FAILED); + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "keypressure\t%d\t%d\t%d", chan, key, val); + } + + fluid_channel_set_key_pressure(synth->channel[chan], key, val); + result = fluid_synth_update_key_pressure_LOCAL(synth, chan, key); + + FLUID_API_RETURN(result); +} + +/* Updates key pressure from within synthesis thread */ +static int +fluid_synth_update_key_pressure_LOCAL(fluid_synth_t *synth, int chan, int key) +{ + fluid_voice_t *voice; + int i; + int result = FLUID_OK; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(voice->chan == chan && voice->key == key) + { + result = fluid_voice_modulate(voice, 0, FLUID_MOD_KEYPRESSURE); + + if(result != FLUID_OK) + { + return result; + } + } + } + + return result; +} + +/** + * Set the MIDI pitch bend controller value on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param val MIDI pitch bend value (0-16383 with 8192 being center) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_pitch_bend(fluid_synth_t *synth, int chan, int val) +{ + int result; + fluid_return_val_if_fail(val >= 0 && val <= 16383, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "pitchb\t%d\t%d", chan, val); + } + + fluid_channel_set_pitch_bend(synth->channel[chan], val); + result = fluid_synth_update_pitch_bend_LOCAL(synth, chan); + + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of pitch bend */ +static int +fluid_synth_update_pitch_bend_LOCAL(fluid_synth_t *synth, int chan) +{ + return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_PITCHWHEEL); +} + +/** + * Get the MIDI pitch bend controller value on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param ppitch_bend Location to store MIDI pitch bend value (0-16383 with + * 8192 being center) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_get_pitch_bend(fluid_synth_t *synth, int chan, int *ppitch_bend) +{ + int result; + fluid_return_val_if_fail(ppitch_bend != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + *ppitch_bend = fluid_channel_get_pitch_bend(synth->channel[chan]); + result = FLUID_OK; + + FLUID_API_RETURN(result); +} + +/** + * Set MIDI pitch wheel sensitivity on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param val Pitch wheel sensitivity value in semitones + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_pitch_wheel_sens(fluid_synth_t *synth, int chan, int val) +{ + int result; + fluid_return_val_if_fail(val >= 0 && val <= 72, FLUID_FAILED); /* 6 octaves!? Better than no limit.. */ + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "pitchsens\t%d\t%d", chan, val); + } + + fluid_channel_set_pitch_wheel_sensitivity(synth->channel[chan], val); + result = fluid_synth_update_pitch_wheel_sens_LOCAL(synth, chan); + + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of set pitch wheel sensitivity */ +static int +fluid_synth_update_pitch_wheel_sens_LOCAL(fluid_synth_t *synth, int chan) +{ + return fluid_synth_modulate_voices_LOCAL(synth, chan, 0, FLUID_MOD_PITCHWHEELSENS); +} + +/** + * Get MIDI pitch wheel sensitivity on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param pval Location to store pitch wheel sensitivity value in semitones + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since Sometime AFTER v1.0 API freeze. + */ +int +fluid_synth_get_pitch_wheel_sens(fluid_synth_t *synth, int chan, int *pval) +{ + int result; + fluid_return_val_if_fail(pval != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + *pval = fluid_channel_get_pitch_wheel_sensitivity(synth->channel[chan]); + result = FLUID_OK; + + FLUID_API_RETURN(result); +} + +/** + * Assign a preset to a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param preset Preset to assign to channel or NULL to clear (ownership is taken over) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +static int +fluid_synth_set_preset(fluid_synth_t *synth, int chan, fluid_preset_t *preset) +{ + fluid_channel_t *channel; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(chan >= 0 && chan < synth->midi_channels, FLUID_FAILED); + + channel = synth->channel[chan]; + + return fluid_channel_set_preset(channel, preset); +} + +/* Get a preset by SoundFont, bank and program numbers. + * Returns preset pointer or NULL. + */ +static fluid_preset_t * +fluid_synth_get_preset(fluid_synth_t *synth, int sfontnum, + int banknum, int prognum) +{ + fluid_sfont_t *sfont; + fluid_list_t *list; + + /* 128 indicates an "unset" operation" */ + if(prognum == FLUID_UNSET_PROGRAM) + { + return NULL; + } + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == sfontnum) + { + return fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); + } + } + + return NULL; +} + +/* Get a preset by SoundFont name, bank and program. + * Returns preset pointer or NULL. + */ +static fluid_preset_t * +fluid_synth_get_preset_by_sfont_name(fluid_synth_t *synth, const char *sfontname, + int banknum, int prognum) +{ + fluid_sfont_t *sfont; + fluid_list_t *list; + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(FLUID_STRCMP(fluid_sfont_get_name(sfont), sfontname) == 0) + { + return fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); + } + } + + return NULL; +} + +/* Find a preset by bank and program numbers. + * Returns preset pointer or NULL. + */ +fluid_preset_t * +fluid_synth_find_preset(fluid_synth_t *synth, int banknum, + int prognum) +{ + fluid_preset_t *preset; + fluid_sfont_t *sfont; + fluid_list_t *list; + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + preset = fluid_sfont_get_preset(sfont, banknum - sfont->bankofs, prognum); + + if(preset) + { + return preset; + } + } + + return NULL; +} + +/** + * Send a program change event on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param prognum MIDI program number (0-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +/* As of 1.1.1 prognum can be set to 128 to unset the preset. Not documented + * since fluid_synth_unset_program() should be used instead. */ +int +fluid_synth_program_change(fluid_synth_t *synth, int chan, int prognum) +{ + fluid_preset_t *preset = NULL; + fluid_channel_t *channel; + int subst_bank, subst_prog, banknum = 0, result = FLUID_FAILED; + + fluid_return_val_if_fail(prognum >= 0 && prognum <= 128, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + channel = synth->channel[chan]; + + if(channel->channel_type == CHANNEL_TYPE_DRUM) + { + banknum = DRUM_INST_BANK; + } + else + { + fluid_channel_get_sfont_bank_prog(channel, NULL, &banknum, NULL); + } + + if(synth->verbose) + { + FLUID_LOG(FLUID_INFO, "prog\t%d\t%d\t%d", chan, banknum, prognum); + } + + /* I think this is a hack for MIDI files that do bank changes in GM mode. + * Proper way to handle this would probably be to ignore bank changes when in + * GM mode. - JG + * This is now possible by setting synth.midi-bank-select=gm, but let the hack + * stay for the time being. - DH + */ + if(prognum != FLUID_UNSET_PROGRAM) + { + subst_bank = banknum; + subst_prog = prognum; + + preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); + + /* Fallback to another preset if not found */ + if(!preset) + { + /* Percussion: Fallback to preset 0 in percussion bank */ + if(channel->channel_type == CHANNEL_TYPE_DRUM) + { + subst_prog = 0; + subst_bank = DRUM_INST_BANK; + preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); + } + /* Melodic instrument */ + else + { + /* Fallback first to bank 0:prognum */ + subst_bank = 0; + preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); + + /* Fallback to first preset in bank 0 (usually piano...) */ + if(!preset) + { + subst_prog = 0; + preset = fluid_synth_find_preset(synth, subst_bank, subst_prog); + } + } + + if(preset) + { + FLUID_LOG(FLUID_WARN, "Instrument not found on channel %d [bank=%d prog=%d], substituted [bank=%d prog=%d]", + chan, banknum, prognum, subst_bank, subst_prog); + } + else + { + FLUID_LOG(FLUID_WARN, "No preset found on channel %d [bank=%d prog=%d]", chan, banknum, prognum); + } + } + } + + /* Assign the SoundFont ID and program number to the channel */ + fluid_channel_set_sfont_bank_prog(channel, preset ? fluid_sfont_get_id(preset->sfont) : 0, + -1, prognum); + result = fluid_synth_set_preset(synth, chan, preset); + + FLUID_API_RETURN(result); +} + +/** + * Set instrument bank number on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param bank MIDI bank number + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @note This function does not change the instrument currently assigned to \c chan, + * as it is usually called prior to fluid_synth_program_change(). If you still want + * instrument changes to take effect immediately, call fluid_synth_program_reset() + * after having set up the bank configuration. + * + */ +int +fluid_synth_bank_select(fluid_synth_t *synth, int chan, int bank) +{ + int result; + fluid_return_val_if_fail(bank <= 16383, FLUID_FAILED); + fluid_return_val_if_fail(bank >= 0, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + fluid_channel_set_sfont_bank_prog(synth->channel[chan], -1, bank, -1); + result = FLUID_OK; + + FLUID_API_RETURN(result); +} + +/** + * Set SoundFont ID on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param sfont_id ID of a loaded SoundFont + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @note This function does not change the instrument currently assigned to \c chan, + * as it is usually called prior to fluid_synth_bank_select() or fluid_synth_program_change(). + * If you still want instrument changes to take effect immediately, call fluid_synth_program_reset() + * after having selected the soundfont. + */ +int +fluid_synth_sfont_select(fluid_synth_t *synth, int chan, int sfont_id) +{ + int result; + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + fluid_channel_set_sfont_bank_prog(synth->channel[chan], sfont_id, -1, -1); + result = FLUID_OK; + + FLUID_API_RETURN(result); +} + +/** + * Set the preset of a MIDI channel to an unassigned state. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.1 + * + * @note Channel retains its SoundFont ID and bank numbers, while the program + * number is set to an "unset" state. MIDI program changes may re-assign a + * preset if one matches. + */ +int +fluid_synth_unset_program(fluid_synth_t *synth, int chan) +{ + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + FLUID_API_RETURN(fluid_synth_program_change(synth, chan, FLUID_UNSET_PROGRAM)); +} + +/** + * Get current SoundFont ID, bank number and program number for a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param sfont_id Location to store SoundFont ID + * @param bank_num Location to store MIDI bank number + * @param preset_num Location to store MIDI program number + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_get_program(fluid_synth_t *synth, int chan, int *sfont_id, + int *bank_num, int *preset_num) +{ + int result; + fluid_channel_t *channel; + + fluid_return_val_if_fail(sfont_id != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank_num != NULL, FLUID_FAILED); + fluid_return_val_if_fail(preset_num != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + channel = synth->channel[chan]; + fluid_channel_get_sfont_bank_prog(channel, sfont_id, bank_num, preset_num); + + /* 128 indicates that the preset is unset. Set to 0 to be backwards compatible. */ + if(*preset_num == FLUID_UNSET_PROGRAM) + { + *preset_num = 0; + } + + result = FLUID_OK; + + FLUID_API_RETURN(result); +} + +/** + * Select an instrument on a MIDI channel by SoundFont ID, bank and program numbers. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param sfont_id ID of a loaded SoundFont + * @param bank_num MIDI bank number + * @param preset_num MIDI program number + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_program_select(fluid_synth_t *synth, int chan, int sfont_id, + int bank_num, int preset_num) +{ + fluid_preset_t *preset = NULL; + fluid_channel_t *channel; + int result; + fluid_return_val_if_fail(bank_num >= 0, FLUID_FAILED); + fluid_return_val_if_fail(preset_num >= 0, FLUID_FAILED); + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + channel = synth->channel[chan]; + + preset = fluid_synth_get_preset(synth, sfont_id, bank_num, preset_num); + + if(preset == NULL) + { + FLUID_LOG(FLUID_ERR, + "There is no preset with bank number %d and preset number %d in SoundFont %d", + bank_num, preset_num, sfont_id); + FLUID_API_RETURN(FLUID_FAILED); + } + + /* Assign the new SoundFont ID, bank and program number to the channel */ + fluid_channel_set_sfont_bank_prog(channel, sfont_id, bank_num, preset_num); + result = fluid_synth_set_preset(synth, chan, preset); + + FLUID_API_RETURN(result); +} + +/** + * Pins all samples of the given preset. + * + * @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 the preset was found, pinned and loaded + * into memory successfully. #FLUID_FAILED otherwise. Note that #FLUID_OK + * is returned, even if <code>synth.dynamic-sample-loading</code> 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 +fluid_synth_pin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num) +{ + int ret; + fluid_preset_t *preset; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank_num >= 0, FLUID_FAILED); + fluid_return_val_if_fail(preset_num >= 0, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + preset = fluid_synth_get_preset(synth, sfont_id, bank_num, preset_num); + + if(preset == NULL) + { + FLUID_LOG(FLUID_ERR, + "There is no preset with bank number %d and preset number %d in SoundFont %d", + bank_num, preset_num, sfont_id); + FLUID_API_RETURN(FLUID_FAILED); + } + + ret = fluid_preset_notify(preset, FLUID_PRESET_PIN, -1); // channel unused for pinning messages + + FLUID_API_RETURN(ret); +} + +/** + * Unpin all samples of the given preset. + * + * @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 +fluid_synth_unpin_preset(fluid_synth_t *synth, int sfont_id, int bank_num, int preset_num) +{ + int ret; + fluid_preset_t *preset; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank_num >= 0, FLUID_FAILED); + fluid_return_val_if_fail(preset_num >= 0, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + preset = fluid_synth_get_preset(synth, sfont_id, bank_num, preset_num); + + if(preset == NULL) + { + FLUID_LOG(FLUID_ERR, + "There is no preset with bank number %d and preset number %d in SoundFont %d", + bank_num, preset_num, sfont_id); + FLUID_API_RETURN(FLUID_FAILED); + } + + ret = fluid_preset_notify(preset, FLUID_PRESET_UNPIN, -1); // channel unused for pinning messages + + FLUID_API_RETURN(ret); +} + +/** + * Select an instrument on a MIDI channel by SoundFont name, bank and program numbers. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param sfont_name Name of a loaded SoundFont + * @param bank_num MIDI bank number + * @param preset_num MIDI program number + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + */ +int +fluid_synth_program_select_by_sfont_name(fluid_synth_t *synth, int chan, + const char *sfont_name, int bank_num, + int preset_num) +{ + fluid_preset_t *preset = NULL; + fluid_channel_t *channel; + int result; + fluid_return_val_if_fail(sfont_name != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* Allowed only on MIDI channel enabled */ + FLUID_API_RETURN_IF_CHAN_DISABLED(FLUID_FAILED); + + channel = synth->channel[chan]; + + preset = fluid_synth_get_preset_by_sfont_name(synth, sfont_name, bank_num, + preset_num); + + if(preset == NULL) + { + FLUID_LOG(FLUID_ERR, + "There is no preset with bank number %d and preset number %d in SoundFont %s", + bank_num, preset_num, sfont_name); + FLUID_API_RETURN(FLUID_FAILED); + } + + /* Assign the new SoundFont ID, bank and program number to the channel */ + fluid_channel_set_sfont_bank_prog(channel, fluid_sfont_get_id(preset->sfont), + bank_num, preset_num); + result = fluid_synth_set_preset(synth, chan, preset); + + FLUID_API_RETURN(result); +} + +/* + * This function assures that every MIDI channel has a valid preset + * (NULL is okay). This function is called after a SoundFont is + * unloaded or reloaded. + */ +static void +fluid_synth_update_presets(fluid_synth_t *synth) +{ + fluid_channel_t *channel; + fluid_preset_t *preset; + int sfont, bank, prog; + int chan; + + for(chan = 0; chan < synth->midi_channels; chan++) + { + channel = synth->channel[chan]; + fluid_channel_get_sfont_bank_prog(channel, &sfont, &bank, &prog); + preset = fluid_synth_get_preset(synth, sfont, bank, prog); + fluid_synth_set_preset(synth, chan, preset); + } +} + +static void +fluid_synth_set_sample_rate_LOCAL(fluid_synth_t *synth, float sample_rate) +{ + int i; + fluid_clip(sample_rate, 8000.0f, 96000.0f); + synth->sample_rate = sample_rate; + + synth->min_note_length_ticks = fluid_synth_get_min_note_length_LOCAL(synth); + + for(i = 0; i < synth->polyphony; i++) + { + fluid_voice_set_output_rate(synth->voice[i], sample_rate); + } +} + +/** + * Set up an event to change the sample-rate of the synth during the next rendering call. + * @warning This function is broken-by-design! Don't use it! Instead, specify the sample-rate when creating the synth. + * @deprecated As of fluidsynth 2.1.0 this function has been deprecated. + * Changing the sample-rate is generally not considered to be a real-time use-case, as it always produces some audible artifact ("click", "pop") on the dry sound and effects (because LFOs for chorus and reverb need to be reinitialized). + * The sample-rate change may also require memory allocation deep down in the effect units. + * However, this memory allocation may fail and there is no way for the caller to know that, because the actual change of the sample-rate is executed during rendering. + * This function cannot (must not) do the sample-rate change itself, otherwise the synth needs to be locked down, causing rendering to block. + * Esp. do not use this function if this @p synth instance is used by an audio driver, because the audio driver cannot be notified by this sample-rate change. + * Long story short: don't use it. + * @code{.cpp} + fluid_synth_t* synth; // assume initialized + // [...] + // sample-rate change needed? Delete the audio driver, if any. + delete_fluid_audio_driver(adriver); + // then delete the synth + delete_fluid_synth(synth); + // update the sample-rate + fluid_settings_setnum(settings, "synth.sample-rate", 22050.0); + // and re-create objects + synth = new_fluid_synth(settings); + adriver = new_fluid_audio_driver(settings, synth); + * @endcode + * @param synth FluidSynth instance + * @param sample_rate New sample-rate (Hz) + * @since 1.1.2 + */ +void +fluid_synth_set_sample_rate(fluid_synth_t *synth, float sample_rate) +{ + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + + fluid_synth_set_sample_rate_LOCAL(synth, sample_rate); + + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_samplerate, + 0, synth->sample_rate); + fluid_synth_api_exit(synth); +} + +// internal sample rate change function for the jack driver +// executes immediately, therefore, make sure no rendering call is running! +void +fluid_synth_set_sample_rate_immediately(fluid_synth_t *synth, float sample_rate) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + + fluid_synth_set_sample_rate_LOCAL(synth, sample_rate); + + param[0].i = 0; + param[1].real = synth->sample_rate; + fluid_rvoice_mixer_set_samplerate(synth->eventhandler->mixer, param); + + fluid_synth_api_exit(synth); +} + + +/* Handler for synth.gain setting. */ +static void +fluid_synth_handle_gain(void *data, const char *name, double value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_synth_set_gain(synth, (float) value); +} + +/** + * Set synth output gain value. + * @param synth FluidSynth instance + * @param gain Gain value (function clamps value to the range 0.0 to 10.0) + */ +void +fluid_synth_set_gain(fluid_synth_t *synth, float gain) +{ + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + + fluid_clip(gain, 0.0f, 10.0f); + + synth->gain = gain; + fluid_synth_update_gain_LOCAL(synth); + fluid_synth_api_exit(synth); +} + +/* Called by synthesis thread to update the gain in all voices */ +static void +fluid_synth_update_gain_LOCAL(fluid_synth_t *synth) +{ + fluid_voice_t *voice; + float gain; + int i; + + gain = synth->gain; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice)) + { + fluid_voice_set_gain(voice, gain); + } + } +} + +/** + * Get synth output gain value. + * @param synth FluidSynth instance + * @return Synth gain value (0.0 to 10.0) + */ +float +fluid_synth_get_gain(fluid_synth_t *synth) +{ + float result; + fluid_return_val_if_fail(synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = synth->gain; + FLUID_API_RETURN(result); +} + +/* + * Handler for synth.polyphony setting. + */ +static void +fluid_synth_handle_polyphony(void *data, const char *name, int value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_synth_set_polyphony(synth, value); +} + +/** + * Set synthesizer polyphony (max number of voices). + * @param synth FluidSynth instance + * @param polyphony Polyphony to assign + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.0.6 + */ +int +fluid_synth_set_polyphony(fluid_synth_t *synth, int polyphony) +{ + int result; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(polyphony >= 1 && polyphony <= 65535, FLUID_FAILED); + fluid_synth_api_enter(synth); + + result = fluid_synth_update_polyphony_LOCAL(synth, polyphony); + + FLUID_API_RETURN(result); +} + +/* Called by synthesis thread to update the polyphony value */ +static int +fluid_synth_update_polyphony_LOCAL(fluid_synth_t *synth, int new_polyphony) +{ + fluid_voice_t *voice; + int i; + + if(new_polyphony > synth->nvoice) + { + /* Create more voices */ + fluid_voice_t **new_voices = FLUID_REALLOC(synth->voice, + sizeof(fluid_voice_t *) * new_polyphony); + + if(new_voices == NULL) + { + return FLUID_FAILED; + } + + synth->voice = new_voices; + + for(i = synth->nvoice; i < new_polyphony; i++) + { + synth->voice[i] = new_fluid_voice(synth->eventhandler, synth->sample_rate); + + if(synth->voice[i] == NULL) + { + return FLUID_FAILED; + } + + fluid_voice_set_custom_filter(synth->voice[i], synth->custom_filter_type, synth->custom_filter_flags); + } + + synth->nvoice = new_polyphony; + } + + synth->polyphony = new_polyphony; + + /* turn off any voices above the new limit */ + for(i = synth->polyphony; i < synth->nvoice; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice)) + { + fluid_voice_off(voice); + } + } + + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, + synth->polyphony, 0.0f); + + return FLUID_OK; +} + +/** + * Get current synthesizer polyphony (max number of voices). + * @param synth FluidSynth instance + * @return Synth polyphony value. + * @since 1.0.6 + */ +int +fluid_synth_get_polyphony(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + result = synth->polyphony; + FLUID_API_RETURN(result); +} + +/** + * @brief Get current number of active voices. + * + * I.e. the no. of voices that have been + * started and have not yet finished. Unless called from synthesis context, + * this number does not necessarily have to be equal to the number of voices + * currently processed by the DSP loop, see below. + * @param synth FluidSynth instance + * @return Number of currently active voices. + * @since 1.1.0 + * + * @note To generate accurate continuous statistics of the voice count, caller + * should ensure this function is called synchronously with the audio synthesis + * process. This can be done in the new_fluid_audio_driver2() audio callback + * function for example. Otherwise every call to this function may return different + * voice counts as it may change after any (concurrent) call to fluid_synth_write_*() made by + * e.g. an audio driver or the applications audio rendering thread. + */ +int +fluid_synth_get_active_voice_count(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + result = synth->active_voice_count; + FLUID_API_RETURN(result); +} + +/** + * Get the internal synthesis buffer size value. + * @param synth FluidSynth instance + * @return Internal buffer size in audio frames. + * + * Audio is synthesized at this number of frames at a time. Defaults to 64 frames. I.e. the synth can only react to notes, + * control changes, and other audio affecting events after having processed 64 audio frames. + */ +int +fluid_synth_get_internal_bufsize(fluid_synth_t *synth) +{ + return FLUID_BUFSIZE; +} + +/** + * Resend a bank select and a program change for every channel and assign corresponding instruments. + * @param synth FluidSynth instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * This function is called mainly after a SoundFont has been loaded, + * unloaded or reloaded. + */ +int +fluid_synth_program_reset(fluid_synth_t *synth) +{ + int i, prog; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + /* try to set the correct presets */ + for(i = 0; i < synth->midi_channels; i++) + { + fluid_channel_get_sfont_bank_prog(synth->channel[i], NULL, NULL, &prog); + fluid_synth_program_change(synth, i, prog); + } + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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) + * @param right Array of float buffers to store right channel of planar audio (size: dito) + * @param fx_left Since 1.1.7: If not \c NULL, array of float buffers to store left effect channels (as many as \c synth.effects-channels buffers, each of \c len in size) + * @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(). + * + * Usage example: + * @code{.cpp} + const int FramesToRender = 64; + int channels; + // retrieve number of stereo audio channels + fluid_settings_getint(settings, "synth.audio-channels", &channels); + + // 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 + 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)) + fluid_settings_getint(settings, "synth.effects-channels", &channels); + channels *= 2; + + float** fx_buf = new float*[channels]; + for(int i = 0; i < channels; i++) + { + fx_buf[i] = new float[FramesToRender]; + } + + float** mix_buf_l = mix_buf; + float** mix_buf_r = &mix_buf[channels/2]; + + float** fx_buf_l = fx_buf; + float** fx_buf_r = &fx_buf[channels/2]; + + fluid_synth_nwrite_float(synth, FramesToRender, mix_buf_l, mix_buf_r, fx_buf_l, fx_buf_r) + * @endcode + */ +int +fluid_synth_nwrite_float(fluid_synth_t *synth, int len, + float **left, float **right, + float **fx_left, float **fx_right) +{ + fluid_real_t *left_in, *fx_left_in; + fluid_real_t *right_in, *fx_right_in; + double time = fluid_utime(); + int i, num, available, count; +#ifdef WITH_FLOAT + int bytes; +#endif + float cpu_load; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(left != NULL, FLUID_FAILED); + fluid_return_val_if_fail(right != NULL, FLUID_FAILED); + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below + + /* First, take what's still available in the buffer */ + count = 0; + num = synth->cur; + + if(synth->cur < FLUID_BUFSIZE) + { + available = FLUID_BUFSIZE - synth->cur; + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); + + num = (available > len) ? len : available; +#ifdef WITH_FLOAT + bytes = num * sizeof(float); +#endif + + for(i = 0; i < synth->audio_channels; i++) + { +#ifdef WITH_FLOAT + FLUID_MEMCPY(left[i], &left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); + FLUID_MEMCPY(right[i], &right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); +#else //WITH_FLOAT + int j; + + for(j = 0; j < num; j++) + { + left[i][j] = (float) left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; + right[i][j] = (float) right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; + } + +#endif //WITH_FLOAT + } + + for(i = 0; i < synth->effects_channels; i++) + { +#ifdef WITH_FLOAT + + if(fx_left != NULL) + { + FLUID_MEMCPY(fx_left[i], &fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); + } + + if(fx_right != NULL) + { + FLUID_MEMCPY(fx_right[i], &fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + synth->cur], bytes); + } + +#else //WITH_FLOAT + int j; + + if(fx_left != NULL) + { + for(j = 0; j < num; j++) + { + fx_left[i][j] = (float) fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; + } + } + + if(fx_right != NULL) + { + for(j = 0; j < num; j++) + { + fx_right[i][j] = (float) fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + synth->cur]; + } + } + +#endif //WITH_FLOAT + } + + count += num; + num += synth->cur; /* if we're now done, num becomes the new synth->cur below */ + } + + /* Then, run one_block() and copy till we have 'len' samples */ + while(count < len) + { + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 0); + fluid_synth_render_blocks(synth, 1); // TODO: + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); + + num = (FLUID_BUFSIZE > len - count) ? len - count : FLUID_BUFSIZE; +#ifdef WITH_FLOAT + bytes = num * sizeof(float); +#endif + + for(i = 0; i < synth->audio_channels; i++) + { +#ifdef WITH_FLOAT + FLUID_MEMCPY(left[i] + count, &left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); + FLUID_MEMCPY(right[i] + count, &right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); +#else //WITH_FLOAT + int j; + + for(j = 0; j < num; j++) + { + left[i][j + count] = (float) left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; + right[i][j + count] = (float) right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; + } + +#endif //WITH_FLOAT + } + + for(i = 0; i < synth->effects_channels; i++) + { +#ifdef WITH_FLOAT + + if(fx_left != NULL) + { + FLUID_MEMCPY(fx_left[i] + count, &fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); + } + + if(fx_right != NULL) + { + FLUID_MEMCPY(fx_right[i] + count, &fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT], bytes); + } + +#else //WITH_FLOAT + int j; + + if(fx_left != NULL) + { + for(j = 0; j < num; j++) + { + fx_left[i][j + count] = (float) fx_left_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; + } + } + + if(fx_right != NULL) + { + for(j = 0; j < num; j++) + { + fx_right[i][j + count] = (float) fx_right_in[i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j]; + } + } + +#endif //WITH_FLOAT + } + + count += num; + } + + synth->cur = num; + + time = fluid_utime() - time; + cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set(&synth->cpu_load, cpu_load); + + return FLUID_OK; +} + +/** + * mixes the samples of \p in to \p out + * + * @param out the output sample buffer to mix to + * @param ooff sample offset in \p out + * @param in the rvoice_mixer input sample buffer to mix from + * @param ioff sample offset in \p in + * @param buf_idx the sample buffer index of \p in to mix from + * @param num number of samples to mix + */ +static FLUID_INLINE void fluid_synth_mix_single_buffer(float *FLUID_RESTRICT out, + int ooff, + const fluid_real_t *FLUID_RESTRICT in, + int ioff, + int buf_idx, + int num) +{ + if(out != NULL) + { + int j; + + for(j = 0; j < num; j++) + { + out[j + ooff] += (float) in[buf_idx * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + ioff]; + } + } +} + +/** + * 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 <code>0 <= nfx/2 <= (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups())</code>. + * 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 <code>0 <= nout/2 <= fluid_synth_count_audio_channels()</code>. + * 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, + * - <code>fx == NULL</code> while <code>nfx > 0</code>, or <code>out == NULL</code> while <code>nout > 0</code>. + * - \c nfx or \c nout not multiple of 2. + * - <code>len < 0</code>. + * - \c nfx or \c nout exceed the range explained above. + * + * Synthesize and <strong>mix</strong> audio to a given number of planar audio buffers. + * Therefore pass <code>nout = N*2</code> float buffers to \p out in order to render + * the synthesized audio to \p N stereo channels. Each float buffer must be + * able to hold \p len elements. + * + * \p out contains an array of planar buffers for normal, dry, stereo + * audio (alternating left and right). Like: +@code{.cpp} +out[0] = left_buffer_audio_channel_0 +out[1] = right_buffer_audio_channel_0 +out[2] = left_buffer_audio_channel_1 +out[3] = right_buffer_audio_channel_1 +... +out[ (i * 2 + 0) % nout ] = left_buffer_audio_channel_i +out[ (i * 2 + 1) % nout ] = right_buffer_audio_channel_i +@endcode + * + * for zero-based channel index \p i. + * The buffer layout of \p fx used for storing effects + * like reverb and chorus looks similar: +@code{.cpp} +fx[0] = left_buffer_channel_of_reverb_unit_0 +fx[1] = right_buffer_channel_of_reverb_unit_0 +fx[2] = left_buffer_channel_of_chorus_unit_0 +fx[3] = right_buffer_channel_of_chorus_unit_0 +fx[4] = left_buffer_channel_of_reverb_unit_1 +fx[5] = right_buffer_channel_of_reverb_unit_1 +fx[6] = left_buffer_channel_of_chorus_unit_1 +fx[7] = right_buffer_channel_of_chorus_unit_1 +fx[8] = left_buffer_channel_of_reverb_unit_2 +... +fx[ ((k * fluid_synth_count_effects_channels() + j) * 2 + 0) % nfx ] = left_buffer_for_effect_channel_j_of_unit_k +fx[ ((k * fluid_synth_count_effects_channels() + j) * 2 + 1) % nfx ] = right_buffer_for_effect_channel_j_of_unit_k +@endcode + * where <code>0 <= k < fluid_synth_count_effects_groups()</code> is a zero-based index denoting the effects unit and + * <code>0 <= j < fluid_synth_count_effects_channels()</code> is a zero-based index denoting the effect channel within + * unit \p k. + * + * Any playing voice is assigned to audio channels based on the MIDI channel it's playing on: Let \p chan be the + * zero-based MIDI channel index an arbitrary voice is playing on. To determine the audio channel and effects unit it is + * going to be rendered to use: + * + * <code>i = chan % fluid_synth_count_audio_groups()</code> + * + * <code>k = chan % fluid_synth_count_effects_groups()</code> + * + * @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. + * E.g. if fluid_synth_process() is called from a custom audio driver process function + * (see new_fluid_audio_driver2()), the audio driver takes care of zeroing the buffers. + * @endparblock + * + * @parblock + * @note No matter how many buffers you pass in, fluid_synth_process() + * will always render all audio channels to the + * buffers in \c out and all effects channels to the + * buffers in \c fx, provided that <code>nout > 0</code> and <code>nfx > 0</code> respectively. If + * <code>nout/2 < fluid_synth_count_audio_channels()</code> it will wrap around. Same + * is true for effects audio if <code>nfx/2 < (fluid_synth_count_effects_channels() * fluid_synth_count_effects_groups())</code>. + * See usage examples below. + * @endparblock + * + * @parblock + * @note Should only be called from synthesis thread. + * @endparblock + */ +int +fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], + int nout, float *out[]) +{ + return fluid_synth_process_LOCAL(synth, len, nfx, fx, nout, out, fluid_synth_render_blocks); +} + +/* declared public (instead of static) for testing purpose */ +int +fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], + int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int)) +{ + fluid_real_t *left_in, *fx_left_in; + fluid_real_t *right_in, *fx_right_in; + int nfxchan, nfxunits, naudchan; + + double time = fluid_utime(); + int i, f, num, count, buffered_blocks; + + float cpu_load; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + /* fx NULL while nfx > 0 is invalid */ + fluid_return_val_if_fail((fx != NULL) || (nfx == 0), FLUID_FAILED); + /* nfx must be multiple of 2. Note that 0 value is valid and + allows to skip mixing in fx output buffers + */ + fluid_return_val_if_fail(nfx % 2 == 0, FLUID_FAILED); + + /* out NULL while nout > 0 is invalid */ + fluid_return_val_if_fail((out != NULL) || (nout == 0), FLUID_FAILED); + /* nout must be multiple of 2. Note that 0 value is valid and + allows to skip mixing in out output buffers + */ + fluid_return_val_if_fail(nout % 2 == 0, FLUID_FAILED); + + /* check len value. Note that 0 value is valid, the function does nothing and returns FLUID_OK. + */ + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below + + nfxchan = synth->effects_channels; + nfxunits = synth->effects_groups; + naudchan = synth->audio_channels; + + fluid_return_val_if_fail(0 <= nfx / 2 && nfx / 2 <= nfxchan * nfxunits, FLUID_FAILED); + fluid_return_val_if_fail(0 <= nout / 2 && nout / 2 <= naudchan, FLUID_FAILED); + + /* get internal mixer audio dry buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + /* get internal mixer audio effect buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_fx_bufs(synth->eventhandler->mixer, &fx_left_in, &fx_right_in); + + /* Conversely to fluid_synth_write_float(),fluid_synth_write_s16() (which handle only one + stereo output) we don't want rendered audio effect mixed in internal audio dry buffers. + FALSE instructs the mixer that internal audio effects will be mixed in respective internal + audio effects buffers. + */ + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, FALSE); + + + /* First, take what's still available in the buffer */ + count = 0; + /* synth->cur indicates if available samples are still in internal mixer buffer */ + num = synth->cur; + + buffered_blocks = (synth->cur + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + if(synth->cur < buffered_blocks * FLUID_BUFSIZE) + { + /* yes, available sample are in internal mixer buffer */ + int available = (buffered_blocks * FLUID_BUFSIZE) - synth->cur; + num = (available > len) ? len : available; + + /* mixing dry samples (or skip if requested by the caller) */ + if(nout != 0) + { + for(i = 0; i < naudchan; i++) + { + /* mix num left samples from input mixer buffer (left_in) at input offset + synth->cur to output buffer (out_buf) at offset 0 */ + float *out_buf = out[(i * 2) % nout]; + fluid_synth_mix_single_buffer(out_buf, 0, left_in, synth->cur, i, num); + + /* mix num right samples from input mixer buffer (right_in) at input offset + synth->cur to output buffer (out_buf) at offset 0 */ + out_buf = out[(i * 2 + 1) % nout]; + fluid_synth_mix_single_buffer(out_buf, 0, right_in, synth->cur, i, num); + } + } + + /* mixing effects samples (or skip if requested by the caller) */ + if(nfx != 0) + { + // loop over all effects units + for(f = 0; f < nfxunits; f++) + { + // write out all effects (i.e. reverb and chorus) + for(i = 0; i < nfxchan; i++) + { + int buf_idx = f * nfxchan + i; + + /* mix num left samples from input mixer buffer (fx_left_in) at input offset + synth->cur to output buffer (out_buf) at offset 0 */ + float *out_buf = fx[(buf_idx * 2) % nfx]; + fluid_synth_mix_single_buffer(out_buf, 0, fx_left_in, synth->cur, buf_idx, num); + + /* mix num right samples from input mixer buffer (fx_right_in) at input offset + synth->cur to output buffer (out_buf) at offset 0 */ + out_buf = fx[(buf_idx * 2 + 1) % nfx]; + fluid_synth_mix_single_buffer(out_buf, 0, fx_right_in, synth->cur, buf_idx, num); + } + } + } + + count += num; + num += synth->cur; /* if we're now done, num becomes the new synth->cur below */ + } + + /* Then, render blocks and copy till we have 'len' samples */ + while(count < len) + { + /* always render full bloc multiple of FLUID_BUFSIZE */ + int blocksleft = (len - count + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + /* render audio (dry and effect) to respective internal dry and effect buffers */ + int blockcount = block_render_func(synth, blocksleft); + + num = (blockcount * FLUID_BUFSIZE > len - count) ? len - count : blockcount * FLUID_BUFSIZE; + + /* mixing dry samples (or skip if requested by the caller) */ + if(nout != 0) + { + for(i = 0; i < naudchan; i++) + { + /* mix num left samples from input mixer buffer (left_in) at input offset + 0 to output buffer (out_buf) at offset count */ + float *out_buf = out[(i * 2) % nout]; + fluid_synth_mix_single_buffer(out_buf, count, left_in, 0, i, num); + + /* mix num right samples from input mixer buffer (right_in) at input offset + 0 to output buffer (out_buf) at offset count */ + out_buf = out[(i * 2 + 1) % nout]; + fluid_synth_mix_single_buffer(out_buf, count, right_in, 0, i, num); + } + } + + /* mixing effects samples (or skip if requested by the caller) */ + if(nfx != 0) + { + // loop over all effects units + for(f = 0; f < nfxunits; f++) + { + // write out all effects (i.e. reverb and chorus) + for(i = 0; i < nfxchan; i++) + { + int buf_idx = f * nfxchan + i; + + /* mix num left samples from input mixer buffer (fx_left_in) at input offset + 0 to output buffer (out_buf) at offset count */ + float *out_buf = fx[(buf_idx * 2) % nfx]; + fluid_synth_mix_single_buffer(out_buf, count, fx_left_in, 0, buf_idx, num); + + /* mix num right samples from input mixer buffer (fx_right_in) at input offset + 0 to output buffer (out_buf) at offset count */ + out_buf = fx[(buf_idx * 2 + 1) % nfx]; + fluid_synth_mix_single_buffer(out_buf, count, fx_right_in, 0, buf_idx, num); + } + } + } + + count += num; + } + + synth->cur = num; + + time = fluid_utime() - time; + cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set(&synth->cpu_load, cpu_load); + + return FLUID_OK; +} + + +/** + * Synthesize a block of floating point audio samples to audio buffers. + * @param synth FluidSynth instance + * @param len Count of audio frames to synthesize + * @param lout Array of floats to store left channel of audio + * @param loff Offset index in 'lout' for first sample + * @param lincr Increment between samples stored to 'lout' + * @param rout Array of floats to store right channel of audio + * @param roff Offset index in 'rout' for first sample + * @param rincr Increment between samples stored to 'rout' + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * Useful for storing interleaved stereo (lout = rout, loff = 0, roff = 1, + * lincr = 2, rincr = 2). + * + * @note Should only be called from synthesis thread. + * @note Reverb and Chorus are mixed to \c lout resp. \c rout. + */ +int +fluid_synth_write_float(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr) +{ + void *channels_out[2] = {lout, rout}; + int channels_off[2] = {loff, roff }; + int channels_incr[2] = {lincr, rincr }; + + return fluid_synth_write_float_channels(synth, len, 2, channels_out, + channels_off, channels_incr); +} + +/** + * Synthesize a block of float audio samples channels to audio buffers. + * The function is convenient for audio driver to render multiple stereo + * channels pairs on multi channels audio cards (i.e 2, 4, 6, 8,.. channels). + * + * @param synth FluidSynth instance. + * @param len Count of audio frames to synthesize. + * @param channels_count Count of channels in a frame. + * must be multiple of 2 and channel_count/2 must not exceed the number + * of internal mixer buffers (synth->audio_groups) + * @param channels_out Array of channels_count pointers on 16 bit words to + * store sample channels. Modified on return. + * @param channels_off Array of channels_count offset index to add to respective pointer + * in channels_out for first sample. + * @param channels_incr Array of channels_count increment between consecutive + * samples channels. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + * + * Useful for storing: + * - interleaved channels in a unique buffer. + * - non interleaved channels in an unique buffer (or in distinct buffers). + * + * Example for interleaved 4 channels (c1, c2, c3, c4) and n samples (s1, s2,..sn) + * in a unique buffer: + * { s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4,... + * sn:c1, sn:c2, sn:c3, sn:c4 }. + * + * @note Should only be called from synthesis thread. + * @note Reverb and Chorus are mixed to \c lout resp. \c rout. + */ +int +fluid_synth_write_float_channels(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[]) +{ + return fluid_synth_write_float_channels_LOCAL(synth, len, channels_count, + channels_out, channels_off, channels_incr, + fluid_synth_render_blocks); +} + +int +fluid_synth_write_float_channels_LOCAL(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[], + int (*block_render_func)(fluid_synth_t *, int)) +{ + float **chan_out = (float **)channels_out; + int n, cur, size; + + /* pointers on first input mixer buffer */ + fluid_real_t *left_in; + fluid_real_t *right_in; + int bufs_in_count; /* number of stereo input buffers */ + int i; + + /* start average cpu load probe */ + double time = fluid_utime(); + float cpu_load; + + /* start profiling duration probe (if profiling is enabled) */ + fluid_profile_ref_var(prof_ref); + + /* check parameters */ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below + + /* check for valid channel_count: must be multiple of 2 and + channel_count/2 must not exceed the number of internal mixer buffers + (synth->audio_groups) + */ + fluid_return_val_if_fail(!(channels_count & 1) /* must be multiple of 2 */ + && channels_count >= 2, FLUID_FAILED); + + bufs_in_count = (unsigned int)channels_count >> 1; /* channels_count/2 */ + fluid_return_val_if_fail(bufs_in_count <= synth->audio_groups, FLUID_FAILED); + + fluid_return_val_if_fail(channels_out != NULL, FLUID_FAILED); + fluid_return_val_if_fail(channels_off != NULL, FLUID_FAILED); + fluid_return_val_if_fail(channels_incr != NULL, FLUID_FAILED); + + /* initialize output channels buffers on first sample position */ + i = channels_count; + do + { + i--; + chan_out[i] += channels_off[i]; + } + while(i); + + /* Conversely to fluid_synth_process(), + we want rendered audio effect mixed in internal audio dry buffers. + TRUE instructs the mixer that internal audio effects will be mixed in internal + audio dry buffers. + */ + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, TRUE); + + /* get first internal mixer audio dry buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + + size = len; + + /* synth->cur indicates if available samples are still in internal mixer buffer */ + cur = synth->cur; /* get previous sample position in internal buffer (due to prvious call) */ + + do + { + /* fill up the buffers as needed */ + if(cur >= synth->curmax) + { + /* render audio (dry and effect) to internal dry buffers */ + /* always render full blocs multiple of FLUID_BUFSIZE */ + int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + synth->curmax = FLUID_BUFSIZE * block_render_func(synth, blocksleft); + + /* get first internal mixer audio dry buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + cur = 0; + } + + /* calculate amount of available samples */ + n = synth->curmax - cur; + + /* keep track of emitted samples */ + if(n > size) + { + n = size; + } + + size -= n; + + /* update pointers to current position */ + left_in += cur + n; + right_in += cur + n; + + /* set final cursor position */ + cur += n; + + /* reverse index */ + n = 0 - n; + + do + { + i = bufs_in_count; + do + { + /* input sample index in stereo buffer i */ + int in_idx = --i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + n; + int c = i << 1; /* channel index c to write */ + + /* write left input sample to channel sample */ + *chan_out[c] = (float) left_in[in_idx]; + + /* write right input sample to next channel sample */ + *chan_out[c+1] = (float) right_in[in_idx]; + + /* advance output pointers */ + chan_out[c] += channels_incr[c]; + chan_out[c+1] += channels_incr[c+1]; + } + while(i); + } + while(++n < 0); + } + while(size); + + synth->cur = cur; /* save current sample position. It will be used on next call */ + + /* save average cpu load, use by API for real time cpu load meter */ + time = fluid_utime() - time; + cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set(&synth->cpu_load, cpu_load); + + /* stop duration probe and save performance measurement (if profiling is enabled) */ + fluid_profile_write(FLUID_PROF_WRITE, prof_ref, + fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), + len); + return FLUID_OK; +} + +/* for testing purpose */ +int +fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr, + int (*block_render_func)(fluid_synth_t *, int) + ) +{ + void *channels_out[2] = {lout, rout}; + int channels_off[2] = {loff, roff }; + int channels_incr[2] = {lincr, rincr }; + + return fluid_synth_write_float_channels_LOCAL(synth, len, 2, channels_out, + channels_off, channels_incr, + block_render_func); +} + + +#define DITHER_SIZE 48000 +#define DITHER_CHANNELS 2 + +static float rand_table[DITHER_CHANNELS][DITHER_SIZE]; + +/* Init dither table */ +static void +init_dither(void) +{ + float d, dp; + int c, i; + + for(c = 0; c < DITHER_CHANNELS; c++) + { + dp = 0; + + for(i = 0; i < DITHER_SIZE - 1; i++) + { + d = rand() / (float)RAND_MAX - 0.5f; + rand_table[c][i] = d - dp; + dp = d; + } + + rand_table[c][DITHER_SIZE - 1] = 0 - dp; + } +} + +/* A portable replacement for roundf(), seems it may actually be faster too! */ +static FLUID_INLINE int16_t +round_clip_to_i16(float x) +{ + long i; + + if(x >= 0.0f) + { + i = (long)(x + 0.5f); + + if(FLUID_UNLIKELY(i > 32767)) + { + i = 32767; + } + } + else + { + i = (long)(x - 0.5f); + + if(FLUID_UNLIKELY(i < -32768)) + { + i = -32768; + } + } + + return (int16_t)i; +} + +/** + * Synthesize a block of 16 bit audio samples to audio buffers. + * @param synth FluidSynth instance + * @param len Count of audio frames to synthesize + * @param lout Array of 16 bit words to store left channel of audio + * @param loff Offset index in 'lout' for first sample + * @param lincr Increment between samples stored to 'lout' + * @param rout Array of 16 bit words to store right channel of audio + * @param roff Offset index in 'rout' for first sample + * @param rincr Increment between samples stored to 'rout' + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * Useful for storing interleaved stereo (lout = rout, loff = 0, roff = 1, + * lincr = 2, rincr = 2). + * + * @note Should only be called from synthesis thread. + * @note Reverb and Chorus are mixed to \c lout resp. \c rout. + * @note Dithering is performed when converting from internal floating point to + * 16 bit audio. + */ +int +fluid_synth_write_s16(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr) +{ + void *channels_out[2] = {lout, rout}; + int channels_off[2] = {loff, roff }; + int channels_incr[2] = {lincr, rincr }; + + return fluid_synth_write_s16_channels(synth, len, 2, channels_out, + channels_off, channels_incr); +} + +/** + * Synthesize a block of 16 bit audio samples channels to audio buffers. + * The function is convenient for audio driver to render multiple stereo + * channels pairs on multi channels audio cards (i.e 2, 4, 6, 8,.. channels). + * + * @param synth FluidSynth instance. + * @param len Count of audio frames to synthesize. + * @param channels_count Count of channels in a frame. + * must be multiple of 2 and channel_count/2 must not exceed the number + * of internal mixer buffers (synth->audio_groups) + * @param channels_out Array of channels_count pointers on 16 bit words to + * store sample channels. Modified on return. + * @param channels_off Array of channels_count offset index to add to respective pointer + * in channels_out for first sample. + * @param channels_incr Array of channels_count increment between consecutive + * samples channels. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + * + * Useful for storing: + * - interleaved channels in a unique buffer. + * - non interleaved channels in an unique buffer (or in distinct buffers). + * + * Example for interleaved 4 channels (c1, c2, c3, c4) and n samples (s1, s2,..sn) + * in a unique buffer: + * { s1:c1, s1:c2, s1:c3, s1:c4, s2:c1, s2:c2, s2:c3, s2:c4, .... + * sn:c1, sn:c2, sn:c3, sn:c4 }. + * + * @note Should only be called from synthesis thread. + * @note Reverb and Chorus are mixed to \c lout resp. \c rout. + * @note Dithering is performed when converting from internal floating point to + * 16 bit audio. + */ +int +fluid_synth_write_s16_channels(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[]) +{ + int16_t **chan_out = (int16_t **)channels_out; + int di, n, cur, size; + + /* pointers on first input mixer buffer */ + fluid_real_t *left_in; + fluid_real_t *right_in; + int bufs_in_count; /* number of stereo input buffers */ + int i; + + /* start average cpu load probe */ + double time = fluid_utime(); + float cpu_load; + + /* start profiling duration probe (if profiling is enabled) */ + fluid_profile_ref_var(prof_ref); + + /* check parameters */ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + fluid_return_val_if_fail(len >= 0, FLUID_FAILED); + fluid_return_val_if_fail(len != 0, FLUID_OK); // to avoid raising FE_DIVBYZERO below + + /* check for valid channel_count: must be multiple of 2 and + channel_count/2 must not exceed the number of internal mixer buffers + (synth->audio_groups) + */ + fluid_return_val_if_fail(!(channels_count & 1) /* must be multiple of 2 */ + && channels_count >= 2, FLUID_FAILED); + + bufs_in_count = (unsigned int)channels_count >> 1; /* channels_count/2 */ + fluid_return_val_if_fail(bufs_in_count <= synth->audio_groups, FLUID_FAILED); + + fluid_return_val_if_fail(channels_out != NULL, FLUID_FAILED); + fluid_return_val_if_fail(channels_off != NULL, FLUID_FAILED); + fluid_return_val_if_fail(channels_incr != NULL, FLUID_FAILED); + + /* initialize output channels buffers on first sample position */ + i = channels_count; + do + { + i--; + chan_out[i] += channels_off[i]; + } + while(i); + + /* Conversely to fluid_synth_process(), + we want rendered audio effect mixed in internal audio dry buffers. + TRUE instructs the mixer that internal audio effects will be mixed in internal + audio dry buffers. + */ + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, TRUE); + /* get first internal mixer audio dry buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + + size = len; + /* synth->cur indicates if available samples are still in internal mixer buffer */ + cur = synth->cur; /* get previous sample position in internal buffer (due to prvious call) */ + di = synth->dither_index; + + do + { + /* fill up the buffers as needed */ + if(cur >= synth->curmax) + { + /* render audio (dry and effect) to internal dry buffers */ + /* always render full blocs multiple of FLUID_BUFSIZE */ + int blocksleft = (size + FLUID_BUFSIZE - 1) / FLUID_BUFSIZE; + synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); + + /* get first internal mixer audio dry buffer's pointer (left and right channel) */ + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + cur = 0; + } + + /* calculate amount of available samples */ + n = synth->curmax - cur; + + /* keep track of emitted samples */ + if(n > size) + { + n = size; + } + + size -= n; + + /* update pointers to current position */ + left_in += cur + n; + right_in += cur + n; + + /* set final cursor position */ + cur += n; + + /* reverse index */ + n = 0 - n; + + do + { + i = bufs_in_count; + do + { + /* input sample index in stereo buffer i */ + int in_idx = --i * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + n; + int c = i << 1; /* channel index c to write */ + + /* write left input sample to channel sample */ + *chan_out[c] = round_clip_to_i16(left_in[in_idx] * 32766.0f + + rand_table[0][di]); + + /* write right input sample to next channel sample */ + *chan_out[c+1] = round_clip_to_i16(right_in[in_idx] * 32766.0f + + rand_table[1][di]); + /* advance output pointers */ + chan_out[c] += channels_incr[c]; + chan_out[c+1] += channels_incr[c+1]; + } + while(i); + + if(++di >= DITHER_SIZE) + { + di = 0; + } + } + while(++n < 0); + } + while(size); + + synth->cur = cur; /* save current sample position. It will be used on next call */ + synth->dither_index = di; /* keep dither buffer continuous */ + + /* save average cpu load, used by API for real time cpu load meter */ + time = fluid_utime() - time; + cpu_load = 0.5 * (fluid_atomic_float_get(&synth->cpu_load) + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set(&synth->cpu_load, cpu_load); + + /* stop duration probe and save performance measurement (if profiling is enabled) */ + fluid_profile_write(FLUID_PROF_WRITE, prof_ref, + fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), + len); + return FLUID_OK; +} + +/** + * Converts stereo floating point sample data to signed 16 bit data with dithering. + * @param dither_index Pointer to an integer which should be initialized to 0 + * before the first call and passed unmodified to additional calls which are + * part of the same synthesis output. + * @param len Length in frames to convert + * @param lin Buffer of left audio samples to convert from + * @param rin Buffer of right audio samples to convert from + * @param lout Array of 16 bit words to store left channel of audio + * @param loff Offset index in 'lout' for first sample + * @param lincr Increment between samples stored to 'lout' + * @param rout Array of 16 bit words to store right channel of audio + * @param roff Offset index in 'rout' for first sample + * @param rincr Increment between samples stored to 'rout' + * + * @note Currently private to libfluidsynth. + */ +void +fluid_synth_dither_s16(int *dither_index, int len, const float *lin, const float *rin, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr) +{ + int i, j, k; + int16_t *left_out = lout; + int16_t *right_out = rout; + int di = *dither_index; + fluid_profile_ref_var(prof_ref); + + for(i = 0, j = loff, k = roff; i < len; i++, j += lincr, k += rincr) + { + left_out[j] = round_clip_to_i16(lin[i] * 32766.0f + rand_table[0][di]); + right_out[k] = round_clip_to_i16(rin[i] * 32766.0f + rand_table[1][di]); + + if(++di >= DITHER_SIZE) + { + di = 0; + } + } + + *dither_index = di; /* keep dither buffer continuous */ + + fluid_profile(FLUID_PROF_WRITE, prof_ref, 0, len); +} + +static void +fluid_synth_check_finished_voices(fluid_synth_t *synth) +{ + int j; + fluid_rvoice_t *fv; + + while(NULL != (fv = fluid_rvoice_eventhandler_get_finished_voice(synth->eventhandler))) + { + for(j = 0; j < synth->polyphony; j++) + { + if(synth->voice[j]->rvoice == fv) + { + fluid_voice_unlock_rvoice(synth->voice[j]); + fluid_voice_stop(synth->voice[j]); + break; + } + else if(synth->voice[j]->overflow_rvoice == fv) + { + /* Unlock the overflow_rvoice of the voice. + Decrement the reference count of the sample owned by this + rvoice. + */ + fluid_voice_overflow_rvoice_finished(synth->voice[j]); + + /* Decrement synth active voice count. Must not be incorporated + in fluid_voice_overflow_rvoice_finished() because + fluid_voice_overflow_rvoice_finished() is called also + at synth destruction and in this case the variable should be + accessed via voice->channel->synth->active_voice_count. + And for certain voices which are not playing, the field + voice->channel is NULL. + */ + synth->active_voice_count--; + break; + } + } + } +} + +/** + * Process all waiting events in the rvoice queue. + * Make sure no (other) rendering is running in parallel when + * you call this function! + */ +void fluid_synth_process_event_queue(fluid_synth_t *synth) +{ + fluid_rvoice_eventhandler_dispatch_all(synth->eventhandler); +} + + +/** + * Process blocks (FLUID_BUFSIZE) of audio. + * Must be called from renderer thread only! + * @return number of blocks rendered. Might (often) return less than requested + */ +static int +fluid_synth_render_blocks(fluid_synth_t *synth, int blockcount) +{ + int i, maxblocks; + fluid_profile_ref_var(prof_ref); + + /* Assign ID of synthesis thread */ +// synth->synth_thread_id = fluid_thread_get_id (); + + fluid_check_fpe("??? Just starting up ???"); + + fluid_rvoice_eventhandler_dispatch_all(synth->eventhandler); + + /* do not render more blocks than we can store internally */ + maxblocks = fluid_rvoice_mixer_get_bufcount(synth->eventhandler->mixer); + + if(blockcount > maxblocks) + { + blockcount = maxblocks; + } + + for(i = 0; i < blockcount; i++) + { + fluid_sample_timer_process(synth); + fluid_synth_add_ticks(synth, FLUID_BUFSIZE); + + /* If events have been queued waiting for fluid_rvoice_eventhandler_dispatch_all() + * (should only happen with parallel render) stop processing and go for rendering + */ + if(fluid_rvoice_eventhandler_dispatch_count(synth->eventhandler)) + { + // Something has happened, we can't process more + blockcount = i + 1; + break; + } + } + + fluid_check_fpe("fluid_sample_timer_process"); + + blockcount = fluid_rvoice_mixer_render(synth->eventhandler->mixer, blockcount); + + /* Testcase, that provokes a denormal floating point error */ +#if 0 + { + float num = 1; + + while(num != 0) + { + num *= 0.5; + }; + }; +#endif + fluid_check_fpe("??? Remainder of synth_one_block ???"); + fluid_profile(FLUID_PROF_ONE_BLOCK, prof_ref, + fluid_rvoice_mixer_get_active_voices(synth->eventhandler->mixer), + blockcount * FLUID_BUFSIZE); + return blockcount; +} + +/* + * Handler for synth.reverb.* and synth.chorus.* double settings. + */ +static void fluid_synth_handle_reverb_chorus_num(void *data, const char *name, double value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_return_if_fail(synth != NULL); + + if(FLUID_STRCMP(name, "synth.reverb.room-size") == 0) + { + fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_ROOMSIZE, value); + } + else if(FLUID_STRCMP(name, "synth.reverb.damp") == 0) + { + fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_DAMP, value); + } + else if(FLUID_STRCMP(name, "synth.reverb.width") == 0) + { + fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_WIDTH, value); + } + else if(FLUID_STRCMP(name, "synth.reverb.level") == 0) + { + fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_LEVEL, value); + } + else if(FLUID_STRCMP(name, "synth.chorus.depth") == 0) + { + fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_DEPTH, value); + } + else if(FLUID_STRCMP(name, "synth.chorus.speed") == 0) + { + fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_SPEED, value); + } + else if(FLUID_STRCMP(name, "synth.chorus.level") == 0) + { + fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_LEVEL, value); + } +} + +/* + * Handler for synth.reverb.* and synth.chorus.* integer settings. + */ +static void fluid_synth_handle_reverb_chorus_int(void *data, const char *name, int value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_return_if_fail(synth != NULL); + + if(FLUID_STRCMP(name, "synth.reverb.active") == 0) + { + fluid_synth_reverb_on(synth, -1, value); + } + else if(FLUID_STRCMP(name, "synth.chorus.active") == 0) + { + fluid_synth_chorus_on(synth, -1, value); + } + else if(FLUID_STRCMP(name, "synth.chorus.nr") == 0) + { + fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_NR, (double)value); + } +} + +/* + * Handler for synth.overflow.* settings. + */ +static void fluid_synth_handle_overflow(void *data, const char *name, double value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + fluid_return_if_fail(synth != NULL); + + fluid_synth_api_enter(synth); + + if(FLUID_STRCMP(name, "synth.overflow.percussion") == 0) + { + synth->overflow.percussion = value; + } + else if(FLUID_STRCMP(name, "synth.overflow.released") == 0) + { + synth->overflow.released = value; + } + else if(FLUID_STRCMP(name, "synth.overflow.sustained") == 0) + { + synth->overflow.sustained = value; + } + else if(FLUID_STRCMP(name, "synth.overflow.volume") == 0) + { + synth->overflow.volume = value; + } + else if(FLUID_STRCMP(name, "synth.overflow.age") == 0) + { + synth->overflow.age = value; + } + else if(FLUID_STRCMP(name, "synth.overflow.important") == 0) + { + synth->overflow.important = value; + } + + fluid_synth_api_exit(synth); +} + +/* Selects a voice for killing. */ +static fluid_voice_t * +fluid_synth_free_voice_by_kill_LOCAL(fluid_synth_t *synth) +{ + int i; + float best_prio = OVERFLOW_PRIO_CANNOT_KILL - 1; + float this_voice_prio; + fluid_voice_t *voice; + int best_voice_index = -1; + unsigned int ticks = fluid_synth_get_ticks(synth); + + for(i = 0; i < synth->polyphony; i++) + { + + voice = synth->voice[i]; + + /* safeguard against an available voice. */ + if(_AVAILABLE(voice)) + { + return voice; + } + + this_voice_prio = fluid_voice_get_overflow_prio(voice, &synth->overflow, + ticks); + + /* check if this voice has less priority than the previous candidate. */ + if(this_voice_prio < best_prio) + { + best_voice_index = i; + best_prio = this_voice_prio; + } + } + + if(best_voice_index < 0) + { + return NULL; + } + + voice = synth->voice[best_voice_index]; + FLUID_LOG(FLUID_DBG, "Killing voice %d, index %d, chan %d, key %d ", + fluid_voice_get_id(voice), best_voice_index, fluid_voice_get_channel(voice), fluid_voice_get_key(voice)); + fluid_voice_off(voice); + + return voice; +} + + +/** + * Allocate a synthesis voice. + * @param synth FluidSynth instance + * @param sample Sample to assign to the voice + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param key MIDI note number for the voice (0-127) + * @param vel MIDI velocity for the voice (0-127) + * @return Allocated synthesis voice or NULL on error + * + * This function is called by a SoundFont's preset in response to a noteon event. + * The returned voice comes with default modulators and generators. + * A single noteon event may create any number of voices, when the preset is layered. + * + * @note Should only be called from within synthesis thread, which includes + * SoundFont loader preset noteon method. + */ +fluid_voice_t * +fluid_synth_alloc_voice(fluid_synth_t *synth, fluid_sample_t *sample, + int chan, int key, int vel) +{ + fluid_return_val_if_fail(sample != NULL, NULL); + fluid_return_val_if_fail(sample->data != NULL, NULL); + FLUID_API_ENTRY_CHAN(NULL); + FLUID_API_RETURN(fluid_synth_alloc_voice_LOCAL(synth, sample, chan, key, vel, NULL)); + +} + +fluid_voice_t * +fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int chan, int key, int vel, fluid_zone_range_t *zone_range) +{ + int i, k; + fluid_voice_t *voice = NULL; + fluid_channel_t *channel = NULL; + unsigned int ticks; + + /* check if there's an available synthesis process */ + for(i = 0; i < synth->polyphony; i++) + { + if(_AVAILABLE(synth->voice[i])) + { + voice = synth->voice[i]; + break; + } + } + + /* No success yet? Then stop a running voice. */ + if(voice == NULL) + { + FLUID_LOG(FLUID_DBG, "Polyphony exceeded, trying to kill a voice"); + voice = fluid_synth_free_voice_by_kill_LOCAL(synth); + } + + if(voice == NULL) + { + FLUID_LOG(FLUID_WARN, "Failed to allocate a synthesis process. (chan=%d,key=%d)", chan, key); + return NULL; + } + + ticks = fluid_synth_get_ticks(synth); + + if(synth->verbose) + { + k = 0; + + for(i = 0; i < synth->polyphony; i++) + { + if(!_AVAILABLE(synth->voice[i])) + { + k++; + } + } + + FLUID_LOG(FLUID_INFO, "noteon\t%d\t%d\t%d\t%05d\t%.3f\t%.3f\t%.3f\t%d", + chan, key, vel, synth->storeid, + (float) ticks / 44100.0f, + (fluid_curtime() - synth->start) / 1000.0f, + 0.0f, + k); + } + + channel = synth->channel[chan]; + + if(fluid_voice_init(voice, sample, zone_range, channel, key, vel, + synth->storeid, ticks, synth->gain) != FLUID_OK) + { + FLUID_LOG(FLUID_WARN, "Failed to initialize voice"); + return NULL; + } + + /* add the default modulators to the synthesis process. */ + /* custom_breath2att_modulator is not a default modulator specified in SF + it is intended to replace default_vel2att_mod for this channel on demand using + API fluid_synth_set_breath_mode() or shell command setbreathmode for this channel. + */ + { + int mono = fluid_channel_is_playing_mono(channel); + fluid_mod_t *default_mod = synth->default_mod; + + while(default_mod != NULL) + { + if( + /* See if default_mod is the velocity_to_attenuation modulator */ + fluid_mod_test_identity(default_mod, &default_vel2att_mod) && + // See if a replacement by custom_breath2att_modulator has been demanded + // for this channel + ((!mono && (channel->mode & FLUID_CHANNEL_BREATH_POLY)) || + (mono && (channel->mode & FLUID_CHANNEL_BREATH_MONO))) + ) + { + // Replacement of default_vel2att modulator by custom_breath2att_modulator + fluid_voice_add_mod_local(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT, 0); + } + else + { + fluid_voice_add_mod_local(voice, default_mod, FLUID_VOICE_DEFAULT, 0); + } + + // Next default modulator to add to the voice + default_mod = default_mod->next; + } + } + + return voice; +} + +/* Kill all voices on a given channel, which have the same exclusive class + * generator as new_voice. + */ +static void +fluid_synth_kill_by_exclusive_class_LOCAL(fluid_synth_t *synth, + fluid_voice_t *new_voice) +{ + int excl_class = fluid_voice_gen_value(new_voice, GEN_EXCLUSIVECLASS); + int i; + + /* Excl. class 0: No exclusive class */ + if(excl_class == 0) + { + return; + } + + /* Kill all notes on the same channel with the same exclusive class */ + for(i = 0; i < synth->polyphony; i++) + { + fluid_voice_t *existing_voice = synth->voice[i]; + + /* If voice is playing, on the same channel, has same exclusive + * class and is not part of the same noteon event (voice group), then kill it */ + + if(fluid_voice_is_playing(existing_voice) + && fluid_voice_get_channel(existing_voice) == fluid_voice_get_channel(new_voice) + && fluid_voice_gen_value(existing_voice, GEN_EXCLUSIVECLASS) == excl_class + && fluid_voice_get_id(existing_voice) != fluid_voice_get_id(new_voice)) + { + fluid_voice_kill_excl(existing_voice); + } + } +} + +/** + * Activate a voice previously allocated with fluid_synth_alloc_voice(). + * @param synth FluidSynth instance + * @param voice Voice to activate + * + * This function is called by a SoundFont's preset in response to a noteon + * event. Exclusive classes are processed here. + * + * @note Should only be called from within synthesis thread, which includes + * SoundFont loader preset noteon method. + */ +void +fluid_synth_start_voice(fluid_synth_t *synth, fluid_voice_t *voice) +{ + fluid_return_if_fail(synth != NULL); + fluid_return_if_fail(voice != NULL); +// fluid_return_if_fail (fluid_synth_is_synth_thread (synth)); + fluid_synth_api_enter(synth); + + /* Find the exclusive class of this voice. If set, kill all voices + * that match the exclusive class and are younger than the first + * voice process created by this noteon event. */ + fluid_synth_kill_by_exclusive_class_LOCAL(synth, voice); + + fluid_voice_start(voice); /* Start the new voice */ + fluid_voice_lock_rvoice(voice); + fluid_rvoice_eventhandler_add_rvoice(synth->eventhandler, voice->rvoice); + fluid_synth_api_exit(synth); +} + +/** + * Add a SoundFont loader to the synth. This function takes ownership of \c loader + * and frees it automatically upon \c synth destruction. + * @param synth FluidSynth instance + * @param loader Loader API structure + * + * SoundFont loaders are used to add custom instrument loading to FluidSynth. + * The caller supplied functions for loading files, allocating presets, + * retrieving information on them and synthesizing note-on events. Using this + * method even non SoundFont instruments can be synthesized, although limited + * to the SoundFont synthesis model. + * + * @note Should only be called before any SoundFont files are loaded. + */ +void +fluid_synth_add_sfloader(fluid_synth_t *synth, fluid_sfloader_t *loader) +{ + fluid_return_if_fail(synth != NULL); + fluid_return_if_fail(loader != NULL); + fluid_synth_api_enter(synth); + + /* Test if sfont is already loaded */ + if(synth->sfont == NULL) + { + synth->loaders = fluid_list_prepend(synth->loaders, loader); + } + + fluid_synth_api_exit(synth); +} + +/** + * Load a SoundFont file (filename is interpreted by SoundFont loaders). + * The newly loaded SoundFont will be put on top of the SoundFont + * stack. Presets are searched starting from the SoundFont on the + * top of the stack, working the way down the stack until a preset is found. + * + * @param synth FluidSynth instance + * @param filename File to load + * @param reset_presets TRUE to re-assign presets for all MIDI channels (equivalent to calling fluid_synth_program_reset()) + * @return SoundFont ID on success, #FLUID_FAILED on error + * + * @note Since FluidSynth 2.2.0 @c filename is treated as an UTF8 encoded string on Windows. FluidSynth will convert it + * to wide-char internally and then pass it to <code>_wfopen()</code>. Before FluidSynth 2.2.0, @c filename was treated as ANSI string + * on Windows. All other platforms directly pass it to <code>fopen()</code> without any conversion (usually, UTF8 is accepted). + */ +int +fluid_synth_sfload(fluid_synth_t *synth, const char *filename, int reset_presets) +{ + fluid_sfont_t *sfont; + fluid_list_t *list; + fluid_sfloader_t *loader; + int sfont_id; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(filename != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + sfont_id = synth->sfont_id; + + if(++sfont_id != FLUID_FAILED) + { + /* MT NOTE: Loaders list should not change. */ + + for(list = synth->loaders; list; list = fluid_list_next(list)) + { + loader = (fluid_sfloader_t *) fluid_list_get(list); + + sfont = fluid_sfloader_load(loader, filename); + + if(sfont != NULL) + { + sfont->refcount++; + synth->sfont_id = sfont->id = sfont_id; + + synth->sfont = fluid_list_prepend(synth->sfont, sfont); /* prepend to list */ + + /* reset the presets for all channels if requested */ + if(reset_presets) + { + fluid_synth_program_reset(synth); + } + + FLUID_API_RETURN(sfont_id); + } + } + } + + FLUID_LOG(FLUID_ERR, "Failed to load SoundFont "%s"", filename); + FLUID_API_RETURN(FLUID_FAILED); +} + +/** + * Schedule a SoundFont for unloading. + * + * If the SoundFont isn't used anymore by any playing voices, it will be unloaded immediately. + * + * If any samples of the given SoundFont are still required by active voices, + * the SoundFont will be unloaded in a lazy manner, once those voices have finished synthesizing. + * If you call delete_fluid_synth(), all voices will be destroyed and the SoundFont + * will be unloaded in any case. + * Once this function returned, fluid_synth_sfcount() and similar functions will behave as if + * the SoundFont has already been unloaded, even though the lazy-unloading is still pending. + * + * @note This lazy-unloading mechanism was broken between FluidSynth 1.1.4 and 2.1.5 . As a + * consequence, SoundFonts scheduled for lazy-unloading may be never freed under certain + * conditions. Calling delete_fluid_synth() does not recover this situation either. + * + * @param synth FluidSynth instance + * @param id ID of SoundFont to unload + * @param reset_presets TRUE to re-assign presets for all MIDI channels + * @return #FLUID_OK if the given @p id was found, #FLUID_FAILED otherwise. + */ +int +fluid_synth_sfunload(fluid_synth_t *synth, int id, int reset_presets) +{ + fluid_sfont_t *sfont = NULL; + fluid_list_t *list; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + /* remove the SoundFont from the list */ + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == id) + { + synth->sfont = fluid_list_remove(synth->sfont, sfont); + break; + } + } + + if(!list) + { + FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", id); + FLUID_API_RETURN(FLUID_FAILED); + } + + /* reset the presets for all channels (SoundFont will be freed when there are no more references) */ + if(reset_presets) + { + fluid_synth_program_reset(synth); + } + else + { + fluid_synth_update_presets(synth); + } + + /* -- Remove synth->sfont list's reference to SoundFont */ + fluid_synth_sfont_unref(synth, sfont); + + FLUID_API_RETURN(FLUID_OK); +} + +/* Unref a SoundFont and destroy if no more references */ +void +fluid_synth_sfont_unref(fluid_synth_t *synth, fluid_sfont_t *sfont) +{ + fluid_return_if_fail(sfont != NULL); /* Shouldn't happen, programming error if so */ + + sfont->refcount--; /* -- Remove the sfont list's reference */ + + if(sfont->refcount == 0) /* No more references? - Attempt delete */ + { + if(fluid_sfont_delete_internal(sfont) == 0) /* SoundFont loader can block SoundFont unload */ + { + FLUID_LOG(FLUID_DBG, "Unloaded SoundFont"); + } /* spin off a timer thread to unload the sfont later (SoundFont loader blocked unload) */ + else + { + fluid_timer_t* timer = new_fluid_timer(100, fluid_synth_sfunload_callback, sfont, TRUE, FALSE, FALSE); + synth->fonts_to_be_unloaded = fluid_list_prepend(synth->fonts_to_be_unloaded, timer); + } + } +} + +/* Callback to continually attempt to unload a SoundFont, + * only if a SoundFont loader blocked the unload operation */ +static int +fluid_synth_sfunload_callback(void *data, unsigned int msec) +{ + fluid_sfont_t *sfont = data; + + if(fluid_sfont_delete_internal(sfont) == 0) + { + FLUID_LOG(FLUID_DBG, "Unloaded SoundFont"); + return FALSE; + } + else + { + return TRUE; + } +} + +/** + * Reload a SoundFont. The SoundFont retains its ID and index on the SoundFont stack. + * @param synth FluidSynth instance + * @param id ID of SoundFont to reload + * @return SoundFont ID on success, #FLUID_FAILED on error + */ +int +fluid_synth_sfreload(fluid_synth_t *synth, int id) +{ + char *filename = NULL; + fluid_sfont_t *sfont; + fluid_sfloader_t *loader; + fluid_list_t *list; + int index, ret = FLUID_FAILED; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + /* Search for SoundFont and get its index */ + for(list = synth->sfont, index = 0; list; list = fluid_list_next(list), index++) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == id) + { + break; + } + } + + if(!list) + { + FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", id); + goto exit; + } + + /* keep a copy of the SoundFont's filename */ + filename = FLUID_STRDUP(fluid_sfont_get_name(sfont)); + + if(filename == NULL || fluid_synth_sfunload(synth, id, FALSE) != FLUID_OK) + { + goto exit; + } + + /* MT Note: SoundFont loader list will not change */ + + for(list = synth->loaders; list; list = fluid_list_next(list)) + { + loader = (fluid_sfloader_t *) fluid_list_get(list); + + sfont = fluid_sfloader_load(loader, filename); + + if(sfont != NULL) + { + sfont->id = id; + sfont->refcount++; + + synth->sfont = fluid_list_insert_at(synth->sfont, index, sfont); /* insert the sfont at the same index */ + + /* reset the presets for all channels */ + fluid_synth_update_presets(synth); + ret = id; + goto exit; + } + } + + FLUID_LOG(FLUID_ERR, "Failed to load SoundFont "%s"", filename); + +exit: + FLUID_FREE(filename); + FLUID_API_RETURN(ret); +} + +/** + * Add a SoundFont. The SoundFont will be added to the top of the SoundFont stack and ownership is transferred to @p synth. + * @param synth FluidSynth instance + * @param sfont SoundFont to add + * @return New assigned SoundFont ID or #FLUID_FAILED on error + */ +int +fluid_synth_add_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont) +{ + int sfont_id; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + sfont_id = synth->sfont_id; + + if(++sfont_id != FLUID_FAILED) + { + synth->sfont_id = sfont->id = sfont_id; + synth->sfont = fluid_list_prepend(synth->sfont, sfont); /* prepend to list */ + + /* reset the presets for all channels */ + fluid_synth_program_reset(synth); + } + + FLUID_API_RETURN(sfont_id); +} + +/** + * Remove a SoundFont from the SoundFont stack without deleting it. + * @param synth FluidSynth instance + * @param sfont SoundFont to remove + * @return #FLUID_OK if \c sfont successfully removed, #FLUID_FAILED otherwise + * + * SoundFont is not freed and is left as the responsibility of the caller. + * + * @note The SoundFont should only be freed after there are no presets + * referencing it. This can only be ensured by the SoundFont loader and + * therefore this function should not normally be used. + */ +int +fluid_synth_remove_sfont(fluid_synth_t *synth, fluid_sfont_t *sfont) +{ + fluid_sfont_t *sfont_tmp; + fluid_list_t *list; + int ret = FLUID_FAILED; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(sfont != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + /* remove the SoundFont from the list */ + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont_tmp = fluid_list_get(list); + + if(sfont_tmp == sfont) + { + synth->sfont = fluid_list_remove(synth->sfont, sfont_tmp); + ret = FLUID_OK; + break; + } + } + + /* reset the presets for all channels */ + fluid_synth_program_reset(synth); + + FLUID_API_RETURN(ret); +} + +/** + * Count number of loaded SoundFont files. + * @param synth FluidSynth instance + * @return Count of loaded SoundFont files. + */ +int +fluid_synth_sfcount(fluid_synth_t *synth) +{ + int count; + + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + count = fluid_list_size(synth->sfont); + FLUID_API_RETURN(count); +} + +/** + * Get SoundFont by index. + * @param synth FluidSynth instance + * @param num SoundFont index on the stack (starting from 0 for top of stack). + * @return SoundFont instance or NULL if invalid index + * + * @note Caller should be certain that SoundFont is not deleted (unloaded) for + * the duration of use of the returned pointer. + */ +fluid_sfont_t * +fluid_synth_get_sfont(fluid_synth_t *synth, unsigned int num) +{ + fluid_sfont_t *sfont = NULL; + fluid_list_t *list; + + fluid_return_val_if_fail(synth != NULL, NULL); + fluid_synth_api_enter(synth); + list = fluid_list_nth(synth->sfont, num); + + if(list) + { + sfont = fluid_list_get(list); + } + + FLUID_API_RETURN(sfont); +} + +/** + * Get SoundFont by ID. + * @param synth FluidSynth instance + * @param id SoundFont ID + * @return SoundFont instance or NULL if invalid ID + * + * @note Caller should be certain that SoundFont is not deleted (unloaded) for + * the duration of use of the returned pointer. + */ +fluid_sfont_t * +fluid_synth_get_sfont_by_id(fluid_synth_t *synth, int id) +{ + fluid_sfont_t *sfont = NULL; + fluid_list_t *list; + + fluid_return_val_if_fail(synth != NULL, NULL); + fluid_synth_api_enter(synth); + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == id) + { + break; + } + } + + FLUID_API_RETURN(list ? sfont : NULL); +} + +/** + * Get SoundFont by name. + * @param synth FluidSynth instance + * @param name Name of SoundFont + * @return SoundFont instance or NULL if invalid name + * @since 1.1.0 + * + * @note Caller should be certain that SoundFont is not deleted (unloaded) for + * the duration of use of the returned pointer. + */ +fluid_sfont_t * +fluid_synth_get_sfont_by_name(fluid_synth_t *synth, const char *name) +{ + fluid_sfont_t *sfont = NULL; + fluid_list_t *list; + + fluid_return_val_if_fail(synth != NULL, NULL); + fluid_return_val_if_fail(name != NULL, NULL); + fluid_synth_api_enter(synth); + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(FLUID_STRCMP(fluid_sfont_get_name(sfont), name) == 0) + { + break; + } + } + + FLUID_API_RETURN(list ? sfont : NULL); +} + +/** + * Get active preset on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @return Preset or NULL if no preset active on \c chan + * + * @note Should only be called from within synthesis thread, which includes + * SoundFont loader preset noteon methods. Not thread safe otherwise. + */ +fluid_preset_t * +fluid_synth_get_channel_preset(fluid_synth_t *synth, int chan) +{ + fluid_preset_t *result; + fluid_channel_t *channel; + FLUID_API_ENTRY_CHAN(NULL); + + channel = synth->channel[chan]; + result = channel->preset; + fluid_synth_api_exit(synth); + return result; +} + +/** + * Get list of currently playing voices. + * @param synth FluidSynth instance + * @param buf Array to store voices to (NULL terminated if not filled completely) + * @param bufsize Count of indexes in buf + * @param id Voice ID to search for or < 0 to return list of all playing voices + * + * @note Should only be called from within synthesis thread, which includes + * SoundFont loader preset noteon methods. Voices are only guaranteed to remain + * unchanged until next synthesis process iteration. + */ +void +fluid_synth_get_voicelist(fluid_synth_t *synth, fluid_voice_t *buf[], int bufsize, + int id) +{ + int count = 0; + int i; + + fluid_return_if_fail(synth != NULL); + fluid_return_if_fail(buf != NULL); + fluid_synth_api_enter(synth); + + for(i = 0; i < synth->polyphony && count < bufsize; i++) + { + fluid_voice_t *voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice) && (id < 0 || (int)voice->id == id)) + { + buf[count++] = voice; + } + } + + if(count < bufsize) + { + buf[count] = NULL; + } + + fluid_synth_api_exit(synth); +} + +/** + * Enable or disable reverb effect. + * @param synth FluidSynth instance + * @param on TRUE to enable chorus, FALSE to disable + * @deprecated Use fluid_synth_reverb_on() instead. + */ +void +fluid_synth_set_reverb_on(fluid_synth_t *synth, int on) +{ + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + + synth->with_reverb = (on != 0); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_reverb_enabled, + on != 0, 0.0f); + fluid_synth_api_exit(synth); +} + +/** + * Enable or disable reverb on one fx group unit. + * @param synth FluidSynth instance + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param on TRUE to enable reverb, FALSE to disable + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_reverb_on(fluid_synth_t *synth, int fx_group, int on) +{ + int ret; + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + if(fx_group < 0 ) + { + synth->with_reverb = (on != 0); + } + + param[0].i = fx_group; + param[1].i = on; + ret = fluid_rvoice_eventhandler_push(synth->eventhandler, + fluid_rvoice_mixer_reverb_enable, + synth->eventhandler->mixer, + param); + + FLUID_API_RETURN(ret); +} + +/** + * Activate a reverb preset. + * @param synth FluidSynth instance + * @param num Reverb preset number + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note Currently private to libfluidsynth. + */ +int +fluid_synth_set_reverb_preset(fluid_synth_t *synth, unsigned int num) +{ + double values[FLUID_REVERB_PARAM_LAST]; + + fluid_return_val_if_fail( + num < FLUID_N_ELEMENTS(revmodel_preset), + FLUID_FAILED + ); + + values[FLUID_REVERB_ROOMSIZE] = revmodel_preset[num].roomsize; + values[FLUID_REVERB_DAMP] = revmodel_preset[num].damp; + values[FLUID_REVERB_WIDTH] = revmodel_preset[num].width; + values[FLUID_REVERB_LEVEL] = revmodel_preset[num].level; + fluid_synth_set_reverb_full(synth, -1, FLUID_REVMODEL_SET_ALL, values); + return FLUID_OK; +} + +/** + * Set reverb parameters to all groups. + * + * @param synth FluidSynth instance + * @param roomsize Reverb room size value (0.0-1.0) + * @param damping Reverb damping value (0.0-1.0) + * @param width Reverb width value (0.0-100.0) + * @param level Reverb level value (0.0-1.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use the individual reverb setter functions in new code instead. + */ +int +fluid_synth_set_reverb(fluid_synth_t *synth, double roomsize, double damping, + double width, double level) +{ + double values[FLUID_REVERB_PARAM_LAST]; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + values[FLUID_REVERB_ROOMSIZE] = roomsize; + values[FLUID_REVERB_DAMP] = damping; + values[FLUID_REVERB_WIDTH] = width; + values[FLUID_REVERB_LEVEL] = level; + return fluid_synth_set_reverb_full(synth, -1, FLUID_REVMODEL_SET_ALL, values); +} + +/** + * Set reverb roomsize of all groups. + * + * @param synth FluidSynth instance + * @param roomsize Reverb room size value (0.0-1.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_reverb_group_roomsize() in new code instead. + */ +int fluid_synth_set_reverb_roomsize(fluid_synth_t *synth, double roomsize) +{ + return fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_ROOMSIZE, roomsize); +} + +/** + * Set reverb damping of all groups. + * + * @param synth FluidSynth instance + * @param damping Reverb damping value (0.0-1.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_reverb_group_damp() in new code instead. + */ +int fluid_synth_set_reverb_damp(fluid_synth_t *synth, double damping) +{ + return fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_DAMP, damping); +} + +/** + * Set reverb width of all groups. + * + * @param synth FluidSynth instance + * @param width Reverb width value (0.0-100.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_reverb_group_width() in new code instead. + */ +int fluid_synth_set_reverb_width(fluid_synth_t *synth, double width) +{ + return fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_WIDTH, width); +} + +/** + * Set reverb level of all groups. + * + * @param synth FluidSynth instance + * @param level Reverb level value (0.0-1.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_reverb_group_level() in new code instead. + */ +int fluid_synth_set_reverb_level(fluid_synth_t *synth, double level) +{ + return fluid_synth_reverb_set_param(synth, -1, FLUID_REVERB_LEVEL, level); +} + +/** + * Set reverb roomsize to one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param roomsize roomsize value to set. Must be in the range indicated by + * synth.reverb.room-size setting. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int fluid_synth_set_reverb_group_roomsize(fluid_synth_t *synth, int fx_group, + double roomsize) +{ + return fluid_synth_reverb_set_param(synth, fx_group, FLUID_REVERB_ROOMSIZE, roomsize); +} + +/** + * Set reverb damp to one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param damping damping value to set. Must be in the range indicated by + * synth.reverb.damp setting. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_set_reverb_group_damp(fluid_synth_t *synth, int fx_group, + double damping) +{ + return fluid_synth_reverb_set_param(synth, fx_group, FLUID_REVERB_DAMP, damping); +} + +/** + * Set reverb width to one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param width width value to set. Must be in the range indicated by + * synth.reverb.width setting. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_set_reverb_group_width(fluid_synth_t *synth, int fx_group, + double width) +{ + return fluid_synth_reverb_set_param(synth, fx_group, FLUID_REVERB_WIDTH, width); +} + +/** + * Set reverb level to one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param level output level to set. Must be in the range indicated by + * synth.reverb.level setting. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_set_reverb_group_level(fluid_synth_t *synth, int fx_group, + double level) +{ + return fluid_synth_reverb_set_param(synth, fx_group, FLUID_REVERB_LEVEL, level); +} + +/** + * Set one reverb parameter to one fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param enum indicating the parameter to set (#fluid_reverb_param). + * FLUID_REVERB_ROOMSIZE, roomsize Reverb room size value (0.0-1.0) + * FLUID_REVERB_DAMP, reverb damping value (0.0-1.0) + * FLUID_REVERB_WIDTH, reverb width value (0.0-100.0) + * FLUID_REVERB_LEVEL, reverb level value (0.0-1.0) + * @param value, parameter value + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_reverb_set_param(fluid_synth_t *synth, int fx_group, + int param, double value) +{ + int ret; + double values[FLUID_REVERB_PARAM_LAST] = {0.0}; + static const char *name[FLUID_REVERB_PARAM_LAST] = + { + "synth.reverb.room-size", "synth.reverb.damp", + "synth.reverb.width", "synth.reverb.level" + }; + + double min; /* minimum value */ + double max; /* maximum value */ + + /* check parameters */ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail((param >= 0) && (param < FLUID_REVERB_PARAM_LAST), FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + /* check if reverb value is in max min range */ + fluid_settings_getnum_range(synth->settings, name[param], &min, &max); + if(value < min || value > max) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + /* set the value */ + values[param] = value; + ret = fluid_synth_set_reverb_full(synth, fx_group, FLUID_REVPARAM_TO_SETFLAG(param), values); + FLUID_API_RETURN(ret); +} + +int +fluid_synth_set_reverb_full(fluid_synth_t *synth, int fx_group, int set, + const double values[]) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + + /* if non of the flags is set, fail */ + fluid_return_val_if_fail(set & FLUID_REVMODEL_SET_ALL, FLUID_FAILED); + + /* fx group shadow values are set here so that they will be returned if queried */ + fluid_rvoice_mixer_set_reverb_full(synth->eventhandler->mixer, fx_group, set, + values); + + /* Synth shadow values are set here so that they will be returned if queried */ + if (fx_group < 0) + { + int i; + for(i = 0; i < FLUID_REVERB_PARAM_LAST; i++) + { + if(set & FLUID_REVPARAM_TO_SETFLAG(i)) + { + synth->reverb_param[i] = values[i]; + } + } + } + + param[0].i = fx_group; + param[1].i = set; + param[2].real = values[FLUID_REVERB_ROOMSIZE]; + param[3].real = values[FLUID_REVERB_DAMP]; + param[4].real = values[FLUID_REVERB_WIDTH]; + param[5].real = values[FLUID_REVERB_LEVEL]; + /* finally enqueue an rvoice event to the mixer to actual update reverb */ + return fluid_rvoice_eventhandler_push(synth->eventhandler, + fluid_rvoice_mixer_set_reverb_params, + synth->eventhandler->mixer, + param); +} + +/** + * Get reverb room size of all fx groups. + * @param synth FluidSynth instance + * @return Reverb room size (0.0-1.2) + * @deprecated Use fluid_synth_get_reverb_group_roomsize() in new code instead. + */ +double +fluid_synth_get_reverb_roomsize(fluid_synth_t *synth) +{ + double roomsize = 0.0; + fluid_synth_reverb_get_param(synth, -1, FLUID_REVERB_ROOMSIZE, &roomsize); + return roomsize; +} + +/** + * Get reverb damping of all fx groups. + * @param synth FluidSynth instance + * @return Reverb damping value (0.0-1.0) + * @deprecated Use fluid_synth_get_reverb_group_damp() in new code instead. + */ +double +fluid_synth_get_reverb_damp(fluid_synth_t *synth) +{ + double damp = 0.0; + fluid_synth_reverb_get_param(synth, -1, FLUID_REVERB_DAMP, &damp); + return damp; +} + +/** + * Get reverb level of all fx groups. + * @param synth FluidSynth instance + * @return Reverb level value (0.0-1.0) + * @deprecated Use fluid_synth_get_reverb_group_level() in new code instead. + */ +double +fluid_synth_get_reverb_level(fluid_synth_t *synth) +{ + double level = 0.0; + fluid_synth_reverb_get_param(synth, -1, FLUID_REVERB_LEVEL, &level); + return level; +} + +/** + * Get reverb width of all fx groups. + * @param synth FluidSynth instance + * @return Reverb width value (0.0-100.0) + * @deprecated Use fluid_synth_get_reverb_group_width() in new code instead. + */ +double +fluid_synth_get_reverb_width(fluid_synth_t *synth) +{ + double width = 0.0; + fluid_synth_reverb_get_param(synth, -1, FLUID_REVERB_WIDTH, &width); + return width; +} + +/** + * get reverb roomsize of one or all groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param roomsize valid pointer on the value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int fluid_synth_get_reverb_group_roomsize(fluid_synth_t *synth, int fx_group, + double *roomsize) +{ + return fluid_synth_reverb_get_param(synth, fx_group, FLUID_REVERB_ROOMSIZE, roomsize); +} + +/** + * get reverb damp of one or all groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param damping valid pointer on the value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_get_reverb_group_damp(fluid_synth_t *synth, int fx_group, + double *damping) +{ + return fluid_synth_reverb_get_param(synth, fx_group, FLUID_REVERB_DAMP, damping); +} + +/** + * get reverb width of one or all groups + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param width valid pointer on the value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_get_reverb_group_width(fluid_synth_t *synth, int fx_group, + double *width) +{ + return fluid_synth_reverb_get_param(synth, fx_group, FLUID_REVERB_WIDTH, width); +} + +/** + * get reverb level of one or all groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param level valid pointer on the value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int fluid_synth_get_reverb_group_level(fluid_synth_t *synth, int fx_group, + double *level) +{ + return fluid_synth_reverb_get_param(synth, fx_group, FLUID_REVERB_LEVEL, level); +} + + +/** + * Get one reverb parameter value of one fx groups. + * @param synth FluidSynth instance + * @param fx_group index of the fx group to get parameter value from. + * Must be in the range -1 to synth->effects_groups-1. If -1 get the + * parameter common to all fx groups. + * @param enum indicating the parameter to get (#fluid_reverb_param). + * FLUID_REVERB_ROOMSIZE, reverb room size value. + * FLUID_REVERB_DAMP, reverb damping value. + * FLUID_REVERB_WIDTH, reverb width value. + * FLUID_REVERB_LEVEL, reverb level value. + * @param value pointer on the value to return. + * @return FLUID_OK if success, FLUID_FAILED otherwise. + */ +static int fluid_synth_reverb_get_param(fluid_synth_t *synth, int fx_group, + int param, double *value) +{ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail((param >= 0) && (param < FLUID_REVERB_PARAM_LAST), FLUID_FAILED); + fluid_return_val_if_fail(value != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + if (fx_group < 0) + { + /* return reverb param common to all fx groups */ + *value = synth->reverb_param[param]; + } + else + { + /* return reverb param of fx group at index fx_group */ + *value = fluid_rvoice_mixer_reverb_get_param(synth->eventhandler->mixer, + fx_group, param); + } + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Enable or disable all chorus groups. + * @param synth FluidSynth instance + * @param on TRUE to enable chorus, FALSE to disable + * @deprecated Use fluid_synth_chorus_on() in new code instead. + */ +void +fluid_synth_set_chorus_on(fluid_synth_t *synth, int on) +{ + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + + synth->with_chorus = (on != 0); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_chorus_enabled, + on != 0, 0.0f); + fluid_synth_api_exit(synth); +} + +/** + * Enable or disable chorus on one or all groups. + * @param synth FluidSynth instance + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all fx groups. + * @param on TRUE to enable chorus, FALSE to disable + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_chorus_on(fluid_synth_t *synth, int fx_group, int on) +{ + int ret; + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + if(fx_group < 0 ) + { + synth->with_chorus = (on != 0); + } + + param[0].i = fx_group; + param[1].i = on; + ret = fluid_rvoice_eventhandler_push(synth->eventhandler, + fluid_rvoice_mixer_chorus_enable, + synth->eventhandler->mixer, + param); + + FLUID_API_RETURN(ret); +} + +/** + * Set chorus parameters to all fx groups. + * Keep in mind, that the needed CPU time is proportional to 'nr'. + * @param synth FluidSynth instance + * @param nr Chorus voice count (0-99, CPU time consumption proportional to + * this value) + * @param level Chorus level (0.0-10.0) + * @param speed Chorus speed in Hz (0.1-5.0) + * @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) + * @param type Chorus waveform type (#fluid_chorus_mod) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use the individual chorus setter functions in new code instead. + * + * 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) +{ + double values[FLUID_CHORUS_PARAM_LAST]; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + values[FLUID_CHORUS_NR] = nr; + values[FLUID_CHORUS_LEVEL] = level; + values[FLUID_CHORUS_SPEED] = speed; + values[FLUID_CHORUS_DEPTH] = depth_ms; + values[FLUID_CHORUS_TYPE] = type; + return fluid_synth_set_chorus_full(synth, -1, FLUID_CHORUS_SET_ALL, values); +} + +/** + * Set the chorus voice count of all groups. + * + * @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 + * @deprecated Use fluid_synth_set_chorus_group_nr() in new code instead. + */ +int fluid_synth_set_chorus_nr(fluid_synth_t *synth, int nr) +{ + return fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_NR, nr); +} + +/** + * Set the chorus level of all groups. + * + * @param synth FluidSynth instance + * @param level Chorus level (0.0-10.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_chorus_group_level() in new code instead. + */ +int fluid_synth_set_chorus_level(fluid_synth_t *synth, double level) +{ + return fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_LEVEL, level); +} + +/** + * Set the chorus speed of all groups. + * + * @param synth FluidSynth instance + * @param speed Chorus speed in Hz (0.1-5.0) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_chorus_group_speed() in new code instead. + */ +int fluid_synth_set_chorus_speed(fluid_synth_t *synth, double speed) +{ + return fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_SPEED, speed); +} + +/** + * Set the chorus depth of all groups. + * + * @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 + * @deprecated Use fluid_synth_set_chorus_group_depth() in new code instead. + */ +int fluid_synth_set_chorus_depth(fluid_synth_t *synth, double depth_ms) +{ + return fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_DEPTH, depth_ms); +} + +/** + * Set the chorus type of all groups. + * + * @param synth FluidSynth instance + * @param type Chorus waveform type (#fluid_chorus_mod) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @deprecated Use fluid_synth_set_chorus_group_type() in new code instead. + */ +int fluid_synth_set_chorus_type(fluid_synth_t *synth, int type) +{ + return fluid_synth_chorus_set_param(synth, -1, FLUID_CHORUS_TYPE, type); +} + +/** + * Set chorus voice count nr to one or all chorus groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param nr Voice count to set. Must be in the range indicated by \setting{synth_chorus_nr} + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_set_chorus_group_nr(fluid_synth_t *synth, int fx_group, int nr) +{ + return fluid_synth_chorus_set_param(synth, fx_group, FLUID_CHORUS_NR, (double)nr); +} + +/** + * Set chorus output level to one or all chorus groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param level Output level to set. Must be in the range indicated by \setting{synth_chorus_level} + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_set_chorus_group_level(fluid_synth_t *synth, int fx_group, double level) +{ + return fluid_synth_chorus_set_param(synth, fx_group, FLUID_CHORUS_LEVEL, level); +} + +/** + * Set chorus lfo speed to one or all chorus groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param speed Lfo speed to set. Must be in the range indicated by \setting{synth_chorus_speed} + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_set_chorus_group_speed(fluid_synth_t *synth, int fx_group, double speed) +{ + return fluid_synth_chorus_set_param(synth, fx_group, FLUID_CHORUS_SPEED, speed); +} + +/** + * Set chorus lfo depth to one or all chorus groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param depth_ms lfo depth to set. Must be in the range indicated by \setting{synth_chorus_depth} + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_set_chorus_group_depth(fluid_synth_t *synth, int fx_group, double depth_ms) +{ + return fluid_synth_chorus_set_param(synth, fx_group, FLUID_CHORUS_DEPTH, depth_ms); +} + +/** + * Set chorus lfo waveform type to one or all chorus groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param type Lfo waveform type to set. (#fluid_chorus_mod) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_set_chorus_group_type(fluid_synth_t *synth, int fx_group, int type) +{ + return fluid_synth_chorus_set_param(synth, fx_group, FLUID_CHORUS_TYPE, (double)type); +} + +/** + * Set one chorus parameter to one fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter will be applied to all groups. + * @param enum indicating the parameter to set (#fluid_chorus_param). + * FLUID_CHORUS_NR, chorus voice count (0-99, CPU time consumption proportional to + * this value). + * FLUID_CHORUS_LEVEL, chorus level (0.0-10.0). + * FLUID_CHORUS_SPEED, chorus speed in Hz (0.1-5.0). + * FLUID_CHORUS_DEPTH, chorus depth (max value depends on synth sample-rate, + * 0.0-21.0 is safe for sample-rate values up to 96KHz). + * FLUID_CHORUS_TYPE, chorus waveform type (#fluid_chorus_mod) + * @param value, parameter value + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_chorus_set_param(fluid_synth_t *synth, int fx_group, int param, + double value) +{ + int ret; + double values[FLUID_CHORUS_PARAM_LAST] = {0.0}; + + /* setting name (except lfo waveform type) */ + static const char *name[FLUID_CHORUS_PARAM_LAST-1] = + { + "synth.chorus.nr", "synth.chorus.level", + "synth.chorus.speed", "synth.chorus.depth" + }; + + /* check parameters */ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail((param >= 0) && (param < FLUID_CHORUS_PARAM_LAST), FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + /* check if chorus value is in max min range */ + if(param == FLUID_CHORUS_TYPE || param == FLUID_CHORUS_NR) /* integer value */ + { + int min = FLUID_CHORUS_MOD_SINE; + int max = FLUID_CHORUS_MOD_TRIANGLE; + if(param == FLUID_CHORUS_NR) + { + fluid_settings_getint_range(synth->settings, name[param], &min, &max); + } + if((int)value < min || (int)value > max) + { + FLUID_API_RETURN(FLUID_FAILED); + } + } + else /* float value */ + { + double min; + double max; + fluid_settings_getnum_range(synth->settings, name[param], &min, &max); + if(value < min || value > max) + { + FLUID_API_RETURN(FLUID_FAILED); + } + } + + /* set the value */ + values[param] = value; + ret = fluid_synth_set_chorus_full(synth, fx_group, + FLUID_CHORPARAM_TO_SETFLAG(param), values); + FLUID_API_RETURN(ret); +} + +int +fluid_synth_set_chorus_full(fluid_synth_t *synth, int fx_group, int set, + const double values[]) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + + /* if non of the flags is set, fail */ + fluid_return_val_if_fail(set & FLUID_CHORUS_SET_ALL, FLUID_FAILED); + + /* fx group shadow values are set here so that they will be returned if queried */ + fluid_rvoice_mixer_set_chorus_full(synth->eventhandler->mixer, fx_group, + set, values); + + /* Synth shadow values are set here so that they will be returned if queried */ + if (fx_group < 0) + { + int i; + for(i = 0; i < FLUID_CHORUS_PARAM_LAST; i++) + { + if(set & FLUID_CHORPARAM_TO_SETFLAG(i)) + { + synth->chorus_param[i] = values[i]; + } + } + } + + param[0].i = fx_group; + param[1].i = set; + param[2].i = (int)values[FLUID_CHORUS_NR]; + param[3].real = values[FLUID_CHORUS_LEVEL]; + param[4].real = values[FLUID_CHORUS_SPEED]; + param[5].real = values[FLUID_CHORUS_DEPTH]; + param[6].i = (int)values[FLUID_CHORUS_TYPE]; + return fluid_rvoice_eventhandler_push(synth->eventhandler, + fluid_rvoice_mixer_set_chorus_params, + synth->eventhandler->mixer, + param); +} + +/** + * Get chorus voice number (delay line count) value of all fx groups. + * @param synth FluidSynth instance + * @return Chorus voice count + * @deprecated Use fluid_synth_get_chorus_group_nr() in new code instead. + */ +int +fluid_synth_get_chorus_nr(fluid_synth_t *synth) +{ + double nr = 0.0; + fluid_synth_chorus_get_param(synth, -1, FLUID_CHORUS_NR, &nr); + return (int)nr; +} + +/** + * Get chorus level of all fx groups. + * @param synth FluidSynth instance + * @return Chorus level value + * @deprecated Use fluid_synth_get_chorus_group_level() in new code instead. + */ +double +fluid_synth_get_chorus_level(fluid_synth_t *synth) +{ + double level = 0.0; + fluid_synth_chorus_get_param(synth, -1, FLUID_CHORUS_LEVEL, &level); + return level; +} + +/** + * Get chorus speed in Hz of all fx groups. + * @param synth FluidSynth instance + * @return Chorus speed in Hz + * @deprecated Use fluid_synth_get_chorus_group_speed() in new code instead. + */ +double +fluid_synth_get_chorus_speed(fluid_synth_t *synth) +{ + double speed = 0.0; + fluid_synth_chorus_get_param(synth, -1, FLUID_CHORUS_SPEED, &speed); + return speed; +} + +/** + * Get chorus depth of all fx groups. + * @param synth FluidSynth instance + * @return Chorus depth + * @deprecated Use fluid_synth_get_chorus_group_depth() in new code instead. + */ +double +fluid_synth_get_chorus_depth(fluid_synth_t *synth) +{ + double depth = 0.0; + fluid_synth_chorus_get_param(synth, -1, FLUID_CHORUS_DEPTH, &depth); + return depth; +} + +/** + * Get chorus waveform type of all fx groups. + * @param synth FluidSynth instance + * @return Chorus waveform type (#fluid_chorus_mod) + * @deprecated Use fluid_synth_get_chorus_group_type() in new code instead. + */ +int +fluid_synth_get_chorus_type(fluid_synth_t *synth) +{ + double type = 0.0; + fluid_synth_chorus_get_param(synth, -1, FLUID_CHORUS_TYPE, &type); + return (int)type; +} + +/** + * Get chorus count nr of one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group from which to fetch the chorus voice count. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param nr valid pointer on value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_get_chorus_group_nr(fluid_synth_t *synth, int fx_group, int *nr) +{ + double num_nr = 0.0; + int status; + status = fluid_synth_chorus_get_param(synth, fx_group, FLUID_CHORUS_NR, &num_nr); + *nr = (int)num_nr; + return status; +} + +/** + * Get chorus output level of one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group from which chorus level to fetch. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param level valid pointer on value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_get_chorus_group_level(fluid_synth_t *synth, int fx_group, double *level) +{ + return fluid_synth_chorus_get_param(synth, fx_group, FLUID_CHORUS_LEVEL, level); +} + +/** + * Get chorus waveform lfo speed of one or all fx groups. + * @param synth FluidSynth instance. + * @param fx_group Index of the fx group from which lfo speed to fetch. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param speed valid pointer on value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise. + */ +int +fluid_synth_get_chorus_group_speed(fluid_synth_t *synth, int fx_group, double *speed) +{ + return fluid_synth_chorus_get_param(synth, fx_group, FLUID_CHORUS_SPEED, speed); +} + +/** + * Get chorus lfo depth of one or all fx groups. + * @param synth FluidSynth instance + * @param fx_group Index of the fx group from which lfo depth to fetch. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param depth_ms valid pointer on value to return. + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_get_chorus_group_depth(fluid_synth_t *synth, int fx_group, double *depth_ms) +{ + return fluid_synth_chorus_get_param(synth, fx_group, FLUID_CHORUS_DEPTH, depth_ms); +} + +/** + * Get chorus waveform type of one or all fx groups. + * @param synth FluidSynth instance + * @param fx_group Index of the fx group from which to fetch the waveform type. + * Must be in the range <code>-1 to (fluid_synth_count_effects_groups()-1)</code>. If -1 the + * parameter common to all fx groups is fetched. + * @param type valid pointer on waveform type to return (#fluid_chorus_mod) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_get_chorus_group_type(fluid_synth_t *synth, int fx_group, int *type) +{ + double num_type = 0.0; + int status; + status = fluid_synth_chorus_get_param(synth, fx_group, FLUID_CHORUS_TYPE, &num_type); + *type = (int)num_type; + return status; +} + +/** + * Get chorus parameter value of one or all fx groups. + * @param synth FluidSynth instance + * @param fx_group index of the fx group + * @param enum indicating the parameter to get. + * FLUID_CHORUS_NR, chorus voice count. + * FLUID_CHORUS_LEVEL, chorus level. + * FLUID_CHORUS_SPEED, chorus speed. + * FLUID_CHORUS_DEPTH, chorus depth. + * FLUID_CHORUS_TYPE, chorus waveform type. + * @param value pointer on the value to return. + * @return FLUID_OK if success, FLUID_FAILED otherwise. + */ +static int fluid_synth_chorus_get_param(fluid_synth_t *synth, int fx_group, + int param, double *value) +{ + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail((param >= 0) && (param < FLUID_CHORUS_PARAM_LAST), FLUID_FAILED); + fluid_return_val_if_fail(value != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(fx_group < -1 || fx_group >= synth->effects_groups) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + if (fx_group < 0) + { + /* return chorus param common to all fx groups */ + *value = synth->chorus_param[param]; + } + else + { + /* return chorus param of fx group at index group */ + *value = fluid_rvoice_mixer_chorus_get_param(synth->eventhandler->mixer, + fx_group, param); + } + + FLUID_API_RETURN(FLUID_OK); +} + +/* + * If the same note is hit twice on the same channel, then the older + * voice process is advanced to the release stage. Using a mechanical + * MIDI controller, the only way this can happen is when the sustain + * pedal is held. In this case the behaviour implemented here is + * natural for many instruments. Note: One noteon event can trigger + * several voice processes, for example a stereo sample. Don't + * release those... + */ +void +fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t *synth, int chan, + int key) +{ + int i; + fluid_voice_t *voice; + + /* storeid is a parameter for fluid_voice_init() */ + synth->storeid = synth->noteid++; + + /* for "monophonic playing" key is the previous sustained note + if it exists (0 to 127) or INVALID_NOTE otherwise */ + if(key == INVALID_NOTE) + { + return; + } + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_playing(voice) + && (fluid_voice_get_channel(voice) == chan) + && (fluid_voice_get_key(voice) == key) + && (fluid_voice_get_id(voice) != synth->noteid)) + { + /* Id of voices that was sustained by sostenuto */ + if(fluid_voice_is_sostenuto(voice)) + { + synth->storeid = fluid_voice_get_id(voice); + } + + /* Force the voice into release stage except if pedaling + (sostenuto or sustain) is active */ + fluid_voice_noteoff(voice); + } + } +} + +/** + * Set synthesis interpolation method on one or all MIDI channels. + * @param synth FluidSynth instance + * @param chan MIDI channel to set interpolation method on or -1 for all channels + * @param interp_method Interpolation method (#fluid_interp) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_set_interp_method(fluid_synth_t *synth, int chan, int interp_method) +{ + int i; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + if(chan < -1 || chan >= synth->midi_channels) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + if(synth->channel[0] == NULL) + { + FLUID_LOG(FLUID_ERR, "Channels don't exist (yet)!"); + FLUID_API_RETURN(FLUID_FAILED); + } + + for(i = 0; i < synth->midi_channels; i++) + { + if(chan < 0 || fluid_channel_get_num(synth->channel[i]) == chan) + { + fluid_channel_set_interp_method(synth->channel[i], interp_method); + } + } + + FLUID_API_RETURN(FLUID_OK); +}; + +/** + * Get the total count of MIDI channels. + * @param synth FluidSynth instance + * @return Count of MIDI channels + */ +int +fluid_synth_count_midi_channels(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + result = synth->midi_channels; + FLUID_API_RETURN(result); +} + +/** + * Get the total count of audio channels. + * @param synth FluidSynth instance + * @return Count of audio channel stereo pairs (1 = 2 channels, 2 = 4, etc) + */ +int +fluid_synth_count_audio_channels(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + result = synth->audio_channels; + FLUID_API_RETURN(result); +} + +/** + * Get the total number of allocated audio channels. Usually identical to the + * number of audio channels. Can be employed by LADSPA effects subsystem. + * + * @param synth FluidSynth instance + * @return Count of audio group stereo pairs (1 = 2 channels, 2 = 4, etc) + */ +int +fluid_synth_count_audio_groups(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + result = synth->audio_groups; + FLUID_API_RETURN(result); +} + +/** + * Get the total number of allocated effects channels. + * @param synth FluidSynth instance + * @return Count of allocated effects channels + */ +int +fluid_synth_count_effects_channels(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + result = synth->effects_channels; + FLUID_API_RETURN(result); +} + +/** + * Get the total number of allocated effects units. + * + * This is the same number as initially provided by the setting \setting{synth_effects-groups}. + * @param synth FluidSynth instance + * @return Count of allocated effects units + */ +int +fluid_synth_count_effects_groups(fluid_synth_t *synth) +{ + int result; + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + result = synth->effects_groups; + FLUID_API_RETURN(result); +} + +/** + * Get the synth CPU load value. + * @param synth FluidSynth instance + * @return Estimated CPU load value in percent (0-100) + */ +double +fluid_synth_get_cpu_load(fluid_synth_t *synth) +{ + fluid_return_val_if_fail(synth != NULL, 0); + return fluid_atomic_float_get(&synth->cpu_load); +} + +/* Get tuning for a given bank:program */ +static fluid_tuning_t * +fluid_synth_get_tuning(fluid_synth_t *synth, int bank, int prog) +{ + + if((synth->tuning == NULL) || + (synth->tuning[bank] == NULL) || + (synth->tuning[bank][prog] == NULL)) + { + return NULL; + } + + return synth->tuning[bank][prog]; +} + +/* Replace tuning on a given bank:program (need not already exist). + * Synth mutex should already be locked by caller. */ +static int +fluid_synth_replace_tuning_LOCK(fluid_synth_t *synth, fluid_tuning_t *tuning, + int bank, int prog, int apply) +{ + fluid_tuning_t *old_tuning; + + if(synth->tuning == NULL) + { + synth->tuning = FLUID_ARRAY(fluid_tuning_t **, 128); + + if(synth->tuning == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + FLUID_MEMSET(synth->tuning, 0, 128 * sizeof(fluid_tuning_t **)); + } + + if(synth->tuning[bank] == NULL) + { + synth->tuning[bank] = FLUID_ARRAY(fluid_tuning_t *, 128); + + if(synth->tuning[bank] == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return FLUID_FAILED; + } + + FLUID_MEMSET(synth->tuning[bank], 0, 128 * sizeof(fluid_tuning_t *)); + } + + old_tuning = synth->tuning[bank][prog]; + synth->tuning[bank][prog] = tuning; + + if(old_tuning) + { + if(!fluid_tuning_unref(old_tuning, 1)) /* -- unref old tuning */ + { + /* Replace old tuning if present */ + fluid_synth_replace_tuning_LOCAL(synth, old_tuning, tuning, apply, FALSE); + } + } + + return FLUID_OK; +} + +/* Replace a tuning with a new one in all MIDI channels. new_tuning can be + * NULL, in which case channels are reset to default equal tempered scale. */ +static void +fluid_synth_replace_tuning_LOCAL(fluid_synth_t *synth, fluid_tuning_t *old_tuning, + fluid_tuning_t *new_tuning, int apply, int unref_new) +{ + fluid_channel_t *channel; + int old_tuning_unref = 0; + int i; + + for(i = 0; i < synth->midi_channels; i++) + { + channel = synth->channel[i]; + + if(fluid_channel_get_tuning(channel) == old_tuning) + { + old_tuning_unref++; + + if(new_tuning) + { + fluid_tuning_ref(new_tuning); /* ++ ref new tuning for channel */ + } + + fluid_channel_set_tuning(channel, new_tuning); + + if(apply) + { + fluid_synth_update_voice_tuning_LOCAL(synth, channel); + } + } + } + + /* Send unref old tuning event if any unrefs */ + if(old_tuning && old_tuning_unref) + { + fluid_tuning_unref(old_tuning, old_tuning_unref); + } + + if(!unref_new || !new_tuning) + { + return; + } + + fluid_tuning_unref(new_tuning, 1); +} + +/* Update voice tunings in realtime */ +static void +fluid_synth_update_voice_tuning_LOCAL(fluid_synth_t *synth, fluid_channel_t *channel) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_on(voice) && (voice->channel == channel)) + { + fluid_voice_calculate_gen_pitch(voice); + fluid_voice_update_param(voice, GEN_PITCH); + } + } +} + +/** + * Set the tuning of the entire MIDI note scale. + * @param synth FluidSynth instance + * @param bank Tuning bank number (0-127), not related to MIDI instrument bank + * @param prog Tuning preset number (0-127), not related to MIDI instrument program + * @param name Label name for this tuning + * @param pitch Array of pitch values (length of 128, each value is number of + * cents, for example normally note 0 is 0.0, 1 is 100.0, 60 is 6000.0, etc). + * Pass NULL to create a equal tempered (normal) scale. + * @param apply TRUE to apply new tuning in realtime to existing notes which + * are using the replaced tuning (if any), FALSE otherwise + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + */ +int +fluid_synth_activate_key_tuning(fluid_synth_t *synth, int bank, int prog, + const char *name, const double *pitch, int apply) +{ + fluid_tuning_t *tuning; + int retval = FLUID_OK; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); + fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + tuning = new_fluid_tuning(name, bank, prog); + + if(tuning) + { + if(pitch) + { + fluid_tuning_set_all(tuning, pitch); + } + + retval = fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, apply); + + if(retval == FLUID_FAILED) + { + fluid_tuning_unref(tuning, 1); + } + } + else + { + retval = FLUID_FAILED; + } + + FLUID_API_RETURN(retval); +} + +/** + * Activate an octave tuning on every octave in the MIDI note scale. + * @param synth FluidSynth instance + * @param bank Tuning bank number (0-127), not related to MIDI instrument bank + * @param prog Tuning preset number (0-127), not related to MIDI instrument program + * @param name Label name for this tuning + * @param pitch Array of pitch values (length of 12 for each note of an octave + * starting at note C, values are number of offset cents to add to the normal + * tuning amount) + * @param apply TRUE to apply new tuning in realtime to existing notes which + * are using the replaced tuning (if any), FALSE otherwise + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + */ +int +fluid_synth_activate_octave_tuning(fluid_synth_t *synth, int bank, int prog, + const char *name, const double *pitch, int apply) +{ + fluid_tuning_t *tuning; + int retval = FLUID_OK; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); + fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(pitch != NULL, FLUID_FAILED); + + fluid_synth_api_enter(synth); + tuning = new_fluid_tuning(name, bank, prog); + + if(tuning) + { + fluid_tuning_set_octave(tuning, pitch); + retval = fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, apply); + + if(retval == FLUID_FAILED) + { + fluid_tuning_unref(tuning, 1); + } + } + else + { + retval = FLUID_FAILED; + } + + FLUID_API_RETURN(retval); +} + +/** + * Set tuning values for one or more MIDI notes for an existing tuning. + * @param synth FluidSynth instance + * @param bank Tuning bank number (0-127), not related to MIDI instrument bank + * @param prog Tuning preset number (0-127), not related to MIDI instrument program + * @param len Number of MIDI notes to assign + * @param key Array of MIDI key numbers (length of 'len', values 0-127) + * @param pitch Array of pitch values (length of 'len', values are number of + * cents from MIDI note 0) + * @param apply TRUE to apply tuning change in realtime to existing notes using + * the specified tuning, FALSE otherwise + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note Prior to version 1.1.0 it was an error to specify a tuning that didn't + * already exist. Starting with 1.1.0, the default equal tempered scale will be + * used as a basis, if no tuning exists for the given bank and prog. + */ +int +fluid_synth_tune_notes(fluid_synth_t *synth, int bank, int prog, + int len, const int *key, const double *pitch, int apply) +{ + fluid_tuning_t *old_tuning, *new_tuning; + int retval = FLUID_OK; + int i; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); + fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); + fluid_return_val_if_fail(len > 0, FLUID_FAILED); + fluid_return_val_if_fail(key != NULL, FLUID_FAILED); + fluid_return_val_if_fail(pitch != NULL, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + old_tuning = fluid_synth_get_tuning(synth, bank, prog); + + if(old_tuning) + { + new_tuning = fluid_tuning_duplicate(old_tuning); + } + else + { + new_tuning = new_fluid_tuning("Unnamed", bank, prog); + } + + if(new_tuning) + { + for(i = 0; i < len; i++) + { + fluid_tuning_set_pitch(new_tuning, key[i], pitch[i]); + } + + retval = fluid_synth_replace_tuning_LOCK(synth, new_tuning, bank, prog, apply); + + if(retval == FLUID_FAILED) + { + fluid_tuning_unref(new_tuning, 1); + } + } + else + { + retval = FLUID_FAILED; + } + + FLUID_API_RETURN(retval); +} + +/** + * Activate a tuning scale on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param bank Tuning bank number (0-127), not related to MIDI instrument bank + * @param prog Tuning preset number (0-127), not related to MIDI instrument program + * @param apply TRUE to apply tuning change to active notes, FALSE otherwise + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + * + * @note A default equal tempered scale will be created, if no tuning exists + * on the given bank and prog. + */ +int +fluid_synth_activate_tuning(fluid_synth_t *synth, int chan, int bank, int prog, + int apply) +{ + fluid_tuning_t *tuning; + int retval = FLUID_OK; + + //fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + //fluid_return_val_if_fail (chan >= 0 && chan < synth->midi_channels, FLUID_FAILED); + fluid_return_val_if_fail(bank >= 0 && bank < 128, FLUID_FAILED); + fluid_return_val_if_fail(prog >= 0 && prog < 128, FLUID_FAILED); + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + tuning = fluid_synth_get_tuning(synth, bank, prog); + + /* If no tuning exists, create a new default tuning. We do this, so that + * it can be replaced later, if any changes are made. */ + if(!tuning) + { + tuning = new_fluid_tuning("Unnamed", bank, prog); + + if(tuning) + { + fluid_synth_replace_tuning_LOCK(synth, tuning, bank, prog, FALSE); + } + } + + if(tuning) + { + fluid_tuning_ref(tuning); /* ++ ref for outside of lock */ + } + + if(!tuning) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + fluid_tuning_ref(tuning); /* ++ ref new tuning for following function */ + retval = fluid_synth_set_tuning_LOCAL(synth, chan, tuning, apply); + + fluid_tuning_unref(tuning, 1); /* -- unref for outside of lock */ + + FLUID_API_RETURN(retval); +} + +/* Local synthesis thread set tuning function (takes over tuning reference) */ +static int +fluid_synth_set_tuning_LOCAL(fluid_synth_t *synth, int chan, + fluid_tuning_t *tuning, int apply) +{ + fluid_tuning_t *old_tuning; + fluid_channel_t *channel; + + channel = synth->channel[chan]; + + old_tuning = fluid_channel_get_tuning(channel); + fluid_channel_set_tuning(channel, tuning); /* !! Takes over callers reference */ + + if(apply) + { + fluid_synth_update_voice_tuning_LOCAL(synth, channel); + } + + /* Send unref old tuning event */ + if(old_tuning) + { + fluid_tuning_unref(old_tuning, 1); + } + + + return FLUID_OK; +} + +/** + * Clear tuning scale on a MIDI channel (use default equal tempered scale). + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param apply TRUE to apply tuning change to active notes, FALSE otherwise + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.0 + */ +int +fluid_synth_deactivate_tuning(fluid_synth_t *synth, int chan, int apply) +{ + int retval = FLUID_OK; + + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + retval = fluid_synth_set_tuning_LOCAL(synth, chan, NULL, apply); + + FLUID_API_RETURN(retval); +} + +/** + * Start tuning iteration. + * @param synth FluidSynth instance + */ +void +fluid_synth_tuning_iteration_start(fluid_synth_t *synth) +{ + fluid_return_if_fail(synth != NULL); + fluid_synth_api_enter(synth); + fluid_private_set(synth->tuning_iter, FLUID_INT_TO_POINTER(0)); + fluid_synth_api_exit(synth); +} + +/** + * Advance to next tuning. + * @param synth FluidSynth instance + * @param bank Location to store MIDI bank number of next tuning scale + * @param prog Location to store MIDI program number of next tuning scale + * @return 1 if tuning iteration advanced, 0 if no more tunings + */ +int +fluid_synth_tuning_iteration_next(fluid_synth_t *synth, int *bank, int *prog) +{ + void *pval; + int b = 0, p = 0; + + fluid_return_val_if_fail(synth != NULL, 0); + fluid_return_val_if_fail(bank != NULL, 0); + fluid_return_val_if_fail(prog != NULL, 0); + fluid_synth_api_enter(synth); + + /* Current tuning iteration stored as: bank << 8 | program */ + pval = fluid_private_get(synth->tuning_iter); + p = FLUID_POINTER_TO_INT(pval); + b = (p >> 8) & 0xFF; + p &= 0xFF; + + if(!synth->tuning) + { + FLUID_API_RETURN(0); + } + + for(; b < 128; b++, p = 0) + { + if(synth->tuning[b] == NULL) + { + continue; + } + + for(; p < 128; p++) + { + if(synth->tuning[b][p] == NULL) + { + continue; + } + + *bank = b; + *prog = p; + + if(p < 127) + { + fluid_private_set(synth->tuning_iter, + FLUID_INT_TO_POINTER(b << 8 | (p + 1))); + } + else + { + fluid_private_set(synth->tuning_iter, FLUID_INT_TO_POINTER((b + 1) << 8)); + } + + FLUID_API_RETURN(1); + } + } + + FLUID_API_RETURN(0); +} + +/** + * Get the entire note tuning for a given MIDI bank and program. + * @param synth FluidSynth instance + * @param bank MIDI bank number of tuning + * @param prog MIDI program number of tuning + * @param name Location to store tuning name or NULL to ignore + * @param len Maximum number of chars to store to 'name' (including NULL byte) + * @param pitch Array to store tuning scale to or NULL to ignore (len of 128) + * @return #FLUID_OK if matching tuning was found, #FLUID_FAILED otherwise + */ +int +fluid_synth_tuning_dump(fluid_synth_t *synth, int bank, int prog, + char *name, int len, double *pitch) +{ + fluid_tuning_t *tuning; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + tuning = fluid_synth_get_tuning(synth, bank, prog); + + if(tuning) + { + if(name) + { + FLUID_SNPRINTF(name, len - 1, "%s", fluid_tuning_get_name(tuning)); + name[len - 1] = 0; /* make sure the string is null terminated */ + } + + if(pitch) + { + FLUID_MEMCPY(pitch, fluid_tuning_get_all(tuning), 128 * sizeof(double)); + } + } + + FLUID_API_RETURN(tuning ? FLUID_OK : FLUID_FAILED); +} + +/** + * Get settings assigned to a synth. + * @param synth FluidSynth instance + * @return FluidSynth settings which are assigned to the synth + */ +fluid_settings_t * +fluid_synth_get_settings(fluid_synth_t *synth) +{ + fluid_return_val_if_fail(synth != NULL, NULL); + + return synth->settings; +} + +/** + * Apply an offset to a SoundFont generator on a MIDI channel. + * + * This function allows to set an offset for the specified destination generator in real-time. + * The offset will be applied immediately to all voices that are currently and subsequently playing + * on the given MIDI channel. This functionality works equivalent to using NRPN MIDI messages to + * manipulate synthesis parameters. See SoundFont spec, paragraph 8.1.3, for details on SoundFont + * generator parameters and valid ranges, as well as paragraph 9.6 for details on NRPN messages. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param param SoundFont generator ID (#fluid_gen_type) + * @param value Offset value (in native units of the generator) to assign to the MIDI channel + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int fluid_synth_set_gen(fluid_synth_t *synth, int chan, int param, float value) +{ + fluid_return_val_if_fail(param >= 0 && param < GEN_LAST, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + fluid_synth_set_gen_LOCAL(synth, chan, param, value); + + FLUID_API_RETURN(FLUID_OK); +} + +/* Synthesis thread local set gen function */ +static void +fluid_synth_set_gen_LOCAL(fluid_synth_t *synth, int chan, int param, float value) +{ + fluid_voice_t *voice; + int i; + + fluid_channel_set_gen(synth->channel[chan], param, value); + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_get_channel(voice) == chan) + { + fluid_voice_set_param(voice, param, value); + } + } +} + +/** + * Retrieve the generator NRPN offset assigned to a MIDI channel. + * + * The value returned is in native units of the generator. By default, the offset is zero. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param param SoundFont generator ID (#fluid_gen_type) + * @return Current NRPN generator offset value assigned to the MIDI channel + */ +float +fluid_synth_get_gen(fluid_synth_t *synth, int chan, int param) +{ + float result; + fluid_return_val_if_fail(param >= 0 && param < GEN_LAST, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + result = fluid_channel_get_gen(synth->channel[chan], param); + FLUID_API_RETURN(result); +} + +/** + * Handle MIDI event from MIDI router, used as a callback function. + * @param data FluidSynth instance + * @param event MIDI event to handle + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_synth_handle_midi_event(void *data, fluid_midi_event_t *event) +{ + fluid_synth_t *synth = (fluid_synth_t *) data; + int type = fluid_midi_event_get_type(event); + int chan = fluid_midi_event_get_channel(event); + + switch(type) + { + case NOTE_ON: + return fluid_synth_noteon(synth, chan, + fluid_midi_event_get_key(event), + fluid_midi_event_get_velocity(event)); + + case NOTE_OFF: + return fluid_synth_noteoff(synth, chan, fluid_midi_event_get_key(event)); + + case CONTROL_CHANGE: + return fluid_synth_cc(synth, chan, + fluid_midi_event_get_control(event), + fluid_midi_event_get_value(event)); + + case PROGRAM_CHANGE: + return fluid_synth_program_change(synth, chan, fluid_midi_event_get_program(event)); + + case CHANNEL_PRESSURE: + return fluid_synth_channel_pressure(synth, chan, fluid_midi_event_get_program(event)); + + case KEY_PRESSURE: + return fluid_synth_key_pressure(synth, chan, + fluid_midi_event_get_key(event), + fluid_midi_event_get_value(event)); + + case PITCH_BEND: + return fluid_synth_pitch_bend(synth, chan, fluid_midi_event_get_pitch(event)); + + case MIDI_SYSTEM_RESET: + return fluid_synth_system_reset(synth); + + case MIDI_SYSEX: + return fluid_synth_sysex(synth, event->paramptr, event->param1, NULL, NULL, NULL, FALSE); + + case MIDI_TEXT: + case MIDI_LYRIC: + case MIDI_SET_TEMPO: + return FLUID_OK; + } + + return FLUID_FAILED; +} + +/** + * Create and start voices using an arbitrary preset and a MIDI note on event. + * + * Using this function is only supported when the setting @c synth.dynamic-sample-loading is false! + * @param synth FluidSynth instance + * @param id Voice group ID to use (can be used with fluid_synth_stop()). + * @param preset Preset to synthesize + * @param audio_chan Unused currently, set to 0 + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param key MIDI note number (0-127) + * @param vel MIDI velocity number (1-127) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note Should only be called from within synthesis thread, which includes + * SoundFont loader preset noteon method. + */ +int +fluid_synth_start(fluid_synth_t *synth, unsigned int id, fluid_preset_t *preset, + int audio_chan, int chan, int key, int vel) +{ + int result, dynamic_samples; + fluid_return_val_if_fail(preset != NULL, FLUID_FAILED); + fluid_return_val_if_fail(key >= 0 && key <= 127, FLUID_FAILED); + fluid_return_val_if_fail(vel >= 1 && vel <= 127, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + fluid_settings_getint(fluid_synth_get_settings(synth), "synth.dynamic-sample-loading", &dynamic_samples); + if(dynamic_samples) + { + // The preset might not be currently used, thus its sample data may not be loaded. + // This guard is to avoid a NULL deref in rvoice_write(). + FLUID_LOG(FLUID_ERR, "Calling fluid_synth_start() while synth.dynamic-sample-loading is enabled is not supported."); + // Although we would be able to select the preset (and load it's samples) we have no way to + // unselect the preset again in fluid_synth_stop(). Also dynamic sample loading was intended + // to be used only when presets have been selected on a MIDI channel. + // Note that even if the preset is currently selected on a channel, it could be unselected at + // any time. And we would end up with a NULL sample->data again, because we are not referencing + // the preset here. Thus failure is our only option. + result = FLUID_FAILED; + } + else + { + synth->storeid = id; + result = fluid_preset_noteon(preset, synth, chan, key, vel); + } + + FLUID_API_RETURN(result); +} + +/** + * Stop notes for a given note event voice ID. + * @param synth FluidSynth instance + * @param id Voice note event ID + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * @note In FluidSynth versions prior to 1.1.0 #FLUID_FAILED would be returned + * if no matching voice note event ID was found. Versions after 1.1.0 only + * return #FLUID_FAILED if an error occurs. + */ +int +fluid_synth_stop(fluid_synth_t *synth, unsigned int id) +{ + int result; + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + fluid_synth_stop_LOCAL(synth, id); + result = FLUID_OK; + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of fluid_synth_stop */ +static void +fluid_synth_stop_LOCAL(fluid_synth_t *synth, unsigned int id) +{ + fluid_voice_t *voice; + int i; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_on(voice) && (fluid_voice_get_id(voice) == id)) + { + fluid_voice_noteoff(voice); + } + } +} + +/** + * Offset the bank numbers of a loaded SoundFont, i.e.\ subtract + * \c offset from any bank number when assigning instruments. + * + * @param synth FluidSynth instance + * @param sfont_id ID of a loaded SoundFont + * @param offset Bank offset value to apply to all instruments + * @return #FLUID_OK if the offset was set successfully, #FLUID_FAILED otherwise + */ +int +fluid_synth_set_bank_offset(fluid_synth_t *synth, int sfont_id, int offset) +{ + fluid_sfont_t *sfont; + fluid_list_t *list; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == sfont_id) + { + sfont->bankofs = offset; + break; + } + } + + if(!list) + { + FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", sfont_id); + FLUID_API_RETURN(FLUID_FAILED); + } + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Get bank offset of a loaded SoundFont. + * @param synth FluidSynth instance + * @param sfont_id ID of a loaded SoundFont + * @return SoundFont bank offset value + */ +int +fluid_synth_get_bank_offset(fluid_synth_t *synth, int sfont_id) +{ + fluid_sfont_t *sfont; + fluid_list_t *list; + int offset = 0; + + fluid_return_val_if_fail(synth != NULL, 0); + fluid_synth_api_enter(synth); + + for(list = synth->sfont; list; list = fluid_list_next(list)) + { + sfont = fluid_list_get(list); + + if(fluid_sfont_get_id(sfont) == sfont_id) + { + offset = sfont->bankofs; + break; + } + } + + if(!list) + { + FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", sfont_id); + FLUID_API_RETURN(0); + } + + FLUID_API_RETURN(offset); +} + +void +fluid_synth_api_enter(fluid_synth_t *synth) +{ + if(synth->use_mutex) + { + fluid_rec_mutex_lock(synth->mutex); + } + + if(!synth->public_api_count) + { + fluid_synth_check_finished_voices(synth); + } + + synth->public_api_count++; +} + +void fluid_synth_api_exit(fluid_synth_t *synth) +{ + synth->public_api_count--; + + if(!synth->public_api_count) + { + fluid_rvoice_eventhandler_flush(synth->eventhandler); + } + + if(synth->use_mutex) + { + fluid_rec_mutex_unlock(synth->mutex); + } + +} + +/** + * Set midi channel type + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param type MIDI channel type (#fluid_midi_channel_type) + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.4 + */ +int fluid_synth_set_channel_type(fluid_synth_t *synth, int chan, int type) +{ + fluid_return_val_if_fail((type >= CHANNEL_TYPE_MELODIC) && (type <= CHANNEL_TYPE_DRUM), FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + synth->channel[chan]->channel_type = type; + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Return the LADSPA effects instance used by FluidSynth + * + * @param synth FluidSynth instance + * @return pointer to LADSPA fx or NULL if compiled without LADSPA support or LADSPA is not active + */ +fluid_ladspa_fx_t *fluid_synth_get_ladspa_fx(fluid_synth_t *synth) +{ + fluid_return_val_if_fail(synth != NULL, NULL); + + return synth->ladspa_fx; +} + +/** + * Configure a general-purpose IIR biquad filter. + * + * @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) +{ + int i; + fluid_voice_t *voice; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail(type >= FLUID_IIR_DISABLED && type < FLUID_IIR_LAST, FLUID_FAILED); + + fluid_synth_api_enter(synth); + + synth->custom_filter_type = type; + synth->custom_filter_flags = flags; + + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + fluid_voice_set_custom_filter(voice, type, flags); + } + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Set the important channels for voice overflow priority calculation. + * + * @param synth FluidSynth instance + * @param channels comma-separated list of channel numbers + * @return #FLUID_OK on success, otherwise #FLUID_FAILED + */ +static int fluid_synth_set_important_channels(fluid_synth_t *synth, const char *channels) +{ + int i; + int retval = FLUID_FAILED; + int *values = NULL; + int num_values; + fluid_overflow_prio_t *scores; + + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + + scores = &synth->overflow; + + if(scores->num_important_channels < synth->midi_channels) + { + scores->important_channels = FLUID_REALLOC(scores->important_channels, + sizeof(*scores->important_channels) * synth->midi_channels); + + if(scores->important_channels == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto exit; + } + + scores->num_important_channels = synth->midi_channels; + } + + FLUID_MEMSET(scores->important_channels, FALSE, + sizeof(*scores->important_channels) * scores->num_important_channels); + + if(channels != NULL) + { + values = FLUID_ARRAY(int, synth->midi_channels); + + if(values == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto exit; + } + + /* Every channel given in the comma-separated list of channel numbers + * is set to TRUE, i.e. flagging it as "important". Channel numbers are + * 1-based. */ + num_values = fluid_settings_split_csv(channels, values, synth->midi_channels); + + for(i = 0; i < num_values; i++) + { + if(values[i] > 0 && values[i] <= synth->midi_channels) + { + scores->important_channels[values[i] - 1] = TRUE; + } + } + } + + retval = FLUID_OK; + +exit: + FLUID_FREE(values); + return retval; +} + +/* + * Handler for synth.overflow.important-channels setting. + */ +static void fluid_synth_handle_important_channels(void *data, const char *name, + const char *value) +{ + fluid_synth_t *synth = (fluid_synth_t *)data; + + fluid_synth_api_enter(synth); + fluid_synth_set_important_channels(synth, value); + fluid_synth_api_exit(synth); +} + + +/* API legato mode *********************************************************/ + +/** + * Sets the legato mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param legatomode The legato mode as indicated by #fluid_channel_legato_mode. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a legatomode is invalid. + */ +int fluid_synth_set_legato_mode(fluid_synth_t *synth, int chan, int legatomode) +{ + /* checks parameters first */ + fluid_return_val_if_fail(legatomode >= 0, FLUID_FAILED); + fluid_return_val_if_fail(legatomode < FLUID_CHANNEL_LEGATO_MODE_LAST, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + synth->channel[chan]->legatomode = legatomode; + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Gets the legato mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param legatomode The legato mode as indicated by #fluid_channel_legato_mode. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a legatomode is NULL. + */ +int fluid_synth_get_legato_mode(fluid_synth_t *synth, int chan, int *legatomode) +{ + /* checks parameters first */ + fluid_return_val_if_fail(legatomode != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + * legatomode = synth->channel[chan]->legatomode; + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/* API portamento mode *********************************************************/ + +/** + * Sets the portamento mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param portamentomode The portamento mode as indicated by #fluid_channel_portamento_mode. + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a portamentomode is invalid. + */ +int fluid_synth_set_portamento_mode(fluid_synth_t *synth, int chan, + int portamentomode) +{ + /* checks parameters first */ + fluid_return_val_if_fail(portamentomode >= 0, FLUID_FAILED); + fluid_return_val_if_fail(portamentomode < FLUID_CHANNEL_PORTAMENTO_MODE_LAST, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + synth->channel[chan]->portamentomode = portamentomode; + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Gets the portamento mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param portamentomode Pointer to the portamento mode as indicated by #fluid_channel_portamento_mode. + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a portamentomode is NULL. + */ +int fluid_synth_get_portamento_mode(fluid_synth_t *synth, int chan, + int *portamentomode) +{ + /* checks parameters first */ + fluid_return_val_if_fail(portamentomode != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + * portamentomode = synth->channel[chan]->portamentomode; + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/* API breath mode *********************************************************/ + +/** + * Sets the breath mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param breathmode The breath mode as indicated by #fluid_channel_breath_flags. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + */ +int fluid_synth_set_breath_mode(fluid_synth_t *synth, int chan, int breathmode) +{ + /* checks parameters first */ + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + fluid_channel_set_breath_info(synth->channel[chan], breathmode); + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Gets the breath mode of a channel. + * + * @param synth the synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param breathmode Pointer to the returned breath mode as indicated by #fluid_channel_breath_flags. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a breathmode is NULL. + */ +int fluid_synth_get_breath_mode(fluid_synth_t *synth, int chan, int *breathmode) +{ + /* checks parameters first */ + fluid_return_val_if_fail(breathmode != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + /**/ + * breathmode = fluid_channel_get_breath_info(synth->channel[chan]); + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/** API Poly/mono mode ******************************************************/ + +/* + * Resets a basic channel group of MIDI channels. + * @param synth the synth instance. + * @param chan the beginning channel of the group. + * @param nbr_chan the number of channel in the group. +*/ +static void +fluid_synth_reset_basic_channel_LOCAL(fluid_synth_t *synth, int chan, int nbr_chan) +{ + int i; + + for(i = chan; i < chan + nbr_chan; i++) + { + fluid_channel_reset_basic_channel_info(synth->channel[i]); + synth->channel[i]->mode_val = 0; + } +} + +/** + * Disables and unassigns all channels from a basic channel group. + * + * @param synth The synth instance. + * @param chan The basic channel of the group to reset or -1 to reset all channels. + * @note By default (i.e. on creation after new_fluid_synth() and after fluid_synth_system_reset()) + * a synth instance has one basic channel at channel 0 in mode #FLUID_CHANNEL_MODE_OMNION_POLY. + * All other channels belong to this basic channel group. Make sure to call this function before + * setting any custom basic channel setup. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a chan isn't a basic channel. + */ +int fluid_synth_reset_basic_channel(fluid_synth_t *synth, int chan) +{ + int nbr_chan; + + /* checks parameters first */ + if(chan < 0) + { + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + /* The range is all MIDI channels from 0 to MIDI channel count -1 */ + chan = 0; /* beginning chan */ + nbr_chan = synth->midi_channels; /* MIDI Channels number */ + } + else + { + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /* checks if chan is a basic channel */ + if(!(synth->channel[chan]->mode & FLUID_CHANNEL_BASIC)) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + /* The range is all MIDI channels in the group from chan */ + nbr_chan = synth->channel[chan]->mode_val; /* nbr of channels in the group */ + } + + /* resets the range of MIDI channels */ + fluid_synth_reset_basic_channel_LOCAL(synth, chan, nbr_chan); + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Checks if a new basic channel group overlaps the next basic channel group. + * + * On success the function returns the possible number of channel for this + * new basic channel group. + * The function fails if the new group overlaps the next basic channel group. + * + * @param see fluid_synth_set_basic_channel. + * @return + * - On success, the effective number of channels for this new basic channel group, + * #FLUID_FAILED otherwise. + * - #FLUID_FAILED + * - \a val has a number of channels overlapping next basic channel group or been + * above MIDI channel count. + */ +static int +fluid_synth_check_next_basic_channel(fluid_synth_t *synth, int basicchan, int mode, int val) +{ + int i, n_chan = synth->midi_channels; /* MIDI Channels count */ + int real_val = val; /* real number of channels in the group */ + + /* adjusts val range */ + if(mode == FLUID_CHANNEL_MODE_OMNIOFF_POLY) + { + real_val = 1; /* mode poly omnioff implies a group of only one channel.*/ + } + else if(val == 0) + { + /* mode poly omnion (0), mono omnion (1), mono omni off (3) */ + /* value 0 means all possible channels from basicchan to MIDI channel count -1.*/ + real_val = n_chan - basicchan; + } + /* checks if val range is above MIDI channel count */ + else if(basicchan + val > n_chan) + { + return FLUID_FAILED; + } + + /* checks if this basic channel group overlaps next basic channel group */ + for(i = basicchan + 1; i < basicchan + real_val; i++) + { + if(synth->channel[i]->mode & FLUID_CHANNEL_BASIC) + { + /* A value of 0 for val means all possible channels from basicchan to + to the next basic channel -1 (if any). + When i reaches the next basic channel group, real_val will be + limited if it is possible */ + if(val == 0) + { + /* limitation of real_val */ + real_val = i - basicchan; + break; + } + + /* overlap with the next basic channel group */ + return FLUID_FAILED; + } + } + + return real_val; +} + +/** + * Sets a new basic channel group only. The function doesn't allow to change an + * existing basic channel. + * + * The function fails if any channel overlaps any existing basic channel group. + * To make room if necessary, basic channel groups can be cleared using + * fluid_synth_reset_basic_channel(). + * + * @param synth the synth instance. + * @param chan the basic Channel number (0 to MIDI channel count-1). + * @param mode the MIDI mode to use for chan (see #fluid_basic_channel_modes). + * @param val number of channels in the group. + * @note \a val is only relevant for mode #FLUID_CHANNEL_MODE_OMNION_POLY, + * #FLUID_CHANNEL_MODE_OMNION_MONO and #FLUID_CHANNEL_MODE_OMNIOFF_MONO. A value + * of 0 means all possible channels from \a chan to to next basic channel minus 1 (if any) + * or to MIDI channel count minus 1. Val is ignored for #FLUID_CHANNEL_MODE_OMNIOFF_POLY + * as this mode implies a group of only one channel. + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + * - \a mode is invalid. + * - \a val has a number of channels overlapping another basic channel group or been + * above MIDI channel count. + * - When the function fails, any existing basic channels aren't modified. + */ +int fluid_synth_set_basic_channel(fluid_synth_t *synth, int chan, int mode, int val) +{ + /* check parameters */ + fluid_return_val_if_fail(mode >= 0, FLUID_FAILED); + fluid_return_val_if_fail(mode < FLUID_CHANNEL_MODE_LAST, FLUID_FAILED); + fluid_return_val_if_fail(val >= 0, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + /**/ + if(val > 0 && chan + val > synth->midi_channels) + { + FLUID_API_RETURN(FLUID_FAILED); + } + + /* Checks if there is an overlap with the next basic channel */ + val = fluid_synth_check_next_basic_channel(synth, chan, mode, val); + + if(val == FLUID_FAILED || synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED) + { + /* overlap with the next or previous channel group */ + FLUID_LOG(FLUID_INFO, "basic channel %d overlaps another group", chan); + FLUID_API_RETURN(FLUID_FAILED); + } + + /* sets a new basic channel group */ + fluid_synth_set_basic_channel_LOCAL(synth, chan, mode, val); + /**/ + FLUID_API_RETURN(FLUID_OK); +} + +/* + * Local version of fluid_synth_set_basic_channel(), called internally: + * - by fluid_synth_set_basic_channel() to set a new basic channel group. + * - during creation new_fluid_synth() or on CC reset to set a default basic channel group. + * - on CC ominoff, CC omnion, CC poly , CC mono to change an existing basic channel group. + * + * @param see fluid_synth_set_basic_channel() +*/ +static void +fluid_synth_set_basic_channel_LOCAL(fluid_synth_t *synth, int basicchan, int mode, int val) +{ + int i; + + /* sets the basic channel group */ + for(i = basicchan; i < basicchan + val; i++) + { + int new_mode = mode; /* OMNI_OFF/ON, MONO/POLY ,others bits are zero */ + int new_val; + /* MIDI specs: when mode is changed, channel must receive ALL_NOTES_OFF */ + fluid_synth_all_notes_off_LOCAL(synth, i); + + if(i == basicchan) + { + new_mode |= FLUID_CHANNEL_BASIC; /* First channel in the group */ + new_val = val; /* number of channels in the group */ + } + else + { + new_val = 0; /* val is 0 for other channel than basic channel */ + } + + /* Channel is enabled */ + new_mode |= FLUID_CHANNEL_ENABLED; + /* Now new_mode is OMNI OFF/ON,MONO/POLY, BASIC_CHANNEL or not and enabled */ + fluid_channel_set_basic_channel_info(synth->channel[i], new_mode); + synth->channel[i]->mode_val = new_val; + } +} + +/** + * Searches a previous basic channel starting from chan. + * + * @param synth the synth instance. + * @param chan starting index of the search (including chan). + * @return index of the basic channel if found , FLUID_FAILED otherwise. + */ +static int fluid_synth_get_previous_basic_channel(fluid_synth_t *synth, int chan) +{ + for(; chan >= 0; chan--) + { + /* searches previous basic channel */ + if(synth->channel[chan]->mode & FLUID_CHANNEL_BASIC) + { + /* chan is the previous basic channel */ + return chan; + } + } + + return FLUID_FAILED; +} + +/** + * Returns poly mono mode information of any MIDI channel. + * + * @param synth the synth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param basic_chan_out Buffer to store the basic channel \a chan belongs to or #FLUID_FAILED if \a chan is disabled. + * @param mode_out Buffer to store the mode of \a chan (see #fluid_basic_channel_modes) or #FLUID_FAILED if \a chan is disabled. + * @param val_out Buffer to store the total number of channels in this basic channel group or #FLUID_FAILED if \a chan is disabled. + * @note If any of \a basic_chan_out, \a mode_out, \a val_out pointer is NULL + * the corresponding information isn't returned. + * + * @return + * - #FLUID_OK on success. + * - #FLUID_FAILED + * - \a synth is NULL. + * - \a chan is outside MIDI channel count. + */ +int fluid_synth_get_basic_channel(fluid_synth_t *synth, int chan, + int *basic_chan_out, + int *mode_out, + int *val_out) +{ + int basic_chan = FLUID_FAILED; + int mode = FLUID_FAILED; + int val = FLUID_FAILED; + + /* checks parameters first */ + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + if((synth->channel[chan]->mode & FLUID_CHANNEL_ENABLED) && + /* chan is enabled , we search the basic channel chan belongs to */ + (basic_chan = fluid_synth_get_previous_basic_channel(synth, chan)) != FLUID_FAILED) + { + mode = synth->channel[chan]->mode & FLUID_CHANNEL_MODE_MASK; + val = synth->channel[basic_chan]->mode_val; + } + + /* returns the information if they are requested */ + if(basic_chan_out) + { + * basic_chan_out = basic_chan; + } + + if(mode_out) + { + * mode_out = mode; + } + + if(val_out) + { + * val_out = val; + } + + FLUID_API_RETURN(FLUID_OK); +} diff --git a/libs/fluidsynth/src/synth/fluid_synth.h b/libs/fluidsynth/src/synth/fluid_synth.h new file mode 100644 index 00000000000..cb838e92462 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_synth.h @@ -0,0 +1,263 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_SYNTH_H +#define _FLUID_SYNTH_H + + +/*************************************************************** + * + * INCLUDES + */ + +#include "fluid_sys.h" +#include "fluid_list.h" +#include "fluid_rev.h" +#include "fluid_voice.h" +#include "fluid_chorus.h" +#include "fluid_ladspa.h" +#include "fluid_midi_router.h" +#include "fluid_rvoice_event.h" + +/*************************************************************** + * + * DEFINES + */ +#define FLUID_NUM_PROGRAMS 128 +#define DRUM_INST_BANK 128 + +#define FLUID_UNSET_PROGRAM 128 /* Program number used to unset a preset */ + +#define FLUID_REVERB_DEFAULT_ROOMSIZE 0.2f /**< Default reverb room size */ +#define FLUID_REVERB_DEFAULT_DAMP 0.0f /**< Default reverb damping */ +#define FLUID_REVERB_DEFAULT_WIDTH 0.5f /**< Default reverb width */ +#define FLUID_REVERB_DEFAULT_LEVEL 0.9f /**< Default reverb level */ + +#define FLUID_CHORUS_DEFAULT_N 3 /**< Default chorus voice count */ +#define FLUID_CHORUS_DEFAULT_LEVEL 2.0f /**< Default chorus level */ +#define FLUID_CHORUS_DEFAULT_SPEED 0.3f /**< Default chorus speed */ +#define FLUID_CHORUS_DEFAULT_DEPTH 8.0f /**< Default chorus depth */ +#define FLUID_CHORUS_DEFAULT_TYPE FLUID_CHORUS_MOD_SINE /**< Default chorus waveform type */ + +/*************************************************************** + * + * ENUM + */ + +/** + * Bank Select MIDI message styles. Default style is GS. + */ +enum fluid_midi_bank_select +{ + FLUID_BANK_STYLE_GM, /**< GM style, bank = 0 always (CC0/MSB and CC32/LSB ignored) */ + FLUID_BANK_STYLE_GS, /**< GS style, bank = CC0/MSB (CC32/LSB ignored) */ + FLUID_BANK_STYLE_XG, /**< XG style, bank = CC32/LSB (CC0/MSB ignored) */ + FLUID_BANK_STYLE_MMA /**< MMA style bank = 128*MSB+LSB */ +}; + +enum fluid_synth_status +{ + FLUID_SYNTH_CLEAN, + FLUID_SYNTH_PLAYING, + FLUID_SYNTH_QUIET, + FLUID_SYNTH_STOPPED +}; + +#define SYNTH_REVERB_CHANNEL 0 +#define SYNTH_CHORUS_CHANNEL 1 + +/* + * fluid_synth_t + * + * Mutual exclusion notes (as of 1.1.2): + * + * All variables are considered belongning to the "public API" thread, + * which processes all MIDI, except for: + * + * ticks_since_start - atomic, set by rendering thread only + * cpu_load - atomic, set by rendering thread only + * cur, curmax, dither_index - used by rendering thread only + * ladspa_fx - same instance copied in rendering thread. Synchronising handled internally. + * + */ + +struct _fluid_synth_t +{ + fluid_rec_mutex_t mutex; /**< Lock for public API */ + int use_mutex; /**< Use mutex for all public API functions? */ + int public_api_count; /**< How many times the mutex is currently locked */ + + fluid_settings_t *settings; /**< the synthesizer settings */ + int device_id; /**< Device ID used for SYSEX messages */ + int polyphony; /**< Maximum polyphony */ + int with_reverb; /**< Should the synth use the built-in reverb unit? */ + int with_chorus; /**< Should the synth use the built-in chorus unit? */ + int verbose; /**< Turn verbose mode on? */ + double sample_rate; /**< The sample rate */ + int midi_channels; /**< the number of MIDI channels (>= 16) */ + int bank_select; /**< the style of Bank Select MIDI messages */ + int audio_channels; /**< the number of audio channels (1 channel=left+right) */ + int audio_groups; /**< the number of (stereo) 'sub'groups from the synth. + Typically equal to audio_channels. */ + int effects_channels; /**< the number of effects channels (>= 2) */ + int effects_groups; /**< the number of effects units (>= 1) */ + int state; /**< the synthesizer state */ + fluid_atomic_uint_t ticks_since_start; /**< the number of audio samples since the start */ + unsigned int start; /**< the start in msec, as returned by system clock */ + fluid_overflow_prio_t overflow; /**< parameters for overflow priority (aka voice-stealing) */ + + fluid_list_t *loaders; /**< the SoundFont loaders */ + fluid_list_t *sfont; /**< List of fluid_sfont_info_t for each loaded SoundFont (remains until SoundFont is unloaded) */ + int sfont_id; /**< Incrementing ID assigned to each loaded SoundFont */ + fluid_list_t *fonts_to_be_unloaded; /**< list of timers that try to unload a soundfont */ + + float gain; /**< master gain */ + fluid_channel_t **channel; /**< the channels */ + int nvoice; /**< the length of the synthesis process array (max polyphony allowed) */ + fluid_voice_t **voice; /**< the synthesis voices */ + int active_voice_count; /**< count of active voices */ + unsigned int noteid; /**< the id is incremented for every new note. it's used for noteoff's */ + unsigned int storeid; + int fromkey_portamento; /**< fromkey portamento */ + fluid_rvoice_eventhandler_t *eventhandler; + + /**< Shadow of reverb parameter: roomsize, damping, width, level */ + double reverb_param[FLUID_REVERB_PARAM_LAST]; + + /**< Shadow of chorus parameter: chorus number, level, speed, depth, type */ + double chorus_param[FLUID_CHORUS_PARAM_LAST]; + + int cur; /**< the current sample in the audio buffers to be output */ + int curmax; /**< current amount of samples present in the audio buffers */ + int dither_index; /**< current index in random dither value buffer: fluid_synth_(write_s16|dither_s16) */ + + fluid_atomic_float_t cpu_load; /**< CPU load in percent (CPU time required / audio synthesized time * 100) */ + + fluid_tuning_t ***tuning; /**< 128 banks of 128 programs for the tunings */ + fluid_private_t tuning_iter; /**< Tuning iterators per each thread */ + + fluid_sample_timer_t *sample_timers; /**< List of timers triggered before a block is processed */ + unsigned int min_note_length_ticks; /**< If note-offs are triggered just after a note-on, they will be delayed */ + + int cores; /**< Number of CPU cores (1 by default) */ + + fluid_mod_t *default_mod; /**< the (dynamic) list of default modulators */ + + fluid_ladspa_fx_t *ladspa_fx; /**< Effects unit for LADSPA support */ + enum fluid_iir_filter_type custom_filter_type; /**< filter type of the user-defined filter currently used for all voices */ + enum fluid_iir_filter_flags custom_filter_flags; /**< filter type of the user-defined filter currently used for all voices */ +}; + +/** + * Type definition of the synthesizer's audio callback function. + * @param synth FluidSynth instance + * @param len Count of audio frames to synthesize + * @param out1 Array to store left channel of audio to + * @param loff Offset index in 'out1' for first sample + * @param lincr Increment between samples stored to 'out1' + * @param out2 Array to store right channel of audio to + * @param roff Offset index in 'out2' for first sample + * @param rincr Increment between samples stored to 'out2' + */ +typedef int (*fluid_audio_callback_t)(fluid_synth_t *synth, int len, + void *out1, int loff, int lincr, + void *out2, int roff, int rincr); + +typedef int (*fluid_audio_channels_callback_t)(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[]); + +int +fluid_synth_write_float_channels_LOCAL(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[], + int (*block_render_func)(fluid_synth_t *, int)); + +int +fluid_synth_write_s16_channels(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[]); +int +fluid_synth_write_float_channels(fluid_synth_t *synth, int len, + int channels_count, + void *channels_out[], int channels_off[], + int channels_incr[]); + +fluid_preset_t *fluid_synth_find_preset(fluid_synth_t *synth, + int banknum, + int prognum); +void fluid_synth_sfont_unref(fluid_synth_t *synth, fluid_sfont_t *sfont); + +void fluid_synth_dither_s16(int *dither_index, int len, const float *lin, const float *rin, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr); + +int fluid_synth_reset_reverb(fluid_synth_t *synth); +int fluid_synth_set_reverb_preset(fluid_synth_t *synth, unsigned int num); +int fluid_synth_reverb_set_param(fluid_synth_t *synth, int fx_group, + int param, + double value); +int fluid_synth_set_reverb_full(fluid_synth_t *synth, int fx_group, int set, + const double values[]); + +int fluid_synth_reset_chorus(fluid_synth_t *synth); +int fluid_synth_chorus_set_param(fluid_synth_t *synth, int fx_group, + int param, double value); +int fluid_synth_set_chorus_full(fluid_synth_t *synth, int fx_group, int set, + const double values[]); + +fluid_sample_timer_t *new_fluid_sample_timer(fluid_synth_t *synth, fluid_timer_callback_t callback, void *data); +void delete_fluid_sample_timer(fluid_synth_t *synth, fluid_sample_timer_t *timer); +void fluid_sample_timer_reset(fluid_synth_t *synth, fluid_sample_timer_t *timer); + +void fluid_synth_process_event_queue(fluid_synth_t *synth); + +int +fluid_synth_process_LOCAL(fluid_synth_t *synth, int len, int nfx, float *fx[], + int nout, float *out[], int (*block_render_func)(fluid_synth_t *, int)); +int +fluid_synth_write_float_LOCAL(fluid_synth_t *synth, int len, + void *lout, int loff, int lincr, + void *rout, int roff, int rincr, + int (*block_render_func)(fluid_synth_t *, int)); +/* + * misc + */ +void fluid_synth_settings(fluid_settings_t *settings); +void fluid_synth_set_sample_rate_immediately(fluid_synth_t *synth, float sample_rate); + + +/* extern declared in fluid_synth_monopoly.c */ + +int fluid_synth_noteon_mono_staccato(fluid_synth_t *synth, int chan, int key, int vel); +int fluid_synth_noteon_mono_LOCAL(fluid_synth_t *synth, int chan, int key, int vel); +int fluid_synth_noteoff_mono_LOCAL(fluid_synth_t *synth, int chan, int key); +int fluid_synth_noteon_monopoly_legato(fluid_synth_t *synth, int chan, int fromkey, int tokey, int vel); +int fluid_synth_noteoff_monopoly(fluid_synth_t *synth, int chan, int key, char Mono); + +fluid_voice_t * +fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int chan, int key, int vel, fluid_zone_range_t *zone_range); + +void fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t *synth, int chan, int key); +#endif /* _FLUID_SYNTH_H */ diff --git a/libs/fluidsynth/src/synth/fluid_synth_monopoly.c b/libs/fluidsynth/src/synth/fluid_synth_monopoly.c new file mode 100644 index 00000000000..d1de13196ec --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_synth_monopoly.c @@ -0,0 +1,722 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_synth.h" +#include "fluid_chan.h" +#include "fluid_defsfont.h" + + +/****************************************************************************** + The legato detector is composed as this, + variables: + - monolist: monophonic list variable. + - prev_note: to store the most recent note before adding on noteon or before + removing on noteoff. + - FLUID_CHANNEL_LEGATO_PLAYING: legato/staccato state bit that informs on + legato or staccato playing. + functions: + - fluid_channel_add_monolist(), for inserting a new note. + - fluid_channel_search_monolist(), for searching the position of a note + into the list. + - fluid_channel_remove_monolist(), for removing a note from the list. + + The monophonic list + +------------------------------------------------+ + | +----+ +----+ +----+ +----+ | + | |note| |note| |note| |note| | + +--->|vel |-->|vel |-->....-->|vel |-->|vel |----+ + +----+ +----+ +----+ +----+ + /|\ /|\ + | | + i_first i_last + + The list allows an easy automatic detection of a legato passage when it is + played on a MIDI keyboard input device. + It is useful also when the input device is an ewi (electronic wind instrument) + or evi (electronic valve instrument) and these instruments are unable to send + MIDI CC legato on/off. + + The list memorizes the notes in playing order. + - (a) On noteOn n2, if a previous note n1 exists, there is a legato + detection with n1 (with or without portamento from n1 to n2 See note below). + - (b) On noteOff of the running note n2, if a previous note n1 exists, + there is a legato detection from n2 to n1, allowing fast trills playing + (with or without portamento from n2 to n1. See note below). + + Notes in the list are inserted to the end of the list that works like a + circular buffer.The features are: + + 1) It is always possible to play an infinite legato passage in + direct order (n1_On,n2_On,n3_On,....). + + 2) Playing legato in the reverse order (n10_Off, n9_Off,,...) helps in + fast trills playing as the list memorizes 10 most recent notes. + + 3) Playing an infinite lagato passage in ascendant or descendant order, + without playing trills is always possible using the usual way like this: + First we begin with an ascendant passage, + n1On, (n2On,n1Off), (n3On,n2Off) , (n4On,n3Off), then + we continue with a descendant passage + (n3On,n4off), (n2On,n3off), (n1On,n2off), n1Off...and so on + + Each MIDI channel have a legato detector. + + Note: + Portamento is a feature independent of the legato detector. So + portamento isn't part of the lagato detector. However portamento + (when enabled) is triggered at noteOn (like legato). Like in legato + situation it is usual to have a portamento from a note 'fromkey' to another + note 'tokey'. Portamento fromkey note choice is determined at noteOn by + fluid_synth_get_fromkey_portamento_legato() (see below). + + More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). +******************************************************************************/ + + +/***************************************************************************** + Portamento related functions in Poly or Mono mode +******************************************************************************/ + +/** + * fluid_synth_get_fromkey_portamento_legato returns two information: + * - fromkey note for portamento. + * - fromkey note for legato. + * +-----> fromkey_portamento + * ______|________ + * portamento modes >------->| | + * | get_fromkey | + * Porta.on/off >------------------------->|_______________| + * (PTC) | + * +-----> fromkey_legato + * + * The functions is intended to be call on noteOn mono + * see fluid_synth_noteon_mono_staccato(), fluid_synth_noteon_monopoly_legato() + * ------- + * 1)The function determines if a portamento must occur on next noteOn. + * The value returned is 'fromkey portamento' which is the pitchstart key + * of a portamento, as function of PTC or (default_fromkey, prev_note) both + * if Portamento On. By order of precedence the result is: + * 1.1) PTC have precedence over Portamento On. + * If CC PTC has been received, its value supersedes and any + * portamento pedal On, default_fromkey,prev_note or portamento mode. + * 1.2) Otherwise ,when Portamento On the function takes the following value: + * - default_fromkey if valid + * - otherwise prev_note(prev_note is the note prior the most recent + * note played). + * Then portamento mode is applied to validate the value chosen. + * Where portamento mode is: + * - each note, a portamento occurs on each note. + * - legato only, portamento only on notes played legato. + * - staccato only, portamento only on notes played staccato. + * 1.3) Otherwise, portamento is off,INVALID_NOTE is returned (portamento is disabled). + * ------ + * 2)The function determines if a legato playing must occur on next noteOn. + * 'fromkey legato note' is returned as a function of default_fromkey, PTC, + * current mono/poly mode,actual 'staccato/legato' playing state and prev_note. + * By order of precedence the result is: + * 2.1) If valid, default_fromkey have precedence over any others values. + * 2.2) Otherwise if CC PTC has been received its value is returned. + * 2.3) Otherwise fromkey legato is determined from the mono/poly mode, + * the actual 'staccato/legato' playing state (FLUID_CHANNEL_LEGATO_PLAYING) and prev_note + * as this: + * - in (poly/Mono) staccato , INVALID_NOTE is returned. + * - in poly legato , actually we don't want playing legato. So + * INVALID_NOTE is returned. + * - in mono legato , prev_note is returned. + * + * On input + * @param chan fluid_channel_t. + * @param defaultFromkey, the default 'fromkey portamento' note or 'fromkey legato' + * note (see description above). + * + * @return + * 1)'fromkey portamento' is returned in fluid_synth_t.fromkey_portamento. + * If valid,it means that portamento is enabled . + * + * 2)The 'fromkey legato' note is returned. + * + * Notes about usage: + * The function is intended to be called when the following event occurs: + * - On noteOn (Poly or Mono) after insertion in the monophonic list. + * - On noteOff(mono legato playing). In this case, default_fromkey must be valid. + * + * Typical calling usage: + * - In poly, default_fromkey must be INVALID_NOTE. + * - In mono staccato playing,default_fromkey must be INVALID_NOTE. + * - In mono legato playing,default_fromkey must be valid. + */ +static char fluid_synth_get_fromkey_portamento_legato(fluid_channel_t *chan, + int default_fromkey) +{ + unsigned char ptc = fluid_channel_get_cc(chan, PORTAMENTO_CTRL); + + if(fluid_channel_is_valid_note(ptc)) + { + /* CC PTC has been received */ + fluid_channel_clear_portamento(chan); /* clears the CC PTC receive */ + chan->synth->fromkey_portamento = ptc;/* returns fromkey portamento */ + + /* returns fromkey legato */ + if(!fluid_channel_is_valid_note(default_fromkey)) + { + default_fromkey = ptc; + } + } + else + { + /* determines and returns fromkey portamento */ + unsigned char fromkey_portamento = INVALID_NOTE; + + if(fluid_channel_portamento(chan)) + { + /* Portamento when Portamento pedal is On */ + /* 'fromkey portamento'is determined from the portamento mode + and the most recent note played (prev_note)*/ + enum fluid_channel_portamento_mode portamentomode = chan->portamentomode; + + if(fluid_channel_is_valid_note(default_fromkey)) + { + fromkey_portamento = default_fromkey; /* on each note */ + } + else + { + fromkey_portamento = fluid_channel_prev_note(chan); /* on each note */ + } + + if(portamentomode == FLUID_CHANNEL_PORTAMENTO_MODE_LEGATO_ONLY) + { + /* Mode portamento:legato only */ + if(!(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) + { + fromkey_portamento = INVALID_NOTE; + } + } + else if(portamentomode == FLUID_CHANNEL_PORTAMENTO_MODE_STACCATO_ONLY) + { + /* Mode portamento:staccato only */ + if(chan->mode & FLUID_CHANNEL_LEGATO_PLAYING) + { + fromkey_portamento = INVALID_NOTE; + } + } + + /* else Mode portamento: on each note (staccato/legato) */ + } + + /* Returns fromkey portamento */ + chan->synth->fromkey_portamento = fromkey_portamento; + + /* Determines and returns fromkey legato */ + if(!fluid_channel_is_valid_note(default_fromkey)) + { + /* in staccato (poly/Mono) returns INVALID_NOTE */ + /* In mono mode legato playing returns the note prior most + recent note played */ + if(fluid_channel_is_playing_mono(chan) && (chan->mode & FLUID_CHANNEL_LEGATO_PLAYING)) + { + default_fromkey = fluid_channel_prev_note(chan); /* note prior last note */ + } + + /* In poly mode legato playing, actually we don't want playing legato. + So returns INVALID_NOTE */ + } + } + + return default_fromkey; /* Returns legato fromkey */ +} + +/***************************************************************************** + noteon - noteoff functions in Mono mode +******************************************************************************/ +/* + * noteon - noteoff on a channel in "monophonic playing". + * + * A channel needs to be played monophonic if this channel has been set in + * monophonic mode by basic channel API.(see fluid_synth_polymono.c). + * A channel needs also to be played monophonic if it has been set in + * polyphonic mode and legato pedal is On during the playing. + * When a channel is in "monophonic playing" state, only one note at a time can be + * played in a staccato or legato manner (with or without portamento). + * More information in FluidPolyMono-0004.pdf chapter 4 (Appendices). + * _______________ + * ________________ | noteon | + * | legato detector| O-->| mono_staccato |--*-> preset_noteon + * noteon_mono ->| (add_monolist) |--O-- |_______________| | (with or without) + * LOCAL |________________| O /|\ | (portamento) + * /|\ set_onenote | | fromkey | + * | | | portamento| + * noteOn poly >---*------------------* | | + * | | | + * | _____ |________ | + * portamento modes >--- | ->| | | + * | | get_fromkey | | + * Porta.on/off >--------------------- | ->|_______________| | + * (PTC) | | | + * | fromkey | fromkey | + * | legato | portamento| + * | _____|/_______ | + * *-->| noteon |--/ + * | | monopoly | + * | | legato |----> voices + * legato modes >------- | ->|_______________| triggering + * | (with or without) + * | (portamento) + * | + * | + * noteOff poly >---*----------------- | ---------+ + * | clear | | + * _|/_____________ | | + * | legato detector | O | + * noteoff_mono->|(search_monolist)|-O-- _____|/_______ + * LOCAL |(remove_monolist)| O-->| noteoff | + * |_________________| | monopoly |----> noteoff + * Sust.on/off >------------------------->|_______________| + * Sost.on/off +------------------------------------------------------------------------------*/ + +/** + * Plays a noteon event for a Synth instance in "monophonic playing" state. + * Please see the description above about "monophonic playing". + * _______________ + * ________________ | noteon | + * | legato detector| O-->| mono_staccato |--->preset_noteon + * noteon_mono ->| (add_monolist) |--O-- |_______________| + * LOCAL |________________| O + * | + * | + * | + * | + * | + * | + * | + * | + * | + * | _______________ + * | | noteon | + * +-->| monopoly | + * | legato |---> voices + * |_______________| triggering + * + * The function uses the legato detector (see above) to determine if the note must + * be played staccato or legato. + * + * @param synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param key MIDI note number (0-127). + * @param vel MIDI velocity (0-127). + * @return FLUID_OK on success, FLUID_FAILED otherwise. + */ +int fluid_synth_noteon_mono_LOCAL(fluid_synth_t *synth, int chan, + int key, int vel) +{ + fluid_channel_t *channel = synth->channel[chan]; + + /* Adds the note into the monophonic list */ + fluid_channel_add_monolist(channel, key, vel, 0); + + /* in Breath Sync mode, the noteon triggering is postponed + until the musician starts blowing in the breath controller */ + if(!(channel->mode & FLUID_CHANNEL_BREATH_SYNC) || + fluid_channel_breath_msb(channel)) + { + /* legato/staccato playing detection */ + if(channel->mode & FLUID_CHANNEL_LEGATO_PLAYING) + { + /* legato playing */ + /* legato from prev_note to key */ + /* the voices from prev_note key number are to be used to play key number */ + /* fromkey must be valid */ + return fluid_synth_noteon_monopoly_legato(synth, chan, + fluid_channel_prev_note(channel), key, vel); + } + else + { + /* staccato playing */ + return fluid_synth_noteon_mono_staccato(synth, chan, key, vel); + } + } + else + { + return FLUID_OK; + } +} + +/** + * Plays a noteoff event for a Synth instance in "monophonic playing" state. + * Please see the description above about "monophonic playing" + * + * _______________ + * | noteon | + * +-->| monopoly | + * | | legato |----> voices + * | |_______________| triggering + * | (with or without) + * | (portamento) + * | + * | + * | + * | + * | + * | + * _________________ | + * | legato detector | O + * noteoff_mono->|(search_monolist)|-O-- _______________ + * LOCAL |(remove_monolist)| O-->| noteoff | + * |_________________| | monopoly |----> noteoff + * |_______________| + * + * The function uses the legato detector (see above) to determine if the noteoff must + * be played staccato or legato. + * + * @param synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param key MIDI note number (0-127). + * @return FLUID_OK on success, FLUID_FAILED otherwise. + */ +int fluid_synth_noteoff_mono_LOCAL(fluid_synth_t *synth, int chan, int key) +{ + int status; + int i, i_prev; + fluid_channel_t *channel = synth->channel[chan]; + /* searching the note in the monophonic list */ + i = fluid_channel_search_monolist(channel, key, &i_prev); + + if(i >= 0) + { + /* the note is in the monophonic list */ + /* Removes the note from the monophonic list */ + fluid_channel_remove_monolist(channel, i, &i_prev); + + /* in Breath Sync mode, the noteoff triggering is done + if the musician is blowing in the breath controller */ + if(!(channel->mode & FLUID_CHANNEL_BREATH_SYNC) || + fluid_channel_breath_msb(channel)) + { + /* legato playing detection */ + if(channel->mode & FLUID_CHANNEL_LEGATO_PLAYING) + { + /* the list contains others notes */ + if(i_prev >= 0) + { + /* legato playing detection on noteoff */ + /* legato from key to i_prev key */ + /* the voices from key number are to be used to + play i_prev key number. */ + status = fluid_synth_noteon_monopoly_legato(synth, chan, + key, channel->monolist[i_prev].note, + channel->monolist[i_prev].vel); + } + /* else the note doesn't need to be played off */ + else + { + status = FLUID_OK; + } + } + else + { + /* the monophonic list is empty */ + /* plays the monophonic note noteoff and eventually held + by sustain/sostenuto */ + status = fluid_synth_noteoff_monopoly(synth, chan, key, 1); + } + } + else + { + status = FLUID_OK; + } + } + else + { + /* the note is not found in the list so the note was + played On when the channel was in polyphonic playing */ + /* plays the noteoff as for polyphonic */ + status = fluid_synth_noteoff_monopoly(synth, chan, key, 0); + } + + return status; +} + +/*---------------------------------------------------------------------------- + staccato playing +-----------------------------------------------------------------------------*/ +/** + * Plays noteon for a monophonic note in staccato manner. + * Please see the description above about "monophonic playing". + * _______________ + * | noteon | + * noteon_mono >------------------------>| mono_staccato |----> preset_noteon + * |_______________| (with or without) + * LOCAL /|\ (portamento) + * | fromkey + * | portamento + * | + * | + * ______|________ + * portamento modes >----->| | + * | get_fromkey | + * Porta.on/off >----------------------->|_______________| + * Portamento + * (PTC) + * + * We are in staccato situation (where no previous note have been depressed). + * Before the note been passed to fluid_preset_noteon(), the function must determine + * the from_key_portamento parameter used by fluid_preset_noteon(). + * + * from_key_portamento is returned by fluid_synth_get_fromkey_portamento_legato() function. + * fromkey_portamento is set to valid/invalid key value depending of the portamento + * modes (see portamento mode API) , CC portamento On/Off , and CC portamento control + * (PTC). + * + * @param synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param key MIDI note number (0-127). + * @param vel MIDI velocity (0-127). + * @return FLUID_OK on success, FLUID_FAILED otherwise. + */ +int +fluid_synth_noteon_mono_staccato(fluid_synth_t *synth, int chan, int key, int vel) +{ + fluid_channel_t *channel = synth->channel[chan]; + + /* Before playing a new note, if a previous monophonic note is currently + sustained it needs to be released */ + fluid_synth_release_voice_on_same_note_LOCAL(synth, chan, channel->key_mono_sustained); + /* Get possible 'fromkey portamento' */ + fluid_synth_get_fromkey_portamento_legato(channel, INVALID_NOTE); + /* The note needs to be played by voices allocation */ + return fluid_preset_noteon(channel->preset, synth, chan, key, vel); +} + +/** + * Plays noteoff for a polyphonic or monophonic note + * Please see the description above about "monophonic playing". + * + * + * noteOff poly >---------------------------------+ + * | + * | + * | + * noteoff_mono _____|/_______ + * LOCAL >------------------------->| noteoff | + * | monopoly |----> noteoff + * Sust.on/off >------------------------->|_______________| + * Sost.on/off + * + * The function has the same behaviour when the noteoff is poly of mono, except + * that for mono noteoff, if any pedal (sustain or sostenuto ) is depressed, the + * key is memorized. This is necessary when the next mono note will be played + * staccato, as any current mono note currently sustained will need to be released + * (see fluid_synth_noteon_mono_staccato()). + * Note also that for a monophonic legato passage, the function is called only when + * the last noteoff of the passage occurs. That means that if sustain or sostenuto + * is depressed, only the last note of a legato passage will be sustained. + * + * @param synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param key MIDI note number (0-127). + * @param Mono, 1 noteoff on monophonic note. + * 0 noteoff on polyphonic note. + * @return FLUID_OK on success, FLUID_FAILED otherwise. + * + * Note: On return, on monophonic, possible sustained note is memorized in + * key_mono_sustained. Memorization is done here on noteOff. + */ +int fluid_synth_noteoff_monopoly(fluid_synth_t *synth, int chan, int key, + char Mono) +{ + int status = FLUID_FAILED; + fluid_voice_t *voice; + int i; + fluid_channel_t *channel = synth->channel[chan]; + + /* Key_sustained is prepared to return no note sustained (INVALID_NOTE) */ + if(Mono) + { + channel->key_mono_sustained = INVALID_NOTE; /* no mono note sustained */ + } + + /* noteoff for all voices with same chan and same key */ + for(i = 0; i < synth->polyphony; i++) + { + voice = synth->voice[i]; + + if(fluid_voice_is_on(voice) && + fluid_voice_get_channel(voice) == chan && + fluid_voice_get_key(voice) == key) + { + if(synth->verbose) + { + int used_voices = 0; + int k; + + for(k = 0; k < synth->polyphony; k++) + { + if(!_AVAILABLE(synth->voice[k])) + { + used_voices++; + } + } + + FLUID_LOG(FLUID_INFO, "noteoff\t%d\t%d\t%d\t%05d\t%.3f\t%d", + fluid_voice_get_channel(voice), fluid_voice_get_key(voice), 0, + fluid_voice_get_id(voice), + (fluid_curtime() - synth->start) / 1000.0f, + used_voices); + } /* if verbose */ + + fluid_voice_noteoff(voice); + + /* noteoff on monophonic note */ + /* Key memorization if the note is sustained */ + if(Mono && + (fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice))) + { + channel->key_mono_sustained = key; + } + + status = FLUID_OK; + } /* if voice on */ + } /* for all voices */ + + return status; +} + +/*---------------------------------------------------------------------------- + legato playing +-----------------------------------------------------------------------------*/ +/** + * Plays noteon for a monophonic note played legato. + * Please see the description above about "monophonic playing". + * + * + * _______________ + * portamento modes >----->| | + * | get_fromkey | + * Porta.on/off >----------------------->|_______________| + * Portamento | + * (PTC) | +-->preset_noteon + * fromkey | fromkey | (with or without) + * legato | portamento| (portamento) + * _____|/_______ | + * | noteon |--+ + * noteon_mono >------------------------>| monopoly | + * LOCAL | legato |----->voices + * |_______________| triggering + * /|\ (with or without) + * | (portamento) + * legato modes >-----------------+ + * + * We are in legato situation (where a previous note has been depressed). + * The function must determine the from_key_portamento and from_key_legato parameters + * used respectively by fluid_preset_noteon() function and voices triggering functions. + * + * from_key_portamento and from_key_legato are returned by + * fluid_synth_get_fromkey_portamento_legato() function. + * fromkey_portamento is set to valid/invalid key value depending of the portamento + * modes (see portamento mode API), CC portamento On/Off, and CC portamento control + * (PTC). + * Then, depending of the legato modes (see legato mode API), the function will call + * the appropriate triggering functions. + * @param synth instance. + * @param chan MIDI channel number (0 to MIDI channel count - 1). + * @param fromkey MIDI note number (0-127). + * @param tokey MIDI note number (0-127). + * @param vel MIDI velocity (0-127). + * @return FLUID_OK on success, FLUID_FAILED otherwise. + * + * Note: The voices with key 'fromkey' are to be used to play key 'tokey'. + * The function is able to play legato through Preset Zone(s) (PZ) and + * Instrument Zone(s) (IZ) as far as possible. + * When key tokey is outside the current Instrument Zone, Preset Zone, + * current 'fromkey' voices are released. If necessary new voices + * are restarted when tokey enters inside new Instrument(s) Zones,Preset Zone(s). + * More information in FluidPolyMono-0004.pdf chapter 4.7 (Appendices). + */ +int fluid_synth_noteon_monopoly_legato(fluid_synth_t *synth, int chan, + int fromkey, int tokey, int vel) +{ + fluid_channel_t *channel = synth->channel[chan]; + enum fluid_channel_legato_mode legatomode = channel->legatomode; + fluid_voice_t *voice; + int i ; + /* Gets possible 'fromkey portamento' and possible 'fromkey legato' note */ + fromkey = fluid_synth_get_fromkey_portamento_legato(channel, fromkey); + + if(fluid_channel_is_valid_note(fromkey)) + { + for(i = 0; i < synth->polyphony; i++) + { + /* searching fromkey voices: only those who don't have 'note off' */ + voice = synth->voice[i]; + + if(fluid_voice_is_on(voice) && + fluid_voice_get_channel(voice) == chan && + fluid_voice_get_key(voice) == fromkey) + { + fluid_zone_range_t *zone_range = voice->zone_range; + + /* Ignores voice when there is no instrument zone (i.e no zone_range). Otherwise + checks if tokey is inside the range of the running voice */ + if(zone_range && fluid_zone_inside_range(zone_range, tokey, vel)) + { + switch(legatomode) + { + case FLUID_CHANNEL_LEGATO_MODE_RETRIGGER: /* mode 0 */ + fluid_voice_release(voice); /* normal release */ + break; + + case FLUID_CHANNEL_LEGATO_MODE_MULTI_RETRIGGER: /* mode 1 */ + /* Skip in attack section */ + fluid_voice_update_multi_retrigger_attack(voice, tokey, vel); + + /* Starts portamento if enabled */ + if(fluid_channel_is_valid_note(synth->fromkey_portamento)) + { + /* Sends portamento parameters to the voice dsp */ + fluid_voice_update_portamento(voice, + synth->fromkey_portamento, + tokey); + } + + /* The voice is now used to play tokey in legato manner */ + /* Marks this Instrument Zone to be ignored during next + fluid_preset_noteon() */ + zone_range->ignore = TRUE; + break; + + default: /* Invalid mode: this should never happen */ + FLUID_LOG(FLUID_WARN, "Failed to execute legato mode: %d", + legatomode); + return FLUID_FAILED; + } + } + else + { + /* tokey note is outside the voice range, so the voice is released */ + fluid_voice_release(voice); + } + } + } + } + + /* May be,tokey will enter in new others Insrument Zone(s),Preset Zone(s), in + this case it needs to be played by voices allocation */ + return fluid_preset_noteon(channel->preset, synth, chan, tokey, vel); +} diff --git a/libs/fluidsynth/src/synth/fluid_tuning.c b/libs/fluidsynth/src/synth/fluid_tuning.c new file mode 100644 index 00000000000..0df248b7bfb --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_tuning.c @@ -0,0 +1,190 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#include "fluid_tuning.h" +#include "fluid_sys.h" + + +fluid_tuning_t *new_fluid_tuning(const char *name, int bank, int prog) +{ + fluid_tuning_t *tuning; + int i; + + tuning = FLUID_NEW(fluid_tuning_t); + + if(tuning == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(tuning, 0, sizeof(fluid_tuning_t)); + + if(fluid_tuning_set_name(tuning, name) != FLUID_OK) + { + delete_fluid_tuning(tuning); + return NULL; + } + + tuning->bank = bank; + tuning->prog = prog; + + for(i = 0; i < 128; i++) + { + tuning->pitch[i] = i * 100.0; + } + + fluid_atomic_int_set(&tuning->refcount, 1); /* Start with a refcount of 1 */ + + return tuning; +} + +/* Duplicate a tuning */ +fluid_tuning_t * +fluid_tuning_duplicate(fluid_tuning_t *tuning) +{ + fluid_tuning_t *new_tuning; + int i; + + new_tuning = FLUID_NEW(fluid_tuning_t); + + if(!new_tuning) + { + FLUID_LOG(FLUID_PANIC, "Out of memory"); + return NULL; + } + + FLUID_MEMSET(new_tuning, 0, sizeof(fluid_tuning_t)); + + if(fluid_tuning_set_name(new_tuning, tuning->name) != FLUID_OK) + { + delete_fluid_tuning(new_tuning); + return NULL; + } + + new_tuning->bank = tuning->bank; + new_tuning->prog = tuning->prog; + + for(i = 0; i < 128; i++) + { + new_tuning->pitch[i] = tuning->pitch[i]; + } + + fluid_atomic_int_set(&new_tuning->refcount, 1); /* Start with a refcount of 1 */ + + return new_tuning; +} + +void +delete_fluid_tuning(fluid_tuning_t *tuning) +{ + fluid_return_if_fail(tuning != NULL); + + FLUID_FREE(tuning->name); + FLUID_FREE(tuning); +} + +/* Add a reference to a tuning object */ +void +fluid_tuning_ref(fluid_tuning_t *tuning) +{ + fluid_return_if_fail(tuning != NULL); + + fluid_atomic_int_inc(&tuning->refcount); +} + +/* Unref a tuning object, when it reaches 0 it is deleted, returns TRUE if deleted */ +int +fluid_tuning_unref(fluid_tuning_t *tuning, int count) +{ + fluid_return_val_if_fail(tuning != NULL, FALSE); + + /* Add and compare are separate, but that is OK, since refcount will only + * reach 0 when there are no references and therefore no possibility of + * another thread adding a reference in between */ + fluid_atomic_int_add(&tuning->refcount, -count); + + /* Delete when refcount reaches 0 */ + if(!fluid_atomic_int_get(&tuning->refcount)) + { + delete_fluid_tuning(tuning); + return TRUE; + } + else + { + return FALSE; + } +} + +int fluid_tuning_set_name(fluid_tuning_t *tuning, const char *name) +{ + if(tuning->name != NULL) + { + FLUID_FREE(tuning->name); + tuning->name = NULL; + } + + if(name != NULL) + { + tuning->name = FLUID_STRDUP(name); + + if(tuning->name == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + } + + return FLUID_OK; +} + +char *fluid_tuning_get_name(fluid_tuning_t *tuning) +{ + return tuning->name; +} + +void fluid_tuning_set_octave(fluid_tuning_t *tuning, const double *pitch_deriv) +{ + int i; + + for(i = 0; i < 128; i++) + { + tuning->pitch[i] = i * 100.0 + pitch_deriv[i % 12]; + } +} + +void fluid_tuning_set_all(fluid_tuning_t *tuning, const double *pitch) +{ + int i; + + for(i = 0; i < 128; i++) + { + tuning->pitch[i] = pitch[i]; + } +} + +void fluid_tuning_set_pitch(fluid_tuning_t *tuning, int key, double pitch) +{ + if((key >= 0) && (key < 128)) + { + tuning->pitch[key] = pitch; + } +} diff --git a/libs/fluidsynth/src/synth/fluid_tuning.h b/libs/fluidsynth/src/synth/fluid_tuning.h new file mode 100644 index 00000000000..542d2ced68f --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_tuning.h @@ -0,0 +1,69 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +/* + + More information about micro tuning can be found at: + + https://www.midi.org/about-midi/tuning.htm + https://www.midi.org/about-midi/tuning-scale.htm + https://www.midi.org/about-midi/tuning_extens.htm + +*/ + +#ifndef _FLUID_TUNING_H +#define _FLUID_TUNING_H + +#include "fluidsynth_priv.h" + +struct _fluid_tuning_t +{ + char *name; + int bank; + int prog; + double pitch[128]; /* the pitch of every key, in cents */ + fluid_atomic_int_t refcount; /* Tuning reference count */ +}; + +fluid_tuning_t *new_fluid_tuning(const char *name, int bank, int prog); +void delete_fluid_tuning(fluid_tuning_t *tuning); +fluid_tuning_t *fluid_tuning_duplicate(fluid_tuning_t *tuning); +void fluid_tuning_ref(fluid_tuning_t *tuning); +int fluid_tuning_unref(fluid_tuning_t *tuning, int count); + +int fluid_tuning_set_name(fluid_tuning_t *tuning, const char *name); +char *fluid_tuning_get_name(fluid_tuning_t *tuning); + +#define fluid_tuning_get_bank(_t) ((_t)->bank) +#define fluid_tuning_get_prog(_t) ((_t)->prog) + +void fluid_tuning_set_pitch(fluid_tuning_t *tuning, int key, double pitch); +#define fluid_tuning_get_pitch(_t, _key) ((_t)->pitch[_key]) + +void fluid_tuning_set_octave(fluid_tuning_t *tuning, const double *pitch_deriv); + +void fluid_tuning_set_all(fluid_tuning_t *tuning, const double *pitch); +#define fluid_tuning_get_all(_t) (&(_t)->pitch[0]) + + + + +#endif /* _FLUID_TUNING_H */ diff --git a/libs/fluidsynth/src/synth/fluid_voice.c b/libs/fluidsynth/src/synth/fluid_voice.c new file mode 100644 index 00000000000..f605832e049 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_voice.c @@ -0,0 +1,2052 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_sys.h" +#include "fluid_voice.h" +#include "fluid_mod.h" +#include "fluid_chan.h" +#include "fluid_conv.h" +#include "fluid_synth.h" +#include "fluid_sys.h" +#include "fluid_sfont.h" +#include "fluid_rvoice_event.h" +#include "fluid_defsfont.h" + +/* used for filter turn off optimization - if filter cutoff is above the + specified value and filter q is below the other value, turn filter off */ +#define FLUID_MAX_AUDIBLE_FILTER_FC 19000.0f +#define FLUID_MIN_AUDIBLE_FILTER_Q 1.2f + +/* min vol envelope release (to stop clicks) in SoundFont timecents */ +#define FLUID_MIN_VOLENVRELEASE -7200.0f /* ~16ms */ + + +static const int32_t INT24_MAX = (1 << (16 + 8 - 1)); + +static int fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice); +static int calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base, + int gen_key2base, int is_decay); +static fluid_real_t +fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice); + +#define UPDATE_RVOICE0(proc) \ + do { \ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ + fluid_rvoice_eventhandler_push(voice->eventhandler, proc, voice->rvoice, param); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_R1(proc, obj, rarg) \ + do { \ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ + param[0].real = rarg; \ + fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_I1(proc, obj, iarg) \ + do { \ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ + param[0].i = iarg; \ + fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_I2(proc, obj, iarg1, iarg2) \ + do { \ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ + param[0].i = iarg1; \ + param[1].i = iarg2; \ + fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_IR(proc, obj, iarg, rarg) \ + do { \ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; \ + param[0].i = iarg; \ + param[1].real = rarg; \ + fluid_rvoice_eventhandler_push(voice->eventhandler, proc, obj, param); \ + } while (0) + + +#define UPDATE_RVOICE_R1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, voice->rvoice, arg1) +#define UPDATE_RVOICE_I1(proc, arg1) UPDATE_RVOICE_GENERIC_I1(proc, voice->rvoice, arg1) + +#define UPDATE_RVOICE_BUFFERS_AMP(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, &voice->rvoice->buffers, iarg, rarg) +#define UPDATE_RVOICE_ENVLFO_R1(proc, envp, rarg) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->envlfo.envp, rarg) +#define UPDATE_RVOICE_ENVLFO_I1(proc, envp, iarg) UPDATE_RVOICE_GENERIC_I1(proc, &voice->rvoice->envlfo.envp, iarg) + +static FLUID_INLINE void +fluid_voice_update_volenv(fluid_voice_t *voice, + int enqueue, + fluid_adsr_env_section_t section, + unsigned int count, + fluid_real_t coeff, + fluid_real_t increment, + fluid_real_t min, + fluid_real_t max) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + + param[0].i = section; + param[1].i = count; + param[2].real = coeff; + param[3].real = increment; + param[4].real = min; + param[5].real = max; + + if(enqueue) + { + fluid_rvoice_eventhandler_push(voice->eventhandler, + fluid_adsr_env_set_data, + &voice->rvoice->envlfo.volenv, + param); + } + else + { + fluid_adsr_env_set_data(&voice->rvoice->envlfo.volenv, param); + } +} + +static FLUID_INLINE void +fluid_voice_update_modenv(fluid_voice_t *voice, + int enqueue, + fluid_adsr_env_section_t section, + unsigned int count, + fluid_real_t coeff, + fluid_real_t increment, + fluid_real_t min, + fluid_real_t max) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + + param[0].i = section; + param[1].i = count; + param[2].real = coeff; + param[3].real = increment; + param[4].real = min; + param[5].real = max; + + if(enqueue) + { + fluid_rvoice_eventhandler_push(voice->eventhandler, + fluid_adsr_env_set_data, + &voice->rvoice->envlfo.modenv, + param); + } + else + { + fluid_adsr_env_set_data(&voice->rvoice->envlfo.modenv, param); + } +} + +static FLUID_INLINE void fluid_voice_sample_unref(fluid_sample_t **sample) +{ + if(*sample != NULL) + { + fluid_sample_decr_ref(*sample); + *sample = NULL; + } +} + +/* + * Swaps the current rvoice with the current overflow_rvoice + */ +static void fluid_voice_swap_rvoice(fluid_voice_t *voice) +{ + fluid_rvoice_t *rtemp = voice->rvoice; + int ctemp = voice->can_access_rvoice; + voice->rvoice = voice->overflow_rvoice; + voice->can_access_rvoice = voice->can_access_overflow_rvoice; + voice->overflow_rvoice = rtemp; + voice->can_access_overflow_rvoice = ctemp; + voice->overflow_sample = voice->sample; +} + +static void fluid_voice_initialize_rvoice(fluid_voice_t *voice, fluid_real_t output_rate) +{ + fluid_rvoice_param_t param[MAX_EVENT_PARAMS]; + + FLUID_MEMSET(voice->rvoice, 0, sizeof(fluid_rvoice_t)); + + /* The 'sustain' and 'finished' segments of the volume / modulation + * envelope are constant. They are never affected by any modulator + * or generator. Therefore it is enough to initialize them once + * during the lifetime of the synth. + */ + fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN, + 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); + fluid_voice_update_volenv(voice, FALSE, FLUID_VOICE_ENVFINISHED, + 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); + fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVSUSTAIN, + 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); + fluid_voice_update_modenv(voice, FALSE, FLUID_VOICE_ENVFINISHED, + 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); + + param[0].i = FLUID_IIR_LOWPASS; + param[1].i = 0; + fluid_iir_filter_init(&voice->rvoice->resonant_filter, param); + + param[0].i = FLUID_IIR_DISABLED; + fluid_iir_filter_init(&voice->rvoice->resonant_custom_filter, param); + + param[0].real = output_rate; + fluid_rvoice_set_output_rate(voice->rvoice, param); +} + +/* + * new_fluid_voice + */ +fluid_voice_t * +new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate) +{ + fluid_voice_t *voice; + voice = FLUID_NEW(fluid_voice_t); + + if(voice == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + voice->can_access_rvoice = TRUE; + voice->can_access_overflow_rvoice = TRUE; + + voice->rvoice = FLUID_NEW(fluid_rvoice_t); + voice->overflow_rvoice = FLUID_NEW(fluid_rvoice_t); + + if(voice->rvoice == NULL || voice->overflow_rvoice == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + delete_fluid_voice(voice); + return NULL; + } + + voice->status = FLUID_VOICE_CLEAN; + voice->chan = NO_CHANNEL; + voice->key = 0; + voice->vel = 0; + voice->eventhandler = handler; + voice->channel = NULL; + voice->sample = NULL; + voice->overflow_sample = NULL; + voice->output_rate = output_rate; + + /* Initialize both the rvoice and overflow_rvoice */ + fluid_voice_initialize_rvoice(voice, output_rate); + fluid_voice_swap_rvoice(voice); + fluid_voice_initialize_rvoice(voice, output_rate); + + return voice; +} + +/* + * delete_fluid_voice + */ +void +delete_fluid_voice(fluid_voice_t *voice) +{ + fluid_return_if_fail(voice != NULL); + + if(!voice->can_access_rvoice || !voice->can_access_overflow_rvoice) + { + FLUID_LOG(FLUID_WARN, "Deleting voice %u which has locked rvoices!", voice->id); + } + + FLUID_FREE(voice->overflow_rvoice); + FLUID_FREE(voice->rvoice); + FLUID_FREE(voice); +} + +/* fluid_voice_init + * + * Initialize the synthesis process + * inst_zone, the Instrument Zone contains the sample, Keyrange,Velrange + * of the voice. + * When playing legato (n1,n2) in mono mode, n2 will use n1 voices + * as far as n2 still enters in Keyrange,Velrange of n1. + */ +int +fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample, + fluid_zone_range_t *inst_zone_range, + fluid_channel_t *channel, int key, int vel, unsigned int id, + unsigned int start_time, fluid_real_t gain) +{ + /* Note: The voice parameters will be initialized later, when the + * generators have been retrieved from the sound font. Here, only + * the 'working memory' of the voice (position in envelopes, history + * of IIR filters, position in sample etc) is initialized. */ + int i; + + if(!voice->can_access_rvoice) + { + if(voice->can_access_overflow_rvoice) + { + fluid_voice_swap_rvoice(voice); + } + else + { + FLUID_LOG(FLUID_ERR, "Internal error: Cannot access an rvoice in fluid_voice_init!"); + return FLUID_FAILED; + } + } + + /* We are now guaranteed to have access to the rvoice */ + + if(voice->sample) + { + fluid_voice_off(voice); + } + + voice->zone_range = inst_zone_range; /* Instrument zone range for legato */ + voice->id = id; + voice->chan = fluid_channel_get_num(channel); + voice->key = (unsigned char) key; + voice->vel = (unsigned char) vel; + voice->channel = channel; + voice->mod_count = 0; + voice->start_time = start_time; + voice->has_noteoff = 0; + UPDATE_RVOICE0(fluid_rvoice_reset); + + /* + We increment the reference count of the sample to indicate that this + sample is about to be owned by the rvoice. This will prevent the + unloading of the soundfont while this rvoice is playing. + */ + fluid_sample_incr_ref(sample); + fluid_rvoice_eventhandler_push_ptr(voice->eventhandler, fluid_rvoice_set_sample, voice->rvoice, sample); + voice->sample = sample; + + i = fluid_channel_get_interp_method(channel); + UPDATE_RVOICE_I1(fluid_rvoice_set_interp_method, i); + + /* Set all the generators to their default value, according to SF + * 2.01 section 8.1.3 (page 48). The value of NRPN messages are + * copied from the channel to the voice's generators. The sound font + * loader overwrites them. The generator values are later converted + * into voice parameters in + * fluid_voice_calculate_runtime_synthesis_parameters. */ + fluid_gen_init(&voice->gen[0], channel); + UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, _SAMPLEMODE(voice)); + + voice->synth_gain = gain; + + /* avoid division by zero later*/ + if(voice->synth_gain < 0.0000001f) + { + voice->synth_gain = 0.0000001f; + } + + UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, voice->synth_gain); + + /* Set up buffer mapping, should be done more flexible in the future. */ + i = 2 * channel->synth->audio_groups; + i += (voice->chan % channel->synth->effects_groups) * channel->synth->effects_channels; + UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 2, i + SYNTH_REVERB_CHANNEL); + UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 3, i + SYNTH_CHORUS_CHANNEL); + + i = 2 * (voice->chan % channel->synth->audio_groups); + UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 0, i); + UPDATE_RVOICE_GENERIC_I2(fluid_rvoice_buffers_set_mapping, &voice->rvoice->buffers, 1, i + 1); + + return FLUID_OK; +} + + +/** + * Update sample rate. + * + * @note If the voice is active, it will be turned off. + */ +void +fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value) +{ + if(fluid_voice_is_playing(voice)) + { + fluid_voice_off(voice); + } + + voice->output_rate = value; + UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->rvoice, value); + UPDATE_RVOICE_GENERIC_R1(fluid_rvoice_set_output_rate, voice->overflow_rvoice, value); +} + + +/** + * Set the value of a generator. + * + * @param voice Voice instance + * @param i Generator ID (#fluid_gen_type) + * @param val Generator value + */ +void +fluid_voice_gen_set(fluid_voice_t *voice, int i, float val) +{ + voice->gen[i].val = val; + voice->gen[i].flags = GEN_SET; + + if(i == GEN_SAMPLEMODE) + { + UPDATE_RVOICE_I1(fluid_rvoice_set_samplemode, (int) 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 + */ +void +fluid_voice_gen_incr(fluid_voice_t *voice, int i, float val) +{ + voice->gen[i].val += val; + voice->gen[i].flags = GEN_SET; +} + +/** + * Get the value of a generator. + * + * @param voice Voice instance + * @param gen Generator ID (#fluid_gen_type) + * @return Current generator value + */ +float +fluid_voice_gen_get(fluid_voice_t *voice, int gen) +{ + return voice->gen[gen].val; +} + +fluid_real_t fluid_voice_gen_value(const fluid_voice_t *voice, int num) +{ + return (fluid_real_t)(voice->gen[num].val + voice->gen[num].mod + voice->gen[num].nrpn); +} + +/* + * fluid_voice_start + */ +void fluid_voice_start(fluid_voice_t *voice) +{ + /* The maximum volume of the loop is calculated and cached once for each + * sample with its nominal loop settings. This happens, when the sample is used + * for the first time.*/ + + fluid_voice_calculate_runtime_synthesis_parameters(voice); + +#ifdef WITH_PROFILING + voice->ref = fluid_profile_ref(); +#endif + + voice->status = FLUID_VOICE_ON; + + /* Increment voice count */ + voice->channel->synth->active_voice_count++; +} + +/** + * Calculate the amplitude of a voice. + * + * @param gain The gain value in the range [0.0 ; 1.0] + * @return An amplitude used by rvoice_mixer's buffers + */ +static FLUID_INLINE fluid_real_t +fluid_voice_calculate_gain_amplitude(const fluid_voice_t *voice, fluid_real_t gain) +{ + /* we use 24bit samples in fluid_rvoice_dsp. in order to normalize float + * samples to [0.0;1.0] divide samples by the max. value of an int24 and + * amplify them with the gain */ + return gain * voice->synth_gain / (INT24_MAX * 1.0f); +} + +/* Useful to return the nominal pitch of a key */ +/* The nominal pitch is dependent of voice->root_pitch,tuning, and + GEN_SCALETUNE generator. + This is useful to set the value of GEN_PITCH generator on noteOn. + This is useful to get the beginning/ending pitch for portamento. +*/ +fluid_real_t fluid_voice_calculate_pitch(fluid_voice_t *voice, int key) +{ + fluid_tuning_t *tuning; + fluid_real_t x, pitch; + + /* Now the nominal pitch of the key is returned. + * Note about SCALETUNE: SF2.01 8.1.3 says, that this generator is a + * non-realtime parameter. So we don't allow modulation (as opposed + * to fluid_voice_gen_value(voice, GEN_SCALETUNE) When the scale tuning is varied, + * one key remains fixed. Here C3 (MIDI number 60) is used. + */ + if(fluid_channel_has_tuning(voice->channel)) + { + tuning = fluid_channel_get_tuning(voice->channel); + x = fluid_tuning_get_pitch(tuning, (int)(voice->root_pitch / 100.0f)); + pitch = voice->gen[GEN_SCALETUNE].val / 100.0f * + (fluid_tuning_get_pitch(tuning, key) - x) + x; + } + else + { + pitch = voice->gen[GEN_SCALETUNE].val + * (key - voice->root_pitch / 100.0f) + voice->root_pitch; + } + + return pitch; +} + +void +fluid_voice_calculate_gen_pitch(fluid_voice_t *voice) +{ + voice->gen[GEN_PITCH].val = fluid_voice_calculate_pitch(voice, fluid_voice_get_actual_key(voice)); +} + +/* + * fluid_voice_calculate_runtime_synthesis_parameters + * + * in this function we calculate the values of all the parameters. the + * parameters are converted to their most useful unit for the DSP + * algorithm, for example, number of samples instead of + * timecents. Some parameters keep their "perceptual" unit and + * conversion will be done in the DSP function. This is the case, for + * example, for the pitch since it is modulated by the controllers in + * cents. */ +static int +fluid_voice_calculate_runtime_synthesis_parameters(fluid_voice_t *voice) +{ + int i; + unsigned int n; + + static int const list_of_generators_to_initialize[] = + { + GEN_STARTADDROFS, /* SF2.01 page 48 #0 */ + GEN_ENDADDROFS, /* #1 */ + GEN_STARTLOOPADDROFS, /* #2 */ + GEN_ENDLOOPADDROFS, /* #3 */ + /* GEN_STARTADDRCOARSEOFS see comment below [1] #4 */ + GEN_MODLFOTOPITCH, /* #5 */ + GEN_VIBLFOTOPITCH, /* #6 */ + GEN_MODENVTOPITCH, /* #7 */ + GEN_FILTERFC, /* #8 */ + GEN_FILTERQ, /* #9 */ + GEN_MODLFOTOFILTERFC, /* #10 */ + GEN_MODENVTOFILTERFC, /* #11 */ + /* GEN_ENDADDRCOARSEOFS [1] #12 */ + GEN_MODLFOTOVOL, /* #13 */ + /* not defined #14 */ + GEN_CHORUSSEND, /* #15 */ + GEN_REVERBSEND, /* #16 */ + GEN_PAN, /* #17 */ + /* not defined #18 */ + /* not defined #19 */ + /* not defined #20 */ + GEN_MODLFODELAY, /* #21 */ + GEN_MODLFOFREQ, /* #22 */ + GEN_VIBLFODELAY, /* #23 */ + GEN_VIBLFOFREQ, /* #24 */ + GEN_MODENVDELAY, /* #25 */ + GEN_MODENVATTACK, /* #26 */ + GEN_MODENVHOLD, /* #27 */ + GEN_MODENVDECAY, /* #28 */ + /* GEN_MODENVSUSTAIN [1] #29 */ + GEN_MODENVRELEASE, /* #30 */ + /* GEN_KEYTOMODENVHOLD [1] #31 */ + /* GEN_KEYTOMODENVDECAY [1] #32 */ + GEN_VOLENVDELAY, /* #33 */ + GEN_VOLENVATTACK, /* #34 */ + GEN_VOLENVHOLD, /* #35 */ + GEN_VOLENVDECAY, /* #36 */ + /* GEN_VOLENVSUSTAIN [1] #37 */ + GEN_VOLENVRELEASE, /* #38 */ + /* GEN_KEYTOVOLENVHOLD [1] #39 */ + /* GEN_KEYTOVOLENVDECAY [1] #40 */ + /* GEN_STARTLOOPADDRCOARSEOFS [1] #45 */ + GEN_KEYNUM, /* #46 */ + GEN_VELOCITY, /* #47 */ + GEN_ATTENUATION, /* #48 */ + /* GEN_ENDLOOPADDRCOARSEOFS [1] #50 */ + /* GEN_COARSETUNE [1] #51 */ + /* GEN_FINETUNE [1] #52 */ + GEN_OVERRIDEROOTKEY, /* #58 */ + GEN_PITCH, /* --- */ + GEN_CUSTOM_BALANCE, /* --- */ + GEN_CUSTOM_FILTERFC, /* --- */ + GEN_CUSTOM_FILTERQ /* --- */ + }; + + /* When the voice is made ready for the synthesis process, a lot of + * voice-internal parameters have to be calculated. + * + * At this point, the sound font has already set the -nominal- value + * for all generators (excluding GEN_PITCH). Most generators can be + * modulated - they include a nominal value and an offset (which + * changes with velocity, note number, channel parameters like + * aftertouch, mod wheel...) Now this offset will be calculated as + * follows: + * + * - Process each modulator once. + * - Calculate its output value. + * - Find the target generator. + * - Add the output value to the modulation value of the generator. + * + * Note: The generators have been initialized with + * fluid_gen_init(). + */ + + for(i = 0; i < voice->mod_count; i++) + { + fluid_mod_t *mod = &voice->mod[i]; + fluid_real_t modval = fluid_mod_get_value(mod, voice); + int dest_gen_index = mod->dest; + fluid_gen_t *dest_gen = &voice->gen[dest_gen_index]; + dest_gen->mod += modval; + /* fluid_dump_modulator(mod); */ + } + + /* Now the generators are initialized, nominal and modulation value. + * The voice parameters (which depend on generators) are calculated + * with fluid_voice_update_param. Processing the list of generator + * changes will calculate each voice parameter once. + * + * Note [1]: Some voice parameters depend on several generators. For + * example, the pitch depends on GEN_COARSETUNE, GEN_FINETUNE and + * GEN_PITCH. voice->pitch. Unnecessary recalculation is avoided + * by removing all but one generator from the list of voice + * parameters. Same with GEN_XXX and GEN_XXXCOARSE: the + * initialisation list contains only GEN_XXX. + */ + + /* Calculate the voice parameter(s) dependent on each generator. */ + for(n = 0; n < FLUID_N_ELEMENTS(list_of_generators_to_initialize); n++) + { + fluid_voice_update_param(voice, list_of_generators_to_initialize[n]); + } + + /* Start portamento if enabled */ + { + /* fromkey note comes from "GetFromKeyPortamentoLegato()" detector. + When fromkey is set to ValidNote , portamento is started */ + /* Return fromkey portamento */ + int fromkey = voice->channel->synth->fromkey_portamento; + + if(fluid_channel_is_valid_note(fromkey)) + { + /* Send portamento parameters to the voice dsp */ + fluid_voice_update_portamento(voice, fromkey, fluid_voice_get_actual_key(voice)); + } + } + + /* Make an estimate on how loud this voice can get at any time (attenuation). */ + UPDATE_RVOICE_R1(fluid_rvoice_set_min_attenuation_cB, + fluid_voice_get_lower_boundary_for_attenuation(voice)); + return FLUID_OK; +} + +/* + * calculate_hold_decay_buffers + */ +static int +calculate_hold_decay_buffers(fluid_voice_t *voice, int gen_base, + int gen_key2base, int is_decay) +{ + /* Purpose: + * + * Returns the number of DSP loops, that correspond to the hold + * (is_decay=0) or decay (is_decay=1) time. + * gen_base=GEN_VOLENVHOLD, GEN_VOLENVDECAY, GEN_MODENVHOLD, + * GEN_MODENVDECAY gen_key2base=GEN_KEYTOVOLENVHOLD, + * GEN_KEYTOVOLENVDECAY, GEN_KEYTOMODENVHOLD, GEN_KEYTOMODENVDECAY + */ + + fluid_real_t keysteps; + fluid_real_t timecents; + fluid_real_t seconds; + int buffers; + + /* SF2.01 section 8.4.3 # 31, 32, 39, 40 + * GEN_KEYTOxxxENVxxx uses key 60 as 'origin'. + * The unit of the generator is timecents per key number. + * If KEYTOxxxENVxxx is 100, a key one octave over key 60 (72) + * will cause (60-72)*100=-1200 timecents of time variation. + * The time is cut in half. + */ + + keysteps = 60.0f - fluid_channel_get_key_pitch(voice->channel, fluid_voice_get_actual_key(voice)) / 100.0f; + timecents = fluid_voice_gen_value(voice, gen_base) + fluid_voice_gen_value(voice, gen_key2base) * keysteps; + + /* Range checking */ + if(is_decay) + { + /* SF 2.01 section 8.1.3 # 28, 36 */ + if(timecents > 8000.f) + { + timecents = 8000.f; + } + } + else + { + /* SF 2.01 section 8.1.3 # 27, 35 */ + if(timecents > 5000.f) + { + timecents = 5000.f; + } + + /* SF 2.01 section 8.1.2 # 27, 35: + * The most negative number indicates no hold time + */ + if(timecents <= -32768.f) + { + return 0; + } + } + + /* SF 2.01 section 8.1.3 # 27, 28, 35, 36 */ + if(timecents < -12000.f) + { + timecents = -12000.f; + } + + seconds = fluid_tc2sec(timecents); + /* Each DSP loop processes FLUID_BUFSIZE samples. */ + + /* round to next full number of buffers */ + buffers = (int)(((fluid_real_t)voice->output_rate * seconds) + / (fluid_real_t)FLUID_BUFSIZE + + 0.5f); + + return buffers; +} + +/* + * The value of a generator (gen) has changed. (The different + * generators are listed in fluidsynth.h, or in SF2.01 page 48-49) + * Now the dependent 'voice' parameters are calculated. + * + * fluid_voice_update_param can be called during the setup of the + * voice (to calculate the initial value for a voice parameter), or + * during its operation (a generator has been changed due to + * real-time parameter modifications like pitch-bend). + * + * Note: The generator holds three values: The base value .val, an + * offset caused by modulators .mod, and an offset caused by the + * 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. + * + * @param voice Voice instance + * @param gen Generator id (#fluid_gen_type) + * + * 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) +{ + unsigned int count, z; + fluid_real_t x = fluid_voice_gen_value(voice, gen); + + switch(gen) + { + + case GEN_PAN: + case GEN_CUSTOM_BALANCE: + /* range checking is done in the fluid_pan and fluid_balance functions */ + voice->pan = fluid_voice_gen_value(voice, GEN_PAN); + voice->balance = fluid_voice_gen_value(voice, GEN_CUSTOM_BALANCE); + + /* left amp */ + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0, + fluid_voice_calculate_gain_amplitude(voice, + fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1))); + + /* right amp */ + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1, + fluid_voice_calculate_gain_amplitude(voice, + fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0))); + break; + + case GEN_ATTENUATION: + voice->attenuation = x; + + /* Range: SF2.01 section 8.1.3 # 48 + * Motivation for range checking: + * OHPiano.SF2 sets initial attenuation to a whooping -96 dB */ + fluid_clip(voice->attenuation, 0.f, 1440.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_attenuation, voice->attenuation); + break; + + /* The pitch is calculated from three different generators. + * Read comment in fluidsynth.h about GEN_PITCH. + */ + case GEN_PITCH: + case GEN_COARSETUNE: + case GEN_FINETUNE: + /* The testing for allowed range is done in 'fluid_ct2hz' */ + voice->pitch = (fluid_voice_gen_value(voice, GEN_PITCH) + + 100.0f * fluid_voice_gen_value(voice, GEN_COARSETUNE) + + fluid_voice_gen_value(voice, GEN_FINETUNE)); + UPDATE_RVOICE_R1(fluid_rvoice_set_pitch, voice->pitch); + break; + + case GEN_REVERBSEND: + /* The generator unit is 'tenths of a percent'. */ + voice->reverb_send = x / 1000.0f; + fluid_clip(voice->reverb_send, 0.f, 1.f); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send)); + break; + + case GEN_CHORUSSEND: + /* The generator unit is 'tenths of a percent'. */ + voice->chorus_send = x / 1000.0f; + fluid_clip(voice->chorus_send, 0.f, 1.f); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send)); + break; + + case GEN_OVERRIDEROOTKEY: + + /* This is a non-realtime parameter. Therefore the .mod part of the generator + * can be neglected. + * NOTE: origpitch sets MIDI root note while pitchadj is a fine tuning amount + * which offsets the original rate. This means that the fine tuning is + * inverted with respect to the root note (so subtract it, not add). + */ + if(voice->sample != NULL) + { + if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1) //FIXME: use flag instead of -1 + { + voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f + - voice->sample->pitchadj; + } + else + { + voice->root_pitch = voice->sample->origpitch * 100.0f - voice->sample->pitchadj; + } + + x = (fluid_ct2hz_real(voice->root_pitch) * ((fluid_real_t) voice->output_rate / voice->sample->samplerate)); + } + else + { + if(voice->gen[GEN_OVERRIDEROOTKEY].val > -1) //FIXME: use flag instead of -1 + { + voice->root_pitch = voice->gen[GEN_OVERRIDEROOTKEY].val * 100.0f; + } + else + { + voice->root_pitch = 0; + } + + x = fluid_ct2hz_real(voice->root_pitch); + } + + /* voice->pitch depends on voice->root_pitch, so calculate voice->pitch now */ + fluid_voice_calculate_gen_pitch(voice); + UPDATE_RVOICE_R1(fluid_rvoice_set_root_pitch_hz, x); + + break; + + case GEN_FILTERFC: + /* The resonance frequency is converted from absolute cents to + * midicents .val and .mod are both used, this permits real-time + * modulation. The allowed range is tested in the 'fluid_ct2hz' + * function [PH,20021214] + */ + UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_filter, x); + break; + + case GEN_FILTERQ: + UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_filter, x); + break; + + /* same as the two above, only for the custom filter */ + case GEN_CUSTOM_FILTERFC: + UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_fres, &voice->rvoice->resonant_custom_filter, x); + break; + + case GEN_CUSTOM_FILTERQ: + UPDATE_RVOICE_GENERIC_R1(fluid_iir_filter_set_q, &voice->rvoice->resonant_custom_filter, x); + break; + + case GEN_MODLFOTOPITCH: + fluid_clip(x, -12000.f, 12000.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_pitch, x); + break; + + case GEN_MODLFOTOVOL: + fluid_clip(x, -960.f, 960.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_vol, x); + break; + + case GEN_MODLFOTOFILTERFC: + fluid_clip(x, -12000.f, 12000.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_fc, x); + break; + + case GEN_MODLFODELAY: + fluid_clip(x, -12000.0f, 5000.0f); + z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x)); + UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, modlfo, z); + break; + + case GEN_MODLFOFREQ: + /* - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples + * - the delay into a sample delay + */ + fluid_clip(x, -16000.0f, 4500.0f); + x = (4.0f * FLUID_BUFSIZE * fluid_ct2hz_real(x) / voice->output_rate); + UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, modlfo, x); + break; + + case GEN_VIBLFOFREQ: + /* vib lfo + * + * - the frequency is converted into a delta value, per buffer of FLUID_BUFSIZE samples + * - the delay into a sample delay + */ + fluid_clip(x, -16000.0f, 4500.0f); + x = 4.0f * FLUID_BUFSIZE * fluid_ct2hz_real(x) / voice->output_rate; + UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, viblfo, x); + break; + + case GEN_VIBLFODELAY: + fluid_clip(x, -12000.0f, 5000.0f); + z = (unsigned int)(voice->output_rate * fluid_tc2sec_delay(x)); + UPDATE_RVOICE_ENVLFO_I1(fluid_lfo_set_delay, viblfo, z); + break; + + case GEN_VIBLFOTOPITCH: + fluid_clip(x, -12000.f, 12000.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_viblfo_to_pitch, x); + break; + + case GEN_KEYNUM: + /* GEN_KEYNUM: SF2.01 page 46, item 46 + * + * If this generator is active, it forces the key number to its + * value. Non-realtime controller. + * + * There is a flag, which should indicate, whether a generator is + * enabled or not. But here we rely on the default value of -1. + */ + + /* 2017-09-02: do not change the voice's key here, otherwise it will + * never be released on a noteoff event + */ +#if 0 + x = fluid_voice_gen_value(voice, GEN_KEYNUM); + + if(x >= 0) + { + voice->key = x; + } + +#endif + break; + + case GEN_VELOCITY: + /* GEN_VELOCITY: SF2.01 page 46, item 47 + * + * If this generator is active, it forces the velocity to its + * value. Non-realtime controller. + * + * There is a flag, which should indicate, whether a generator is + * enabled or not. But here we rely on the default value of -1. + */ + /* 2017-09-02: do not change the voice's velocity here, use + * fluid_voice_get_actual_velocity() to get the value of this generator + * if active. + */ +#if 0 + x = fluid_voice_gen_value(voice, GEN_VELOCITY); + + if(x > 0) + { + voice->vel = x; + } + +#endif + break; + + case GEN_MODENVTOPITCH: + fluid_clip(x, -12000.f, 12000.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_pitch, x); + break; + + case GEN_MODENVTOFILTERFC: + /* Range: SF2.01 section 8.1.3 # 1 + * Motivation for range checking: + * Filter is reported to make funny noises now and then + */ + fluid_clip(x, -12000.f, 12000.f); + UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_fc, x); + break; + + + /* sample start and ends points + * + * Range checking is initiated via the + * voice->check_sample_sanity flag, + * because it is impossible to check here: + * During the voice setup, all modulators are processed, while + * the voice is inactive. Therefore, illegal settings may + * occur during the setup (for example: First move the loop + * end point ahead of the loop start point => invalid, then + * move the loop start point forward => valid again. + */ + case GEN_STARTADDROFS: /* SF2.01 section 8.1.3 # 0 */ + case GEN_STARTADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 4 */ + if(voice->sample != NULL) + { + fluid_real_t start_fine = fluid_voice_gen_value(voice, GEN_STARTADDROFS); + fluid_real_t start_coar = fluid_voice_gen_value(voice, GEN_STARTADDRCOARSEOFS); + + z = voice->sample->start + (int)start_fine + 32768 * (int)start_coar; + UPDATE_RVOICE_I1(fluid_rvoice_set_start, z); + } + + break; + + case GEN_ENDADDROFS: /* SF2.01 section 8.1.3 # 1 */ + case GEN_ENDADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 12 */ + if(voice->sample != NULL) + { + fluid_real_t end_fine = fluid_voice_gen_value(voice, GEN_ENDADDROFS); + fluid_real_t end_coar = fluid_voice_gen_value(voice, GEN_ENDADDRCOARSEOFS); + + z = voice->sample->end + (int)end_fine + 32768 * (int)end_coar; + UPDATE_RVOICE_I1(fluid_rvoice_set_end, z); + } + + break; + + case GEN_STARTLOOPADDROFS: /* SF2.01 section 8.1.3 # 2 */ + case GEN_STARTLOOPADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 45 */ + if(voice->sample != NULL) + { + fluid_real_t lstart_fine = fluid_voice_gen_value(voice, GEN_STARTLOOPADDROFS); + fluid_real_t lstart_coar = fluid_voice_gen_value(voice, GEN_STARTLOOPADDRCOARSEOFS); + + z = voice->sample->loopstart + (int)lstart_fine + 32768 * (int)lstart_coar; + UPDATE_RVOICE_I1(fluid_rvoice_set_loopstart, z); + } + + break; + + case GEN_ENDLOOPADDROFS: /* SF2.01 section 8.1.3 # 3 */ + case GEN_ENDLOOPADDRCOARSEOFS: /* SF2.01 section 8.1.3 # 50 */ + if(voice->sample != NULL) + { + fluid_real_t lend_fine = fluid_voice_gen_value(voice, GEN_ENDLOOPADDROFS); + fluid_real_t lend_coar = fluid_voice_gen_value(voice, GEN_ENDLOOPADDRCOARSEOFS); + + z = voice->sample->loopend + (int)lend_fine + 32768 * (int)lend_coar; + UPDATE_RVOICE_I1(fluid_rvoice_set_loopend, z); + } + + break; + + /* Conversion functions differ in range limit */ +#define NUM_BUFFERS_DELAY(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_delay(_v) / FLUID_BUFSIZE) +#define NUM_BUFFERS_ATTACK(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_attack(_v) / FLUID_BUFSIZE) +#define NUM_BUFFERS_RELEASE(_v) (unsigned int) (voice->output_rate * fluid_tc2sec_release(_v) / FLUID_BUFSIZE) + + /* volume envelope + * + * - delay and hold times are converted to absolute number of samples + * - sustain is converted to its absolute value + * - attack, decay and release are converted to their increment per sample + */ + case GEN_VOLENVDELAY: /* SF2.01 section 8.1.3 # 33 */ + fluid_clip(x, -12000.0f, 5000.0f); + count = NUM_BUFFERS_DELAY(x); + fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDELAY, + count, 0.0f, 0.0f, -1.0f, 1.0f); + break; + + case GEN_VOLENVATTACK: /* SF2.01 section 8.1.3 # 34 */ + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_ATTACK(x); + fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVATTACK, + count, 1.0f, 1.0f / count, -1.0f, 1.0f); + break; + + case GEN_VOLENVHOLD: /* SF2.01 section 8.1.3 # 35 */ + case GEN_KEYTOVOLENVHOLD: /* SF2.01 section 8.1.3 # 39 */ + count = calculate_hold_decay_buffers(voice, GEN_VOLENVHOLD, GEN_KEYTOVOLENVHOLD, 0); /* 0 means: hold */ + fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVHOLD, + count, 1.0f, 0.0f, -1.0f, 2.0f); + break; + + case GEN_VOLENVDECAY: /* SF2.01 section 8.1.3 # 36 */ + case GEN_VOLENVSUSTAIN: /* SF2.01 section 8.1.3 # 37 */ + case GEN_KEYTOVOLENVDECAY: /* SF2.01 section 8.1.3 # 40 */ + x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_VOLENVSUSTAIN); + fluid_clip(x, 0.0f, 1.0f); + count = calculate_hold_decay_buffers(voice, GEN_VOLENVDECAY, GEN_KEYTOVOLENVDECAY, 1); /* 1 for decay */ + fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVDECAY, + count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f); + break; + + case GEN_VOLENVRELEASE: /* SF2.01 section 8.1.3 # 38 */ + fluid_clip(x, FLUID_MIN_VOLENVRELEASE, 8000.0f); + count = 1 + NUM_BUFFERS_RELEASE(x); + fluid_voice_update_volenv(voice, TRUE, FLUID_VOICE_ENVRELEASE, + count, 1.0f, -1.0f / count, 0.0f, 1.0f); + break; + + /* Modulation envelope */ + case GEN_MODENVDELAY: /* SF2.01 section 8.1.3 # 25 */ + fluid_clip(x, -12000.0f, 5000.0f); + count = NUM_BUFFERS_DELAY(x); + fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDELAY, + count, 0.0f, 0.0f, -1.0f, 1.0f); + break; + + case GEN_MODENVATTACK: /* SF2.01 section 8.1.3 # 26 */ + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_ATTACK(x); + fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVATTACK, + count, 1.0f, 1.0f / count, -1.0f, 1.0f); + break; + + case GEN_MODENVHOLD: /* SF2.01 section 8.1.3 # 27 */ + case GEN_KEYTOMODENVHOLD: /* SF2.01 section 8.1.3 # 31 */ + count = calculate_hold_decay_buffers(voice, GEN_MODENVHOLD, GEN_KEYTOMODENVHOLD, 0); /* 1 means: hold */ + fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVHOLD, + count, 1.0f, 0.0f, -1.0f, 2.0f); + break; + + case GEN_MODENVDECAY: /* SF 2.01 section 8.1.3 # 28 */ + case GEN_MODENVSUSTAIN: /* SF 2.01 section 8.1.3 # 29 */ + case GEN_KEYTOMODENVDECAY: /* SF 2.01 section 8.1.3 # 32 */ + count = calculate_hold_decay_buffers(voice, GEN_MODENVDECAY, GEN_KEYTOMODENVDECAY, 1); /* 1 for decay */ + x = 1.0f - 0.001f * fluid_voice_gen_value(voice, GEN_MODENVSUSTAIN); + fluid_clip(x, 0.0f, 1.0f); + fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVDECAY, + count, 1.0f, count ? -1.0f / count : 0.0f, x, 2.0f); + break; + + case GEN_MODENVRELEASE: /* SF 2.01 section 8.1.3 # 30 */ + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_RELEASE(x); + fluid_voice_update_modenv(voice, TRUE, FLUID_VOICE_ENVRELEASE, + count, 1.0f, -1.0f / count, 0.0f, 2.0f); + + break; + + } /* switch 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: + * when >=0, only modulators's destination having ctrl as source are updated. + * when -1, all modulators's destination are updated (regardless of ctrl). + * + * In this implementation, I want to make sure that all controllers + * are event based: the parameter values of the DSP algorithm should + * only be updates when a controller event arrived and not at every + * iteration of the audio cycle (which would probably be feasible if + * the synth was made in silicon). + * + * The update is done in two steps: + * + * - step 1: first, we look for all the modulators that have the changed + * controller as a source. This will yield a generator that will be changed + * because of the controller event. + * + * - step 2: For this generator, calculate its new value. This is the + * sum of its original value plus the values of all the attached modulators. + * The generator flag is set to indicate the parameters must be updated. + * This avoid the risk to call 'fluid_voice_update_param' several + * times for the same generator if several modulators have that generator as + * destination. So every changed generators are updated only once. + */ + + /* bit table for each generator being updated. The bits are packed in variables + Each variable have NBR_BIT_BY_VAR bits represented by NBR_BIT_BY_VAR_LN2. + The size of the table is the number of variables: SIZE_UPDATED_GEN_BIT. + + Note: In this implementation NBR_BIT_BY_VAR_LN2 is set to 5 (convenient for 32 bits cpu) + but this could be set to 6 for 64 bits cpu. + */ + +#define NBR_BIT_BY_VAR_LN2 5 /* for 32 bits variables */ +#define NBR_BIT_BY_VAR (1 << NBR_BIT_BY_VAR_LN2) +#define NBR_BIT_BY_VAR_ANDMASK (NBR_BIT_BY_VAR - 1) +#define SIZE_UPDATED_GEN_BIT ((GEN_LAST + NBR_BIT_BY_VAR_ANDMASK) / NBR_BIT_BY_VAR) + +#define is_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] & (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) +#define set_gen_updated(bit,gen) (bit[gen >> NBR_BIT_BY_VAR_LN2] |= (1 << (gen & NBR_BIT_BY_VAR_ANDMASK))) + +int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) +{ + int i, k; + fluid_mod_t *mod; + uint32_t gen; + fluid_real_t modval; + + /* Clears registered bits table of updated generators */ + uint32_t updated_gen_bit[SIZE_UPDATED_GEN_BIT] = {0}; + + /* printf("Chan=%d, CC=%d, Src=%d, Val=%d\n", voice->channel->channum, cc, ctrl, val); */ + + for(i = 0; i < voice->mod_count; i++) + { + mod = &voice->mod[i]; + + /* step 1: find all the modulators that have the changed controller + as input source. When ctrl is -1 all modulators destination + are updated */ + if(ctrl < 0 || fluid_mod_has_source(mod, cc, ctrl)) + { + gen = fluid_mod_get_dest(mod); + + /* Skip if this generator has already been updated */ + if(!is_gen_updated(updated_gen_bit, gen)) + { + modval = 0.0; + + /* step 2: for every attached modulator, calculate the modulation + * value for the generator gen */ + for(k = 0; k < voice->mod_count; k++) + { + if(fluid_mod_has_dest(&voice->mod[k], gen)) + { + modval += fluid_mod_get_value(&voice->mod[k], voice); + } + } + + fluid_gen_set_mod(&voice->gen[gen], modval); + + /* now recalculate the parameter values that are derived from the + generator */ + fluid_voice_update_param(voice, gen); + + /* set the bit that indicates this generator is updated */ + set_gen_updated(updated_gen_bit, gen); + } + } + } + + return FLUID_OK; +} + +/** + * Update all the modulators. + * + * 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 --------------------------------------------------*/ + +/* Updates voice portamento parameters + * + * @voice voice the synthesis voice + * @fromkey the beginning pitch of portamento. + * @tokey the ending pitch of portamento. + * + * The function calculates pitch offset and increment, then these parameters + * are send to the dsp. +*/ +void fluid_voice_update_portamento(fluid_voice_t *voice, int fromkey, int tokey) + +{ + fluid_channel_t *channel = voice->channel; + + /* calculates pitch offset */ + fluid_real_t PitchBeg = fluid_voice_calculate_pitch(voice, fromkey); + fluid_real_t PitchEnd = fluid_voice_calculate_pitch(voice, tokey); + fluid_real_t pitchoffset = PitchBeg - PitchEnd; + + /* Calculates increment countinc */ + /* Increment is function of PortamentoTime (ms)*/ + unsigned int countinc = (unsigned int)(((fluid_real_t)voice->output_rate * + 0.001f * + (fluid_real_t)fluid_channel_portamentotime(channel)) / + (fluid_real_t)FLUID_BUFSIZE + 0.5f); + + /* Send portamento parameters to the voice dsp */ + UPDATE_RVOICE_GENERIC_IR(fluid_rvoice_set_portamento, voice->rvoice, countinc, pitchoffset); +} + +/*---------------------------------------------------------------*/ +/*legato mode 1: multi_retrigger + * + * Modulates all generators dependent of key,vel. + * Forces the voice envelopes in the attack section (legato mode 1). + * + * @voice voice the synthesis voice + * @tokey the new key to be applied to this voice. + * @vel the new velocity to be applied to this voice. + */ +void fluid_voice_update_multi_retrigger_attack(fluid_voice_t *voice, + int tokey, int vel) +{ + voice->key = tokey; /* new note */ + voice->vel = vel; /* new velocity */ + /* Updates generators dependent of velocity */ + /* Modulates GEN_ATTENUATION (and others ) before calling + fluid_rvoice_multi_retrigger_attack().*/ + fluid_voice_modulate(voice, FALSE, FLUID_MOD_VELOCITY); + + /* Updates generator dependent of voice->key */ + fluid_voice_update_param(voice, GEN_KEYTOMODENVHOLD); + fluid_voice_update_param(voice, GEN_KEYTOMODENVDECAY); + fluid_voice_update_param(voice, GEN_KEYTOVOLENVHOLD); + fluid_voice_update_param(voice, GEN_KEYTOVOLENVDECAY); + + /* Updates pitch generator */ + fluid_voice_calculate_gen_pitch(voice); + fluid_voice_update_param(voice, GEN_PITCH); + + /* updates adsr generator */ + UPDATE_RVOICE0(fluid_rvoice_multi_retrigger_attack); +} +/** end of legato update functions */ + +/* + Force the voice into release stage. Useful anywhere a voice + needs to be damped even if pedals (sustain sostenuto) are depressed. + See fluid_synth_damp_voices_by_sustain_LOCAL(), + fluid_synth_damp_voices_by_sostenuto_LOCAL, + fluid_voice_noteoff(). +*/ +void +fluid_voice_release(fluid_voice_t *voice) +{ + unsigned int at_tick = fluid_channel_get_min_note_length_ticks(voice->channel); + UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick); + voice->has_noteoff = 1; // voice is marked as noteoff occurred +} + +/* + * fluid_voice_noteoff + * + * Sending a noteoff event will advance the envelopes to section 5 (release). + * The function is convenient for polyphonic or monophonic note + */ +void +fluid_voice_noteoff(fluid_voice_t *voice) +{ + fluid_channel_t *channel; + + fluid_profile(FLUID_PROF_VOICE_NOTE, voice->ref, 0, 0); + + channel = voice->channel; + + /* Sustain a note under Sostenuto pedal */ + if(fluid_channel_sostenuto(channel) && + channel->sostenuto_orderid > voice->id) + { + // Sostenuto depressed after note + voice->status = FLUID_VOICE_HELD_BY_SOSTENUTO; + } + /* Or sustain a note under Sustain pedal */ + else if(fluid_channel_sustained(channel)) + { + voice->status = FLUID_VOICE_SUSTAINED; + } + /* Or force the voice to release stage */ + else + { + fluid_voice_release(voice); + } +} + +/* + * fluid_voice_kill_excl + * + * Percussion sounds can be mutually exclusive: for example, a 'closed + * hihat' sound will terminate an 'open hihat' sound ringing at the + * same time. This behaviour is modeled using 'exclusive classes', + * turning on a voice with an exclusive class other than 0 will kill + * all other voices having that exclusive class within the same preset + * or channel. fluid_voice_kill_excl gets called, when 'voice' is to + * be killed for that reason. + */ + +int +fluid_voice_kill_excl(fluid_voice_t *voice) +{ + + unsigned int at_tick; + + if(!fluid_voice_is_playing(voice)) + { + return FLUID_OK; + } + + /* Turn off the exclusive class information for this voice, + so that it doesn't get killed twice + */ + fluid_voice_gen_set(voice, GEN_EXCLUSIVECLASS, 0); + + /* Speed up the volume envelope */ + /* The value was found through listening tests with hi-hat samples. */ + fluid_voice_gen_set(voice, GEN_VOLENVRELEASE, -200); + fluid_voice_update_param(voice, GEN_VOLENVRELEASE); + + /* Speed up the modulation envelope */ + fluid_voice_gen_set(voice, GEN_MODENVRELEASE, -200); + fluid_voice_update_param(voice, GEN_MODENVRELEASE); + + at_tick = fluid_channel_get_min_note_length_ticks(voice->channel); + UPDATE_RVOICE_I1(fluid_rvoice_noteoff, at_tick); + + + return FLUID_OK; +} + +/* + * Unlock the overflow rvoice of the voice. + * Decrement the reference count of the sample owned by this rvoice. + * + * Called by fluid_synth when the overflow rvoice has finished by itself. + * Must be called also explicitly at synth destruction to ensure that + * the soundfont be unloaded successfully. + */ +void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice) +{ + voice->can_access_overflow_rvoice = 1; + + /* Decrement the reference count of the sample to indicate + that this sample isn't owned by the rvoice anymore */ + fluid_voice_sample_unref(&voice->overflow_sample); +} + +/* + * fluid_voice_off + * + * Force the voice into finished stage. Useful anywhere a voice + * needs to be cancelled from MIDI API. + */ +void fluid_voice_off(fluid_voice_t *voice) +{ + UPDATE_RVOICE0(fluid_rvoice_voiceoff); /* request to finish the voice */ +} + +/* + * fluid_voice_stop + * + * Purpose: + * Turns off a voice, meaning that it is not processed anymore by the + * DSP loop, i.e. contrary part to fluid_voice_start(). + */ +void +fluid_voice_stop(fluid_voice_t *voice) +{ + fluid_profile(FLUID_PROF_VOICE_RELEASE, voice->ref, 0, 0); + + voice->chan = NO_CHANNEL; + + /* Decrement the reference count of the sample, to indicate + that this sample isn't owned by the rvoice anymore. + */ + fluid_voice_sample_unref(&voice->sample); + + voice->status = FLUID_VOICE_OFF; + voice->has_noteoff = 1; + + /* Decrement voice count */ + voice->channel->synth->active_voice_count--; +} + +/** + * 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. + * #FLUID_VOICE_ADD to add (offset) the modulator amounts, + * #FLUID_VOICE_OVERWRITE to replace the modulator, + * #FLUID_VOICE_DEFAULT when adding a default modulator - no duplicate should + * exist so don't check. + */ +void +fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) +{ + /* Ignore the modulator if its sources inputs are invalid */ + if(fluid_mod_check_sources(mod, "api fluid_voice_add_mod mod")) + { + fluid_voice_add_mod_local(voice, mod, mode, FLUID_NUM_MOD); + } +} + +/** + * Adds a modulator to the voice. + * local version of fluid_voice_add_mod function. Called at noteon time. + * @param voice, mod, mode, same as for fluid_voice_add_mod() (see above). + * @param check_limit_count is the modulator number limit to handle with existing + * identical modulator(i.e mode FLUID_VOICE_OVERWRITE, FLUID_VOICE_ADD). + * - When FLUID_NUM_MOD, all the voices modulators (since the previous call) + * are checked for identity. + * - When check_count_limit is below the actual number of voices modulators + * (voice->mod_count), this will restrict identity check to this number, + * This is useful when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at noteon. + */ +void +fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count) +{ + int i; + + /* check_limit_count cannot be above voice->mod_count */ + if(check_limit_count > voice->mod_count) + { + check_limit_count = voice->mod_count; + } + + if(mode == FLUID_VOICE_ADD) + { + + /* if identical modulator exists, add them */ + for(i = 0; i < check_limit_count; i++) + { + if(fluid_mod_test_identity(&voice->mod[i], mod)) + { + // printf("Adding modulator...\n"); + voice->mod[i].amount += mod->amount; + return; + } + } + + } + else if(mode == FLUID_VOICE_OVERWRITE) + { + + /* if identical modulator exists, replace it (only the amount has to be changed) */ + for(i = 0; i < check_limit_count; i++) + { + if(fluid_mod_test_identity(&voice->mod[i], mod)) + { + // printf("Replacing modulator...amount is %f\n",mod->amount); + voice->mod[i].amount = mod->amount; + return; + } + } + } + + /* Add a new modulator (No existing modulator to add / overwrite). + Also, default modulators (FLUID_VOICE_DEFAULT) are added without + checking, if the same modulator already exists. */ + if(voice->mod_count < FLUID_NUM_MOD) + { + fluid_mod_clone(&voice->mod[voice->mod_count++], mod); + } + else + { + FLUID_LOG(FLUID_WARN, "Voice %i has more modulators than supported, ignoring.", voice->id); + } +} + +/** + * Get the unique ID of the noteon-event. + * + * @param voice Voice instance + * @return Note on unique ID + * + * 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 internally which are + * 'recycled' and never deallocated. + * + * However, before modifying an existing voice, check + * - that its state is still 'playing' + * - that the ID is still the same + * + * Otherwise the voice has finished playing. + */ +unsigned int fluid_voice_get_id(const fluid_voice_t *voice) +{ + return voice->id; +} + +/** + * Check if a voice is producing sound. + * + * Like fluid_voice_is_on() this will return TRUE once a call to + * fluid_synth_start_voice() has been made. Contrary to fluid_voice_is_on(), + * this might also return TRUE after the voice received a noteoff event, as it may + * still be playing in release phase, or because it has been sustained or + * sostenuto'ed. + * + * @param voice Voice instance + * @return TRUE if playing, FALSE otherwise + */ +int fluid_voice_is_playing(const fluid_voice_t *voice) +{ + return (voice->status == FLUID_VOICE_ON) + || fluid_voice_is_sustained(voice) + || fluid_voice_is_sostenuto(voice); + +} + +/** + * Check if a voice is ON. + * + * A voice is in ON state as soon as a call to fluid_synth_start_voice() has been made + * (which is typically done in a fluid_preset_t's noteon function). + * A voice stays ON as long as it has not received a noteoff event. + * + * @param voice Voice instance + * @return TRUE if on, FALSE otherwise + * + * @since 1.1.7 + */ +int fluid_voice_is_on(const fluid_voice_t *voice) +{ + return (voice->status == FLUID_VOICE_ON && !voice->has_noteoff); +} + +/** + * 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) +{ + return (voice->status == FLUID_VOICE_SUSTAINED); +} + +/** + * 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) +{ + return (voice->status == FLUID_VOICE_HELD_BY_SOSTENUTO); +} + +/** + * 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) +{ + return voice->chan; +} + +/** + * Return the effective MIDI key of the playing voice. + * + * @param voice Voice instance + * @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) +{ + fluid_real_t x = fluid_voice_gen_value(voice, GEN_KEYNUM); + + if(x >= 0) + { + return (int)x; + } + else + { + return fluid_voice_get_key(voice); + } +} + +/** + * 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 + * + * @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) +{ + return voice->key; +} + +/** + * Return the effective MIDI velocity of the playing voice. + * + * @param voice Voice instance + * @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) +{ + fluid_real_t x = fluid_voice_gen_value(voice, GEN_VELOCITY); + + if(x > 0) + { + return (int)x; + } + else + { + return fluid_voice_get_velocity(voice); + } +} + +/** + * Return the MIDI velocity from the starting noteon event. + * + * @param voice Voice instance + * @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) +{ + return voice->vel; +} + +/* + * fluid_voice_get_lower_boundary_for_attenuation + * + * Purpose: + * + * A lower boundary for the attenuation (as in 'the minimum + * attenuation of this voice, with volume pedals, modulators + * etc. resulting in minimum attenuation, cannot fall below x cB) is + * calculated. This has to be called during fluid_voice_start, after + * all modulators have been run on the voice once. Also, + * voice->attenuation has to be initialized. + * (see fluid_voice_calculate_runtime_synthesis_parameters()) + */ +static fluid_real_t +fluid_voice_get_lower_boundary_for_attenuation(fluid_voice_t *voice) +{ + int i; + fluid_mod_t *mod; + fluid_real_t possible_att_reduction_cB = 0; + fluid_real_t lower_bound; + + for(i = 0; i < voice->mod_count; i++) + { + mod = &voice->mod[i]; + + /* Modulator has attenuation as target and can change over time? */ + if((mod->dest == GEN_ATTENUATION) + && ((mod->flags1 & FLUID_MOD_CC) + || (mod->flags2 & FLUID_MOD_CC) + || (mod->src1 == FLUID_MOD_CHANNELPRESSURE) + || (mod->src1 == FLUID_MOD_KEYPRESSURE) + || (mod->src1 == FLUID_MOD_PITCHWHEEL) + || (mod->src2 == FLUID_MOD_CHANNELPRESSURE) + || (mod->src2 == FLUID_MOD_KEYPRESSURE) + || (mod->src2 == FLUID_MOD_PITCHWHEEL))) + { + + fluid_real_t current_val = fluid_mod_get_value(mod, voice); + /* min_val is the possible minimum value for this modulator. + it depends of 3 things : + 1)the minimum values of src1,src2 (i.e -1 if mapping is bipolar + or 0 if mapping is unipolar). + 2)the sign of amount. + 3)absolute value of amount. + + When at least one source mapping is bipolar: + min_val is -|amount| regardless the sign of amount. + When both sources mapping are unipolar: + min_val is -|amount|, if amount is negative. + min_val is 0, if amount is positive + */ + fluid_real_t min_val = fabs(mod->amount); + + /* Can this modulator produce a negative contribution? */ + if((mod->flags1 & FLUID_MOD_BIPOLAR) + || (mod->flags2 & FLUID_MOD_BIPOLAR) + || (mod->amount < 0)) + { + min_val = -min_val; /* min_val = - |amount|*/ + } + else + { + /* No negative value possible. But still, the minimum contribution is 0. */ + min_val = 0; + } + + /* For example: + * - current_val=100 + * - min_val=-4000 + * - possible reduction contribution of this modulator = current_val - min_val = 4100 + */ + if(current_val > min_val) + { + possible_att_reduction_cB += (current_val - min_val); + } + } + } + + lower_bound = voice->attenuation - possible_att_reduction_cB; + + /* SF2.01 specs do not allow negative attenuation */ + if(lower_bound < 0) + { + lower_bound = 0; + } + + return lower_bound; +} + + + + +int fluid_voice_set_param(fluid_voice_t *voice, int gen, fluid_real_t nrpn_value) +{ + voice->gen[gen].nrpn = nrpn_value; + voice->gen[gen].flags = GEN_SET; + fluid_voice_update_param(voice, gen); + return FLUID_OK; +} + +int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain) +{ + fluid_real_t left, right, reverb, chorus; + + /* avoid division by zero*/ + if(gain < 0.0000001f) + { + gain = 0.0000001f; + } + + voice->synth_gain = gain; + left = fluid_voice_calculate_gain_amplitude(voice, + fluid_pan(voice->pan, 1) * fluid_balance(voice->balance, 1)); + right = fluid_voice_calculate_gain_amplitude(voice, + fluid_pan(voice->pan, 0) * fluid_balance(voice->balance, 0)); + reverb = fluid_voice_calculate_gain_amplitude(voice, voice->reverb_send); + chorus = fluid_voice_calculate_gain_amplitude(voice, voice->chorus_send); + + UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, gain); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 0, left); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 1, right); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 2, reverb); + UPDATE_RVOICE_BUFFERS_AMP(fluid_rvoice_buffers_set_amp, 3, chorus); + + return FLUID_OK; +} + +/* - Scan the loop + * - determine the peak level + * - 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 + * + * If the peak volume during the loop is known, then the voice can + * be released earlier during the release phase. Otherwise, the + * voice will operate (inaudibly), until the envelope is at the + * nominal turnoff point. So it's a good idea to call + * fluid_voice_optimize_sample() on each sample once. + */ +int +fluid_voice_optimize_sample(fluid_sample_t *s) +{ + int32_t peak_max = 0; + int32_t peak_min = 0; + int32_t peak; + fluid_real_t normalized_amplitude_during_loop; + double result; + unsigned int i; + + /* ignore disabled samples */ + if(s->start == s->end) + { + return (FLUID_OK); + } + + if(!s->amplitude_that_reaches_noise_floor_is_valid) /* Only once */ + { + /* Scan the loop */ + for(i = s->loopstart; i < s->loopend; i++) + { + int32_t val = fluid_rvoice_get_sample(s->data, s->data24, i); + + if(val > peak_max) + { + peak_max = val; + } + else if(val < peak_min) + { + peak_min = val; + } + } + + /* Determine the peak level */ + if(peak_max > -peak_min) + { + peak = peak_max; + } + else + { + peak = -peak_min; + } + + if(peak == 0) + { + /* Avoid division by zero */ + peak = 1; + } + + /* Calculate what factor will make the loop inaudible + * For example: Take a peak of 3277 (10 % of 32768). The + * normalized amplitude is 0.1 (10 % of 32768). An amplitude + * factor of 0.0001 (as opposed to the default 0.00001) will + * drop this sample to the noise floor. + */ + + /* 16 bits => 96+4=100 dB dynamic range => 0.00001 */ + normalized_amplitude_during_loop = ((fluid_real_t)peak) / (INT24_MAX * 1.0f); + result = FLUID_NOISE_FLOOR / normalized_amplitude_during_loop; + + /* Store in sample */ + s->amplitude_that_reaches_noise_floor = (double)result; + s->amplitude_that_reaches_noise_floor_is_valid = 1; +#if 0 + printf("Sample peak detection: factor %f\n", (double)result); +#endif + } + + return FLUID_OK; +} + +float +fluid_voice_get_overflow_prio(fluid_voice_t *voice, + fluid_overflow_prio_t *score, + unsigned int cur_time) +{ + float this_voice_prio = 0; + int channel; + + /* Are we already overflowing? */ + if(!voice->can_access_overflow_rvoice) + { + return OVERFLOW_PRIO_CANNOT_KILL; + } + + /* Is this voice on the drum channel? + * Then it is very important. + * Also skip the released and sustained scores. + */ + if(voice->channel->channel_type == CHANNEL_TYPE_DRUM) + { + this_voice_prio += score->percussion; + } + else if(voice->has_noteoff) + { + /* Noteoff has */ + this_voice_prio += score->released; + } + else if(fluid_voice_is_sustained(voice) || fluid_voice_is_sostenuto(voice)) + { + /* This voice is still active, since the sustain pedal is held down. + * Consider it less important than non-sustained channels. + * This decision is somehow subjective. But usually the sustain pedal + * is used to play 'more-voices-than-fingers', so it shouldn't hurt + * if we kill one voice. + */ + this_voice_prio += score->sustained; + } + + /* We are not enthusiastic about releasing voices, which have just been started. + * Otherwise hitting a chord may result in killing notes belonging to that very same + * chord. So give newer voices a higher score. */ + if(score->age) + { + cur_time -= voice->start_time; + + if(cur_time < 1) + { + cur_time = 1; // Avoid div by zero + } + + this_voice_prio += (score->age * voice->output_rate) / cur_time; + } + + /* take a rough estimate of loudness into account. Louder voices are more important. */ + if(score->volume) + { + fluid_real_t a = voice->attenuation; + + if(voice->has_noteoff) + { + // FIXME: Should take into account where on the envelope we are...? + } + + if(a < 0.1f) + { + a = 0.1f; // Avoid div by zero + } + + this_voice_prio += score->volume / a; + } + + /* Check if this voice is on an important channel. If so, then add the + * score for important channels */ + channel = fluid_voice_get_channel(voice); + + if(channel < score->num_important_channels && score->important_channels[channel]) + { + this_voice_prio += score->important; + } + + return this_voice_prio; +} + + +void fluid_voice_set_custom_filter(fluid_voice_t *voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags) +{ + UPDATE_RVOICE_GENERIC_I2(fluid_iir_filter_init, &voice->rvoice->resonant_custom_filter, type, flags); +} + diff --git a/libs/fluidsynth/src/synth/fluid_voice.h b/libs/fluidsynth/src/synth/fluid_voice.h new file mode 100644 index 00000000000..4ce6c2b7ab5 --- /dev/null +++ b/libs/fluidsynth/src/synth/fluid_voice.h @@ -0,0 +1,198 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_VOICE_H +#define _FLUID_VOICE_H + +#include "fluid_phase.h" +#include "fluid_gen.h" +#include "fluid_mod.h" +#include "fluid_iir_filter.h" +#include "fluid_adsr_env.h" +#include "fluid_lfo.h" +#include "fluid_rvoice.h" +#include "fluid_rvoice_event.h" + +#define NO_CHANNEL 0xff + +typedef struct _fluid_overflow_prio_t fluid_overflow_prio_t; + +struct _fluid_overflow_prio_t +{ + float percussion; /**< Is this voice on the drum channel? Then add this score */ + float released; /**< Is this voice in release stage? Then add this score (usually negative) */ + float sustained; /**< Is this voice sustained? Then add this score (usually negative) */ + float volume; /**< Multiply current (or future) volume (a value between 0 and 1) */ + float age; /**< This score will be divided by the number of seconds the voice has lasted */ + float important; /**< This score will be added to all important channels */ + char *important_channels; /**< "important" flags indexed by MIDI channel number */ + int num_important_channels; /**< Number of elements in the important_channels array */ +}; + +enum fluid_voice_status +{ + FLUID_VOICE_CLEAN, + FLUID_VOICE_ON, + FLUID_VOICE_SUSTAINED, /* Sustained by Sustain pedal */ + FLUID_VOICE_HELD_BY_SOSTENUTO, /* Sustained by Sostenuto pedal */ + FLUID_VOICE_OFF +}; + + +/* + * fluid_voice_t + */ +struct _fluid_voice_t +{ + unsigned int id; /* the id is incremented for every new noteon. + it's used for noteoff's */ + unsigned char status; + unsigned char chan; /* the channel number, quick access for channel messages */ + unsigned char key; /* the key of the noteon event, quick access for noteoff */ + unsigned char vel; /* the velocity of the noteon event */ + fluid_channel_t *channel; + fluid_rvoice_eventhandler_t *eventhandler; + fluid_zone_range_t *zone_range; /* instrument zone range*/ + fluid_sample_t *sample; /* Pointer to sample (dupe in rvoice) */ + fluid_sample_t *overflow_sample; /* Pointer to sample (dupe in overflow_rvoice) */ + + unsigned int start_time; + int mod_count; + fluid_mod_t mod[FLUID_NUM_MOD]; + fluid_gen_t gen[GEN_LAST]; + + /* basic parameters */ + fluid_real_t output_rate; /* the sample rate of the synthesizer (dupe in rvoice) */ + + /* basic parameters */ + fluid_real_t pitch; /* the pitch in midicents (dupe in rvoice) */ + fluid_real_t attenuation; /* the attenuation in centibels (dupe in rvoice) */ + fluid_real_t root_pitch; + + /* master gain (dupe in rvoice) */ + fluid_real_t synth_gain; + + /* pan */ + fluid_real_t pan; + + /* balance */ + fluid_real_t balance; + + /* reverb */ + fluid_real_t reverb_send; + + /* chorus */ + fluid_real_t chorus_send; + + /* rvoice control */ + fluid_rvoice_t *rvoice; + fluid_rvoice_t *overflow_rvoice; /* Used temporarily and only in overflow situations */ + char can_access_rvoice; /* False if rvoice is being rendered in separate thread */ + char can_access_overflow_rvoice; /* False if overflow_rvoice is being rendered in separate thread */ + char has_noteoff; /* Flag set when noteoff has been sent */ + +#ifdef WITH_PROFILING + /* for debugging */ + double ref; +#endif +}; + + +fluid_voice_t *new_fluid_voice(fluid_rvoice_eventhandler_t *handler, fluid_real_t output_rate); +void delete_fluid_voice(fluid_voice_t *voice); + +void fluid_voice_start(fluid_voice_t *voice); +void fluid_voice_calculate_gen_pitch(fluid_voice_t *voice); + +int fluid_voice_init(fluid_voice_t *voice, fluid_sample_t *sample, + fluid_zone_range_t *inst_zone_range, + fluid_channel_t *channel, int key, int vel, + unsigned int id, unsigned int time, fluid_real_t gain); + +int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl); +int fluid_voice_modulate_all(fluid_voice_t *voice); + +/** Set the NRPN value of a generator. */ +int fluid_voice_set_param(fluid_voice_t *voice, int gen, fluid_real_t value); + + +/** Set the gain. */ +int fluid_voice_set_gain(fluid_voice_t *voice, fluid_real_t gain); + +void fluid_voice_set_output_rate(fluid_voice_t *voice, fluid_real_t value); + + +/** Update all the synthesis parameters, which depend on generator + 'gen'. This is only necessary after changing a generator of an + already operating voice. Most applications will not need this + function.*/ +void fluid_voice_update_param(fluid_voice_t *voice, int gen); + +/** legato modes */ +/* force in the attack section for legato mode multi_retrigger: 1 */ +void fluid_voice_update_multi_retrigger_attack(fluid_voice_t *voice, int tokey, int vel); +/* Update portamento parameter */ +void fluid_voice_update_portamento(fluid_voice_t *voice, int fromkey, int tokey); + + +void fluid_voice_release(fluid_voice_t *voice); +void fluid_voice_noteoff(fluid_voice_t *voice); +void fluid_voice_off(fluid_voice_t *voice); +void fluid_voice_stop(fluid_voice_t *voice); +void fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count); +void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice); + +int fluid_voice_kill_excl(fluid_voice_t *voice); +float fluid_voice_get_overflow_prio(fluid_voice_t *voice, + fluid_overflow_prio_t *score, + unsigned int cur_time); + +#define OVERFLOW_PRIO_CANNOT_KILL 999999. + +/** + * Locks the rvoice for rendering, so it can't be modified directly + */ +static FLUID_INLINE void +fluid_voice_lock_rvoice(fluid_voice_t *voice) +{ + voice->can_access_rvoice = 0; +} + +/** + * Unlocks the rvoice for rendering, so it can be modified directly + */ +static FLUID_INLINE void +fluid_voice_unlock_rvoice(fluid_voice_t *voice) +{ + voice->can_access_rvoice = 1; +} + +#define _AVAILABLE(voice) ((voice)->can_access_rvoice && \ + (((voice)->status == FLUID_VOICE_CLEAN) || ((voice)->status == FLUID_VOICE_OFF))) +//#define _RELEASED(voice) ((voice)->chan == NO_CHANNEL) +#define _SAMPLEMODE(voice) ((int)(voice)->gen[GEN_SAMPLEMODE].val) + + +fluid_real_t fluid_voice_gen_value(const fluid_voice_t *voice, int num); +void fluid_voice_set_custom_filter(fluid_voice_t *voice, enum fluid_iir_filter_type type, enum fluid_iir_filter_flags flags); + + +#endif /* _FLUID_VOICE_H */ diff --git a/libs/fluidsynth/src/utils/fluid_conv.c b/libs/fluidsynth/src/utils/fluid_conv.c new file mode 100644 index 00000000000..28740ea4f1a --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_conv.c @@ -0,0 +1,334 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_conv.h" +#include "fluid_sys.h" +#include "fluid_conv_tables.inc.h" + +/* + * Converts absolute cents to Hertz + * + * As per sfspec section 9.3: + * + * ABSOLUTE CENTS - An absolute logarithmic measure of frequency based on a + * reference of MIDI key number scaled by 100. + * A cent is 1/1200 of an octave [which is the twelve hundredth root of two], + * and value 6900 is 440 Hz (A-440). + * + * Implemented below basically is the following: + * 440 * 2^((cents-6900)/1200) + * = 440 * 2^((int)((cents-6900)/1200)) * 2^(((int)cents-6900)%1200)) + * = 2^((int)((cents-6900)/1200)) * (440 * 2^(((int)cents-6900)%1200))) + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * This second factor is stored in the lookup table. + * + * The first factor can be implemented with a fast shift when the exponent + * is always an int. This is the case when using 440/2^6 Hz rather than 440Hz + * reference. + */ +fluid_real_t +fluid_ct2hz_real(fluid_real_t cents) +{ + if(FLUID_UNLIKELY(cents < 0)) + { + return fluid_act2hz(cents); + } + else + { + unsigned int mult, fac, rem; + unsigned int icents = (unsigned int)cents; + icents += 300u; + + // don't use stdlib div() here, it turned out have poor performance + fac = icents / 1200u; + rem = icents % 1200u; + + // Think of "mult" as the factor that we multiply (440/2^6)Hz with, + // or in other words mult is the "first factor" of the above + // functions comment. + // + // Assuming sizeof(uint)==4 this will give us a maximum range of + // 32 * 1200cents - 300cents == 38100 cents == 29,527,900,160 Hz + // which is much more than ever needed. For bigger values, just + // safely wrap around (the & is just a replacement for the quick + // modulo operation % 32). + mult = 1u << (fac & (sizeof(mult)*8u - 1u)); + + // don't use ldexp() either (poor performance) + return mult * fluid_ct2hz_tab[rem]; + } +} + +/* + * fluid_ct2hz + */ +fluid_real_t +fluid_ct2hz(fluid_real_t cents) +{ + /* Filter fc limit: SF2.01 page 48 # 8 */ + if(cents >= 13500) + { + cents = 13500; /* 20 kHz */ + } + else if(cents < 1500) + { + cents = 1500; /* 20 Hz */ + } + + return fluid_ct2hz_real(cents); +} + +/* + * fluid_cb2amp + * + * in: a value between 0 and 1440, 0 is no attenuation + * out: a value between 1 and 0 + */ +fluid_real_t +fluid_cb2amp(fluid_real_t cb) +{ + /* + * cb: an attenuation in 'centibels' (1/10 dB) + * SF2.01 page 49 # 48 limits it to 144 dB. + * 96 dB is reasonable for 16 bit systems, 144 would make sense for 24 bit. + */ + + /* minimum attenuation: 0 dB */ + if(cb < 0) + { + return 1.0; + } + + if(cb >= FLUID_CB_AMP_SIZE) + { + return 0.0; + } + + return fluid_cb2amp_tab[(int) cb]; +} + +/* + * fluid_tc2sec_delay + */ +fluid_real_t +fluid_tc2sec_delay(fluid_real_t tc) +{ + /* SF2.01 section 8.1.2 items 21, 23, 25, 33 + * SF2.01 section 8.1.3 items 21, 23, 25, 33 + * + * The most negative number indicates a delay of 0. Range is limited + * from -12000 to 5000 */ + if(tc <= -32768.0f) + { + return (fluid_real_t) 0.0f; + }; + + if(tc < -12000.f) + { + tc = (fluid_real_t) -12000.0f; + } + + if(tc > 5000.0f) + { + tc = (fluid_real_t) 5000.0f; + } + + return FLUID_POW(2.f, tc / 1200.f); +} + +/* + * fluid_tc2sec_attack + */ +fluid_real_t +fluid_tc2sec_attack(fluid_real_t tc) +{ + /* SF2.01 section 8.1.2 items 26, 34 + * SF2.01 section 8.1.3 items 26, 34 + * The most negative number indicates a delay of 0 + * Range is limited from -12000 to 8000 */ + if(tc <= -32768.f) + { + return (fluid_real_t) 0.f; + }; + + if(tc < -12000.f) + { + tc = (fluid_real_t) -12000.f; + }; + + if(tc > 8000.f) + { + tc = (fluid_real_t) 8000.f; + }; + + return FLUID_POW(2.f, tc / 1200.f); +} + +/* + * fluid_tc2sec + */ +fluid_real_t +fluid_tc2sec(fluid_real_t tc) +{ + /* No range checking here! */ + return FLUID_POW(2.f, tc / 1200.f); +} + +/* + * fluid_tc2sec_release + */ +fluid_real_t +fluid_tc2sec_release(fluid_real_t tc) +{ + /* SF2.01 section 8.1.2 items 30, 38 + * SF2.01 section 8.1.3 items 30, 38 + * No 'most negative number' rule here! + * Range is limited from -12000 to 8000 */ + if(tc <= -32768.f) + { + return (fluid_real_t) 0.f; + }; + + if(tc < -12000.f) + { + tc = (fluid_real_t) -12000.f; + }; + + if(tc > 8000.f) + { + tc = (fluid_real_t) 8000.f; + }; + + return FLUID_POW(2.f, tc / 1200.f); +} + +/* + * fluid_act2hz + * + * Convert from absolute cents to Hertz + * + * The inverse operation, converting from Hertz to cents, was unused and implemented as + * +fluid_hz2ct(fluid_real_t f) +{ + return 6900.f + (1200.f / FLUID_M_LN2) * FLUID_LOGF(f / 440.0f)); +} + */ +double +fluid_act2hz(double c) +{ + // do not use FLUID_POW, otherwise the unit tests will fail when compiled in single precision + return 8.1757989156437073336828122976032719176391831357 * pow(2.f, c / 1200.f); +} + +/* + * fluid_pan + */ +fluid_real_t +fluid_pan(fluid_real_t c, int left) +{ + if(left) + { + c = -c; + } + + if(c <= -500.f) + { + return (fluid_real_t) 0.f; + } + else if(c >= 500.f) + { + return (fluid_real_t) 1.f; + } + else + { + return fluid_pan_tab[(int)(c) + 500]; + } +} + +/* + * Return the amount of attenuation based on the balance for the specified + * channel. If balance is negative (turned toward left channel, only the right + * channel is attenuated. If balance is positive, only the left channel is + * attenuated. + * + * @params balance left/right balance, range [-960;960] in absolute centibels + * @return amount of attenuation [0.0;1.0] + */ +fluid_real_t fluid_balance(fluid_real_t balance, int left) +{ + /* This is the most common case */ + if(balance == 0.f) + { + return 1.0f; + } + + if((left && balance < 0.f) || (!left && balance > 0.f)) + { + return 1.0f; + } + + if(balance < 0.f) + { + balance = -balance; + } + + return fluid_cb2amp(balance); +} + +/* + * fluid_concave + */ +fluid_real_t +fluid_concave(fluid_real_t val) +{ + int ival = (int)val; + if(val < 0.f) + { + return 0.f; + } + else if (ival >= FLUID_VEL_CB_SIZE - 1) + { + return fluid_concave_tab[FLUID_VEL_CB_SIZE - 1]; + } + + return fluid_concave_tab[ival] + (fluid_concave_tab[ival + 1] - fluid_concave_tab[ival]) * (val - ival); +} + +/* + * fluid_convex + */ +fluid_real_t +fluid_convex(fluid_real_t val) +{ + int ival = (int)val; + if(val < 0.f) + { + return 0.f; + } + else if (ival >= FLUID_VEL_CB_SIZE - 1) + { + return fluid_convex_tab[FLUID_VEL_CB_SIZE - 1]; + } + + // interpolation between convex steps: fixes bad sounds with modenv and filter cutoff + return fluid_convex_tab[ival] + (fluid_convex_tab[ival + 1] - fluid_convex_tab[ival]) * (val - ival); +} + diff --git a/libs/fluidsynth/src/utils/fluid_conv.h b/libs/fluidsynth/src/utils/fluid_conv.h new file mode 100644 index 00000000000..985c02d0b38 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_conv.h @@ -0,0 +1,40 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_CONV_H +#define _FLUID_CONV_H + +#include "fluidsynth_priv.h" +#include "utils/fluid_conv_tables.h" + +fluid_real_t fluid_ct2hz_real(fluid_real_t cents); +fluid_real_t fluid_ct2hz(fluid_real_t cents); +fluid_real_t fluid_cb2amp(fluid_real_t cb); +fluid_real_t fluid_tc2sec(fluid_real_t tc); +fluid_real_t fluid_tc2sec_delay(fluid_real_t tc); +fluid_real_t fluid_tc2sec_attack(fluid_real_t tc); +fluid_real_t fluid_tc2sec_release(fluid_real_t tc); +double fluid_act2hz(double c); +fluid_real_t fluid_pan(fluid_real_t c, int left); +fluid_real_t fluid_balance(fluid_real_t balance, int left); +fluid_real_t fluid_concave(fluid_real_t val); +fluid_real_t fluid_convex(fluid_real_t val); + +#endif /* _FLUID_CONV_H */ diff --git a/libs/fluidsynth/src/utils/fluid_conv_tables.h b/libs/fluidsynth/src/utils/fluid_conv_tables.h new file mode 100644 index 00000000000..8d1ae71540a --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_conv_tables.h @@ -0,0 +1,41 @@ + +#ifndef _FLUID_CONV_TABLES_H +#define _FLUID_CONV_TABLES_H + +/* + Attenuation range in centibels. + Attenuation range is the dynamic range of the volume envelope generator + from 0 to the end of attack segment. + fluidsynth is a 24 bit synth, it could (should??) be 144 dB of attenuation. + However the spec makes no distinction between 16 or 24 bit synths, so use + 96 dB here. + + Note about usefulness of 24 bits: + 1)Even fluidsynth is a 24 bit synth, this format is only relevant if + the sample format coming from the soundfont is 24 bits and the audio sample format + chosen by the application (audio.sample.format) is not 16 bits. + + 2)When the sample soundfont is 16 bits, the internal 24 bits number have + 16 bits msb and lsb to 0. Consequently, at the DAC output, the dynamic range of + this 24 bit sample is reduced to the the dynamic of a 16 bits sample (ie 90 db) + even if this sample is produced by the audio driver using an audio sample format + compatible for a 24 bit DAC. + + 3)When the audio sample format settings is 16 bits (audio.sample.format), the + audio driver will make use of a 16 bit DAC, and the dynamic will be reduced to 96 dB + even if the initial sample comes from a 24 bits soundfont. + + In both cases (2) or (3), the real dynamic range is only 96 dB. + + Other consideration for FLUID_NOISE_FLOOR related to case (1),(2,3): + - for case (1), FLUID_NOISE_FLOOR should be the noise floor for 24 bits (i.e -138 dB). + - for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB). + */ +#define FLUID_PEAK_ATTENUATION 960.0f + +#define FLUID_CENTS_HZ_SIZE 1200 +#define FLUID_VEL_CB_SIZE 128 +#define FLUID_CB_AMP_SIZE 1441 +#define FLUID_PAN_SIZE 1002 + +#endif diff --git a/libs/fluidsynth/src/utils/fluid_hash.c b/libs/fluidsynth/src/utils/fluid_hash.c new file mode 100644 index 00000000000..7efd0deddad --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_hash.c @@ -0,0 +1,1407 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 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 Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02110-1301, USA. + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + * + * Adapted for FluidSynth use by Josh Green jgreen@users.sourceforge.net + * September 8, 2009 from glib 2.18.4 + */ + +/* + * MT safe + */ + +#include "fluid_sys.h" +#include "fluid_hash.h" +#include "fluid_list.h" + + +#define HASH_TABLE_MIN_SIZE 11 +#define HASH_TABLE_MAX_SIZE 13845163 + + +typedef struct +{ + fluid_hashtable_t *hashtable; + fluid_hashnode_t *prev_node; + fluid_hashnode_t *node; + int position; + int pre_advanced; // Boolean + int version; +} RealIter; + + +/* Excerpt from glib gprimes.c */ + +static const unsigned int primes[] = +{ + 11, + 19, + 37, + 73, + 109, + 163, + 251, + 367, + 557, + 823, + 1237, + 1861, + 2777, + 4177, + 6247, + 9371, + 14057, + 21089, + 31627, + 47431, + 71143, + 106721, + 160073, + 240101, + 360163, + 540217, + 810343, + 1215497, + 1823231, + 2734867, + 4102283, + 6153409, + 9230113, + 13845163, +}; + +static const unsigned int nprimes = FLUID_N_ELEMENTS(primes); + +static unsigned int +spaced_primes_closest(unsigned int num) +{ + unsigned int i; + + for(i = 0; i < nprimes; i++) + { + if(primes[i] > num) + { + return primes[i]; + } + } + + return primes[nprimes - 1]; +} + +/* End excerpt from glib gprimes.c */ + + +/* + * @hashtable: our #fluid_hashtable_t + * @key: the key to lookup against + * @hash_return: optional key hash return location + * Return value: a pointer to the described #fluid_hashnode_t pointer + * + * Performs a lookup in the hash table. Virtually all hash operations + * will use this function internally. + * + * This function first computes the hash value of the key using the + * user's hash function. + * + * If an entry in the table matching @key is found then this function + * returns a pointer to the pointer to that entry in the table. In + * the case that the entry is at the head of a chain, this pointer + * will be an item in the nodes[] array. In the case that the entry + * is not at the head of a chain, this pointer will be the ->next + * pointer on the node that precedes it. + * + * In the case that no matching entry exists in the table, a pointer + * to a %NULL pointer will be returned. To insert a item, this %NULL + * pointer should be updated to point to the new #fluid_hashnode_t. + * + * If @hash_return is a pass-by-reference parameter. If it is + * non-%NULL then the computed hash value is returned. This is to + * save insertions from having to compute the hash record again for + * the new record. + */ +static FLUID_INLINE fluid_hashnode_t ** +fluid_hashtable_lookup_node(fluid_hashtable_t *hashtable, const void *key, + unsigned int *hash_return) +{ + fluid_hashnode_t **node_ptr, *node; + unsigned int hash_value; + + hash_value = (* hashtable->hash_func)(key); + node_ptr = &hashtable->nodes[hash_value % hashtable->size]; + + if(hash_return) + { + *hash_return = hash_value; + } + + /* Hash table lookup needs to be fast. + * We therefore remove the extra conditional of testing + * whether to call the key_equal_func or not from + * the inner loop. + * + * Additional optimisation: first check if our full hash + * values are equal so we can avoid calling the full-blown + * key equality function in most cases. + */ + if(hashtable->key_equal_func) + { + while((node = *node_ptr)) + { + if(node->key_hash == hash_value && + hashtable->key_equal_func(node->key, key)) + { + break; + } + + node_ptr = &(*node_ptr)->next; + } + } + else + { + while((node = *node_ptr)) + { + if(node->key == key) + { + break; + } + + node_ptr = &(*node_ptr)->next; + } + } + + return node_ptr; +} + +/* + * @hashtable: our #fluid_hashtable_t + * @node_ptr_ptr: a pointer to the return value from + * fluid_hashtable_lookup_node() + * @notify: %TRUE if the destroy notify handlers are to be called + * + * Removes a node from the hash table and updates the node count. The + * node is freed. No table resize is performed. + * + * If @notify is %TRUE then the destroy notify functions are called + * for the key and value of the hash node. + * + * @node_ptr_ptr is a pass-by-reference in/out parameter. When the + * function is called, it should point to the pointer to the node to + * remove. This level of indirection is required so that the pointer + * may be updated appropriately once the node has been removed. + * + * Before the function returns, the pointer at @node_ptr_ptr will be + * updated to point to the position in the table that contains the + * pointer to the "next" node in the chain. This makes this function + * convenient to use from functions that iterate over the entire + * table. If there is no further item in the chain then the + * #fluid_hashnode_t pointer will be %NULL (ie: **node_ptr_ptr == %NULL). + * + * Since the pointer in the table to the removed node is replaced with + * either a pointer to the next node or a %NULL pointer as + * appropriate, the pointer at the end of @node_ptr_ptr will never be + * modified at all. Stay tuned. :) + */ +static void +fluid_hashtable_remove_node(fluid_hashtable_t *hashtable, + fluid_hashnode_t ***node_ptr_ptr, int notify) +{ + fluid_hashnode_t **node_ptr, *node; + + node_ptr = *node_ptr_ptr; + node = *node_ptr; + + *node_ptr = node->next; + + if(notify && hashtable->key_destroy_func) + { + hashtable->key_destroy_func(node->key); + } + + if(notify && hashtable->value_destroy_func) + { + hashtable->value_destroy_func(node->value); + } + + FLUID_FREE(node); + + hashtable->nnodes--; +} + +/* + * fluid_hashtable_remove_all_nodes: + * @hashtable: our #fluid_hashtable_t + * @notify: %TRUE if the destroy notify handlers are to be called + * + * Removes all nodes from the table. Since this may be a precursor to + * freeing the table entirely, no resize is performed. + * + * If @notify is %TRUE then the destroy notify functions are called + * for the key and value of the hash node. + */ +static void +fluid_hashtable_remove_all_nodes(fluid_hashtable_t *hashtable, int notify) +{ + fluid_hashnode_t **node_ptr; + int i; + + for(i = 0; i < hashtable->size; i++) + { + for(node_ptr = &hashtable->nodes[i]; *node_ptr != NULL;) + { + fluid_hashtable_remove_node(hashtable, &node_ptr, notify); + } + } + + hashtable->nnodes = 0; +} + +/* + * fluid_hashtable_resize: + * @hashtable: our #fluid_hashtable_t + * + * Resizes the hash table to the optimal size based on the number of + * nodes currently held. If you call this function then a resize will + * occur, even if one does not need to occur. Use + * fluid_hashtable_maybe_resize() instead. + */ +static void +fluid_hashtable_resize(fluid_hashtable_t *hashtable) +{ + fluid_hashnode_t **new_nodes; + fluid_hashnode_t *node; + fluid_hashnode_t *next; + unsigned int hash_val; + int new_size; + int i; + + new_size = spaced_primes_closest(hashtable->nnodes); + new_size = (new_size < HASH_TABLE_MIN_SIZE) ? HASH_TABLE_MIN_SIZE : + ((new_size > HASH_TABLE_MAX_SIZE) ? HASH_TABLE_MAX_SIZE : new_size); + + new_nodes = FLUID_ARRAY(fluid_hashnode_t *, new_size); + + if(!new_nodes) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return; + } + + FLUID_MEMSET(new_nodes, 0, new_size * sizeof(fluid_hashnode_t *)); + + for(i = 0; i < hashtable->size; i++) + { + for(node = hashtable->nodes[i]; node; node = next) + { + next = node->next; + + hash_val = node->key_hash % new_size; + + node->next = new_nodes[hash_val]; + new_nodes[hash_val] = node; + } + } + + FLUID_FREE(hashtable->nodes); + hashtable->nodes = new_nodes; + hashtable->size = new_size; +} + +/* + * fluid_hashtable_maybe_resize: + * @hashtable: our #fluid_hashtable_t + * + * Resizes the hash table, if needed. + * + * Essentially, calls fluid_hashtable_resize() if the table has strayed + * too far from its ideal size for its number of nodes. + */ +static FLUID_INLINE void +fluid_hashtable_maybe_resize(fluid_hashtable_t *hashtable) +{ + int nnodes = hashtable->nnodes; + int size = hashtable->size; + + if((size >= 3 * nnodes && size > HASH_TABLE_MIN_SIZE) || + (3 * size <= nnodes && size < HASH_TABLE_MAX_SIZE)) + { + fluid_hashtable_resize(hashtable); + } +} + +/** + * new_fluid_hashtable: + * @hash_func: a function to create a hash value from a key. + * Hash values are used to determine where keys are stored within the + * #fluid_hashtable_t data structure. The fluid_direct_hash(), fluid_int_hash() and + * fluid_str_hash() functions are provided for some common types of keys. + * If hash_func is %NULL, fluid_direct_hash() is used. + * @key_equal_func: a function to check two keys for equality. This is + * used when looking up keys in the #fluid_hashtable_t. The fluid_direct_equal(), + * fluid_int_equal() and fluid_str_equal() functions are provided for the most + * common types of keys. If @key_equal_func is %NULL, keys are compared + * directly in a similar fashion to fluid_direct_equal(), but without the + * overhead of a function call. + * + * Creates a new #fluid_hashtable_t with a reference count of 1. + * + * Return value: a new #fluid_hashtable_t. + **/ +fluid_hashtable_t * +new_fluid_hashtable(fluid_hash_func_t hash_func, fluid_equal_func_t key_equal_func) +{ + return new_fluid_hashtable_full(hash_func, key_equal_func, NULL, NULL); +} + + +/** + * new_fluid_hashtable_full: + * @hash_func: a function to create a hash value from a key. + * @key_equal_func: a function to check two keys for equality. + * @key_destroy_func: a function to free the memory allocated for the key + * used when removing the entry from the #fluid_hashtable_t or %NULL if you + * don't want to supply such a function. + * @value_destroy_func: a function to free the memory allocated for the + * value used when removing the entry from the #fluid_hashtable_t or %NULL if + * you don't want to supply such a function. + * + * Creates a new #fluid_hashtable_t like fluid_hashtable_new() with a reference count + * of 1 and allows to specify functions to free the memory allocated for the + * key and value that get called when removing the entry from the #fluid_hashtable_t. + * + * Return value: a new #fluid_hashtable_t. + **/ +fluid_hashtable_t * +new_fluid_hashtable_full(fluid_hash_func_t hash_func, + fluid_equal_func_t key_equal_func, + fluid_destroy_notify_t key_destroy_func, + fluid_destroy_notify_t value_destroy_func) +{ + fluid_hashtable_t *hashtable; + + hashtable = FLUID_NEW(fluid_hashtable_t); + + if(!hashtable) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + hashtable->size = HASH_TABLE_MIN_SIZE; + hashtable->nnodes = 0; + hashtable->hash_func = hash_func ? hash_func : fluid_direct_hash; + hashtable->key_equal_func = key_equal_func; + fluid_atomic_int_set(&hashtable->ref_count, 1); + hashtable->key_destroy_func = key_destroy_func; + hashtable->value_destroy_func = value_destroy_func; + hashtable->nodes = FLUID_ARRAY(fluid_hashnode_t *, hashtable->size); + if(hashtable->nodes == NULL) + { + delete_fluid_hashtable(hashtable); + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + FLUID_MEMSET(hashtable->nodes, 0, hashtable->size * sizeof(*hashtable->nodes)); + + return hashtable; +} + +/** + * fluid_hashtable_iter_init: + * @iter: an uninitialized #fluid_hashtable_iter_t. + * @hashtable: a #fluid_hashtable_t. + * + * Initializes a key/value pair iterator and associates it with + * @hashtable. Modifying the hash table after calling this function + * invalidates the returned iterator. + * |[ + * fluid_hashtable_iter_t iter; + * gpointer key, value; + * + * fluid_hashtable_iter_init (&iter, hashtable); + * while (fluid_hashtable_iter_next (&iter, &key, &value)) + * { + * /* do something with key and value */ + * } + * ]| + * + * Since: 2.16 + **/ +void +fluid_hashtable_iter_init(fluid_hashtable_iter_t *iter, + fluid_hashtable_t *hashtable) +{ + RealIter *ri = (RealIter *) iter; + + fluid_return_if_fail(iter != NULL); + fluid_return_if_fail(hashtable != NULL); + + ri->hashtable = hashtable; + ri->prev_node = NULL; + ri->node = NULL; + ri->position = -1; + ri->pre_advanced = FALSE; +} + +/** + * fluid_hashtable_iter_next: + * @iter: an initialized #fluid_hashtable_iter_t. + * @key: a location to store the key, or %NULL. + * @value: a location to store the value, or %NULL. + * + * Advances @iter and retrieves the key and/or value that are now + * pointed to as a result of this advancement. If %FALSE is returned, + * @key and @value are not set, and the iterator becomes invalid. + * + * Return value: %FALSE if the end of the #fluid_hashtable_t has been reached. + * + * Since: 2.16 + **/ +int +fluid_hashtable_iter_next(fluid_hashtable_iter_t *iter, void **key, + void **value) +{ + RealIter *ri = (RealIter *) iter; + + fluid_return_val_if_fail(iter != NULL, FALSE); + + if(ri->pre_advanced) + { + ri->pre_advanced = FALSE; + + if(ri->node == NULL) + { + return FALSE; + } + } + else + { + if(ri->node != NULL) + { + ri->prev_node = ri->node; + ri->node = ri->node->next; + } + + while(ri->node == NULL) + { + ri->position++; + + if(ri->position >= ri->hashtable->size) + { + return FALSE; + } + + ri->prev_node = NULL; + ri->node = ri->hashtable->nodes[ri->position]; + } + } + + if(key != NULL) + { + *key = ri->node->key; + } + + if(value != NULL) + { + *value = ri->node->value; + } + + return TRUE; +} + +/** + * fluid_hashtable_iter_get_hash_table: + * @iter: an initialized #fluid_hashtable_iter_t. + * + * Returns the #fluid_hashtable_t associated with @iter. + * + * Return value: the #fluid_hashtable_t associated with @iter. + * + * Since: 2.16 + **/ +fluid_hashtable_t * +fluid_hashtable_iter_get_hash_table(fluid_hashtable_iter_t *iter) +{ + fluid_return_val_if_fail(iter != NULL, NULL); + + return ((RealIter *) iter)->hashtable; +} + +static void +iter_remove_or_steal(RealIter *ri, int notify) +{ + fluid_hashnode_t *prev; + fluid_hashnode_t *node; + int position; + + fluid_return_if_fail(ri != NULL); + fluid_return_if_fail(ri->node != NULL); + + prev = ri->prev_node; + node = ri->node; + position = ri->position; + + /* pre-advance the iterator since we will remove the node */ + + ri->node = ri->node->next; + /* ri->prev_node is still the correct previous node */ + + while(ri->node == NULL) + { + ri->position++; + + if(ri->position >= ri->hashtable->size) + { + break; + } + + ri->prev_node = NULL; + ri->node = ri->hashtable->nodes[ri->position]; + } + + ri->pre_advanced = TRUE; + + /* remove the node */ + + if(prev != NULL) + { + prev->next = node->next; + } + else + { + ri->hashtable->nodes[position] = node->next; + } + + if(notify) + { + if(ri->hashtable->key_destroy_func) + { + ri->hashtable->key_destroy_func(node->key); + } + + if(ri->hashtable->value_destroy_func) + { + ri->hashtable->value_destroy_func(node->value); + } + } + + FLUID_FREE(node); + + ri->hashtable->nnodes--; +} + +/** + * fluid_hashtable_iter_remove(): + * @iter: an initialized #fluid_hashtable_iter_t. + * + * Removes the key/value pair currently pointed to by the iterator + * from its associated #fluid_hashtable_t. Can only be called after + * fluid_hashtable_iter_next() returned %TRUE, and cannot be called more + * than once for the same key/value pair. + * + * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the + * key and value are freed using the supplied destroy functions, otherwise + * you have to make sure that any dynamically allocated values are freed + * yourself. + * + * Since: 2.16 + **/ +void +fluid_hashtable_iter_remove(fluid_hashtable_iter_t *iter) +{ + iter_remove_or_steal((RealIter *) iter, TRUE); +} + +/** + * fluid_hashtable_iter_steal(): + * @iter: an initialized #fluid_hashtable_iter_t. + * + * Removes the key/value pair currently pointed to by the iterator + * from its associated #fluid_hashtable_t, without calling the key and value + * destroy functions. Can only be called after + * fluid_hashtable_iter_next() returned %TRUE, and cannot be called more + * than once for the same key/value pair. + * + * Since: 2.16 + **/ +void +fluid_hashtable_iter_steal(fluid_hashtable_iter_t *iter) +{ + iter_remove_or_steal((RealIter *) iter, FALSE); +} + + +/** + * fluid_hashtable_ref: + * @hashtable: a valid #fluid_hashtable_t. + * + * Atomically increments the reference count of @hashtable by one. + * This function is MT-safe and may be called from any thread. + * + * Return value: the passed in #fluid_hashtable_t. + * + * Since: 2.10 + **/ +fluid_hashtable_t * +fluid_hashtable_ref(fluid_hashtable_t *hashtable) +{ + fluid_return_val_if_fail(hashtable != NULL, NULL); + fluid_return_val_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0, hashtable); + + fluid_atomic_int_add(&hashtable->ref_count, 1); + return hashtable; +} + +/** + * fluid_hashtable_unref: + * @hashtable: a valid #fluid_hashtable_t. + * + * Atomically decrements the reference count of @hashtable by one. + * If the reference count drops to 0, all keys and values will be + * destroyed, and all memory allocated by the hash table is released. + * This function is MT-safe and may be called from any thread. + * + * Since: 2.10 + **/ +void +fluid_hashtable_unref(fluid_hashtable_t *hashtable) +{ + fluid_return_if_fail(hashtable != NULL); + fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); + + if(fluid_atomic_int_exchange_and_add(&hashtable->ref_count, -1) - 1 == 0) + { + fluid_hashtable_remove_all_nodes(hashtable, TRUE); + FLUID_FREE(hashtable->nodes); + FLUID_FREE(hashtable); + } +} + +/** + * delete_fluid_hashtable: + * @hashtable: a #fluid_hashtable_t. + * + * Destroys all keys and values in the #fluid_hashtable_t and decrements its + * reference count by 1. If keys and/or values are dynamically allocated, + * you should either free them first or create the #fluid_hashtable_t with destroy + * notifiers using fluid_hashtable_new_full(). In the latter case the destroy + * functions you supplied will be called on all keys and values during the + * destruction phase. + **/ +void +delete_fluid_hashtable(fluid_hashtable_t *hashtable) +{ + fluid_return_if_fail(hashtable != NULL); + fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); + + fluid_hashtable_remove_all(hashtable); + fluid_hashtable_unref(hashtable); +} + +/** + * fluid_hashtable_lookup: + * @hashtable: a #fluid_hashtable_t. + * @key: the key to look up. + * + * Looks up a key in a #fluid_hashtable_t. Note that this function cannot + * distinguish between a key that is not present and one which is present + * and has the value %NULL. If you need this distinction, use + * fluid_hashtable_lookup_extended(). + * + * Return value: the associated value, or %NULL if the key is not found. + **/ +void * +fluid_hashtable_lookup(fluid_hashtable_t *hashtable, const void *key) +{ + fluid_hashnode_t *node; + + fluid_return_val_if_fail(hashtable != NULL, NULL); + + node = *fluid_hashtable_lookup_node(hashtable, key, NULL); + + return node ? node->value : NULL; +} + +/** + * fluid_hashtable_lookup_extended: + * @hashtable: a #fluid_hashtable_t. + * @lookup_key: the key to look up. + * @orig_key: returns the original key. + * @value: returns the value associated with the key. + * + * Looks up a key in the #fluid_hashtable_t, returning the original key and the + * associated value and a #gboolean which is %TRUE if the key was found. This + * is useful if you need to free the memory allocated for the original key, + * for example before calling fluid_hashtable_remove(). + * + * Return value: %TRUE if the key was found in the #fluid_hashtable_t. + **/ +int +fluid_hashtable_lookup_extended(fluid_hashtable_t *hashtable, + const void *lookup_key, + void **orig_key, void **value) +{ + fluid_hashnode_t *node; + + fluid_return_val_if_fail(hashtable != NULL, FALSE); + + node = *fluid_hashtable_lookup_node(hashtable, lookup_key, NULL); + + if(node == NULL) + { + return FALSE; + } + + if(orig_key) + { + *orig_key = node->key; + } + + if(value) + { + *value = node->value; + } + + return TRUE; +} + +/* + * fluid_hashtable_insert_internal: + * @hashtable: our #fluid_hashtable_t + * @key: the key to insert + * @value: the value to insert + * @keep_new_key: if %TRUE and this key already exists in the table + * then call the destroy notify function on the old key. If %FALSE + * then call the destroy notify function on the new key. + * + * Implements the common logic for the fluid_hashtable_insert() and + * fluid_hashtable_replace() functions. + * + * Do a lookup of @key. If it is found, replace it with the new + * @value (and perhaps the new @key). If it is not found, create a + * new node. + */ +static void +fluid_hashtable_insert_internal(fluid_hashtable_t *hashtable, void *key, + void *value, int keep_new_key) +{ + fluid_hashnode_t **node_ptr, *node; + unsigned int key_hash; + + fluid_return_if_fail(hashtable != NULL); + fluid_return_if_fail(fluid_atomic_int_get(&hashtable->ref_count) > 0); + + node_ptr = fluid_hashtable_lookup_node(hashtable, key, &key_hash); + + if((node = *node_ptr)) + { + if(keep_new_key) + { + if(hashtable->key_destroy_func) + { + hashtable->key_destroy_func(node->key); + } + + node->key = key; + } + else + { + if(hashtable->key_destroy_func) + { + hashtable->key_destroy_func(key); + } + } + + if(hashtable->value_destroy_func) + { + hashtable->value_destroy_func(node->value); + } + + node->value = value; + } + else + { + node = FLUID_NEW(fluid_hashnode_t); + + if(!node) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return; + } + + node->key = key; + node->value = value; + node->key_hash = key_hash; + node->next = NULL; + + *node_ptr = node; + hashtable->nnodes++; + fluid_hashtable_maybe_resize(hashtable); + } +} + +/** + * fluid_hashtable_insert: + * @hashtable: a #fluid_hashtable_t. + * @key: a key to insert. + * @value: the value to associate with the key. + * + * Inserts a new key and value into a #fluid_hashtable_t. + * + * If the key already exists in the #fluid_hashtable_t its current value is replaced + * with the new value. If you supplied a @value_destroy_func when creating the + * #fluid_hashtable_t, the old value is freed using that function. If you supplied + * a @key_destroy_func when creating the #fluid_hashtable_t, the passed key is freed + * using that function. + **/ +void +fluid_hashtable_insert(fluid_hashtable_t *hashtable, void *key, void *value) +{ + fluid_hashtable_insert_internal(hashtable, key, value, FALSE); +} + +/** + * fluid_hashtable_replace: + * @hashtable: a #fluid_hashtable_t. + * @key: a key to insert. + * @value: the value to associate with the key. + * + * Inserts a new key and value into a #fluid_hashtable_t similar to + * fluid_hashtable_insert(). The difference is that if the key already exists + * in the #fluid_hashtable_t, it gets replaced by the new key. If you supplied a + * @value_destroy_func when creating the #fluid_hashtable_t, the old value is freed + * using that function. If you supplied a @key_destroy_func when creating the + * #fluid_hashtable_t, the old key is freed using that function. + **/ +void +fluid_hashtable_replace(fluid_hashtable_t *hashtable, void *key, void *value) +{ + fluid_hashtable_insert_internal(hashtable, key, value, TRUE); +} + +/* + * fluid_hashtable_remove_internal: + * @hashtable: our #fluid_hashtable_t + * @key: the key to remove + * @notify: %TRUE if the destroy notify handlers are to be called + * Return value: %TRUE if a node was found and removed, else %FALSE + * + * Implements the common logic for the fluid_hashtable_remove() and + * fluid_hashtable_steal() functions. + * + * Do a lookup of @key and remove it if it is found, calling the + * destroy notify handlers only if @notify is %TRUE. + */ +static int +fluid_hashtable_remove_internal(fluid_hashtable_t *hashtable, const void *key, + int notify) +{ + fluid_hashnode_t **node_ptr; + + fluid_return_val_if_fail(hashtable != NULL, FALSE); + + node_ptr = fluid_hashtable_lookup_node(hashtable, key, NULL); + + if(*node_ptr == NULL) + { + return FALSE; + } + + fluid_hashtable_remove_node(hashtable, &node_ptr, notify); + fluid_hashtable_maybe_resize(hashtable); + + return TRUE; +} + +/** + * fluid_hashtable_remove: + * @hashtable: a #fluid_hashtable_t. + * @key: the key to remove. + * + * Removes a key and its associated value from a #fluid_hashtable_t. + * + * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the + * key and value are freed using the supplied destroy functions, otherwise + * you have to make sure that any dynamically allocated values are freed + * yourself. + * + * Return value: %TRUE if the key was found and removed from the #fluid_hashtable_t. + **/ +int +fluid_hashtable_remove(fluid_hashtable_t *hashtable, const void *key) +{ + return fluid_hashtable_remove_internal(hashtable, key, TRUE); +} + +/** + * fluid_hashtable_steal: + * @hashtable: a #fluid_hashtable_t. + * @key: the key to remove. + * + * Removes a key and its associated value from a #fluid_hashtable_t without + * calling the key and value destroy functions. + * + * Return value: %TRUE if the key was found and removed from the #fluid_hashtable_t. + **/ +int +fluid_hashtable_steal(fluid_hashtable_t *hashtable, const void *key) +{ + return fluid_hashtable_remove_internal(hashtable, key, FALSE); +} + +/** + * fluid_hashtable_remove_all: + * @hashtable: a #fluid_hashtable_t + * + * Removes all keys and their associated values from a #fluid_hashtable_t. + * + * If the #fluid_hashtable_t was created using fluid_hashtable_new_full(), the keys + * and values are freed using the supplied destroy functions, otherwise you + * have to make sure that any dynamically allocated values are freed + * yourself. + * + * Since: 2.12 + **/ +void +fluid_hashtable_remove_all(fluid_hashtable_t *hashtable) +{ + fluid_return_if_fail(hashtable != NULL); + + fluid_hashtable_remove_all_nodes(hashtable, TRUE); + fluid_hashtable_maybe_resize(hashtable); +} + +/** + * fluid_hashtable_steal_all: + * @hashtable: a #fluid_hashtable_t. + * + * Removes all keys and their associated values from a #fluid_hashtable_t + * without calling the key and value destroy functions. + * + * Since: 2.12 + **/ +void +fluid_hashtable_steal_all(fluid_hashtable_t *hashtable) +{ + fluid_return_if_fail(hashtable != NULL); + + fluid_hashtable_remove_all_nodes(hashtable, FALSE); + fluid_hashtable_maybe_resize(hashtable); +} + +/* + * fluid_hashtable_foreach_remove_or_steal: + * @hashtable: our #fluid_hashtable_t + * @func: the user's callback function + * @user_data: data for @func + * @notify: %TRUE if the destroy notify handlers are to be called + * + * Implements the common logic for fluid_hashtable_foreach_remove() and + * fluid_hashtable_foreach_steal(). + * + * Iterates over every node in the table, calling @func with the key + * and value of the node (and @user_data). If @func returns %TRUE the + * node is removed from the table. + * + * If @notify is true then the destroy notify handlers will be called + * for each removed node. + */ +static unsigned int +fluid_hashtable_foreach_remove_or_steal(fluid_hashtable_t *hashtable, + fluid_hr_func_t func, void *user_data, + int notify) +{ + fluid_hashnode_t *node, **node_ptr; + unsigned int deleted = 0; + int i; + + for(i = 0; i < hashtable->size; i++) + { + for(node_ptr = &hashtable->nodes[i]; (node = *node_ptr) != NULL;) + { + if((* func)(node->key, node->value, user_data)) + { + fluid_hashtable_remove_node(hashtable, &node_ptr, notify); + deleted++; + } + else + { + node_ptr = &node->next; + } + } + } + + fluid_hashtable_maybe_resize(hashtable); + + return deleted; +} + +#if 0 +/** + * fluid_hashtable_foreach_remove: + * @hashtable: a #fluid_hashtable_t. + * @func: the function to call for each key/value pair. + * @user_data: user data to pass to the function. + * + * Calls the given function for each key/value pair in the #fluid_hashtable_t. + * If the function returns %TRUE, then the key/value pair is removed from the + * #fluid_hashtable_t. If you supplied key or value destroy functions when creating + * the #fluid_hashtable_t, they are used to free the memory allocated for the removed + * keys and values. + * + * See #fluid_hashtable_iter_t for an alternative way to loop over the + * key/value pairs in the hash table. + * + * Return value: the number of key/value pairs removed. + **/ +static unsigned int +fluid_hashtable_foreach_remove(fluid_hashtable_t *hashtable, + fluid_hr_func_t func, void *user_data) +{ + fluid_return_val_if_fail(hashtable != NULL, 0); + fluid_return_val_if_fail(func != NULL, 0); + + return fluid_hashtable_foreach_remove_or_steal(hashtable, func, user_data, TRUE); +} +#endif + +/** + * fluid_hashtable_foreach_steal: + * @hashtable: a #fluid_hashtable_t. + * @func: the function to call for each key/value pair. + * @user_data: user data to pass to the function. + * + * Calls the given function for each key/value pair in the #fluid_hashtable_t. + * If the function returns %TRUE, then the key/value pair is removed from the + * #fluid_hashtable_t, but no key or value destroy functions are called. + * + * See #fluid_hashtable_iter_t for an alternative way to loop over the + * key/value pairs in the hash table. + * + * Return value: the number of key/value pairs removed. + **/ +unsigned int +fluid_hashtable_foreach_steal(fluid_hashtable_t *hashtable, + fluid_hr_func_t func, void *user_data) +{ + fluid_return_val_if_fail(hashtable != NULL, 0); + fluid_return_val_if_fail(func != NULL, 0); + + return fluid_hashtable_foreach_remove_or_steal(hashtable, func, user_data, FALSE); +} + +/** + * fluid_hashtable_foreach: + * @hashtable: a #fluid_hashtable_t. + * @func: the function to call for each key/value pair. + * @user_data: user data to pass to the function. + * + * Calls the given function for each of the key/value pairs in the + * #fluid_hashtable_t. The function is passed the key and value of each + * pair, and the given @user_data parameter. The hash table may not + * be modified while iterating over it (you can't add/remove + * items). To remove all items matching a predicate, use + * fluid_hashtable_foreach_remove(). + * + * See fluid_hashtable_find() for performance caveats for linear + * order searches in contrast to fluid_hashtable_lookup(). + **/ +void +fluid_hashtable_foreach(fluid_hashtable_t *hashtable, fluid_hr_func_t func, + void *user_data) +{ + fluid_hashnode_t *node; + int i; + + fluid_return_if_fail(hashtable != NULL); + fluid_return_if_fail(func != NULL); + + for(i = 0; i < hashtable->size; i++) + { + for(node = hashtable->nodes[i]; node; node = node->next) + { + (* func)(node->key, node->value, user_data); + } + } +} + +/** + * fluid_hashtable_find: + * @hashtable: a #fluid_hashtable_t. + * @predicate: function to test the key/value pairs for a certain property. + * @user_data: user data to pass to the function. + * + * Calls the given function for key/value pairs in the #fluid_hashtable_t until + * @predicate returns %TRUE. The function is passed the key and value of + * each pair, and the given @user_data parameter. The hash table may not + * be modified while iterating over it (you can't add/remove items). + * + * Note, that hash tables are really only optimized for forward lookups, + * i.e. fluid_hashtable_lookup(). + * So code that frequently issues fluid_hashtable_find() or + * fluid_hashtable_foreach() (e.g. in the order of once per every entry in a + * hash table) should probably be reworked to use additional or different + * data structures for reverse lookups (keep in mind that an O(n) find/foreach + * operation issued for all n values in a hash table ends up needing O(n*n) + * operations). + * + * Return value: The value of the first key/value pair is returned, for which + * func evaluates to %TRUE. If no pair with the requested property is found, + * %NULL is returned. + * + * Since: 2.4 + **/ +void * +fluid_hashtable_find(fluid_hashtable_t *hashtable, fluid_hr_func_t predicate, + void *user_data) +{ + fluid_hashnode_t *node; + int i; + + fluid_return_val_if_fail(hashtable != NULL, NULL); + fluid_return_val_if_fail(predicate != NULL, NULL); + + for(i = 0; i < hashtable->size; i++) + { + for(node = hashtable->nodes[i]; node; node = node->next) + { + if(predicate(node->key, node->value, user_data)) + { + return node->value; + } + } + } + + return NULL; +} + +/** + * fluid_hashtable_size: + * @hashtable: a #fluid_hashtable_t. + * + * Returns the number of elements contained in the #fluid_hashtable_t. + * + * Return value: the number of key/value pairs in the #fluid_hashtable_t. + **/ +unsigned int +fluid_hashtable_size(fluid_hashtable_t *hashtable) +{ + fluid_return_val_if_fail(hashtable != NULL, 0); + + return hashtable->nnodes; +} + +/** + * fluid_hashtable_get_keys: + * @hashtable: a #fluid_hashtable_t + * + * Retrieves every key inside @hashtable. The returned data is valid + * until @hashtable is modified. + * + * Return value: a #GList containing all the keys inside the hash + * table. The content of the list is owned by the hash table and + * should not be modified or freed. Use delete_fluid_list() when done + * using the list. + * + * Since: 2.14 + */ +fluid_list_t * +fluid_hashtable_get_keys(fluid_hashtable_t *hashtable) +{ + fluid_hashnode_t *node; + int i; + fluid_list_t *retval; + + fluid_return_val_if_fail(hashtable != NULL, NULL); + + retval = NULL; + + for(i = 0; i < hashtable->size; i++) + { + for(node = hashtable->nodes[i]; node; node = node->next) + { + retval = fluid_list_prepend(retval, node->key); + } + } + + return retval; +} + +/** + * fluid_hashtable_get_values: + * @hashtable: a #fluid_hashtable_t + * + * Retrieves every value inside @hashtable. The returned data is + * valid until @hashtable is modified. + * + * Return value: a #GList containing all the values inside the hash + * table. The content of the list is owned by the hash table and + * should not be modified or freed. Use delete_fluid_list() when done + * using the list. + * + * Since: 2.14 + */ +fluid_list_t * +fluid_hashtable_get_values(fluid_hashtable_t *hashtable) +{ + fluid_hashnode_t *node; + int i; + fluid_list_t *retval; + + fluid_return_val_if_fail(hashtable != NULL, NULL); + + retval = NULL; + + for(i = 0; i < hashtable->size; i++) + { + for(node = hashtable->nodes[i]; node; node = node->next) + { + retval = fluid_list_prepend(retval, node->value); + } + } + + return retval; +} + + +/* Extracted from glib/gstring.c */ + + +/** + * fluid_str_equal: + * @v1: a key + * @v2: a key to compare with @v1 + * + * Compares two strings for byte-by-byte equality and returns %TRUE + * if they are equal. It can be passed to new_fluid_hashtable() as the + * @key_equal_func parameter, when using strings as keys in a #Ghashtable. + * + * Returns: %TRUE if the two keys match + */ +int +fluid_str_equal(const void *v1, const void *v2) +{ + const char *string1 = v1; + const char *string2 = v2; + + return FLUID_STRCMP(string1, string2) == 0; +} + +/** + * fluid_str_hash: + * @v: a string key + * + * Converts a string to a hash value. + * It can be passed to new_fluid_hashtable() as the @hash_func + * parameter, when using strings as keys in a #fluid_hashtable_t. + * + * Returns: a hash value corresponding to the key + */ +unsigned int +fluid_str_hash(const void *v) +{ + /* 31 bit hash function */ + const signed char *p = v; + uint32_t h = *p; + + if(h) + { + for(p += 1; *p != '\0'; p++) + { + h = (h << 5) - h + *p; + } + } + + return h; +} + + +/* Extracted from glib/gutils.c */ + + +/** + * fluid_direct_equal: + * @v1: a key. + * @v2: a key to compare with @v1. + * + * Compares two #gpointer arguments and returns %TRUE if they are equal. + * It can be passed to new_fluid_hashtable() as the @key_equal_func + * parameter, when using pointers as keys in a #fluid_hashtable_t. + * + * Returns: %TRUE if the two keys match. + */ +int +fluid_direct_equal(const void *v1, const void *v2) +{ + return v1 == v2; +} + +/** + * fluid_direct_hash: + * @v: a void * key + * + * Converts a gpointer to a hash value. + * It can be passed to g_hashtable_new() as the @hash_func parameter, + * when using pointers as keys in a #fluid_hashtable_t. + * + * Returns: a hash value corresponding to the key. + */ +unsigned int +fluid_direct_hash(const void *v) +{ + return FLUID_POINTER_TO_UINT(v); +} + +/** + * fluid_int_equal: + * @v1: a pointer to a int key. + * @v2: a pointer to a int key to compare with @v1. + * + * Compares the two #gint values being pointed to and returns + * %TRUE if they are equal. + * It can be passed to g_hashtable_new() as the @key_equal_func + * parameter, when using pointers to integers as keys in a #fluid_hashtable_t. + * + * Returns: %TRUE if the two keys match. + */ +int +fluid_int_equal(const void *v1, const void *v2) +{ + return *((const int *) v1) == *((const int *) v2); +} + +/** + * fluid_int_hash: + * @v: a pointer to a int key + * + * Converts a pointer to a #gint to a hash value. + * It can be passed to g_hashtable_new() as the @hash_func parameter, + * when using pointers to integers values as keys in a #fluid_hashtable_t. + * + * Returns: a hash value corresponding to the key. + */ +unsigned int +fluid_int_hash(const void *v) +{ + return *(const int *) v; +} diff --git a/libs/fluidsynth/src/utils/fluid_hash.h b/libs/fluidsynth/src/utils/fluid_hash.h new file mode 100644 index 00000000000..96b2471be51 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_hash.h @@ -0,0 +1,131 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 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 Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02110-1301, USA. + */ + +/* + * Modified by the GLib Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* + * Adapted for FluidSynth use by Josh Green jgreen@users.sourceforge.net + * September 8, 2009 from glib 2.18.4 + * + * - Self contained (no dependencies on glib) + * - changed names to fluid_hashtable_... + */ + +#ifndef _FLUID_HASH_H +#define _FLUID_HASH_H + +#include "fluidsynth_priv.h" +#include "fluid_list.h" +#include "fluid_sys.h" + +/* Extracted from gtypes.h */ +typedef void (*fluid_destroy_notify_t)(void *data); +typedef unsigned int (*fluid_hash_func_t)(const void *key); +typedef int (*fluid_equal_func_t)(const void *a, const void *b); +/* End gtypes.h extraction */ + +typedef int (*fluid_hr_func_t)(void *key, void *value, void *user_data); +typedef struct _fluid_hashtable_iter_t fluid_hashtable_iter_t; + +typedef struct _fluid_hashnode_t fluid_hashnode_t; + +struct _fluid_hashnode_t +{ + void *key; + void *value; + fluid_hashnode_t *next; + unsigned int key_hash; +}; + +struct _fluid_hashtable_t +{ + int size; + int nnodes; + fluid_hashnode_t **nodes; + fluid_hash_func_t hash_func; + fluid_equal_func_t key_equal_func; + fluid_atomic_int_t ref_count; + fluid_destroy_notify_t key_destroy_func; + fluid_destroy_notify_t value_destroy_func; + fluid_rec_mutex_t mutex; // Optionally used in other modules (fluid_settings.c for example) +}; + +struct _fluid_hashtable_iter_t +{ + /*< private >*/ + void *dummy1; + void *dummy2; + void *dummy3; + int dummy4; + int dummy5; // Bool + void *dummy6; +}; + +fluid_hashtable_t *new_fluid_hashtable(fluid_hash_func_t hash_func, + fluid_equal_func_t key_equal_func); +fluid_hashtable_t *new_fluid_hashtable_full(fluid_hash_func_t hash_func, + fluid_equal_func_t key_equal_func, + fluid_destroy_notify_t key_destroy_func, + fluid_destroy_notify_t value_destroy_func); +void delete_fluid_hashtable(fluid_hashtable_t *hashtable); + +void fluid_hashtable_iter_init(fluid_hashtable_iter_t *iter, fluid_hashtable_t *hashtable); +int fluid_hashtable_iter_next(fluid_hashtable_iter_t *iter, void **key, void **value); +fluid_hashtable_t *fluid_hashtable_iter_get_hash_table(fluid_hashtable_iter_t *iter); +void fluid_hashtable_iter_remove(fluid_hashtable_iter_t *iter); +void fluid_hashtable_iter_steal(fluid_hashtable_iter_t *iter); + +fluid_hashtable_t *fluid_hashtable_ref(fluid_hashtable_t *hashtable); +void fluid_hashtable_unref(fluid_hashtable_t *hashtable); + +void *fluid_hashtable_lookup(fluid_hashtable_t *hashtable, const void *key); +int fluid_hashtable_lookup_extended(fluid_hashtable_t *hashtable, const void *lookup_key, + void **orig_key, void **value); + +void fluid_hashtable_insert(fluid_hashtable_t *hashtable, void *key, void *value); +void fluid_hashtable_replace(fluid_hashtable_t *hashtable, void *key, void *value); + +int fluid_hashtable_remove(fluid_hashtable_t *hashtable, const void *key); +int fluid_hashtable_steal(fluid_hashtable_t *hashtable, const void *key); +void fluid_hashtable_remove_all(fluid_hashtable_t *hashtable); +void fluid_hashtable_steal_all(fluid_hashtable_t *hashtable); +unsigned int fluid_hashtable_foreach_steal(fluid_hashtable_t *hashtable, + fluid_hr_func_t func, void *user_data); +void fluid_hashtable_foreach(fluid_hashtable_t *hashtable, fluid_hr_func_t func, + void *user_data); +void *fluid_hashtable_find(fluid_hashtable_t *hashtable, fluid_hr_func_t predicate, + void *user_data); +unsigned int fluid_hashtable_size(fluid_hashtable_t *hashtable); +fluid_list_t *fluid_hashtable_get_keys(fluid_hashtable_t *hashtable); +fluid_list_t *fluid_hashtable_get_values(fluid_hashtable_t *hashtable); + +int fluid_str_equal(const void *v1, const void *v2); +unsigned int fluid_str_hash(const void *v); +int fluid_direct_equal(const void *v1, const void *v2); +unsigned int fluid_direct_hash(const void *v); +int fluid_int_equal(const void *v1, const void *v2); +unsigned int fluid_int_hash(const void *v); + +#endif /* _FLUID_HASH_H */ + diff --git a/libs/fluidsynth/src/utils/fluid_list.c b/libs/fluidsynth/src/utils/fluid_list.c new file mode 100644 index 00000000000..c88e2aec097 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_list.c @@ -0,0 +1,337 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 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 Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02110-1301, USA. + */ + +/* + * Modified by the GLib Team and others 1997-1999. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + + + +#include "fluid_sys.h" +#include "fluid_list.h" + + +fluid_list_t * +new_fluid_list(void) +{ + fluid_list_t *list; + list = (fluid_list_t *) FLUID_MALLOC(sizeof(fluid_list_t)); + list->data = NULL; + list->next = NULL; + return list; +} + +void +delete_fluid_list(fluid_list_t *list) +{ + fluid_list_t *next; + fluid_return_if_fail(list != NULL); + + while(list) + { + next = list->next; + FLUID_FREE(list); + list = next; + } +} + +void +delete1_fluid_list(fluid_list_t *list) +{ + FLUID_FREE(list); +} + +fluid_list_t * +fluid_list_append(fluid_list_t *list, void *data) +{ + fluid_list_t *new_list; + fluid_list_t *last; + + new_list = new_fluid_list(); + new_list->data = data; + + if(list) + { + last = fluid_list_last(list); + /* g_assert (last != NULL); */ + last->next = new_list; + + return list; + } + else + { + return new_list; + } +} + +fluid_list_t * +fluid_list_prepend(fluid_list_t *list, void *data) +{ + fluid_list_t *new_list; + + new_list = new_fluid_list(); + new_list->data = data; + new_list->next = list; + + return new_list; +} + +fluid_list_t * +fluid_list_nth(fluid_list_t *list, int n) +{ + while((n-- > 0) && list) + { + list = list->next; + } + + return list; +} + +fluid_list_t * +fluid_list_remove(fluid_list_t *list, void *data) +{ + fluid_list_t *tmp; + fluid_list_t *prev; + + prev = NULL; + tmp = list; + + while(tmp) + { + if(tmp->data == data) + { + if(prev) + { + prev->next = tmp->next; + } + + if(list == tmp) + { + list = list->next; + } + + tmp->next = NULL; + delete_fluid_list(tmp); + + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +fluid_list_t * +fluid_list_remove_link(fluid_list_t *list, fluid_list_t *link) +{ + fluid_list_t *tmp; + fluid_list_t *prev; + + prev = NULL; + tmp = list; + + while(tmp) + { + if(tmp == link) + { + if(prev) + { + prev->next = tmp->next; + } + + if(list == tmp) + { + list = list->next; + } + + tmp->next = NULL; + break; + } + + prev = tmp; + tmp = tmp->next; + } + + return list; +} + +static fluid_list_t * +fluid_list_sort_merge(fluid_list_t *l1, fluid_list_t *l2, fluid_compare_func_t compare_func) +{ + fluid_list_t list, *l; + + l = &list; + + while(l1 && l2) + { + if(compare_func(l1->data, l2->data) < 0) + { + l = l->next = l1; + l1 = l1->next; + } + else + { + l = l->next = l2; + l2 = l2->next; + } + } + + l->next = l1 ? l1 : l2; + + return list.next; +} + +fluid_list_t * +fluid_list_sort(fluid_list_t *list, fluid_compare_func_t compare_func) +{ + fluid_list_t *l1, *l2; + + if(!list) + { + return NULL; + } + + if(!list->next) + { + return list; + } + + l1 = list; + l2 = list->next; + + while((l2 = l2->next) != NULL) + { + if((l2 = l2->next) == NULL) + { + break; + } + + l1 = l1->next; + } + + l2 = l1->next; + l1->next = NULL; + + return fluid_list_sort_merge(fluid_list_sort(list, compare_func), + fluid_list_sort(l2, compare_func), + compare_func); +} + + +fluid_list_t * +fluid_list_last(fluid_list_t *list) +{ + if(list) + { + while(list->next) + { + list = list->next; + } + } + + return list; +} + +int +fluid_list_size(fluid_list_t *list) +{ + int n = 0; + + while(list) + { + n++; + list = list->next; + } + + return n; +} + +fluid_list_t *fluid_list_insert_at(fluid_list_t *list, int n, void *data) +{ + fluid_list_t *new_list; + fluid_list_t *cur; + fluid_list_t *prev = NULL; + + new_list = new_fluid_list(); + new_list->data = data; + + cur = list; + + while((n-- > 0) && cur) + { + prev = cur; + cur = cur->next; + } + + new_list->next = cur; + + if(prev) + { + prev->next = new_list; + return list; + } + else + { + return new_list; + } +} + +/* Compare function to sort strings alphabetically, + * for use with fluid_list_sort(). */ +int +fluid_list_str_compare_func(const void *a, const void *b) +{ + if(a && b) + { + return FLUID_STRCMP(a, b); + } + + if(!a && !b) + { + return 0; + } + + if(a) + { + return -1; + } + + return 1; +} + +int fluid_list_idx(fluid_list_t *list, void *data) +{ + int i = 0; + + while(list) + { + if (list->data == data) + { + return i; + } + list = list->next; + } + + return -1; +} diff --git a/libs/fluidsynth/src/utils/fluid_list.h b/libs/fluidsynth/src/utils/fluid_list.h new file mode 100644 index 00000000000..a290135cec0 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_list.h @@ -0,0 +1,63 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * 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 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 Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _FLUID_LIST_H +#define _FLUID_LIST_H + +#include "fluidsynth_priv.h" + +/* + * + * Lists + * + * A sound font loader has to pack the data from the .SF2 file into + * list structures of this type. + * + */ + +typedef struct _fluid_list_t fluid_list_t; + +typedef int (*fluid_compare_func_t)(const void *a, const void *b); + +struct _fluid_list_t +{ + void *data; + fluid_list_t *next; +}; + +fluid_list_t *new_fluid_list(void); +void delete_fluid_list(fluid_list_t *list); +void delete1_fluid_list(fluid_list_t *list); +fluid_list_t *fluid_list_sort(fluid_list_t *list, fluid_compare_func_t compare_func); +fluid_list_t *fluid_list_append(fluid_list_t *list, void *data); +fluid_list_t *fluid_list_prepend(fluid_list_t *list, void *data); +fluid_list_t *fluid_list_remove(fluid_list_t *list, void *data); +fluid_list_t *fluid_list_remove_link(fluid_list_t *list, fluid_list_t *llink); +fluid_list_t *fluid_list_nth(fluid_list_t *list, int n); +fluid_list_t *fluid_list_last(fluid_list_t *list); +fluid_list_t *fluid_list_insert_at(fluid_list_t *list, int n, void *data); +int fluid_list_idx(fluid_list_t *list, void *data); +int fluid_list_size(fluid_list_t *list); + +#define fluid_list_next(slist) ((slist) ? (((fluid_list_t *)(slist))->next) : NULL) +#define fluid_list_get(slist) ((slist) ? ((slist)->data) : NULL) + +int fluid_list_str_compare_func(const void *a, const void *b); + +#endif /* _FLUID_LIST_H */ diff --git a/libs/fluidsynth/src/utils/fluid_ringbuffer.c b/libs/fluidsynth/src/utils/fluid_ringbuffer.c new file mode 100644 index 00000000000..e9fc4ddd358 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_ringbuffer.c @@ -0,0 +1,90 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +/* + * Josh Green josh@resonance.org + * 2009-05-28 + */ + +#include "fluid_ringbuffer.h" +#include "fluid_sys.h" + + +/** + * Create a lock free queue with a fixed maximum count and size of elements. + * @param count Count of elements in queue (fixed max number of queued elements) + * @return New lock free queue or NULL if out of memory (error message logged) + * + * Lockless FIFO queues don't use any locking mechanisms and can therefore be + * advantageous in certain situations, such as passing data between a lower + * priority thread and a higher "real time" thread, without potential lock + * contention which could stall the high priority thread. Note that there may + * only be one producer thread and one consumer thread. + */ +fluid_ringbuffer_t * +new_fluid_ringbuffer(int count, size_t elementsize) +{ + fluid_ringbuffer_t *queue; + + fluid_return_val_if_fail(count > 0, NULL); + + queue = FLUID_NEW(fluid_ringbuffer_t); + + if(!queue) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + queue->array = FLUID_MALLOC(elementsize * count); + + if(!queue->array) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + delete_fluid_ringbuffer(queue); + return NULL; + } + + /* Clear array, in case dynamic pointer reclaiming is being done */ + FLUID_MEMSET(queue->array, 0, elementsize * count); + + queue->totalcount = count; + queue->elementsize = elementsize; + fluid_atomic_int_set(&queue->count, 0); + queue->in = 0; + queue->out = 0; + + return (queue); +} + +/** + * Free an event queue. + * @param queue Lockless queue instance + * + * Care must be taken when freeing a queue, to ensure that the consumer and + * producer threads will no longer access it. + */ +void +delete_fluid_ringbuffer(fluid_ringbuffer_t *queue) +{ + fluid_return_if_fail(queue != NULL); + FLUID_FREE(queue->array); + FLUID_FREE(queue); +} diff --git a/libs/fluidsynth/src/utils/fluid_ringbuffer.h b/libs/fluidsynth/src/utils/fluid_ringbuffer.h new file mode 100644 index 00000000000..6b0a2df37dc --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_ringbuffer.h @@ -0,0 +1,133 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#ifndef _FLUID_RINGBUFFER_H +#define _FLUID_RINGBUFFER_H + +#include "fluid_sys.h" + +/* + * Lockless event queue instance. + */ +struct _fluid_ringbuffer_t +{ + char *array; /**< Queue array of arbitrary size elements */ + int totalcount; /**< Total count of elements in array */ + fluid_atomic_int_t count; /**< Current count of elements */ + int in; /**< Index in queue to store next pushed element */ + int out; /**< Index in queue of next popped element */ + size_t elementsize; /**< Size of each element */ + void *userdata; +}; + +typedef struct _fluid_ringbuffer_t fluid_ringbuffer_t; + + +fluid_ringbuffer_t *new_fluid_ringbuffer(int count, size_t elementsize); +void delete_fluid_ringbuffer(fluid_ringbuffer_t *queue); + +/** + * Get pointer to next input array element in queue. + * @param queue Lockless queue instance + * @param offset Normally zero, or more if you need to push several items at once + * @return Pointer to array element in queue to store data to or NULL if queue is full + * + * This function along with fluid_ringbuffer_next_inptr() form a queue "push" + * operation and is split into 2 functions to avoid an element copy. Note that + * the returned array element pointer may contain the data of a previous element + * if the queue has wrapped around. This can be used to reclaim pointers to + * allocated memory, etc. + */ +static FLUID_INLINE void * +fluid_ringbuffer_get_inptr(fluid_ringbuffer_t *queue, int offset) +{ + return fluid_atomic_int_get(&queue->count) + offset >= queue->totalcount ? NULL + : queue->array + queue->elementsize * ((queue->in + offset) % queue->totalcount); +} + +/** + * Advance the input queue index to complete a "push" operation. + * @param queue Lockless queue instance + * @param count Normally one, or more if you need to push several items at once + * + * This function along with fluid_ringbuffer_get_inptr() form a queue "push" + * operation and is split into 2 functions to avoid element copy. + */ +static FLUID_INLINE void +fluid_ringbuffer_next_inptr(fluid_ringbuffer_t *queue, int count) +{ + fluid_atomic_int_add(&queue->count, count); + + queue->in += count; + + if(queue->in >= queue->totalcount) + { + queue->in -= queue->totalcount; + } +} + +/** + * Get amount of items currently in queue + * @param queue Lockless queue instance + * @return amount of items currently in queue + */ +static FLUID_INLINE int +fluid_ringbuffer_get_count(fluid_ringbuffer_t *queue) +{ + return fluid_atomic_int_get(&queue->count); +} + + +/** + * Get pointer to next output array element in queue. + * @param queue Lockless queue instance + * @return Pointer to array element data in the queue or NULL if empty, can only + * be used up until fluid_ringbuffer_next_outptr() is called. + * + * This function along with fluid_ringbuffer_next_outptr() form a queue "pop" + * operation and is split into 2 functions to avoid an element copy. + */ +static FLUID_INLINE void * +fluid_ringbuffer_get_outptr(fluid_ringbuffer_t *queue) +{ + return fluid_ringbuffer_get_count(queue) == 0 ? NULL + : queue->array + queue->elementsize * queue->out; +} + + +/** + * Advance the output queue index to complete a "pop" operation. + * @param queue Lockless queue instance + * + * This function along with fluid_ringbuffer_get_outptr() form a queue "pop" + * operation and is split into 2 functions to avoid an element copy. + */ +static FLUID_INLINE void +fluid_ringbuffer_next_outptr(fluid_ringbuffer_t *queue) +{ + fluid_atomic_int_add(&queue->count, -1); + + if(++queue->out == queue->totalcount) + { + queue->out = 0; + } +} + +#endif /* _FLUID_ringbuffer_H */ diff --git a/libs/fluidsynth/src/utils/fluid_settings.c b/libs/fluidsynth/src/utils/fluid_settings.c new file mode 100644 index 00000000000..d01170f6550 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_settings.c @@ -0,0 +1,2004 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_sys.h" +#include "fluid_hash.h" +#include "fluid_synth.h" +#ifndef __WINE_PE_BUILD +#include "fluid_cmd.h" +#include "fluid_adriver.h" +#include "fluid_mdriver.h" +#endif +#include "fluid_settings.h" +#include "fluid_midi.h" + +/* maximum allowed components of a settings variable (separated by '.') */ +#define MAX_SETTINGS_TOKENS 8 /* currently only a max of 3 are used */ +#define MAX_SETTINGS_LABEL 256 /* max length of a settings variable label */ + +static void fluid_settings_init(fluid_settings_t *settings); +static void fluid_settings_key_destroy_func(void *value); +static void fluid_settings_value_destroy_func(void *value); +static int fluid_settings_tokenize(const char *s, char *buf, char **ptr); + +/* Common structure to all settings nodes */ +typedef struct +{ + char *value; + char *def; + int hints; + fluid_list_t *options; + fluid_str_update_t update; + void *data; +} fluid_str_setting_t; + +typedef struct +{ + double value; + double def; + double min; + double max; + int hints; + fluid_num_update_t update; + void *data; +} fluid_num_setting_t; + +typedef struct +{ + int value; + int def; + int min; + int max; + int hints; + fluid_int_update_t update; + void *data; +} fluid_int_setting_t; + +typedef struct +{ + fluid_hashtable_t *hashtable; +} fluid_set_setting_t; + +typedef struct +{ + int type; /**< fluid_types_enum */ + + union + { + fluid_str_setting_t str; + fluid_num_setting_t num; + fluid_int_setting_t i; + fluid_set_setting_t set; + }; +} fluid_setting_node_t; + +static fluid_setting_node_t * +new_fluid_str_setting(const char *value, const char *def, int hints) +{ + fluid_setting_node_t *node; + fluid_str_setting_t *str; + + node = FLUID_NEW(fluid_setting_node_t); + + if(!node) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + node->type = FLUID_STR_TYPE; + + str = &node->str; + str->value = value ? FLUID_STRDUP(value) : NULL; + str->def = def ? FLUID_STRDUP(def) : NULL; + str->hints = hints; + str->options = NULL; + str->update = NULL; + str->data = NULL; + return node; +} + +static void +delete_fluid_str_setting(fluid_setting_node_t *node) +{ + fluid_return_if_fail(node != NULL); + + FLUID_ASSERT(node->type == FLUID_STR_TYPE); + + FLUID_FREE(node->str.value); + FLUID_FREE(node->str.def); + + if(node->str.options) + { + fluid_list_t *list = node->str.options; + + while(list) + { + FLUID_FREE(list->data); + list = fluid_list_next(list); + } + + delete_fluid_list(node->str.options); + } + + FLUID_FREE(node); +} + + +static fluid_setting_node_t * +new_fluid_num_setting(double min, double max, double def, int hints) +{ + fluid_setting_node_t *node; + fluid_num_setting_t *num; + + node = FLUID_NEW(fluid_setting_node_t); + + if(!node) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + node->type = FLUID_NUM_TYPE; + + num = &node->num; + num->value = def; + num->def = def; + num->min = min; + num->max = max; + num->hints = hints; + num->update = NULL; + num->data = NULL; + + return node; +} + +static void +delete_fluid_num_setting(fluid_setting_node_t *node) +{ + fluid_return_if_fail(node != NULL); + + FLUID_ASSERT(node->type == FLUID_NUM_TYPE); + FLUID_FREE(node); +} + +static fluid_setting_node_t * +new_fluid_int_setting(int min, int max, int def, int hints) +{ + fluid_setting_node_t *node; + fluid_int_setting_t *i; + + node = FLUID_NEW(fluid_setting_node_t); + + if(!node) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + node->type = FLUID_INT_TYPE; + + i = &node->i; + i->value = def; + i->def = def; + i->min = min; + i->max = max; + i->hints = hints; + i->update = NULL; + i->data = NULL; + return node; +} + +static void +delete_fluid_int_setting(fluid_setting_node_t *node) +{ + fluid_return_if_fail(node != NULL); + + FLUID_ASSERT(node->type == FLUID_INT_TYPE); + FLUID_FREE(node); +} + +static fluid_setting_node_t * +new_fluid_set_setting(void) +{ + fluid_setting_node_t *node; + fluid_set_setting_t *set; + + node = FLUID_NEW(fluid_setting_node_t); + + if(!node) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + node->type = FLUID_SET_TYPE; + set = &node->set; + + set->hashtable = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, + fluid_settings_key_destroy_func, + fluid_settings_value_destroy_func); + + if(!set->hashtable) + { + FLUID_FREE(node); + return NULL; + } + + return node; +} + +static void +delete_fluid_set_setting(fluid_setting_node_t *node) +{ + fluid_return_if_fail(node != NULL); + + FLUID_ASSERT(node->type == FLUID_SET_TYPE); + delete_fluid_hashtable(node->set.hashtable); + FLUID_FREE(node); +} + +/** + * Create a new settings object + * + * @return the pointer to the settings object + */ +fluid_settings_t * +new_fluid_settings(void) +{ + fluid_settings_t *settings; + + settings = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, + fluid_settings_key_destroy_func, + fluid_settings_value_destroy_func); + + if(settings == NULL) + { + return NULL; + } + + fluid_rec_mutex_init(settings->mutex); + fluid_settings_init(settings); + return settings; +} + +/** + * Delete the provided settings object + * + * @param settings a settings object + */ +void +delete_fluid_settings(fluid_settings_t *settings) +{ + fluid_return_if_fail(settings != NULL); + + fluid_rec_mutex_destroy(settings->mutex); + delete_fluid_hashtable(settings); +} + +/* Settings hash key destroy function */ +static void +fluid_settings_key_destroy_func(void *value) +{ + FLUID_FREE(value); /* Free the string key value */ +} + +/* Settings hash value destroy function */ +static void +fluid_settings_value_destroy_func(void *value) +{ + fluid_setting_node_t *node = value; + + switch(node->type) + { + case FLUID_NUM_TYPE: + delete_fluid_num_setting(node); + break; + + case FLUID_INT_TYPE: + delete_fluid_int_setting(node); + break; + + case FLUID_STR_TYPE: + delete_fluid_str_setting(node); + break; + + case FLUID_SET_TYPE: + delete_fluid_set_setting(node); + break; + } +} + +void +fluid_settings_init(fluid_settings_t *settings) +{ + fluid_return_if_fail(settings != NULL); + + fluid_synth_settings(settings); +#ifndef __WINE_PE_BUILD + fluid_shell_settings(settings); + fluid_player_settings(settings); + fluid_file_renderer_settings(settings); + fluid_audio_driver_settings(settings); + fluid_midi_driver_settings(settings); +#endif /* __WINE_PE_BUILD */ +} + +static int +fluid_settings_tokenize(const char *s, char *buf, char **ptr) +{ + char *tokstr, *tok; + int n = 0; + + if(FLUID_STRLEN(s) > MAX_SETTINGS_LABEL) + { + FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max length of %d chars", + MAX_SETTINGS_LABEL); + return 0; + } + + FLUID_STRCPY(buf, s); /* copy string to buffer, since it gets modified */ + tokstr = buf; + + while((tok = fluid_strtok(&tokstr, "."))) + { + if(n >= MAX_SETTINGS_TOKENS) + { + FLUID_LOG(FLUID_ERR, "Setting variable name exceeded max token count of %d", + MAX_SETTINGS_TOKENS); + return 0; + } + else + { + ptr[n++] = tok; + } + } + + return n; +} + +/** + * Get a setting name, value and type + * + * @param settings a settings object + * @param name Settings name + * @param value Location to store setting node if found + * @return #FLUID_OK if the node exists, #FLUID_FAILED otherwise + */ +static int +fluid_settings_get(fluid_settings_t *settings, const char *name, + fluid_setting_node_t **value) +{ + fluid_hashtable_t *table = settings; + fluid_setting_node_t *node = NULL; + char *tokens[MAX_SETTINGS_TOKENS]; + char buf[MAX_SETTINGS_LABEL + 1]; + int ntokens; + int n; + + ntokens = fluid_settings_tokenize(name, buf, tokens); + + if(table == NULL || ntokens <= 0) + { + return FLUID_FAILED; + } + + for(n = 0; n < ntokens; n++) + { + + node = fluid_hashtable_lookup(table, tokens[n]); + + if(!node) + { + return FLUID_FAILED; + } + + table = (node->type == FLUID_SET_TYPE) ? node->set.hashtable : NULL; + } + + if(value) + { + *value = node; + } + + return FLUID_OK; +} + +/** + * Set a setting name, value and type, replacing it if already exists + * + * @param settings a settings object + * @param name Settings name + * @param value Node instance to assign (used directly) + * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise + */ +static int +fluid_settings_set(fluid_settings_t *settings, const char *name, fluid_setting_node_t *value) +{ + fluid_hashtable_t *table = settings; + fluid_setting_node_t *node; + char *tokens[MAX_SETTINGS_TOKENS]; + char buf[MAX_SETTINGS_LABEL + 1]; + int n, num; + char *dupname; + + num = fluid_settings_tokenize(name, buf, tokens); + + if(num == 0) + { + return FLUID_FAILED; + } + + num--; + + for(n = 0; n < num; n++) + { + + node = fluid_hashtable_lookup(table, tokens[n]); + + if(node) + { + + if(node->type == FLUID_SET_TYPE) + { + table = node->set.hashtable; + } + else + { + /* path ends prematurely */ + FLUID_LOG(FLUID_ERR, "'%s' is not a node. Name of the setting was '%s'", tokens[n], name); + return FLUID_FAILED; + } + + } + else + { + /* create a new node */ + fluid_setting_node_t *setnode; + + dupname = FLUID_STRDUP(tokens[n]); + setnode = new_fluid_set_setting(); + + if(!dupname || !setnode) + { + if(dupname) + { + FLUID_FREE(dupname); + } + else + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + } + + if(setnode) + { + delete_fluid_set_setting(setnode); + } + + return FLUID_FAILED; + } + + fluid_hashtable_insert(table, dupname, setnode); + table = setnode->set.hashtable; + } + } + + dupname = FLUID_STRDUP(tokens[num]); + + if(!dupname) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + + fluid_hashtable_insert(table, dupname, value); + + return FLUID_OK; +} + +/** + * Registers a new string value for the specified setting. + * + * @param settings a settings object + * @param name the setting's name + * @param def the default value for the setting + * @param hints the hints for the setting + * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise + */ +int +fluid_settings_register_str(fluid_settings_t *settings, const char *name, const char *def, int hints) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) != FLUID_OK) + { + node = new_fluid_str_setting(def, def, hints); + retval = fluid_settings_set(settings, name, node); + + if(retval != FLUID_OK) + { + delete_fluid_str_setting(node); + } + } + else + { + /* if variable already exists, don't change its value. */ + if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + FLUID_FREE(setting->def); + setting->def = def ? FLUID_STRDUP(def) : NULL; + setting->hints = hints; + retval = FLUID_OK; + } + else + { + FLUID_LOG(FLUID_ERR, "Failed to register string setting '%s' as it already exists with a different type", name); + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Registers a new float value for the specified setting. + * + * @param settings a settings object + * @param name the setting's name + * @param def the default value for the setting + * @param min the smallest allowed value for the setting + * @param max the largest allowed value for the setting + * @param hints the hints for the setting + * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise + */ +int +fluid_settings_register_num(fluid_settings_t *settings, const char *name, double def, + double min, double max, int hints) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + + /* For now, all floating point settings are bounded below and above */ + hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE; + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) != FLUID_OK) + { + /* insert a new setting */ + node = new_fluid_num_setting(min, max, def, hints); + retval = fluid_settings_set(settings, name, node); + + if(retval != FLUID_OK) + { + delete_fluid_num_setting(node); + } + } + else + { + if(node->type == FLUID_NUM_TYPE) + { + /* update the existing setting but don't change its value */ + fluid_num_setting_t *setting = &node->num; + setting->min = min; + setting->max = max; + setting->def = def; + setting->hints = hints; + retval = FLUID_OK; + } + else + { + /* type mismatch */ + FLUID_LOG(FLUID_ERR, "Failed to register numeric setting '%s' as it already exists with a different type", name); + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Registers a new integer value for the specified setting. + * + * @param settings a settings object + * @param name the setting's name + * @param def the default value for the setting + * @param min the smallest allowed value for the setting + * @param max the largest allowed value for the setting + * @param hints the hints for the setting + * @return #FLUID_OK if the value has been register correctly, #FLUID_FAILED otherwise + */ +int +fluid_settings_register_int(fluid_settings_t *settings, const char *name, int def, + int min, int max, int hints) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + + /* For now, all integer settings are bounded below and above */ + hints |= FLUID_HINT_BOUNDED_BELOW | FLUID_HINT_BOUNDED_ABOVE; + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) != FLUID_OK) + { + /* insert a new setting */ + node = new_fluid_int_setting(min, max, def, hints); + retval = fluid_settings_set(settings, name, node); + + if(retval != FLUID_OK) + { + delete_fluid_int_setting(node); + } + } + else + { + if(node->type == FLUID_INT_TYPE) + { + /* update the existing setting but don't change its value */ + fluid_int_setting_t *setting = &node->i; + setting->min = min; + setting->max = max; + setting->def = def; + setting->hints = hints; + retval = FLUID_OK; + } + else + { + /* type mismatch */ + FLUID_LOG(FLUID_ERR, "Failed to register int setting '%s' as it already exists with a different type", name); + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Registers a callback for the specified string setting. + * + * @param settings a settings object + * @param name the setting's name + * @param callback an update function for the setting + * @param data user supplied data passed to the update function + * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise + */ +int fluid_settings_callback_str(fluid_settings_t *settings, const char *name, + fluid_str_update_t callback, void *data) +{ + fluid_setting_node_t *node; + fluid_str_setting_t *setting; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || node->type != FLUID_STR_TYPE) + { + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; + } + + setting = &node->str; + setting->update = callback; + setting->data = data; + + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_OK; +} + +/** + * Registers a callback for the specified numeric setting. + * + * @param settings a settings object + * @param name the setting's name + * @param callback an update function for the setting + * @param data user supplied data passed to the update function + * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise + */ +int fluid_settings_callback_num(fluid_settings_t *settings, const char *name, + fluid_num_update_t callback, void *data) +{ + fluid_setting_node_t *node; + fluid_num_setting_t *setting; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || node->type != FLUID_NUM_TYPE) + { + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; + } + + setting = &node->num; + setting->update = callback; + setting->data = data; + + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_OK; +} + +/** + * Registers a callback for the specified int setting. + * + * @param settings a settings object + * @param name the setting's name + * @param callback an update function for the setting + * @param data user supplied data passed to the update function + * @return #FLUID_OK if the callback has been set, #FLUID_FAILED otherwise + */ +int fluid_settings_callback_int(fluid_settings_t *settings, const char *name, + fluid_int_update_t callback, void *data) +{ + fluid_setting_node_t *node; + fluid_int_setting_t *setting; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || node->type != FLUID_INT_TYPE) + { + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; + } + + setting = &node->i; + setting->update = callback; + setting->data = data; + + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_OK; +} + +void* fluid_settings_get_user_data(fluid_settings_t * settings, const char *name) +{ + fluid_setting_node_t *node; + void* retval = NULL; + + fluid_return_val_if_fail(settings != NULL, NULL); + fluid_return_val_if_fail(name != NULL, NULL); + fluid_return_val_if_fail(name[0] != '\0', NULL); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_NUM_TYPE) + { + fluid_num_setting_t *setting = &node->num; + retval = setting->data; + } + else if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + retval = setting->data; + } + else if(node->type == FLUID_INT_TYPE) + { + fluid_int_setting_t *setting = &node->i; + retval = setting->data; + } + } + + fluid_rec_mutex_unlock(settings->mutex); + return retval; +} + +/** + * Get the type of the setting with the given name + * + * @param settings a settings object + * @param name a setting's name + * @return the type for the named setting (see #fluid_types_enum), or #FLUID_NO_TYPE when it does not exist + */ +int +fluid_settings_get_type(fluid_settings_t *settings, const char *name) +{ + fluid_setting_node_t *node; + int type = FLUID_NO_TYPE; + + fluid_return_val_if_fail(settings != NULL, type); + fluid_return_val_if_fail(name != NULL, type); + fluid_return_val_if_fail(name[0] != '\0', type); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + type = node->type; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return type; +} + +/** + * Get the hints for the named setting as an integer bitmap + * + * @param settings a settings object + * @param name a setting's name + * @param hints set to the hints associated to the setting if it exists + * @return #FLUID_OK if hints associated to the named setting exist, #FLUID_FAILED otherwise + */ +int +fluid_settings_get_hints(fluid_settings_t *settings, const char *name, int *hints) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_NUM_TYPE) + { + fluid_num_setting_t *setting = &node->num; + *hints = setting->hints; + retval = FLUID_OK; + } + else if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + *hints = setting->hints; + retval = FLUID_OK; + } + else if(node->type == FLUID_INT_TYPE) + { + fluid_int_setting_t *setting = &node->i; + *hints = setting->hints; + retval = FLUID_OK; + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Ask whether the setting is changeable in real-time. + * + * @param settings a settings object + * @param name a setting's name + * @return TRUE if the setting is changeable in real-time, FALSE otherwise + * + * @note Before using this function, make sure the @p settings object has already been used to create + * a synthesizer, a MIDI driver, an audio driver, a MIDI player, or a command handler (depending on + * which settings you want to query). + */ +int +fluid_settings_is_realtime(fluid_settings_t *settings, const char *name) +{ + fluid_setting_node_t *node; + int isrealtime = FALSE; + + fluid_return_val_if_fail(settings != NULL, 0); + fluid_return_val_if_fail(name != NULL, 0); + fluid_return_val_if_fail(name[0] != '\0', 0); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_NUM_TYPE) + { + fluid_num_setting_t *setting = &node->num; + isrealtime = setting->update != NULL; + } + else if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + isrealtime = setting->update != NULL; + } + else if(node->type == FLUID_INT_TYPE) + { + fluid_int_setting_t *setting = &node->i; + isrealtime = setting->update != NULL; + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return isrealtime; +} + +/** + * Set a string value for a named setting + * + * @param settings a settings object + * @param name a setting's name + * @param str new string value + * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise + */ +int +fluid_settings_setstr(fluid_settings_t *settings, const char *name, const char *str) +{ + fluid_setting_node_t *node; + fluid_str_setting_t *setting; + char *new_value = NULL; + fluid_str_update_t callback = NULL; + void *data = NULL; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || (node->type != FLUID_STR_TYPE)) + { + FLUID_LOG(FLUID_ERR, "Unknown string setting '%s'", name); + goto error_recovery; + } + + setting = &node->str; + + if(setting->value) + { + FLUID_FREE(setting->value); + } + + if(str) + { + new_value = FLUID_STRDUP(str); + + if(new_value == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_recovery; + } + } + + setting->value = new_value; + + callback = setting->update; + data = setting->data; + + /* Release the mutex before calling the update callback, to avoid + * possible deadlocks with FluidSynths API lock */ + fluid_rec_mutex_unlock(settings->mutex); + + if(callback) + { + (*callback)(data, name, new_value); + } + + return FLUID_OK; + +error_recovery: + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; +} + +/** + * 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 + * + * @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, + char *str, int len) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(str != NULL, retval); + fluid_return_val_if_fail(len > 0, retval); + + str[0] = 0; + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + + if(setting->value) + { + FLUID_STRNCPY(str, setting->value, len); + } + + retval = FLUID_OK; + } + else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = &node->i; + + if(setting->hints & FLUID_HINT_TOGGLED) + { + FLUID_STRNCPY(str, setting->value ? "yes" : "no", len); + + retval = FLUID_OK; + } + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * 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 + * + * 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) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(str != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + + if(setting->value) + { + *str = FLUID_STRDUP(setting->value); + + if(!*str) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + } + } + + if(!setting->value || *str) + { + retval = FLUID_OK; /* Don't set to FLUID_OK if out of memory */ + } + } + else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = &node->i; + + if(setting->hints & FLUID_HINT_TOGGLED) + { + *str = FLUID_STRDUP(setting->value ? "yes" : "no"); + + if(!*str) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + } + + if(!setting->value || *str) + { + retval = FLUID_OK; /* Don't set to FLUID_OK if out of memory */ + } + } + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + + +/** + * Test a string setting for some value. + * + * @param settings a settings object + * @param name a setting's name + * @param s a string to be tested + * @return TRUE if the value exists and is equal to \c s, FALSE otherwise + */ +int +fluid_settings_str_equal(fluid_settings_t *settings, const char *name, const char *s) +{ + fluid_setting_node_t *node; + int retval = FALSE; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(s != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + + if(setting->value) + { + retval = FLUID_STRCMP(setting->value, s) == 0; + } + } + else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = &node->i; + + if(setting->hints & FLUID_HINT_TOGGLED) + { + retval = FLUID_STRCMP(setting->value ? "yes" : "no", s) == 0; + } + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * 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 if a default value exists, 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) +{ + fluid_setting_node_t *node; + char *retval = NULL; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK) + { + if(node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = &node->str; + retval = setting->def; + } + else if(node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = &node->i; + + if(setting->hints & FLUID_HINT_TOGGLED) + { + retval = setting->def ? "yes" : "no"; + } + } + } + + *def = retval; + fluid_rec_mutex_unlock(settings->mutex); + + return retval != NULL ? FLUID_OK : FLUID_FAILED; +} + +/** + * 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 + * @return #FLUID_OK if the setting exists and option was added, #FLUID_FAILED otherwise + * + * Causes the setting's #FLUID_HINT_OPTIONLIST hint to be set. + */ +int +fluid_settings_add_option(fluid_settings_t *settings, const char *name, const char *s) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(s != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_STR_TYPE)) + { + fluid_str_setting_t *setting = &node->str; + char *copy = FLUID_STRDUP(s); + setting->options = fluid_list_append(setting->options, copy); + setting->hints |= FLUID_HINT_OPTIONLIST; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * 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 + * @return #FLUID_OK if the setting exists and option was removed, #FLUID_FAILED otherwise + */ +int +fluid_settings_remove_option(fluid_settings_t *settings, const char *name, const char *s) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(s != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_STR_TYPE)) + { + + fluid_str_setting_t *setting = &node->str; + fluid_list_t *list = setting->options; + + while(list) + { + char *option = (char *) fluid_list_get(list); + + if(FLUID_STRCMP(s, option) == 0) + { + FLUID_FREE(option); + setting->options = fluid_list_remove_link(setting->options, list); + retval = FLUID_OK; + break; + } + + list = fluid_list_next(list); + } + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Set a numeric value for a named setting. + * + * @param settings a settings object + * @param name a setting's name + * @param val new setting's value + * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise + */ +int +fluid_settings_setnum(fluid_settings_t *settings, const char *name, double val) +{ + fluid_setting_node_t *node; + fluid_num_setting_t *setting; + fluid_num_update_t callback = NULL; + void *data = NULL; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || (node->type != FLUID_NUM_TYPE)) + { + FLUID_LOG(FLUID_ERR, "Unknown numeric setting '%s'", name); + goto error_recovery; + } + + setting = &node->num; + + if(val < setting->min || val > setting->max) + { + FLUID_LOG(FLUID_ERR, "requested set value for '%s' out of range", name); + goto error_recovery; + } + + setting->value = val; + + callback = setting->update; + data = setting->data; + + /* Release the mutex before calling the update callback, to avoid + * possible deadlocks with FluidSynths API lock */ + fluid_rec_mutex_unlock(settings->mutex); + + if(callback) + { + (*callback)(data, name, val); + } + + return FLUID_OK; + +error_recovery: + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; +} + +/** + * Get the numeric value of a named setting + * + * @param settings a settings object + * @param name a setting's name + * @param val variable pointer to receive the setting's numeric value + * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise + */ +int +fluid_settings_getnum(fluid_settings_t *settings, const char *name, double *val) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(val != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_NUM_TYPE)) + { + fluid_num_setting_t *setting = &node->num; + *val = setting->value; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * float-typed wrapper for fluid_settings_getnum + * + * @param settings a settings object + * @param name a setting's name + * @param val variable pointer to receive the setting's float value + * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise + */ +int fluid_settings_getnum_float(fluid_settings_t *settings, const char *name, float *val) +{ + double tmp; + + if(fluid_settings_getnum(settings, name, &tmp) == FLUID_OK) + { + *val = tmp; + return FLUID_OK; + } + + return FLUID_FAILED; +} + +/** + * Get the range of values of a numeric setting + * + * @param settings a settings object + * @param name a setting's name + * @param min setting's range lower limit + * @param max setting's range upper limit + * @return #FLUID_OK if the setting's range exists, #FLUID_FAILED otherwise + */ +int +fluid_settings_getnum_range(fluid_settings_t *settings, const char *name, + double *min, double *max) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(min != NULL, retval); + fluid_return_val_if_fail(max != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_NUM_TYPE)) + { + fluid_num_setting_t *setting = &node->num; + *min = setting->min; + *max = setting->max; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Get the default value of a named numeric (double) setting + * + * @param settings a settings object + * @param name a setting's name + * @param val set to the default value if the named setting exists + * @return #FLUID_OK if the default value of the named setting exists, #FLUID_FAILED otherwise + */ +int +fluid_settings_getnum_default(fluid_settings_t *settings, const char *name, double *val) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(val != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_NUM_TYPE)) + { + fluid_num_setting_t *setting = &node->num; + *val = setting->def; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Set an integer value for a setting + * + * @param settings a settings object + * @param name a setting's name + * @param val new setting's integer value + * @return #FLUID_OK if the value has been set, #FLUID_FAILED otherwise + */ +int +fluid_settings_setint(fluid_settings_t *settings, const char *name, int val) +{ + fluid_setting_node_t *node; + fluid_int_setting_t *setting; + fluid_int_update_t callback = NULL; + void *data = NULL; + + fluid_return_val_if_fail(settings != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name != NULL, FLUID_FAILED); + fluid_return_val_if_fail(name[0] != '\0', FLUID_FAILED); + + fluid_rec_mutex_lock(settings->mutex); + + if((fluid_settings_get(settings, name, &node) != FLUID_OK) + || (node->type != FLUID_INT_TYPE)) + { + FLUID_LOG(FLUID_ERR, "Unknown integer parameter '%s'", name); + goto error_recovery; + } + + setting = &node->i; + + if(val < setting->min || val > setting->max) + { + FLUID_LOG(FLUID_ERR, "requested set value for setting '%s' out of range", name); + goto error_recovery; + } + + setting->value = val; + + callback = setting->update; + data = setting->data; + + /* Release the mutex before calling the update callback, to avoid + * possible deadlocks with FluidSynths API lock */ + fluid_rec_mutex_unlock(settings->mutex); + + if(callback) + { + (*callback)(data, name, val); + } + + return FLUID_OK; + +error_recovery: + fluid_rec_mutex_unlock(settings->mutex); + return FLUID_FAILED; +} + +/** + * Get an integer value setting. + * + * @param settings a settings object + * @param name a setting's name + * @param val pointer to a variable to receive the setting's integer value + * @return #FLUID_OK if the value exists, #FLUID_FAILED otherwise + */ +int +fluid_settings_getint(fluid_settings_t *settings, const char *name, int *val) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(val != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_INT_TYPE)) + { + fluid_int_setting_t *setting = &node->i; + *val = setting->value; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * 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 + * @param max setting's range upper limit + * @return #FLUID_OK if the setting's range exists, #FLUID_FAILED otherwise + */ +int +fluid_settings_getint_range(fluid_settings_t *settings, const char *name, + int *min, int *max) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(min != NULL, retval); + fluid_return_val_if_fail(max != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_INT_TYPE)) + { + fluid_int_setting_t *setting = &node->i; + *min = setting->min; + *max = setting->max; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Get the default value of an integer setting. + * + * @param settings a settings object + * @param name a setting's name + * @param val set to the setting's default integer value if it exists + * @return #FLUID_OK if the setting's default integer value exists, #FLUID_FAILED otherwise + */ +int fluid_settings_getint_default(fluid_settings_t *settings, const char *name, int *val) +{ + fluid_setting_node_t *node; + int retval = FLUID_FAILED; + + fluid_return_val_if_fail(settings != NULL, retval); + fluid_return_val_if_fail(name != NULL, retval); + fluid_return_val_if_fail(name[0] != '\0', retval); + fluid_return_val_if_fail(val != NULL, retval); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && (node->type == FLUID_INT_TYPE)) + { + fluid_int_setting_t *setting = &node->i; + *val = setting->def; + retval = FLUID_OK; + } + + fluid_rec_mutex_unlock(settings->mutex); + + return retval; +} + +/** + * Iterate the available options for a named string setting, calling the provided + * callback function for each existing option. + * + * @param settings a settings object + * @param name a setting's name + * @param data any user provided pointer + * @param func callback function to be called on each iteration + * + * @note Starting with FluidSynth 1.1.0 the \p func callback is called for each + * option in alphabetical order. Sort order was undefined in previous versions. + */ +void +fluid_settings_foreach_option(fluid_settings_t *settings, const char *name, + void *data, fluid_settings_foreach_option_t func) +{ + fluid_setting_node_t *node; + fluid_str_setting_t *setting; + fluid_list_t *p, *newlist = NULL; + + fluid_return_if_fail(settings != NULL); + fluid_return_if_fail(name != NULL); + fluid_return_if_fail(name[0] != '\0'); + fluid_return_if_fail(func != NULL); + + fluid_rec_mutex_lock(settings->mutex); /* ++ lock */ + + if(fluid_settings_get(settings, name, &node) != FLUID_OK + || node->type != FLUID_STR_TYPE) + { + fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ + return; + } + + setting = &node->str; + + /* Duplicate option list */ + for(p = setting->options; p; p = p->next) + { + newlist = fluid_list_append(newlist, fluid_list_get(p)); + } + + /* Sort by name */ + newlist = fluid_list_sort(newlist, fluid_list_str_compare_func); + + for(p = newlist; p; p = p->next) + { + (*func)(data, name, (const char *)fluid_list_get(p)); + } + + fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ + + delete_fluid_list(newlist); +} + +/** + * 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 +fluid_settings_option_count(fluid_settings_t *settings, const char *name) +{ + fluid_setting_node_t *node; + int count = -1; + + fluid_return_val_if_fail(settings != NULL, -1); + fluid_return_val_if_fail(name != NULL, -1); + fluid_return_val_if_fail(name[0] != '\0', -1); + + fluid_rec_mutex_lock(settings->mutex); + + if(fluid_settings_get(settings, name, &node) == FLUID_OK + && node->type == FLUID_STR_TYPE) + { + count = fluid_list_size(node->str.options); + } + + fluid_rec_mutex_unlock(settings->mutex); + + return (count); +} + +/** + * 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 * +fluid_settings_option_concat(fluid_settings_t *settings, const char *name, + const char *separator) +{ + fluid_setting_node_t *node; + fluid_list_t *p, *newlist = NULL; + size_t count, len; + char *str, *option; + + fluid_return_val_if_fail(settings != NULL, NULL); + fluid_return_val_if_fail(name != NULL, NULL); + fluid_return_val_if_fail(name[0] != '\0', NULL); + + if(!separator) + { + separator = ", "; + } + + fluid_rec_mutex_lock(settings->mutex); /* ++ lock */ + + if(fluid_settings_get(settings, name, &node) != FLUID_OK + || node->type != FLUID_STR_TYPE) + { + fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ + return (NULL); + } + + /* Duplicate option list, count options and get total string length */ + for(p = node->str.options, count = 0, len = 0; p; p = p->next) + { + option = fluid_list_get(p); + + if(option) + { + newlist = fluid_list_append(newlist, option); + len += FLUID_STRLEN(option); + count++; + } + } + + if(count > 1) + { + len += (count - 1) * FLUID_STRLEN(separator); + } + + len++; /* For terminator */ + + /* Sort by name */ + newlist = fluid_list_sort(newlist, fluid_list_str_compare_func); + + str = FLUID_MALLOC(len); + + if(str) + { + str[0] = 0; + + for(p = newlist; p; p = p->next) + { + option = fluid_list_get(p); + strcat(str, option); + + if(p->next) + { + strcat(str, separator); + } + } + } + + fluid_rec_mutex_unlock(settings->mutex); /* -- unlock */ + + delete_fluid_list(newlist); + + if(!str) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + } + + return (str); +} + +/* Structure passed to fluid_settings_foreach_iter recursive function */ +typedef struct +{ + char path[MAX_SETTINGS_LABEL + 1]; /* Maximum settings label length */ + fluid_list_t *names; /* For fluid_settings_foreach() */ +} fluid_settings_foreach_bag_t; + +static int +fluid_settings_foreach_iter(void *key, void *value, void *data) +{ + fluid_settings_foreach_bag_t *bag = data; + char *keystr = key; + fluid_setting_node_t *node = value; + size_t pathlen; + char *s; + + pathlen = FLUID_STRLEN(bag->path); + + if(pathlen > 0) + { + bag->path[pathlen] = '.'; + bag->path[pathlen + 1] = 0; + } + + strcat(bag->path, keystr); + + switch(node->type) + { + case FLUID_NUM_TYPE: + case FLUID_INT_TYPE: + case FLUID_STR_TYPE: + s = FLUID_STRDUP(bag->path); + + if(s) + { + bag->names = fluid_list_append(bag->names, s); + } + + break; + + case FLUID_SET_TYPE: + fluid_hashtable_foreach(node->set.hashtable, + fluid_settings_foreach_iter, bag); + break; + } + + bag->path[pathlen] = 0; + + return 0; +} + +/** + * Iterate the existing settings defined in a settings object, calling the + * provided callback function for each setting. + * + * @param settings a settings object + * @param data any user provided pointer + * @param func callback function to be called on each iteration + * + * @note Starting with FluidSynth 1.1.0 the \p func callback is called for each + * setting in alphabetical order. Sort order was undefined in previous versions. + */ +void +fluid_settings_foreach(fluid_settings_t *settings, void *data, + fluid_settings_foreach_t func) +{ + fluid_settings_foreach_bag_t bag; + fluid_setting_node_t *node; + fluid_list_t *p; + + fluid_return_if_fail(settings != NULL); + fluid_return_if_fail(func != NULL); + + bag.path[0] = 0; + bag.names = NULL; + + fluid_rec_mutex_lock(settings->mutex); + + /* Add all node names to the bag.names list */ + fluid_hashtable_foreach(settings, fluid_settings_foreach_iter, &bag); + + /* Sort names */ + bag.names = fluid_list_sort(bag.names, fluid_list_str_compare_func); + + /* Loop over names and call the callback */ + for(p = bag.names; p; p = p->next) + { + if(fluid_settings_get(settings, (const char *)(p->data), &node) == FLUID_OK + && node) + { + (*func)(data, (const char *)(p->data), node->type); + } + + FLUID_FREE(p->data); /* -- Free name */ + } + + fluid_rec_mutex_unlock(settings->mutex); + + delete_fluid_list(bag.names); /* -- Free names list */ +} + +/** + * Split a comma-separated list of integers and fill the passed + * in buffer with the parsed values. + * + * @param str the comma-separated string to split + * @param buf user-supplied buffer to hold the parsed numbers + * @param buf_len length of user-supplied buffer + * @return number of parsed values or -1 on failure + */ +int fluid_settings_split_csv(const char *str, int *buf, int buf_len) +{ + char *s; + char *tok; + char *tokstr; + int n = 0; + + s = tokstr = FLUID_STRDUP(str); + + if(s == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return -1; + } + + while((tok = fluid_strtok(&tokstr, ",")) && n < buf_len) + { + buf[n++] = atoi(tok); + } + + FLUID_FREE(s); + + return n; +} diff --git a/libs/fluidsynth/src/utils/fluid_settings.h b/libs/fluidsynth/src/utils/fluid_settings.h new file mode 100644 index 00000000000..73a63e86db5 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_settings.h @@ -0,0 +1,65 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +#ifndef _FLUID_SETTINGS_H +#define _FLUID_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +int fluid_settings_add_option(fluid_settings_t *settings, const char *name, const char *s); +int fluid_settings_remove_option(fluid_settings_t *settings, const char *name, const char *s); + + +typedef void (*fluid_str_update_t)(void *data, const char *name, const char *value); + +int fluid_settings_register_str(fluid_settings_t *settings, const char *name, const char *def, int hints); +int fluid_settings_callback_str(fluid_settings_t *settings, const char *name, + fluid_str_update_t fun, void *data); + + +typedef void (*fluid_num_update_t)(void *data, const char *name, double value); + +int fluid_settings_register_num(fluid_settings_t *settings, const char *name, double def, + double min, double max, int hints); +int fluid_settings_callback_num(fluid_settings_t *settings, const char *name, + fluid_num_update_t fun, void *data); + +/* Type specific wrapper for fluid_settings_getnum */ +int fluid_settings_getnum_float(fluid_settings_t *settings, const char *name, float *val); + + +typedef void (*fluid_int_update_t)(void *data, const char *name, int value); +int fluid_settings_register_int(fluid_settings_t *settings, const char *name, int def, + int min, int max, int hints); +int fluid_settings_callback_int(fluid_settings_t *settings, const char *name, + fluid_int_update_t fun, void *data); + +int fluid_settings_split_csv(const char *str, int *buf, int buf_len); + +void* fluid_settings_get_user_data(fluid_settings_t * settings, const char *name); + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUID_SETTINGS_H */ diff --git a/libs/fluidsynth/src/utils/fluid_sys.c b/libs/fluidsynth/src/utils/fluid_sys.c new file mode 100644 index 00000000000..d5ebd168684 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_sys.c @@ -0,0 +1,1803 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_sys.h" + + +#if READLINE_SUPPORT +#include <readline/readline.h> +#include <readline/history.h> +#endif + +#ifdef DBUS_SUPPORT +#include "fluid_rtkit.h" +#endif + +#if HAVE_PTHREAD_H && !defined(_WIN32) +// Do not include pthread on windows. It includes winsock.h, which collides with ws2tcpip.h from fluid_sys.h +// It isn't need on Windows anyway. +#include <pthread.h> +#endif + +/* WIN32 HACK - Flag used to differentiate between a file descriptor and a socket. + * Should work, so long as no SOCKET or file descriptor ends up with this bit set. - JG */ +#ifdef _WIN32 +#define FLUID_SOCKET_FLAG 0x40000000 +#else +#define FLUID_SOCKET_FLAG 0x00000000 +#define SOCKET_ERROR -1 +#define INVALID_SOCKET -1 +#endif + +/* SCHED_FIFO priority for high priority timer threads */ +#define FLUID_SYS_TIMER_HIGH_PRIO_LEVEL 10 + + +typedef struct +{ + fluid_thread_func_t func; + void *data; + int prio_level; +} fluid_thread_info_t; + +struct _fluid_timer_t +{ + long msec; + + // Pointer to a function to be executed by the timer. + // This field is set to NULL once the timer is finished to indicate completion. + // This allows for timed waits, rather than waiting forever as fluid_timer_join() does. + fluid_timer_callback_t callback; + void *data; + fluid_thread_t *thread; + int cont; + int auto_destroy; +}; + +struct _fluid_server_socket_t +{ + fluid_socket_t socket; + fluid_thread_t *thread; + int cont; + fluid_server_func_t func; + void *data; +}; + + +static int fluid_istream_gets(fluid_istream_t in, char *buf, int len); + +static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL] = +{ + fluid_default_log_function, + fluid_default_log_function, + fluid_default_log_function, + fluid_default_log_function, +#ifdef DEBUG + fluid_default_log_function +#else + NULL +#endif +}; +static void *fluid_log_user_data[LAST_LOG_LEVEL] = { NULL }; + +static const char fluid_libname[] = "fluidsynth"; + +/** + * Installs a new log function for a specified log level. + * @param level Log level to install handler for. + * @param fun Callback function handler to call for logged messages + * @param data User supplied data pointer to pass to log function + * @return The previously installed function. + */ +fluid_log_function_t +fluid_set_log_function(int level, fluid_log_function_t fun, void *data) +{ + fluid_log_function_t old = NULL; + + if((level >= 0) && (level < LAST_LOG_LEVEL)) + { + old = fluid_log_function[level]; + fluid_log_function[level] = fun; + fluid_log_user_data[level] = data; + } + + return old; +} + +/** + * Default log function which prints to the stderr. + * @param level Log level + * @param message Log message + * @param data User supplied data (not used) + */ +void +fluid_default_log_function(int level, const char *message, void *data) +{ + FILE *out; + +#if defined(_WIN32) + out = stdout; +#else + out = stderr; +#endif + + switch(level) + { + case FLUID_PANIC: + FLUID_FPRINTF(out, "%s: panic: %s\n", fluid_libname, message); + break; + + case FLUID_ERR: + FLUID_FPRINTF(out, "%s: error: %s\n", fluid_libname, message); + break; + + case FLUID_WARN: + FLUID_FPRINTF(out, "%s: warning: %s\n", fluid_libname, message); + break; + + case FLUID_INFO: + FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message); + break; + + case FLUID_DBG: + FLUID_FPRINTF(out, "%s: debug: %s\n", fluid_libname, message); + break; + + default: + FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message); + break; + } + + fflush(out); +} + +/** + * Print a message to the log. + * @param level Log level (#fluid_log_level). + * @param fmt Printf style format string for log message + * @param ... Arguments for printf 'fmt' message string + * @return Always returns #FLUID_FAILED + */ +int +fluid_log(int level, const char *fmt, ...) +{ + if((level >= 0) && (level < LAST_LOG_LEVEL)) + { + fluid_log_function_t fun = fluid_log_function[level]; + + if(fun != NULL) + { + char errbuf[1024]; + + va_list args; + va_start(args, fmt); + FLUID_VSNPRINTF(errbuf, sizeof(errbuf), fmt, args); + va_end(args); + + (*fun)(level, errbuf, fluid_log_user_data[level]); + } + } + + return FLUID_FAILED; +} + +void* fluid_alloc(size_t len) +{ + void* ptr = malloc(len); + +#if defined(DEBUG) && !defined(_MSC_VER) + // garbage initialize allocated memory for debug builds to ease reproducing + // bugs like 44453ff23281b3318abbe432fda90888c373022b . + // + // MSVC++ already garbage initializes allocated memory by itself (debug-heap). + // + // 0xCC because + // * it makes pointers reliably crash when dereferencing them, + // * floating points are still some valid but insanely huge negative number, and + // * if for whatever reason this allocated memory is executed, it'll trigger + // INT3 (...at least on x86) + if(ptr != NULL) + { + memset(ptr, 0xCC, len); + } +#endif + return ptr; +} + +/** + * Open a file with a UTF-8 string, even in Windows + * @param filename The name of the file to open + * @param mode The mode to open the file in + */ +FILE *fluid_fopen(const char *filename, const char *mode) +{ +#if defined(_WIN32) + wchar_t *wpath = NULL, *wmode = NULL; + FILE *file = NULL; + int length; + if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, NULL, 0)) == 0) + { + FLUID_LOG(FLUID_ERR, "Unable to perform MultiByteToWideChar() conversion for filename '%s'. Error was: '%s'", filename, fluid_get_windows_error()); + errno = EINVAL; + goto error_recovery; + } + + wpath = FLUID_MALLOC(length * sizeof(wchar_t)); + if (wpath == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory."); + errno = EINVAL; + goto error_recovery; + } + + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, wpath, length); + + if ((length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, NULL, 0)) == 0) + { + FLUID_LOG(FLUID_ERR, "Unable to perform MultiByteToWideChar() conversion for mode '%s'. Error was: '%s'", mode, fluid_get_windows_error()); + errno = EINVAL; + goto error_recovery; + } + + wmode = FLUID_MALLOC(length * sizeof(wchar_t)); + if (wmode == NULL) + { + FLUID_LOG(FLUID_PANIC, "Out of memory."); + errno = EINVAL; + goto error_recovery; + } + + MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode, -1, wmode, length); + + file = _wfopen(wpath, wmode); + +error_recovery: + FLUID_FREE(wpath); + FLUID_FREE(wmode); + + return file; +#else + return fopen(filename, mode); +#endif +} + +/** + * Wrapper for free() that satisfies at least C90 requirements. + * + * @param ptr Pointer to memory region that should be freed + * + * @note Only use this function when the API documentation explicitly says so. Otherwise use + * adequate \c delete_fluid_* functions. + * + * @warning Calling ::free() on memory that is advised to be freed with fluid_free() results in undefined behaviour! + * (cf.: "Potential Errors Passing CRT Objects Across DLL Boundaries" found in MS Docs) + * + * @since 2.0.7 + */ +void fluid_free(void* ptr) +{ + free(ptr); +} + +/** + * An improved strtok, still trashes the input string, but is portable and + * thread safe. Also skips token chars at beginning of token string and never + * returns an empty token (will return NULL if source ends in token chars though). + * NOTE: NOT part of public API + * @internal + * @param str Pointer to a string pointer of source to tokenize. Pointer gets + * updated on each invocation to point to beginning of next token. Note that + * token char gets overwritten with a 0 byte. String pointer is set to NULL + * when final token is returned. + * @param delim String of delimiter chars. + * @return Pointer to the next token or NULL if no more tokens. + */ +char *fluid_strtok(char **str, char *delim) +{ + char *s, *d, *token; + char c; + + if(str == NULL || delim == NULL || !*delim) + { + FLUID_LOG(FLUID_ERR, "Null pointer"); + return NULL; + } + + s = *str; + + if(!s) + { + return NULL; /* str points to a NULL pointer? (tokenize already ended) */ + } + + /* skip delimiter chars at beginning of token */ + do + { + c = *s; + + if(!c) /* end of source string? */ + { + *str = NULL; + return NULL; + } + + for(d = delim; *d; d++) /* is source char a token char? */ + { + if(c == *d) /* token char match? */ + { + s++; /* advance to next source char */ + break; + } + } + } + while(*d); /* while token char match */ + + token = s; /* start of token found */ + + /* search for next token char or end of source string */ + for(s = s + 1; *s; s++) + { + c = *s; + + for(d = delim; *d; d++) /* is source char a token char? */ + { + if(c == *d) /* token char match? */ + { + *s = '\0'; /* overwrite token char with zero byte to terminate token */ + *str = s + 1; /* update str to point to beginning of next token */ + return token; + } + } + } + + /* we get here only if source string ended */ + *str = NULL; + return token; +} + +/** + * Suspend the execution of the current thread for the specified amount of time. + * @param milliseconds to wait. + */ +void fluid_msleep(unsigned int msecs) +{ + g_usleep(msecs * 1000); +} + +/** + * Get time in milliseconds to be used in relative timing operations. + * @return Monotonic time in milliseconds. + */ +unsigned int fluid_curtime(void) +{ + double now; + static double initial_time = 0; + + if(initial_time == 0) + { + initial_time = fluid_utime(); + } + + now = fluid_utime(); + + return (unsigned int)((now - initial_time) / 1000.0); +} + +/** + * Get time in microseconds to be used in relative timing operations. + * @return time in microseconds. + * Note: When used for profiling we need high precision clock given + * by g_get_monotonic_time()if available (glib version >= 2.53.3). + * If glib version is too old and in the case of Windows the function + * uses high precision performance counter instead of g_getmonotic_time(). + */ +double +fluid_utime(void) +{ + double utime; + +#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 28 + /* use high precision monotonic clock if available (g_monotonic_time(). + * For Windows, if this clock is actually implemented as low prec. clock + * (i.e. in case glib is too old), high precision performance counter are + * used instead. + * see: https://bugzilla.gnome.org/show_bug.cgi?id=783340 + */ +#if defined(WITH_PROFILING) && defined(_WIN32) &&\ + /* glib < 2.53.3 */\ + (GLIB_MINOR_VERSION <= 53 && (GLIB_MINOR_VERSION < 53 || GLIB_MICRO_VERSION < 3)) + /* use high precision performance counter. */ + static LARGE_INTEGER freq_cache = {0, 0}; /* Performance Frequency */ + LARGE_INTEGER perf_cpt; + + if(! freq_cache.QuadPart) + { + QueryPerformanceFrequency(&freq_cache); /* Frequency value */ + } + + QueryPerformanceCounter(&perf_cpt); /* Counter value */ + utime = perf_cpt.QuadPart * 1000000.0 / freq_cache.QuadPart; /* time in micros */ +#else + utime = g_get_monotonic_time(); +#endif +#else + /* fallback to less precise clock */ + GTimeVal timeval; + g_get_current_time(&timeval); + utime = (timeval.tv_sec * 1000000.0 + timeval.tv_usec); +#endif + + return utime; +} + + + +#if defined(_WIN32) /* Windoze specific stuff */ + +void +fluid_thread_self_set_prio(int prio_level) +{ + if(prio_level > 0) + { + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + } +} + + +#elif defined(__OS2__) /* OS/2 specific stuff */ + +void +fluid_thread_self_set_prio(int prio_level) +{ + if(prio_level > 0) + { + DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, PRTYD_MAXIMUM, 0); + } +} + +#else /* POSIX stuff.. Nice POSIX.. Good POSIX. */ + +void +fluid_thread_self_set_prio(int prio_level) +{ + struct sched_param priority; + + if(prio_level > 0) + { + + memset(&priority, 0, sizeof(priority)); + priority.sched_priority = prio_level; + + if(pthread_setschedparam(pthread_self(), SCHED_FIFO, &priority) == 0) + { + return; + } + +#ifdef DBUS_SUPPORT + /* Try to gain high priority via rtkit */ + + if(fluid_rtkit_make_realtime(0, prio_level) == 0) + { + return; + } + +#endif + FLUID_LOG(FLUID_WARN, "Failed to set thread to high priority"); + } +} + +#ifdef FPE_CHECK + +/*************************************************************** + * + * Floating point exceptions + * + * The floating point exception functions were taken from Ircam's + * jMax source code. https://www.ircam.fr/jmax + * + * FIXME: check in config for i386 machine + * + * Currently not used. I leave the code here in case we want to pick + * this up again some time later. + */ + +/* Exception flags */ +#define _FPU_STATUS_IE 0x001 /* Invalid Operation */ +#define _FPU_STATUS_DE 0x002 /* Denormalized Operand */ +#define _FPU_STATUS_ZE 0x004 /* Zero Divide */ +#define _FPU_STATUS_OE 0x008 /* Overflow */ +#define _FPU_STATUS_UE 0x010 /* Underflow */ +#define _FPU_STATUS_PE 0x020 /* Precision */ +#define _FPU_STATUS_SF 0x040 /* Stack Fault */ +#define _FPU_STATUS_ES 0x080 /* Error Summary Status */ + +/* Macros for accessing the FPU status word. */ + +/* get the FPU status */ +#define _FPU_GET_SW(sw) __asm__ ("fnstsw %0" : "=m" (*&sw)) + +/* clear the FPU status */ +#define _FPU_CLR_SW() __asm__ ("fnclex" : : ) + +/* Purpose: + * Checks, if the floating point unit has produced an exception, print a message + * if so and clear the exception. + */ +unsigned int fluid_check_fpe_i386(char *explanation) +{ + unsigned int s; + + _FPU_GET_SW(s); + _FPU_CLR_SW(); + + s &= _FPU_STATUS_IE | _FPU_STATUS_DE | _FPU_STATUS_ZE | _FPU_STATUS_OE | _FPU_STATUS_UE; + + if(s) + { + FLUID_LOG(FLUID_WARN, "FPE exception (before or in %s): %s%s%s%s%s", explanation, + (s & _FPU_STATUS_IE) ? "Invalid operation " : "", + (s & _FPU_STATUS_DE) ? "Denormal number " : "", + (s & _FPU_STATUS_ZE) ? "Zero divide " : "", + (s & _FPU_STATUS_OE) ? "Overflow " : "", + (s & _FPU_STATUS_UE) ? "Underflow " : ""); + } + + return s; +} + +/* Purpose: + * Clear floating point exception. + */ +void fluid_clear_fpe_i386(void) +{ + _FPU_CLR_SW(); +} + +#endif // ifdef FPE_CHECK + + +#endif // #else (its POSIX) + + +/*************************************************************** + * + * Profiling (Linux, i586 only) + * + */ + +#if WITH_PROFILING +/* Profiling interface between profiling command shell and audio rendering API + (FluidProfile_0004.pdf- 3.2.2). + Macros are in defined in fluid_sys.h. +*/ + +/* + ----------------------------------------------------------------------------- + Shell task side | Profiling interface | Audio task side + ----------------------------------------------------------------------------- + profiling | Internal | | | Audio + command <---> |<-- profiling -->| Data |<--macros -->| <--> rendering + shell | API | | | API + +*/ +/* default parameters for shell command "prof_start" in fluid_sys.c */ +unsigned short fluid_profile_notes = 0; /* number of generated notes */ +/* preset bank:0 prog:16 (organ) */ +unsigned char fluid_profile_bank = FLUID_PROFILE_DEFAULT_BANK; +unsigned char fluid_profile_prog = FLUID_PROFILE_DEFAULT_PROG; + +/* print mode */ +unsigned char fluid_profile_print = FLUID_PROFILE_DEFAULT_PRINT; +/* number of measures */ +unsigned short fluid_profile_n_prof = FLUID_PROFILE_DEFAULT_N_PROF; +/* measure duration in ms */ +unsigned short fluid_profile_dur = FLUID_PROFILE_DEFAULT_DURATION; +/* lock between multiple-shell */ +fluid_atomic_int_t fluid_profile_lock = 0; +/**/ + +/*---------------------------------------------- + Profiling Data +-----------------------------------------------*/ +unsigned char fluid_profile_status = PROFILE_STOP; /* command and status */ +unsigned int fluid_profile_end_ticks = 0; /* ending position (in ticks) */ +fluid_profile_data_t fluid_profile_data[] = /* Data duration */ +{ + {"synth_write_* ------------>", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block ---------->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block:clear ---->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block:one voice->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block:all voices>", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block:reverb --->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"synth_one_block:chorus --->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"voice:note --------------->", 1e10, 0.0, 0.0, 0, 0, 0}, + {"voice:release ------------>", 1e10, 0.0, 0.0, 0, 0, 0} +}; + + +/*---------------------------------------------- + Internal profiling API +-----------------------------------------------*/ +/* logging profiling data (used on synthesizer instance deletion) */ +void fluid_profiling_print(void) +{ + int i; + + printf("fluid_profiling_print\n"); + + FLUID_LOG(FLUID_INFO, "Estimated times: min/avg/max (micro seconds)"); + + for(i = 0; i < FLUID_PROFILE_NBR; i++) + { + if(fluid_profile_data[i].count > 0) + { + FLUID_LOG(FLUID_INFO, "%s: %.3f/%.3f/%.3f", + fluid_profile_data[i].description, + fluid_profile_data[i].min, + fluid_profile_data[i].total / fluid_profile_data[i].count, + fluid_profile_data[i].max); + } + else + { + FLUID_LOG(FLUID_DBG, "%s: no profiling available", + fluid_profile_data[i].description); + } + } +} + +/* Macro that returns cpu load in percent (%) + * @dur: duration (micro second). + * @sample_rate: sample_rate used in audio driver (Hz). + * @n_amples: number of samples collected during 'dur' duration. +*/ +#define fluid_profile_load(dur,sample_rate,n_samples) \ + (dur * sample_rate / n_samples / 10000.0) + + +/* prints cpu loads only +* +* @param sample_rate the sample rate of audio output. +* @param out output stream device. +* +* ------------------------------------------------------------------------------ +* Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices +* ------------------------------------------------------------------------------ +* nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices +* -------|---------|---------|----------|---------|---------|------------------- +* 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612 +*/ +static void fluid_profiling_print_load(double sample_rate, fluid_ostream_t out) +{ + unsigned int n_voices; /* voices number */ + static const char max_voices_not_available[] = " not available"; + const char *pmax_voices; + char max_voices_available[20]; + + /* First computes data to be printed */ + double total, voices, reverb, chorus, all_voices, voice; + /* voices number */ + n_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ? + fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_voices / + fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count : 0; + + /* total load (%) */ + total = fluid_profile_data[FLUID_PROF_WRITE].count ? + fluid_profile_load(fluid_profile_data[FLUID_PROF_WRITE].total, sample_rate, + fluid_profile_data[FLUID_PROF_WRITE].n_samples) : 0; + + /* reverb load (%) */ + reverb = fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].count ? + fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].total, + sample_rate, + fluid_profile_data[FLUID_PROF_ONE_BLOCK_REVERB].n_samples) : 0; + + /* chorus load (%) */ + chorus = fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].count ? + fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].total, + sample_rate, + fluid_profile_data[FLUID_PROF_ONE_BLOCK_CHORUS].n_samples) : 0; + + /* total voices load: total - reverb - chorus (%) */ + voices = total - reverb - chorus; + + /* One voice load (%): all_voices / n_voices. */ + all_voices = fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].count ? + fluid_profile_load(fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].total, + sample_rate, + fluid_profile_data[FLUID_PROF_ONE_BLOCK_VOICES].n_samples) : 0; + + voice = n_voices ? all_voices / n_voices : 0; + + /* estimated maximum voices number */ + if(voice > 0) + { + FLUID_SNPRINTF(max_voices_available, sizeof(max_voices_available), + "%17d", (unsigned int)((100.0 - reverb - chorus) / voice)); + pmax_voices = max_voices_available; + } + else + { + pmax_voices = max_voices_not_available; + } + + /* Now prints data */ + fluid_ostream_printf(out, + " ------------------------------------------------------------------------------\n"); + fluid_ostream_printf(out, + " Cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond) and maximum voices\n", + sample_rate, 1000000.0 / sample_rate); + fluid_ostream_printf(out, + " ------------------------------------------------------------------------------\n"); + fluid_ostream_printf(out, + " nVoices| total(%%)|voices(%%)| reverb(%%)|chorus(%%)| voice(%%)|estimated maxVoices\n"); + fluid_ostream_printf(out, + " -------|---------|---------|----------|---------|---------|-------------------\n"); + fluid_ostream_printf(out, + "%8d|%9.3f|%9.3f|%10.3f|%9.3f|%9.3f|%s\n", n_voices, total, voices, + reverb, chorus, voice, pmax_voices); +} + +/* +* prints profiling data (used by profile shell command: prof_start). +* The function is an internal profiling API between the "profile" command +* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). +* +* @param sample_rate the sample rate of audio output. +* @param out output stream device. +* +* When print mode is 1, the function prints all the information (see below). +* When print mode is 0, the function prints only the cpu loads. +* +* ------------------------------------------------------------------------------ +* Duration(microsecond) and cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) +* ------------------------------------------------------------------------------ +* Code under profiling |Voices| Duration (microsecond) | Load(%) +* | nbr| min| avg| max| +* ---------------------------|------|--------------------------------|---------- +* synth_write_* ------------>| 250| 3.91| 2188.82| 3275.00| 41.544 +* synth_one_block ---------->| 250| 1150.70| 2273.56| 3241.47| 41.100 +* synth_one_block:clear ---->| 250| 3.07| 4.62| 61.18| 0.084 +* synth_one_block:one voice->| 1| 4.19| 9.02| 1044.27| 0.163 +* synth_one_block:all voices>| 250| 1138.41| 2259.11| 3217.73| 40.839 +* synth_one_block:reverb --->| no profiling available +* synth_one_block:chorus --->| no profiling available +* voice:note --------------->| no profiling available +* voice:release ------------>| no profiling available +* ------------------------------------------------------------------------------ +* Cpu loads(%) (sr: 44100 Hz, sp: 22.68 microsecond) and maximum voices +* ------------------------------------------------------------------------------ +* nVoices| total(%)|voices(%)| reverb(%)|chorus(%)| voice(%)|estimated maxVoices +* -------|---------|---------|----------|---------|---------|------------------- +* 250| 41.544| 41.544| 0.000| 0.000| 0.163| 612 +*/ +void fluid_profiling_print_data(double sample_rate, fluid_ostream_t out) +{ + int i; + + if(fluid_profile_print) + { + /* print all details: Duration(microsecond) and cpu loads(%) */ + fluid_ostream_printf(out, + " ------------------------------------------------------------------------------\n"); + fluid_ostream_printf(out, + " Duration(microsecond) and cpu loads(%%) (sr:%6.0f Hz, sp:%6.2f microsecond)\n", + sample_rate, 1000000.0 / sample_rate); + fluid_ostream_printf(out, + " ------------------------------------------------------------------------------\n"); + fluid_ostream_printf(out, + " Code under profiling |Voices| Duration (microsecond) | Load(%%)\n"); + fluid_ostream_printf(out, + " | nbr| min| avg| max|\n"); + fluid_ostream_printf(out, + " ---------------------------|------|--------------------------------|----------\n"); + + for(i = 0; i < FLUID_PROFILE_NBR; i++) + { + unsigned int count = fluid_profile_data[i].count; + + if(count > 0) + { + /* data are available */ + + if(FLUID_PROF_WRITE <= i && i <= FLUID_PROF_ONE_BLOCK_CHORUS) + { + double load = fluid_profile_load(fluid_profile_data[i].total, sample_rate, + fluid_profile_data[i].n_samples); + fluid_ostream_printf(out, " %s|%6d|%10.2f|%10.2f|%10.2f|%8.3f\n", + fluid_profile_data[i].description, /* code under profiling */ + fluid_profile_data[i].n_voices / count, /* voices number */ + fluid_profile_data[i].min, /* minimum duration */ + fluid_profile_data[i].total / count, /* average duration */ + fluid_profile_data[i].max, /* maximum duration */ + load); /* cpu load */ + } + else + { + /* note and release duration */ + fluid_ostream_printf(out, " %s|%6d|%10.0f|%10.0f|%10.0f|\n", + fluid_profile_data[i].description, /* code under profiling */ + fluid_profile_data[i].n_voices / count, + fluid_profile_data[i].min, /* minimum duration */ + fluid_profile_data[i].total / count, /* average duration */ + fluid_profile_data[i].max); /* maximum duration */ + } + } + else + { + /* data aren't available */ + fluid_ostream_printf(out, + " %s| no profiling available\n", fluid_profile_data[i].description); + } + } + } + + /* prints cpu loads only */ + fluid_profiling_print_load(sample_rate, out);/* prints cpu loads */ +} + +/* + Returns true if the user cancels the current profiling measurement. + Actually this is implemented using the <ENTER> key. To add this functionality: + 1) Adds #define FLUID_PROFILE_CANCEL in fluid_sys.h. + 2) Adds the necessary code inside fluid_profile_is_cancel(). + + When FLUID_PROFILE_CANCEL is not defined, the function return FALSE. +*/ +int fluid_profile_is_cancel_req(void) +{ +#ifdef FLUID_PROFILE_CANCEL + +#if defined(_WIN32) /* Windows specific stuff */ + /* Profile cancellation is supported for Windows */ + /* returns TRUE if key <ENTER> is depressed */ + return(GetAsyncKeyState(VK_RETURN) & 0x1); + +#elif defined(__OS2__) /* OS/2 specific stuff */ + /* Profile cancellation isn't yet supported for OS2 */ + /* For OS2, replaces the following line with the function that returns + true when the keyboard key <ENTER> is depressed */ + return FALSE; /* default value */ + +#else /* POSIX stuff */ + /* Profile cancellation is supported for Linux */ + /* returns true is <ENTER> is depressed */ + { + /* Here select() is used to poll the standard input to see if an input + is ready. As the standard input is usually buffered, the user + needs to depress <ENTER> to set the input to a "ready" state. + */ + struct timeval tv; + fd_set fds; /* just one fds need to be polled */ + tv.tv_sec = 0; /* Setting both values to 0, means a 0 timeout */ + tv.tv_usec = 0; + FD_ZERO(&fds); /* reset fds */ + FD_SET(STDIN_FILENO, &fds); /* sets fds to poll standard input only */ + select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); /* polling */ + return (FD_ISSET(0, &fds)); /* returns true if standard input is ready */ + } +#endif /* OS stuff */ + +#else /* FLUID_PROFILE_CANCEL not defined */ + return FALSE; /* default value */ +#endif /* FLUID_PROFILE_CANCEL */ +} + +/** +* Returns status used in shell command "prof_start". +* The function is an internal profiling API between the "profile" command +* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). +* +* @return status +* - PROFILE_READY profiling data are ready. +* - PROFILE_RUNNING, profiling data are still under acquisition. +* - PROFILE_CANCELED, acquisition has been cancelled by the user. +* - PROFILE_STOP, no acquisition in progress. +* +* When status is PROFILE_RUNNING, the caller can do passive waiting, or other +* work before recalling the function later. +*/ +int fluid_profile_get_status(void) +{ + /* Checks if user has requested to cancel the current measurement */ + /* Cancellation must have precedence over other status */ + if(fluid_profile_is_cancel_req()) + { + fluid_profile_start_stop(0, 0); /* stops the measurement */ + return PROFILE_CANCELED; + } + + switch(fluid_profile_status) + { + case PROFILE_READY: + return PROFILE_READY; /* profiling data are ready */ + + case PROFILE_START: + return PROFILE_RUNNING;/* profiling data are under acquisition */ + + default: + return PROFILE_STOP; + } +} + +/** +* Starts or stops profiling measurement. +* The function is an internal profiling API between the "profile" command +* prof_start and audio rendering API (see FluidProfile_0004.pdf - 3.2.2). +* +* @param end_tick end position of the measure (in ticks). +* - If end_tick is greater then 0, the function starts a measure if a measure +* isn't running. If a measure is already running, the function does nothing +* and returns. +* - If end_tick is 0, the function stops a measure. +* @param clear_data, +* - If clear_data is 0, the function clears fluid_profile_data before starting +* a measure, otherwise, the data from the started measure will be accumulated +* within fluid_profile_data. +*/ +void fluid_profile_start_stop(unsigned int end_ticks, short clear_data) +{ + if(end_ticks) /* This is a "start" request */ + { + /* Checks if a measure is already running */ + if(fluid_profile_status != PROFILE_START) + { + short i; + fluid_profile_end_ticks = end_ticks; + + /* Clears profile data */ + if(clear_data == 0) + { + for(i = 0; i < FLUID_PROFILE_NBR; i++) + { + fluid_profile_data[i].min = 1e10;/* min sets to max value */ + fluid_profile_data[i].max = 0; /* maximum sets to min value */ + fluid_profile_data[i].total = 0; /* total duration microsecond */ + fluid_profile_data[i].count = 0; /* data count */ + fluid_profile_data[i].n_voices = 0; /* voices number */ + fluid_profile_data[i].n_samples = 0;/* audio samples number */ + } + } + + fluid_profile_status = PROFILE_START; /* starts profiling */ + } + + /* else do nothing when profiling is already started */ + } + else /* This is a "stop" request */ + { + /* forces the current running profile (if any) to stop */ + fluid_profile_status = PROFILE_STOP; /* stops profiling */ + } +} + +#endif /* WITH_PROFILING */ + +/*************************************************************** + * + * Threads + * + */ + +#if OLD_GLIB_THREAD_API + +/* Rather than inline this one, we just declare it as a function, to prevent + * GCC warning about inline failure. */ +fluid_cond_t * +new_fluid_cond(void) +{ + if(!g_thread_supported()) + { + g_thread_init(NULL); + } + + return g_cond_new(); +} + +#endif + +static gpointer +fluid_thread_high_prio(gpointer data) +{ + fluid_thread_info_t *info = data; + + fluid_thread_self_set_prio(info->prio_level); + + info->func(info->data); + FLUID_FREE(info); + + return NULL; +} + +/** + * Create a new thread. + * @param func Function to execute in new thread context + * @param data User defined data to pass to func + * @param prio_level Priority level. If greater than 0 then high priority scheduling will + * be used, with the given priority level (used by pthreads only). 0 uses normal scheduling. + * @param detach If TRUE, 'join' does not work and the thread destroys itself when finished. + * @return New thread pointer or NULL on error + */ +fluid_thread_t * +new_fluid_thread(const char *name, fluid_thread_func_t func, void *data, int prio_level, int detach) +{ + GThread *thread; + fluid_thread_info_t *info = NULL; + GError *err = NULL; + + g_return_val_if_fail(func != NULL, NULL); + +#if OLD_GLIB_THREAD_API + + /* Make sure g_thread_init has been called. + * Probably not a good idea in a shared library, + * but what can we do *and* remain backwards compatible? */ + if(!g_thread_supported()) + { + g_thread_init(NULL); + } + +#endif + + if(prio_level > 0) + { + info = FLUID_NEW(fluid_thread_info_t); + + if(!info) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + info->func = func; + info->data = data; + info->prio_level = prio_level; +#if NEW_GLIB_THREAD_API + thread = g_thread_try_new(name, fluid_thread_high_prio, info, &err); +#else + thread = g_thread_create(fluid_thread_high_prio, info, detach == FALSE, &err); +#endif + } + + else + { +#if NEW_GLIB_THREAD_API + thread = g_thread_try_new(name, (GThreadFunc)func, data, &err); +#else + thread = g_thread_create((GThreadFunc)func, data, detach == FALSE, &err); +#endif + } + + if(!thread) + { + FLUID_LOG(FLUID_ERR, "Failed to create the thread: %s", + fluid_gerror_message(err)); + g_clear_error(&err); + FLUID_FREE(info); + return NULL; + } + +#if NEW_GLIB_THREAD_API + + if(detach) + { + g_thread_unref(thread); // Release thread reference, if caller wants to detach + } + +#endif + + return thread; +} + +/** + * Frees data associated with a thread (does not actually stop thread). + * @param thread Thread to free + */ +void +delete_fluid_thread(fluid_thread_t *thread) +{ + /* Threads free themselves when they quit, nothing to do */ +} + +/** + * Join a thread (wait for it to terminate). + * @param thread Thread to join + * @return FLUID_OK + */ +int +fluid_thread_join(fluid_thread_t *thread) +{ + g_thread_join(thread); + + return FLUID_OK; +} + + +static fluid_thread_return_t +fluid_timer_run(void *data) +{ + fluid_timer_t *timer; + int count = 0; + int cont; + long start; + long delay; + + timer = (fluid_timer_t *)data; + + /* keep track of the start time for absolute positioning */ + start = fluid_curtime(); + + while(timer->cont) + { + cont = (*timer->callback)(timer->data, fluid_curtime() - start); + + count++; + + if(!cont) + { + break; + } + + /* to avoid incremental time errors, calculate the delay between + two callbacks bringing in the "absolute" time (count * + timer->msec) */ + delay = (count * timer->msec) - (fluid_curtime() - start); + + if(delay > 0) + { + fluid_msleep(delay); + } + } + + FLUID_LOG(FLUID_DBG, "Timer thread finished"); + timer->callback = NULL; + + if(timer->auto_destroy) + { + FLUID_FREE(timer); + } + + return FLUID_THREAD_RETURN_VALUE; +} + +fluid_timer_t * +new_fluid_timer(int msec, fluid_timer_callback_t callback, void *data, + int new_thread, int auto_destroy, int high_priority) +{ + fluid_timer_t *timer; + + timer = FLUID_NEW(fluid_timer_t); + + if(timer == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + timer->msec = msec; + timer->callback = callback; + timer->data = data; + timer->cont = TRUE ; + timer->thread = NULL; + timer->auto_destroy = auto_destroy; + + if(new_thread) + { + timer->thread = new_fluid_thread("timer", fluid_timer_run, timer, high_priority + ? FLUID_SYS_TIMER_HIGH_PRIO_LEVEL : 0, FALSE); + + if(!timer->thread) + { + FLUID_FREE(timer); + return NULL; + } + } + else + { + fluid_timer_run(timer); /* Run directly, instead of as a separate thread */ + + if(auto_destroy) + { + /* do NOT return freed memory */ + return NULL; + } + } + + return timer; +} + +void +delete_fluid_timer(fluid_timer_t *timer) +{ + int auto_destroy; + fluid_return_if_fail(timer != NULL); + + auto_destroy = timer->auto_destroy; + + timer->cont = 0; + fluid_timer_join(timer); + + /* Shouldn't access timer now if auto_destroy enabled, since it has been destroyed */ + + if(!auto_destroy) + { + FLUID_FREE(timer); + } +} + +int +fluid_timer_join(fluid_timer_t *timer) +{ + int auto_destroy; + + if(timer->thread) + { + auto_destroy = timer->auto_destroy; + fluid_thread_join(timer->thread); + + if(!auto_destroy) + { + timer->thread = NULL; + } + } + + return FLUID_OK; +} + +int +fluid_timer_is_running(const fluid_timer_t *timer) +{ + // for unit test usage only + return timer->callback != NULL; +} + +long fluid_timer_get_interval(const fluid_timer_t * timer) +{ + // for unit test usage only + return timer->msec; +} + + +/*************************************************************** + * + * Sockets and I/O + * + */ + +/** + * Get standard in stream handle. + * @return Standard in stream. + */ +fluid_istream_t +fluid_get_stdin(void) +{ + return STDIN_FILENO; +} + +/** + * Get standard output stream handle. + * @return Standard out stream. + */ +fluid_ostream_t +fluid_get_stdout(void) +{ + return STDOUT_FILENO; +} + +/** + * Read a line from an input stream. + * @return 0 if end-of-stream, -1 if error, non zero otherwise + */ +int +fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt, + char *buf, int len) +{ +#if READLINE_SUPPORT + + if(in == fluid_get_stdin()) + { + char *line; + + line = readline(prompt); + + if(line == NULL) + { + return -1; + } + + FLUID_SNPRINTF(buf, len, "%s", line); + buf[len - 1] = 0; + + if(buf[0] != '\0') + { + add_history(buf); + } + + free(line); + return 1; + } + else +#endif + { + fluid_ostream_printf(out, "%s", prompt); + return fluid_istream_gets(in, buf, len); + } +} + +/** + * Reads a line from an input stream (socket). + * @param in The input socket + * @param buf Buffer to store data to + * @param len Maximum length to store to buf + * @return 1 if a line was read, 0 on end of stream, -1 on error + */ +static int +fluid_istream_gets(fluid_istream_t in, char *buf, int len) +{ + char c; + int n; + + buf[len - 1] = 0; + + while(--len > 0) + { +#ifndef _WIN32 + n = read(in, &c, 1); + + if(n == -1) + { + return -1; + } + +#else + + /* Handle read differently depending on if its a socket or file descriptor */ + if(!(in & FLUID_SOCKET_FLAG)) + { + // usually read() is supposed to return '\n' as last valid character of the user input + // when compiled with compatibility for WinXP however, read() may return 0 (EOF) rather than '\n' + // this would cause the shell to exit early + n = read(in, &c, 1); + + if(n == -1) + { + return -1; + } + } + else + { +#ifdef NETWORK_SUPPORT + n = recv(in & ~FLUID_SOCKET_FLAG, &c, 1, 0); + if(n == SOCKET_ERROR) +#endif + { + return -1; + } + } + +#endif + + if(n == 0) + { + *buf = 0; + // return 1 if read from stdin, else 0, to fix early exit of shell + return (in == STDIN_FILENO); + } + + if(c == '\n') + { + *buf = 0; + return 1; + } + + /* Store all characters excluding CR */ + if(c != '\r') + { + *buf++ = c; + } + } + + return -1; +} + +/** + * Send a printf style string with arguments to an output stream (socket). + * @param out Output stream + * @param format printf style format string + * @param ... Arguments for the printf format string + * @return Number of bytes written or -1 on error + */ +int +fluid_ostream_printf(fluid_ostream_t out, const char *format, ...) +{ + char buf[4096]; + va_list args; + int len; + + va_start(args, format); + len = FLUID_VSNPRINTF(buf, 4095, format, args); + va_end(args); + + if(len == 0) + { + return 0; + } + + if(len < 0) + { + printf("fluid_ostream_printf: buffer overflow"); + return -1; + } + + buf[4095] = 0; + +#ifndef _WIN32 + return write(out, buf, FLUID_STRLEN(buf)); +#else + { + int retval; + + /* Handle write differently depending on if its a socket or file descriptor */ + if(!(out & FLUID_SOCKET_FLAG)) + { + return write(out, buf, (unsigned int)FLUID_STRLEN(buf)); + } + +#ifdef NETWORK_SUPPORT + /* Socket */ + retval = send(out & ~FLUID_SOCKET_FLAG, buf, (int)FLUID_STRLEN(buf), 0); + return retval != SOCKET_ERROR ? retval : -1; +#else + return -1; +#endif + } +#endif +} + +#ifdef NETWORK_SUPPORT + +int fluid_server_socket_join(fluid_server_socket_t *server_socket) +{ + return fluid_thread_join(server_socket->thread); +} + +static int fluid_socket_init(void) +{ +#ifdef _WIN32 + WSADATA wsaData; + int res = WSAStartup(MAKEWORD(2, 2), &wsaData); + + if(res != 0) + { + FLUID_LOG(FLUID_ERR, "Server socket creation error: WSAStartup failed: %d", res); + return FLUID_FAILED; + } + +#endif + + return FLUID_OK; +} + +static void fluid_socket_cleanup(void) +{ +#ifdef _WIN32 + WSACleanup(); +#endif +} + +static int fluid_socket_get_error(void) +{ +#ifdef _WIN32 + return (int)WSAGetLastError(); +#else + return errno; +#endif +} + +fluid_istream_t fluid_socket_get_istream(fluid_socket_t sock) +{ + return sock | FLUID_SOCKET_FLAG; +} + +fluid_ostream_t fluid_socket_get_ostream(fluid_socket_t sock) +{ + return sock | FLUID_SOCKET_FLAG; +} + +void fluid_socket_close(fluid_socket_t sock) +{ + if(sock != INVALID_SOCKET) + { +#ifdef _WIN32 + closesocket(sock); + +#else + close(sock); +#endif + } +} + +static fluid_thread_return_t fluid_server_socket_run(void *data) +{ + fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data; + fluid_socket_t client_socket; +#ifdef IPV6_SUPPORT + struct sockaddr_in6 addr; +#else + struct sockaddr_in addr; +#endif + +#ifdef HAVE_INETNTOP +#ifdef IPV6_SUPPORT + char straddr[INET6_ADDRSTRLEN]; +#else + char straddr[INET_ADDRSTRLEN]; +#endif /* IPV6_SUPPORT */ +#endif /* HAVE_INETNTOP */ + + socklen_t addrlen = sizeof(addr); + int r; + FLUID_MEMSET((char *)&addr, 0, sizeof(addr)); + + FLUID_LOG(FLUID_DBG, "Server listening for connections"); + + while(server_socket->cont) + { + client_socket = accept(server_socket->socket, (struct sockaddr *)&addr, &addrlen); + + FLUID_LOG(FLUID_DBG, "New client connection"); + + if(client_socket == INVALID_SOCKET) + { + if(server_socket->cont) + { + FLUID_LOG(FLUID_ERR, "Failed to accept connection: %d", fluid_socket_get_error()); + } + + server_socket->cont = 0; + return FLUID_THREAD_RETURN_VALUE; + } + else + { +#ifdef HAVE_INETNTOP + +#ifdef IPV6_SUPPORT + inet_ntop(AF_INET6, &addr.sin6_addr, straddr, sizeof(straddr)); +#else + inet_ntop(AF_INET, &addr.sin_addr, straddr, sizeof(straddr)); +#endif + + r = server_socket->func(server_socket->data, client_socket, + straddr); +#else + r = server_socket->func(server_socket->data, client_socket, + inet_ntoa(addr.sin_addr)); +#endif + + if(r != 0) + { + fluid_socket_close(client_socket); + } + } + } + + FLUID_LOG(FLUID_DBG, "Server closing"); + + return FLUID_THREAD_RETURN_VALUE; +} + +fluid_server_socket_t * +new_fluid_server_socket(int port, fluid_server_func_t func, void *data) +{ + fluid_server_socket_t *server_socket; +#ifdef IPV6_SUPPORT + struct sockaddr_in6 addr; +#else + struct sockaddr_in addr; +#endif + + fluid_socket_t sock; + + fluid_return_val_if_fail(func != NULL, NULL); + + if(fluid_socket_init() != FLUID_OK) + { + return NULL; + } + +#ifdef IPV6_SUPPORT + sock = socket(AF_INET6, SOCK_STREAM, 0); + + if(sock == INVALID_SOCKET) + { + FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); + fluid_socket_cleanup(); + return NULL; + } + + FLUID_MEMSET(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons((uint16_t)port); + addr.sin6_addr = in6addr_any; +#else + + sock = socket(AF_INET, SOCK_STREAM, 0); + + if(sock == INVALID_SOCKET) + { + FLUID_LOG(FLUID_ERR, "Failed to create server socket: %d", fluid_socket_get_error()); + fluid_socket_cleanup(); + return NULL; + } + + FLUID_MEMSET(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); +#endif + + if(bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) + { + FLUID_LOG(FLUID_ERR, "Failed to bind server socket: %d", fluid_socket_get_error()); + fluid_socket_close(sock); + fluid_socket_cleanup(); + return NULL; + } + + if(listen(sock, SOMAXCONN) == SOCKET_ERROR) + { + FLUID_LOG(FLUID_ERR, "Failed to listen on server socket: %d", fluid_socket_get_error()); + fluid_socket_close(sock); + fluid_socket_cleanup(); + return NULL; + } + + server_socket = FLUID_NEW(fluid_server_socket_t); + + if(server_socket == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + fluid_socket_close(sock); + fluid_socket_cleanup(); + return NULL; + } + + server_socket->socket = sock; + server_socket->func = func; + server_socket->data = data; + server_socket->cont = 1; + + server_socket->thread = new_fluid_thread("server", fluid_server_socket_run, server_socket, + 0, FALSE); + + if(server_socket->thread == NULL) + { + FLUID_FREE(server_socket); + fluid_socket_close(sock); + fluid_socket_cleanup(); + return NULL; + } + + return server_socket; +} + +void delete_fluid_server_socket(fluid_server_socket_t *server_socket) +{ + fluid_return_if_fail(server_socket != NULL); + + server_socket->cont = 0; + + if(server_socket->socket != INVALID_SOCKET) + { + fluid_socket_close(server_socket->socket); + } + + if(server_socket->thread) + { + fluid_thread_join(server_socket->thread); + delete_fluid_thread(server_socket->thread); + } + + FLUID_FREE(server_socket); + + // Should be called the same number of times as fluid_socket_init() + fluid_socket_cleanup(); +} + +#endif // NETWORK_SUPPORT + +FILE* fluid_file_open(const char* path, const char** errMsg) +{ + static const char ErrExist[] = "File does not exist."; + static const char ErrRegular[] = "File is not regular, refusing to open it."; + static const char ErrNull[] = "File does not exists or insufficient permissions to open it."; + + FILE* handle = NULL; + + if(!fluid_file_test(path, FLUID_FILE_TEST_EXISTS)) + { + if(errMsg != NULL) + { + *errMsg = ErrExist; + } + } + else if(!fluid_file_test(path, FLUID_FILE_TEST_IS_REGULAR)) + { + if(errMsg != NULL) + { + *errMsg = ErrRegular; + } + } + else if((handle = FLUID_FOPEN(path, "rb")) == NULL) + { + if(errMsg != NULL) + { + *errMsg = ErrNull; + } + } + + return handle; +} + +fluid_long_long_t fluid_file_tell(FILE* f) +{ +#ifdef _WIN32 + // On Windows, long is only a 32 bit integer. Thus ftell() does not support to handle files >2GiB. + // We should use _ftelli64() in this case, however its availability depends on MS CRT and might not be + // available on WindowsXP, Win98, etc. + // + // The web recommends to fallback to _telli64() in this case. However, it's return value differs from + // _ftelli64() on Win10: https://github.com/FluidSynth/fluidsynth/pull/629#issuecomment-602238436 + // + // Thus, we use fgetpos(). + fpos_t pos; + if(fgetpos(f, &pos) != 0) + { + return (fluid_long_long_t)-1L; + } + return pos; +#else + return ftell(f); +#endif +} + +#if defined(_WIN32) || defined(__CYGWIN__) +// not thread-safe! +char* fluid_get_windows_error(void) +{ + static TCHAR err[1024]; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + err, + sizeof(err)/sizeof(err[0]), + NULL); + +#ifdef _UNICODE + static char ascii_err[sizeof(err)]; + + WideCharToMultiByte(CP_UTF8, 0, err, -1, ascii_err, sizeof(ascii_err)/sizeof(ascii_err[0]), 0, 0); + return ascii_err; +#else + return err; +#endif +} +#endif diff --git a/libs/fluidsynth/src/utils/fluid_sys.h b/libs/fluidsynth/src/utils/fluid_sys.h new file mode 100644 index 00000000000..bf2e9ba888a --- /dev/null +++ b/libs/fluidsynth/src/utils/fluid_sys.h @@ -0,0 +1,794 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + + +/* + * @file fluid_sys.h + * + * This header contains a bunch of (mostly) system and machine + * dependent functions: + * + * - timers + * - current time in milliseconds and microseconds + * - debug logging + * - profiling + * - memory locking + * - checking for floating point exceptions + * + * fluidsynth's wrapper OSAL so to say; include it in .c files, be careful to include + * it in fluidsynth's private header files (see comment in fluid_coreaudio.c) + */ + +#ifndef _FLUID_SYS_H +#define _FLUID_SYS_H + +#include "fluidsynth_priv.h" + +#if HAVE_MATH_H +#include <math.h> +#endif + +#if HAVE_ERRNO_H +#include <errno.h> +#endif + +#if HAVE_STDARG_H +#include <stdarg.h> +#endif + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#if HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#if HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#if HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif + +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#if HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif + +#if HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#if HAVE_LIMITS_H +#include <limits.h> +#endif + +#if HAVE_OPENMP +#include <omp.h> +#endif + +#if HAVE_IO_H +#include <io.h> // _open(), _close(), read(), write() on windows +#endif + +#if HAVE_SIGNAL_H +#include <signal.h> +#endif + +/** Integer types */ +#if HAVE_STDINT_H +#include <stdint.h> + +#else + +/* Assume GLIB types */ +typedef gint8 int8_t; +typedef guint8 uint8_t; +typedef gint16 int16_t; +typedef guint16 uint16_t; +typedef gint32 int32_t; +typedef guint32 uint32_t; +typedef gint64 int64_t; +typedef guint64 uint64_t; +typedef guintptr uintptr_t; +typedef gintptr intptr_t; + +#endif + +/* + * CYGWIN has its own version of <windows.h>, which can be + * safely included together with POSIX includes. + * Thanks to this, CYGWIN can also run audio output and MIDI + * input drivers from traditional interfaces of Windows. + */ +#if defined(__CYGWIN__) && HAVE_WINDOWS_H +#include <windows.h> +#include <wchar.h> +#endif + +#if defined(_WIN32) && HAVE_WINDOWS_H +#include <winsock2.h> +#include <ws2tcpip.h> /* Provides also socklen_t */ + +/* WIN32 special defines */ +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#pragma warning(disable : 4101) +#pragma warning(disable : 4305) +#pragma warning(disable : 4996) +#endif + +#endif + +/* Darwin special defines (taken from config_macosx.h) */ +#ifdef DARWIN +# define MACINTOSH +# define __Types__ +#endif + +#ifdef LADSPA +#include <gmodule.h> +#endif + +#include <glib/gstdio.h> + +/** + * Macro used for safely accessing a message from a GError and using a default + * message if it is NULL. + * @param err Pointer to a GError to access the message field of. + * @return Message string + */ +#define fluid_gerror_message(err) ((err) ? err->message : "No error details") + +#if defined(_WIN32) || defined(__CYGWIN__) +char* fluid_get_windows_error(void); +#endif + +#define FLUID_INLINE inline + +#define FLUID_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch)) + +/* Integer<->pointer conversion */ +#define FLUID_POINTER_TO_UINT(x) ((unsigned int)(uintptr_t)(x)) +#define FLUID_UINT_TO_POINTER(x) ((void *)(uintptr_t)(x)) +#define FLUID_POINTER_TO_INT(x) ((signed int)(intptr_t)(x)) +#define FLUID_INT_TO_POINTER(x) ((void *)(intptr_t)(x)) + +/* Endian detection */ +#define FLUID_IS_BIG_ENDIAN (G_BYTE_ORDER == G_BIG_ENDIAN) + +#define FLUID_LE32TOH(x) GINT32_FROM_LE(x) +#define FLUID_LE16TOH(x) GINT16_FROM_LE(x) + +#if FLUID_IS_BIG_ENDIAN +#define FLUID_FOURCC(_a, _b, _c, _d) \ + (uint32_t)(((uint32_t)(_a) << 24) | ((uint32_t)(_b) << 16) | ((uint32_t)(_c) << 8) | (uint32_t)(_d)) +#else +#define FLUID_FOURCC(_a, _b, _c, _d) \ + (uint32_t)(((uint32_t)(_d) << 24) | ((uint32_t)(_c) << 16) | ((uint32_t)(_b) << 8) | (uint32_t)(_a)) +#endif + +/* + * Utility functions + */ +char *fluid_strtok(char **str, char *delim); + +#define FLUID_FILE_TEST_EXISTS G_FILE_TEST_EXISTS +#define FLUID_FILE_TEST_IS_REGULAR G_FILE_TEST_IS_REGULAR +#define fluid_file_test(path, flags) g_file_test(path, flags) + +#define fluid_shell_parse_argv(command_line, argcp, argvp) g_shell_parse_argv(command_line, argcp, argvp, NULL) +#define fluid_strfreev g_strfreev + +#if defined(__OS2__) +#define INCL_DOS +#include <os2.h> + +/* Define socklen_t if not provided */ +#if !HAVE_SOCKLEN_T +typedef int socklen_t; +#endif +#endif + +/** + Time functions + + */ + +unsigned int fluid_curtime(void); +double fluid_utime(void); + + +/** + Timers + + */ + +/* if the callback function returns 1 the timer will continue; if it + returns 0 it will stop */ +typedef int (*fluid_timer_callback_t)(void *data, unsigned int msec); + +typedef struct _fluid_timer_t fluid_timer_t; + +fluid_timer_t *new_fluid_timer(int msec, fluid_timer_callback_t callback, + void *data, int new_thread, int auto_destroy, + int high_priority); + +void delete_fluid_timer(fluid_timer_t *timer); +int fluid_timer_join(fluid_timer_t *timer); +int fluid_timer_stop(fluid_timer_t *timer); +int fluid_timer_is_running(const fluid_timer_t *timer); +long fluid_timer_get_interval(const fluid_timer_t * timer); + +// Macros to use for pre-processor if statements to test which Glib thread API we have (pre or post 2.32) +#define NEW_GLIB_THREAD_API GLIB_CHECK_VERSION(2,32,0) +#define OLD_GLIB_THREAD_API !GLIB_CHECK_VERSION(2,32,0) + +/* Muteces */ + +#if NEW_GLIB_THREAD_API + +/* glib 2.32 and newer */ + +/* Regular mutex */ +typedef GMutex fluid_mutex_t; +#define FLUID_MUTEX_INIT { 0 } +#define fluid_mutex_init(_m) g_mutex_init (&(_m)) +#define fluid_mutex_destroy(_m) g_mutex_clear (&(_m)) +#define fluid_mutex_lock(_m) g_mutex_lock(&(_m)) +#define fluid_mutex_unlock(_m) g_mutex_unlock(&(_m)) + +/* Recursive lock capable mutex */ +typedef GRecMutex fluid_rec_mutex_t; +#define fluid_rec_mutex_init(_m) g_rec_mutex_init(&(_m)) +#define fluid_rec_mutex_destroy(_m) g_rec_mutex_clear(&(_m)) +#define fluid_rec_mutex_lock(_m) g_rec_mutex_lock(&(_m)) +#define fluid_rec_mutex_unlock(_m) g_rec_mutex_unlock(&(_m)) + +/* Dynamically allocated mutex suitable for fluid_cond_t use */ +typedef GMutex fluid_cond_mutex_t; +#define fluid_cond_mutex_lock(m) g_mutex_lock(m) +#define fluid_cond_mutex_unlock(m) g_mutex_unlock(m) + +static FLUID_INLINE fluid_cond_mutex_t * +new_fluid_cond_mutex(void) +{ + GMutex *mutex; + mutex = g_new(GMutex, 1); + g_mutex_init(mutex); + return (mutex); +} + +static FLUID_INLINE void +delete_fluid_cond_mutex(fluid_cond_mutex_t *m) +{ + fluid_return_if_fail(m != NULL); + g_mutex_clear(m); + g_free(m); +} + +/* Thread condition signaling */ +typedef GCond fluid_cond_t; +#define fluid_cond_signal(cond) g_cond_signal(cond) +#define fluid_cond_broadcast(cond) g_cond_broadcast(cond) +#define fluid_cond_wait(cond, mutex) g_cond_wait(cond, mutex) + +static FLUID_INLINE fluid_cond_t * +new_fluid_cond(void) +{ + GCond *cond; + cond = g_new(GCond, 1); + g_cond_init(cond); + return (cond); +} + +static FLUID_INLINE void +delete_fluid_cond(fluid_cond_t *cond) +{ + fluid_return_if_fail(cond != NULL); + g_cond_clear(cond); + g_free(cond); +} + +/* Thread private data */ + +#ifdef __WINE_PE_BUILD + +typedef DWORD fluid_private_t; +#define fluid_private_init(_priv) (_priv = TlsAlloc()) +#define fluid_private_free(_priv) TlsFree(_priv); +#define fluid_private_get(_priv) TlsGetValue(_priv) +#define fluid_private_set(_priv, _data) TlsSetValue(_priv, _data) + +#else /* __WINE_PE_BUILD */ + +typedef GPrivate fluid_private_t; +#define fluid_private_init(_priv) memset (&_priv, 0, sizeof (_priv)) +#define fluid_private_free(_priv) +#define fluid_private_get(_priv) g_private_get(&(_priv)) +#define fluid_private_set(_priv, _data) g_private_set(&(_priv), _data) + +#endif /* __WINE_PE_BUILD */ + +#else + +/* glib prior to 2.32 */ + +/* Regular mutex */ +typedef GStaticMutex fluid_mutex_t; +#define FLUID_MUTEX_INIT G_STATIC_MUTEX_INIT +#define fluid_mutex_destroy(_m) g_static_mutex_free(&(_m)) +#define fluid_mutex_lock(_m) g_static_mutex_lock(&(_m)) +#define fluid_mutex_unlock(_m) g_static_mutex_unlock(&(_m)) + +#define fluid_mutex_init(_m) do { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_mutex_init (&(_m)); \ +} while(0) + +/* Recursive lock capable mutex */ +typedef GStaticRecMutex fluid_rec_mutex_t; +#define fluid_rec_mutex_destroy(_m) g_static_rec_mutex_free(&(_m)) +#define fluid_rec_mutex_lock(_m) g_static_rec_mutex_lock(&(_m)) +#define fluid_rec_mutex_unlock(_m) g_static_rec_mutex_unlock(&(_m)) + +#define fluid_rec_mutex_init(_m) do { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_rec_mutex_init (&(_m)); \ +} while(0) + +/* Dynamically allocated mutex suitable for fluid_cond_t use */ +typedef GMutex fluid_cond_mutex_t; +#define delete_fluid_cond_mutex(m) g_mutex_free(m) +#define fluid_cond_mutex_lock(m) g_mutex_lock(m) +#define fluid_cond_mutex_unlock(m) g_mutex_unlock(m) + +static FLUID_INLINE fluid_cond_mutex_t * +new_fluid_cond_mutex(void) +{ + if(!g_thread_supported()) + { + g_thread_init(NULL); + } + + return g_mutex_new(); +} + +/* Thread condition signaling */ +typedef GCond fluid_cond_t; +fluid_cond_t *new_fluid_cond(void); +#define delete_fluid_cond(cond) g_cond_free(cond) +#define fluid_cond_signal(cond) g_cond_signal(cond) +#define fluid_cond_broadcast(cond) g_cond_broadcast(cond) +#define fluid_cond_wait(cond, mutex) g_cond_wait(cond, mutex) + +/* Thread private data */ +typedef GStaticPrivate fluid_private_t; +#define fluid_private_get(_priv) g_static_private_get(&(_priv)) +#define fluid_private_set(_priv, _data) g_static_private_set(&(_priv), _data, NULL) +#define fluid_private_free(_priv) g_static_private_free(&(_priv)) + +#define fluid_private_init(_priv) do { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_private_init (&(_priv)); \ +} while(0) + +#endif + + +/* Atomic operations */ + +#define fluid_atomic_int_inc(_pi) g_atomic_int_inc(_pi) +#define fluid_atomic_int_get(_pi) g_atomic_int_get(_pi) +#define fluid_atomic_int_set(_pi, _val) g_atomic_int_set(_pi, _val) +#define fluid_atomic_int_dec_and_test(_pi) g_atomic_int_dec_and_test(_pi) +#define fluid_atomic_int_compare_and_exchange(_pi, _old, _new) \ + g_atomic_int_compare_and_exchange(_pi, _old, _new) + +#if GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 30) +#define fluid_atomic_int_exchange_and_add(_pi, _add) \ + g_atomic_int_add(_pi, _add) +#define fluid_atomic_int_add(_pi, _add) \ + g_atomic_int_add(_pi, _add) +#else +#define fluid_atomic_int_exchange_and_add(_pi, _add) \ + g_atomic_int_exchange_and_add(_pi, _add) +#define fluid_atomic_int_add(_pi, _add) \ + g_atomic_int_exchange_and_add(_pi, _add) +#endif + +#define fluid_atomic_pointer_get(_pp) g_atomic_pointer_get(_pp) +#define fluid_atomic_pointer_set(_pp, val) g_atomic_pointer_set(_pp, val) +#define fluid_atomic_pointer_compare_and_exchange(_pp, _old, _new) \ + g_atomic_pointer_compare_and_exchange(_pp, _old, _new) + +static FLUID_INLINE void +fluid_atomic_float_set(fluid_atomic_float_t *fptr, float val) +{ + int32_t ival; + memcpy(&ival, &val, 4); + fluid_atomic_int_set((fluid_atomic_int_t *)fptr, ival); +} + +static FLUID_INLINE float +fluid_atomic_float_get(fluid_atomic_float_t *fptr) +{ + int32_t ival; + float fval; + ival = fluid_atomic_int_get((fluid_atomic_int_t *)fptr); + memcpy(&fval, &ival, 4); + return fval; +} + + +/* Threads */ + +/* other thread implementations might change this for their needs */ +typedef void *fluid_thread_return_t; +/* static return value for thread functions which requires a return value */ +#define FLUID_THREAD_RETURN_VALUE (NULL) + +typedef GThread fluid_thread_t; +typedef fluid_thread_return_t (*fluid_thread_func_t)(void *data); + +#define FLUID_THREAD_ID_NULL NULL /* A NULL "ID" value */ +#define fluid_thread_id_t GThread * /* Data type for a thread ID */ +#define fluid_thread_get_id() g_thread_self() /* Get unique "ID" for current thread */ + +fluid_thread_t *new_fluid_thread(const char *name, fluid_thread_func_t func, void *data, + int prio_level, int detach); +void delete_fluid_thread(fluid_thread_t *thread); +void fluid_thread_self_set_prio(int prio_level); +int fluid_thread_join(fluid_thread_t *thread); + +/* Dynamic Module Loading, currently only used by LADSPA subsystem */ +#ifdef LADSPA + +typedef GModule fluid_module_t; + +#define fluid_module_open(_name) g_module_open((_name), G_MODULE_BIND_LOCAL) +#define fluid_module_close(_mod) g_module_close(_mod) +#define fluid_module_error() g_module_error() +#define fluid_module_name(_mod) g_module_name(_mod) +#define fluid_module_symbol(_mod, _name, _ptr) g_module_symbol((_mod), (_name), (_ptr)) + +#endif /* LADSPA */ + +/* Sockets and I/O */ + +int fluid_istream_readline(fluid_istream_t in, fluid_ostream_t out, char *prompt, char *buf, int len); +int fluid_ostream_printf(fluid_ostream_t out, const char *format, ...); + +#if defined(_WIN32) +typedef SOCKET fluid_socket_t; +#else +typedef int fluid_socket_t; +#endif + +/* The function should return 0 if no error occurred, non-zero + otherwise. If the function return non-zero, the socket will be + closed by the server. */ +typedef int (*fluid_server_func_t)(void *data, fluid_socket_t client_socket, char *addr); + +fluid_server_socket_t *new_fluid_server_socket(int port, fluid_server_func_t func, void *data); +void delete_fluid_server_socket(fluid_server_socket_t *sock); +int fluid_server_socket_join(fluid_server_socket_t *sock); +void fluid_socket_close(fluid_socket_t sock); +fluid_istream_t fluid_socket_get_istream(fluid_socket_t sock); +fluid_ostream_t fluid_socket_get_ostream(fluid_socket_t sock); + +/* File access */ +#define fluid_stat(_filename, _statbuf) g_stat((_filename), (_statbuf)) +#if !GLIB_CHECK_VERSION(2, 26, 0) + /* GStatBuf has not been introduced yet, manually typedef to what they had at that time: + * https://github.com/GNOME/glib/blob/e7763678b56e3be073cc55d707a6e92fc2055ee0/... + */ + #if defined(_WIN32) || HAVE_WINDOWS_H // somehow reliably mock G_OS_WIN32?? + // Any effort from our side to reliably mock GStatBuf on Windows is in vain. E.g. glib-2.16 is broken as it uses struct stat rather than struct _stat32 on Win x86. + // Disable it (the user has been warned by cmake). + #undef fluid_stat + #define fluid_stat(_filename, _statbuf) (-1) + typedef struct _fluid_stat_buf_t{int st_mtime;} fluid_stat_buf_t; + #else + /* posix, OS/2, etc. */ + typedef struct stat fluid_stat_buf_t; + #endif +#else +typedef GStatBuf fluid_stat_buf_t; +#endif + +FILE* fluid_file_open(const char* filename, const char** errMsg); +fluid_long_long_t fluid_file_tell(FILE* f); + + +/* Profiling */ +#if WITH_PROFILING +/** profiling interface between Profiling command shell and Audio + rendering API (FluidProfile_0004.pdf- 3.2.2) +*/ + +/* + ----------------------------------------------------------------------------- + Shell task side | Profiling interface | Audio task side + ----------------------------------------------------------------------------- + profiling | Internal | | | Audio + command <---> |<-- profiling -->| Data |<--macros -->| <--> rendering + shell | API | | | API + +*/ + +/* default parameters for shell command "prof_start" in fluid_sys.c */ +#define FLUID_PROFILE_DEFAULT_BANK 0 /* default bank */ +#define FLUID_PROFILE_DEFAULT_PROG 16 /* default prog (organ) */ +#define FLUID_PROFILE_FIRST_KEY 12 /* first key generated */ +#define FLUID_PROFILE_LAST_KEY 108 /* last key generated */ +#define FLUID_PROFILE_DEFAULT_VEL 64 /* default note velocity */ +#define FLUID_PROFILE_VOICE_ATTEN -0.04f /* gain attenuation per voice (dB) */ + + +#define FLUID_PROFILE_DEFAULT_PRINT 0 /* default print mode */ +#define FLUID_PROFILE_DEFAULT_N_PROF 1 /* default number of measures */ +#define FLUID_PROFILE_DEFAULT_DURATION 500 /* default duration (ms) */ + + +extern unsigned short fluid_profile_notes; /* number of generated notes */ +extern unsigned char fluid_profile_bank; /* bank,prog preset used by */ +extern unsigned char fluid_profile_prog; /* generated notes */ +extern unsigned char fluid_profile_print; /* print mode */ + +extern unsigned short fluid_profile_n_prof;/* number of measures */ +extern unsigned short fluid_profile_dur; /* measure duration in ms */ +extern fluid_atomic_int_t fluid_profile_lock ; /* lock between multiple shell */ +/**/ + +/*---------------------------------------------- + Internal profiling API (in fluid_sys.c) +-----------------------------------------------*/ +/* Starts a profiling measure used in shell command "prof_start" */ +void fluid_profile_start_stop(unsigned int end_ticks, short clear_data); + +/* Returns status used in shell command "prof_start" */ +int fluid_profile_get_status(void); + +/* Prints profiling data used in shell command "prof_start" */ +void fluid_profiling_print_data(double sample_rate, fluid_ostream_t out); + +/* Returns True if profiling cancellation has been requested */ +int fluid_profile_is_cancel_req(void); + +/* For OS that implement <ENTER> key for profile cancellation: + 1) Adds #define FLUID_PROFILE_CANCEL + 2) Adds the necessary code inside fluid_profile_is_cancel() see fluid_sys.c +*/ +#if defined(_WIN32) /* Profile cancellation is supported for Windows */ +#define FLUID_PROFILE_CANCEL + +#elif defined(__OS2__) /* OS/2 specific stuff */ +/* Profile cancellation isn't yet supported for OS2 */ + +#else /* POSIX stuff */ +#define FLUID_PROFILE_CANCEL /* Profile cancellation is supported for linux */ +#include <unistd.h> /* STDIN_FILENO */ +#include <sys/select.h> /* select() */ +#endif /* posix */ + +/* logging profiling data (used on synthesizer instance deletion) */ +void fluid_profiling_print(void); + +/*---------------------------------------------- + Profiling Data (in fluid_sys.c) +-----------------------------------------------*/ +/** Profiling data. Keep track of min/avg/max values to profile a + piece of code. */ +typedef struct _fluid_profile_data_t +{ + const char *description; /* name of the piece of code under profiling */ + double min, max, total; /* duration (microsecond) */ + unsigned int count; /* total count */ + unsigned int n_voices; /* voices number */ + unsigned int n_samples; /* audio samples number */ +} fluid_profile_data_t; + +enum +{ + /* commands/status (profiling interface) */ + PROFILE_STOP, /* command to stop a profiling measure */ + PROFILE_START, /* command to start a profile measure */ + PROFILE_READY, /* status to signal that a profiling measure has finished + and ready to be printed */ + /*- State returned by fluid_profile_get_status() -*/ + /* between profiling commands and internal profiling API */ + PROFILE_RUNNING, /* a profiling measure is running */ + PROFILE_CANCELED,/* a profiling measure has been canceled */ +}; + +/* Data interface */ +extern unsigned char fluid_profile_status ; /* command and status */ +extern unsigned int fluid_profile_end_ticks; /* ending position (in ticks) */ +extern fluid_profile_data_t fluid_profile_data[]; /* Profiling data */ + +/*---------------------------------------------- + Probes macros +-----------------------------------------------*/ +/** Macro to obtain a time reference used for the profiling */ +#define fluid_profile_ref() fluid_utime() + +/** Macro to create a variable and assign the current reference time for profiling. + * So we don't get unused variable warnings when profiling is disabled. */ +#define fluid_profile_ref_var(name) double name = fluid_utime() + +/** + * Profile identifier numbers. List all the pieces of code you want to profile + * here. Be sure to add an entry in the fluid_profile_data table in + * fluid_sys.c + */ +enum +{ + FLUID_PROF_WRITE, + FLUID_PROF_ONE_BLOCK, + FLUID_PROF_ONE_BLOCK_CLEAR, + FLUID_PROF_ONE_BLOCK_VOICE, + FLUID_PROF_ONE_BLOCK_VOICES, + FLUID_PROF_ONE_BLOCK_REVERB, + FLUID_PROF_ONE_BLOCK_CHORUS, + FLUID_PROF_VOICE_NOTE, + FLUID_PROF_VOICE_RELEASE, + FLUID_PROFILE_NBR /* number of profile probes */ +}; +/** Those macros are used to calculate the min/avg/max. Needs a profile number, a + time reference, the voices and samples number. */ + +/* local macro : acquiere data */ +#define fluid_profile_data(_num, _ref, voices, samples)\ +{\ + double _now = fluid_utime();\ + double _delta = _now - _ref;\ + fluid_profile_data[_num].min = _delta < fluid_profile_data[_num].min ?\ + _delta : fluid_profile_data[_num].min; \ + fluid_profile_data[_num].max = _delta > fluid_profile_data[_num].max ?\ + _delta : fluid_profile_data[_num].max;\ + fluid_profile_data[_num].total += _delta;\ + fluid_profile_data[_num].count++;\ + fluid_profile_data[_num].n_voices += voices;\ + fluid_profile_data[_num].n_samples += samples;\ + _ref = _now;\ +} + +/** Macro to collect data, called from inner functions inside audio + rendering API */ +#define fluid_profile(_num, _ref, voices, samples)\ +{\ + if ( fluid_profile_status == PROFILE_START)\ + { /* acquires data */\ + fluid_profile_data(_num, _ref, voices, samples)\ + }\ +} + +/** Macro to collect data, called from audio rendering API (fluid_write_xxxx()). + This macro control profiling ending position (in ticks). +*/ +#define fluid_profile_write(_num, _ref, voices, samples)\ +{\ + if (fluid_profile_status == PROFILE_START)\ + {\ + /* acquires data first: must be done before checking that profile is + finished to ensure at least one valid data sample. + */\ + fluid_profile_data(_num, _ref, voices, samples)\ + if (fluid_synth_get_ticks(synth) >= fluid_profile_end_ticks)\ + {\ + /* profiling is finished */\ + fluid_profile_status = PROFILE_READY;\ + }\ + }\ +} + +#else + +/* No profiling */ +#define fluid_profiling_print() +#define fluid_profile_ref() 0 +#define fluid_profile_ref_var(name) +#define fluid_profile(_num,_ref,voices, samples) +#define fluid_profile_write(_num,_ref, voices, samples) +#endif /* WITH_PROFILING */ + +/** + + Memory locking + + Memory locking is used to avoid swapping of the large block of + sample data. + */ + +#if defined(HAVE_SYS_MMAN_H) && !defined(__OS2__) +#define fluid_mlock(_p,_n) mlock(_p, _n) +#define fluid_munlock(_p,_n) munlock(_p,_n) +#else +#define fluid_mlock(_p,_n) 0 +#define fluid_munlock(_p,_n) +#endif + + +/** + + Floating point exceptions + + fluid_check_fpe() checks for "unnormalized numbers" and other + exceptions of the floating point processor. +*/ +#ifdef FPE_CHECK +#define fluid_check_fpe(expl) fluid_check_fpe_i386(expl) +#define fluid_clear_fpe() fluid_clear_fpe_i386() +unsigned int fluid_check_fpe_i386(char *explanation_in_case_of_fpe); +void fluid_clear_fpe_i386(void); +#else +#define fluid_check_fpe(expl) +#define fluid_clear_fpe() +#endif + + +/* System control */ +void fluid_msleep(unsigned int msecs); + +/** + * Advances the given \c ptr to the next \c alignment byte boundary. + * Make sure you've allocated an extra of \c alignment bytes to avoid a buffer overflow. + * + * @note \c alignment must be a power of two + * @return Returned pointer is guaranteed to be aligned to \c alignment boundary and in range \f[ ptr <= returned_ptr < ptr + alignment \f]. + */ +static FLUID_INLINE void *fluid_align_ptr(const void *ptr, unsigned int alignment) +{ + uintptr_t ptr_int = (uintptr_t)ptr; + unsigned int offset = ptr_int & (alignment - 1); + unsigned int add = (alignment - offset) & (alignment - 1); // advance the pointer to the next alignment boundary + ptr_int += add; + + /* assert alignment is power of two */ + FLUID_ASSERT(!(alignment == 0) && !(alignment & (alignment - 1))); + + return (void *)ptr_int; +} + +#define FLUID_DEFAULT_ALIGNMENT (64U) + +#endif /* _FLUID_SYS_H */ diff --git a/libs/fluidsynth/src/utils/fluidsynth_priv.h b/libs/fluidsynth/src/utils/fluidsynth_priv.h new file mode 100644 index 00000000000..67e97abbd10 --- /dev/null +++ b/libs/fluidsynth/src/utils/fluidsynth_priv.h @@ -0,0 +1,331 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 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 + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +/* + * @file fluidsynth_priv.h + * + * lightweight part of fluid_sys.h, containing forward declarations of fluidsynth's private types and private macros + * + * include this one file in fluidsynth's private header files + */ + +#ifndef _FLUIDSYNTH_PRIV_H +#define _FLUIDSYNTH_PRIV_H + +#include "config.h" + +#include <glib.h> + +#if HAVE_STDLIB_H +#include <stdlib.h> // malloc, free +#endif + +#if HAVE_STDIO_H +#include <stdio.h> // printf +#endif + +#if HAVE_STRING_H +#include <string.h> +#endif + +#if HAVE_STRINGS_H +#include <strings.h> +#endif + +#include "fluidsynth.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*************************************************************** + * + * BASIC TYPES + */ + +#if defined(WITH_FLOAT) +typedef float fluid_real_t; +#else +typedef double fluid_real_t; +#endif + +#if defined(SUPPORTS_VLA) +# define FLUID_DECLARE_VLA(_type, _name, _len) \ + _type _name[_len] +#else +# define FLUID_DECLARE_VLA(_type, _name, _len) \ + _type* _name = g_newa(_type, (_len)) +#endif + + +/** Atomic types */ +typedef int fluid_atomic_int_t; +typedef unsigned int fluid_atomic_uint_t; +typedef float fluid_atomic_float_t; + + +/*************************************************************** + * + * FORWARD DECLARATIONS + */ +typedef struct _fluid_env_data_t fluid_env_data_t; +typedef struct _fluid_adriver_definition_t fluid_adriver_definition_t; +typedef struct _fluid_channel_t fluid_channel_t; +typedef struct _fluid_tuning_t fluid_tuning_t; +typedef struct _fluid_hashtable_t fluid_hashtable_t; +typedef struct _fluid_client_t fluid_client_t; +typedef struct _fluid_server_socket_t fluid_server_socket_t; +typedef struct _fluid_sample_timer_t fluid_sample_timer_t; +typedef struct _fluid_zone_range_t fluid_zone_range_t; +typedef struct _fluid_rvoice_eventhandler_t fluid_rvoice_eventhandler_t; + +/* Declare rvoice related typedefs here instead of fluid_rvoice.h, as it's needed + * in fluid_lfo.c and fluid_adsr.c as well */ +typedef union _fluid_rvoice_param_t +{ + void *ptr; + int i; + fluid_real_t real; +} fluid_rvoice_param_t; +enum { MAX_EVENT_PARAMS = 7 }; /**< Maximum number of #fluid_rvoice_param_t to be passed to an #fluid_rvoice_function_t */ +typedef void (*fluid_rvoice_function_t)(void *obj, const fluid_rvoice_param_t param[MAX_EVENT_PARAMS]); + +/* Macro for declaring an rvoice event function (#fluid_rvoice_function_t). The functions may only access + * those params that were previously set in fluid_voice.c + */ +#define DECLARE_FLUID_RVOICE_FUNCTION(name) void name(void* obj, const fluid_rvoice_param_t param[MAX_EVENT_PARAMS]) + + +/*************************************************************** + * + * CONSTANTS + */ + +#define FLUID_BUFSIZE 64 /**< FluidSynth internal buffer size (in samples) */ +#define FLUID_MIXER_MAX_BUFFERS_DEFAULT (8192/FLUID_BUFSIZE) /**< Number of buffers that can be processed in one rendering run */ +#define FLUID_MAX_EVENTS_PER_BUFSIZE 1024 /**< Maximum queued MIDI events per #FLUID_BUFSIZE */ +#define FLUID_MAX_RETURN_EVENTS 1024 /**< Maximum queued synthesis thread return events */ +#define FLUID_MAX_EVENT_QUEUES 16 /**< Maximum number of unique threads queuing events */ +#define FLUID_DEFAULT_AUDIO_RT_PRIO 60 /**< Default setting for audio.realtime-prio */ +#define FLUID_DEFAULT_MIDI_RT_PRIO 50 /**< Default setting for midi.realtime-prio */ +#define FLUID_NUM_MOD 64 /**< Maximum number of modulators in a voice */ + +/*************************************************************** + * + * SYSTEM INTERFACE + */ + +/* Math constants */ +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + +#ifndef M_LN2 +#define M_LN2 0.69314718055994530941723212145818 +#endif + +#ifndef M_LN10 +#define M_LN10 2.3025850929940456840179914546844 +#endif + +#define FLUID_M_PI ((fluid_real_t)M_PI) +#define FLUID_M_LN2 ((fluid_real_t)M_LN2) +#define FLUID_M_LN10 ((fluid_real_t)M_LN10) + +/* Math functions */ +#if defined WITH_FLOAT && defined HAVE_SINF +#define FLUID_SIN sinf +#else +#define FLUID_SIN (fluid_real_t)sin +#endif + +#if defined WITH_FLOAT && defined HAVE_COSF +#define FLUID_COS cosf +#else +#define FLUID_COS (fluid_real_t)cos +#endif + +#if defined WITH_FLOAT && defined HAVE_FABSF +#define FLUID_FABS fabsf +#else +#define FLUID_FABS (fluid_real_t)fabs +#endif + +#if defined WITH_FLOAT && defined HAVE_POWF +#define FLUID_POW powf +#else +#define FLUID_POW (fluid_real_t)pow +#endif + +#if defined WITH_FLOAT && defined HAVE_SQRTF +#define FLUID_SQRT sqrtf +#else +#define FLUID_SQRT (fluid_real_t)sqrt +#endif + +#if defined WITH_FLOAT && defined HAVE_LOGF +#define FLUID_LOGF logf +#else +#define FLUID_LOGF (fluid_real_t)log +#endif + +/* Memory allocation */ +#define FLUID_MALLOC(_n) fluid_alloc(_n) +#define FLUID_REALLOC(_p,_n) realloc(_p,_n) +#define FLUID_FREE(_p) fluid_free(_p) +#define FLUID_NEW(_t) (_t*)FLUID_MALLOC(sizeof(_t)) +#define FLUID_ARRAY_ALIGNED(_t,_n,_a) (_t*)FLUID_MALLOC((_n)*sizeof(_t) + ((unsigned int)_a - 1u)) +#define FLUID_ARRAY(_t,_n) FLUID_ARRAY_ALIGNED(_t,_n,1u) + +void* fluid_alloc(size_t len); + +/* File access */ +#define FLUID_FOPEN(_f,_m) fluid_fopen(_f,_m) +#define FLUID_FCLOSE(_f) fclose(_f) +#define FLUID_FREAD(_p,_s,_n,_f) fread(_p,_s,_n,_f) + +FILE *fluid_fopen(const char *filename, const char *mode); + +#ifdef _WIN32 +#define FLUID_FSEEK(_f,_n,_set) _fseeki64(_f,_n,_set) +#else +#define FLUID_FSEEK(_f,_n,_set) fseek(_f,_n,_set) +#endif + +#define FLUID_FTELL(_f) fluid_file_tell(_f) + +/* Memory functions */ +#define FLUID_MEMCPY(_dst,_src,_n) memcpy(_dst,_src,_n) +#define FLUID_MEMSET(_s,_c,_n) memset(_s,_c,_n) + +/* String functions */ +#define FLUID_STRLEN(_s) strlen(_s) +#define FLUID_STRCMP(_s,_t) strcmp(_s,_t) +#define FLUID_STRNCMP(_s,_t,_n) strncmp(_s,_t,_n) +#define FLUID_STRCPY(_dst,_src) strcpy(_dst,_src) +#define FLUID_STRTOL(_s,_e,_b) strtol(_s,_e,_b) + +#define FLUID_STRNCPY(_dst,_src,_n) \ +do { strncpy(_dst,_src,_n-1); \ + (_dst)[(_n)-1]='\0'; \ +}while(0) + +#define FLUID_STRCHR(_s,_c) strchr(_s,_c) +#define FLUID_STRRCHR(_s,_c) strrchr(_s,_c) + +#ifdef strdup +#define FLUID_STRDUP(s) strdup(s) +#else +#define FLUID_STRDUP(s) FLUID_STRCPY(FLUID_MALLOC(FLUID_STRLEN(s) + 1), s) +#endif + +#define FLUID_SPRINTF sprintf +#define FLUID_FPRINTF fprintf + +#if (defined(_WIN32) && _MSC_VER < 1900) || defined(MINGW32) +/* need to make sure we use a C99 compliant implementation of (v)snprintf(), + * i.e. not microsofts non compliant extension _snprintf() as it doesn't + * reliably null-terminate the buffer + */ +#define FLUID_SNPRINTF g_snprintf +#else +#define FLUID_SNPRINTF snprintf +#endif + +#if (defined(_WIN32) && _MSC_VER < 1500) || defined(MINGW32) +#define FLUID_VSNPRINTF g_vsnprintf +#else +#define FLUID_VSNPRINTF vsnprintf +#endif + +#if defined(_WIN32) && !defined(MINGW32) +#define FLUID_STRCASECMP _stricmp +#else +#define FLUID_STRCASECMP strcasecmp +#endif + +#if defined(_WIN32) && !defined(MINGW32) +#define FLUID_STRNCASECMP _strnicmp +#else +#define FLUID_STRNCASECMP strncasecmp +#endif + + +#define fluid_clip(_val, _min, _max) \ +{ (_val) = ((_val) < (_min))? (_min) : (((_val) > (_max))? (_max) : (_val)); } + +#if WITH_FTS +#define FLUID_PRINTF post +#define FLUID_FLUSH() +#else +#define FLUID_PRINTF printf +#define FLUID_FLUSH() fflush(stdout) +#endif + +/* People who want to reduce the size of the may do this by entirely + * removing the logging system. This will cause all log messages to + * be discarded at compile time, allowing to save about 80 KiB for + * the compiled binary. + */ +#if 0 +#define FLUID_LOG (void)sizeof +#else +#define FLUID_LOG fluid_log +#endif + +#if defined(DEBUG) && !defined(NDEBUG) +#define FLUID_ASSERT(a) g_assert(a) +#else +#define FLUID_ASSERT(a) +#endif + +#define FLUID_LIKELY G_LIKELY +#define FLUID_UNLIKELY G_UNLIKELY + +/* Misc */ +#if defined(__INTEL_COMPILER) +#define FLUID_RESTRICT restrict +#elif defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) +#define FLUID_RESTRICT __restrict__ +#elif defined(_MSC_VER) && _MSC_VER >= 1400 +#define FLUID_RESTRICT __restrict +#else +#warning "Dont know how this compiler handles restrict pointers, refuse to use them." +#define FLUID_RESTRICT +#endif + +#define FLUID_N_ELEMENTS(struct) (sizeof (struct) / sizeof (struct[0])) +#define FLUID_MEMBER_SIZE(struct, member) ( sizeof (((struct *)0)->member) ) + + +#define fluid_return_if_fail(cond) \ +if(cond) \ + ; \ +else \ + return + +#define fluid_return_val_if_fail(cond, val) \ + fluid_return_if_fail(cond) (val) + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_PRIV_H */