From: Robin Gareus Date: Tue, 23 Aug 2016 16:50:54 +0000 (+0200) Subject: Import libfluidsynth into the Ardour codebase X-Git-Tag: 5.1~23 X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=commitdiff_plain;h=ac05f050238d4b51c7b6a042e25cee59dc34da80 Import libfluidsynth into the Ardour codebase --- diff --git a/libs/fluidsynth/README b/libs/fluidsynth/README new file mode 100644 index 0000000000..6032b9c299 --- /dev/null +++ b/libs/fluidsynth/README @@ -0,0 +1,8 @@ +This is a stripped down version of fluidsynth (library only) + +from git://git.code.sf.net/p/fluidsynth/code-git +revisition f52597be038a5a045fc74b6f96d5f9b0bbbbc044 from May/2015 +imported into Ardour Aug/2016 + +fluidsynth is licensed in terms of the LGPL-2+, see individual source +files for (C) holders. diff --git a/libs/fluidsynth/config.h b/libs/fluidsynth/config.h new file mode 100644 index 0000000000..3df4483053 --- /dev/null +++ b/libs/fluidsynth/config.h @@ -0,0 +1,225 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define FLUIDSYNTH_VERSION_MAJOR 1 +#define FLUIDSYNTH_VERSION_MINOR 0 +#define FLUIDSYNTH_VERSION_MICRO 6 +#define FLUIDSYNTH_VERSION "1.0.6" + +/* Define to enable ALSA driver */ +/* #undef ALSA_SUPPORT */ + +/* Define to activate sound output to files */ +/* #undef AUFILE_SUPPORT */ + +/* 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 */ + +/* Define to enable FPE checks */ +/* #undef FPE_CHECK */ + +/* Define to 1 if you have the header file. */ +#ifndef _WIN32 +# define HAVE_ARPA_INET_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_DLFCN_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* whether or not we are supporting ladcca */ +/* #undef HAVE_LADCCA */ + +/* whether or not we are supporting lash */ +/* #undef HAVE_LASH */ + +/* Define to 1 if you have the `dl' library (-ldl). */ +#define HAVE_LIBDL 1 + +/* Define to 1 if you have the `MidiShare' library (-lMidiShare). */ +/* #undef HAVE_LIBMIDISHARE */ + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#define HAVE_LIBPTHREAD TRUE + +/* Define to 1 if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MACHINE_SOUNDCARD_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_MATH_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_MIDISHARE_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_IN_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_NETINET_TCP_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_PTHREAD_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SIGNAL_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_STDARG_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file. */ +#ifndef _WIN32 +# define HAVE_SYS_MMAN_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#ifndef _WIN32 +# define HAVE_SYS_SOCKET_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_SYS_SOUNDCARD_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#ifdef _WIN32 +# define HAVE_WINDOWS_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_GETOPT_H */ + +/* Define to enable JACK driver */ +/* #undef JACK_SUPPORT */ + +/* Include the LADSPA Fx unit */ +/* #undef LADSPA */ + +/* 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 */ +/* #undef MINGW32 */ + +/* Define to 1 if your C compiler doesn't accept -c and -o together. */ +/* #undef NO_MINUS_C_MINUS_O */ + +/* Define to enable OSS driver */ +/* #undef OSS_SUPPORT TRUE */ + +/* 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 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to enable SIGFPE assertions */ +/* #undef TRAP_ON_FPE */ + +/* Version number of package */ +#define VERSION "1.1.6" + +/* 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 WITH_READLINE */ + +/* 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 + +#endif /* CONFIG_H */ diff --git a/libs/fluidsynth/fluidsynth/event.h b/libs/fluidsynth/fluidsynth/event.h new file mode 100644 index 0000000000..b154304515 --- /dev/null +++ b/libs/fluidsynth/fluidsynth/event.h @@ -0,0 +1,135 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file event.h + * @brief Sequencer event functions and defines. + * + * Functions and constants for creating/processing 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 (DOCME) */ + FLUID_SEQ_PITCHBEND, /**< Pitch bend message */ + FLUID_SEQ_PITCHWHEELSENS, /**< Pitch wheel sensitivity set message @since 1.1.0 was mispelled 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 (DOCME) */ + FLUID_SEQ_ANYCONTROLCHANGE, /**< DOCME (used for remove_events only) */ + FLUID_SEQ_CHANNELPRESSURE, /**< Channel aftertouch event @since 1.1.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_LASTEVENT /**< Defines the count of event enums */ +}; + +#define FLUID_SEQ_PITCHWHHELSENS FLUID_SEQ_PITCHWHEELSENS /**< Old deprecated misspelling of #FLUID_SEQ_PITCHWHEELSENS */ + +/* Event alloc/free */ +FLUIDSYNTH_API fluid_event_t* new_fluid_event(void); +FLUIDSYNTH_API void delete_fluid_event(fluid_event_t* evt); + +/* Initializing events */ +FLUIDSYNTH_API void fluid_event_set_source(fluid_event_t* evt, short src); +FLUIDSYNTH_API void fluid_event_set_dest(fluid_event_t* evt, short 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, short 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, short 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, short val); +FLUIDSYNTH_API void fluid_event_modulation(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_sustain(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_pan(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_volume(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_reverb_send(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_chorus_send(fluid_event_t* evt, int channel, short val); + +FLUIDSYNTH_API void fluid_event_channel_pressure(fluid_event_t* evt, int channel, short val); +FLUIDSYNTH_API void fluid_event_system_reset(fluid_event_t* evt); + + +/* Only for removing events */ +FLUIDSYNTH_API void fluid_event_any_control_change(fluid_event_t* evt, int channel); + +/* Only when unregistering clients */ +FLUIDSYNTH_API void fluid_event_unregistering(fluid_event_t* evt); + +/* Accessing event data */ +FLUIDSYNTH_API int fluid_event_get_type(fluid_event_t* evt); +FLUIDSYNTH_API short fluid_event_get_source(fluid_event_t* evt); +FLUIDSYNTH_API short 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 short fluid_event_get_value(fluid_event_t* evt); +FLUIDSYNTH_API short 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 unsigned int fluid_event_get_sfont_id(fluid_event_t* evt); + +#ifdef __cplusplus +} +#endif +#endif /* _FLUIDSYNTH_EVENT_H */ diff --git a/libs/fluidsynth/fluidsynth/fluidsynth.h b/libs/fluidsynth/fluidsynth/fluidsynth.h new file mode 100644 index 0000000000..8c599b5be7 --- /dev/null +++ b/libs/fluidsynth/fluidsynth/fluidsynth.h @@ -0,0 +1,38 @@ +#ifndef _FLUIDSYNTH_H +#define _FLUIDSYNTH_H + +#include + +#if defined(COMPILER_MSVC) +# define FLUIDSYNTH_API +#else +# define FLUIDSYNTH_API __attribute__ ((visibility ("hidden"))) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +FLUIDSYNTH_API void fluid_version(int *major, int *minor, int *micro); +FLUIDSYNTH_API char* fluid_version_str(void); + + +#include "types.h" +#include "settings.h" +#include "synth.h" +#include "sfont.h" +#include "event.h" +#include "midi.h" +#include "log.h" +#include "misc.h" +#include "mod.h" +#include "gen.h" +#include "voice.h" + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_H */ diff --git a/libs/fluidsynth/fluidsynth/gen.h b/libs/fluidsynth/fluidsynth/gen.h new file mode 100644 index 0000000000..e4bbc8ef69 --- /dev/null +++ b/libs/fluidsynth/fluidsynth/gen.h @@ -0,0 +1,135 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file gen.h + * @brief 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 */ + + /* the initial pitch is not a "standard" generator. It is not + * mentioned in the list of generator in the SF2 specifications. It + * is used, however, as the destination for the default pitch wheel + * modulator. */ + GEN_PITCH, /**< Pitch (NOTE: Not a real SoundFont generator) */ + GEN_LAST /**< Value defines the count of generators (#fluid_gen_type) */ +}; + + +/** + * 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 */ + GEN_ABS_NRPN /**< Generator is an absolute value */ +}; + +FLUIDSYNTH_API int fluid_gen_set_default_values(fluid_gen_t* gen); + + + +#ifdef __cplusplus +} +#endif +#endif /* _FLUIDSYNTH_GEN_H */ + diff --git a/libs/fluidsynth/fluidsynth/log.h b/libs/fluidsynth/fluidsynth/log.h new file mode 100644 index 0000000000..85db03e1cb --- /dev/null +++ b/libs/fluidsynth/fluidsynth/log.h @@ -0,0 +1,84 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/** + * @file log.h + * @brief 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 + */ + +/** + * 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 +}; + +/** + * 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, 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, char* message, void* data); + +FLUIDSYNTH_API int fluid_log(int level, const char *fmt, ...); + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_LOG_H */ diff --git a/libs/fluidsynth/fluidsynth/midi.h b/libs/fluidsynth/fluidsynth/midi.h new file mode 100644 index 0000000000..ab1e6a198b --- /dev/null +++ b/libs/fluidsynth/fluidsynth/midi.h @@ -0,0 +1,141 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file midi.h + * @brief Functions for MIDI events, drivers and MIDI file playback. + */ + +FLUIDSYNTH_API fluid_midi_event_t* new_fluid_midi_event(void); +FLUIDSYNTH_API int delete_fluid_midi_event(fluid_midi_event_t* event); + +FLUIDSYNTH_API int fluid_midi_event_set_type(fluid_midi_event_t* evt, int type); +FLUIDSYNTH_API int fluid_midi_event_get_type(fluid_midi_event_t* evt); +FLUIDSYNTH_API int fluid_midi_event_set_channel(fluid_midi_event_t* evt, int chan); +FLUIDSYNTH_API int fluid_midi_event_get_channel(fluid_midi_event_t* evt); +FLUIDSYNTH_API int fluid_midi_event_get_key(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(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(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(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(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(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); + +/** + * 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 /**< Total count of rule types */ +} fluid_midi_router_rule_type; + +/** + * Generic callback function for MIDI events. + * @param data User defined data pointer + * @param event The MIDI event + * @return Should return #FLUID_OK on success, #FLUID_FAILED otherwise + * + * Will be used between + * - MIDI driver and MIDI router + * - MIDI router and synth + * to communicate events. + * In the not-so-far future... + */ +typedef int (*handle_midi_event_func_t)(void* data, fluid_midi_event_t* event); + +FLUIDSYNTH_API fluid_midi_router_t* new_fluid_midi_router(fluid_settings_t* settings, + handle_midi_event_func_t handler, + void* event_handler_data); +FLUIDSYNTH_API int delete_fluid_midi_router(fluid_midi_router_t* handler); +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); +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); +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); + + +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); + + +/** + * MIDI 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_DONE /**< Player is finished playing */ +}; + +FLUIDSYNTH_API fluid_player_t* new_fluid_player(fluid_synth_t* synth); +FLUIDSYNTH_API int delete_fluid_player(fluid_player_t* player); +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_midi_tempo(fluid_player_t* player, int tempo); +FLUIDSYNTH_API int fluid_player_set_bpm(fluid_player_t* player, int bpm); +FLUIDSYNTH_API int fluid_player_get_status(fluid_player_t* player); +FLUIDSYNTH_API int fluid_player_set_playback_callback(fluid_player_t* player, handle_midi_event_func_t handler, void* handler_data); + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_MIDI_H */ diff --git a/libs/fluidsynth/fluidsynth/misc.h b/libs/fluidsynth/fluidsynth/misc.h new file mode 100644 index 0000000000..4f97d8437d --- /dev/null +++ b/libs/fluidsynth/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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/** + * @file misc.h + * @brief Miscellaneous utility functions and defines + */ + +/** + * Value that indicates success, used by most libfluidsynth functions. + * @since 1.1.0 + * + * NOTE: This was not publicly defined prior to libfluidsynth 1.1.0. When + * writing code which should also be compatible with older versions, something + * like the following can be used: + * + * @code + * #include + * + * #ifndef FLUID_OK + * #define FLUID_OK (0) + * #define FLUID_FAILED (-1) + * #endif + * @endcode + */ +#define FLUID_OK (0) + +/** + * Value that indicates failure, used by most libfluidsynth functions. + * @since 1.1.0 + * + * NOTE: See #FLUID_OK for more details. + */ +#define FLUID_FAILED (-1) + + +FLUIDSYNTH_API int fluid_is_soundfont (const char *filename); +FLUIDSYNTH_API int fluid_is_midifile (const char *filename); + + +#ifdef WIN32 +FLUIDSYNTH_API void* fluid_get_hinstance(void); +FLUIDSYNTH_API void fluid_set_hinstance(void* hinstance); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_MISC_H */ diff --git a/libs/fluidsynth/fluidsynth/mod.h b/libs/fluidsynth/fluidsynth/mod.h new file mode 100644 index 0000000000..e34309546b --- /dev/null +++ b/libs/fluidsynth/fluidsynth/mod.h @@ -0,0 +1,112 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file mod.h + * @brief SoundFont modulator functions and constants. + */ + +#define FLUID_NUM_MOD 64 /**< Maximum number of modulators in a voice */ + +/** + * 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; +}; + +/** + * 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) */ +}; + +/** + * 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 */ +}; + +FLUIDSYNTH_API fluid_mod_t* fluid_mod_new(void); +FLUIDSYNTH_API void fluid_mod_delete(fluid_mod_t * mod); + +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(fluid_mod_t* mod); +FLUIDSYNTH_API int fluid_mod_get_flags1(fluid_mod_t* mod); +FLUIDSYNTH_API int fluid_mod_get_source2(fluid_mod_t* mod); +FLUIDSYNTH_API int fluid_mod_get_flags2(fluid_mod_t* mod); +FLUIDSYNTH_API int fluid_mod_get_dest(fluid_mod_t* mod); +FLUIDSYNTH_API double fluid_mod_get_amount(fluid_mod_t* mod); + +FLUIDSYNTH_API int fluid_mod_test_identity(fluid_mod_t * mod1, fluid_mod_t * mod2); + + +#ifdef __cplusplus +} +#endif +#endif /* _FLUIDSYNTH_MOD_H */ + diff --git a/libs/fluidsynth/fluidsynth/settings.h b/libs/fluidsynth/fluidsynth/settings.h new file mode 100644 index 0000000000..3a0502a920 --- /dev/null +++ b/libs/fluidsynth/fluidsynth/settings.h @@ -0,0 +1,219 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file settings.h + * @brief Synthesizer settings + * @defgroup SettingsFunctions 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 + +/** + * Hint FLUID_HINT_SAMPLE_RATE indicates that any bounds specified + * should be interpreted as multiples of the sample rate. For + * instance, a frequency range from 0Hz to the Nyquist frequency (half + * the sample rate) could be requested by this hint in conjunction + * with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds + * at all must support this hint to retain meaning. + */ +#define FLUID_HINT_SAMPLE_RATE 0x8 + +/** + * Hint FLUID_HINT_LOGARITHMIC indicates that it is likely that the + * user will find it more intuitive to view values using a logarithmic + * scale. This is particularly useful for frequencies and gains. + */ +#define FLUID_HINT_LOGARITHMIC 0x10 + +/** + * Hint FLUID_HINT_INTEGER indicates that a user interface would + * probably wish to provide a stepped control taking only integer + * values. + * @deprecated + * + * As there is an integer setting type, this hint is not used. + */ +#define FLUID_HINT_INTEGER 0x20 + + +#define FLUID_HINT_FILENAME 0x01 /**< String setting is a file name */ +#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 */ +}; + + +FLUIDSYNTH_API fluid_settings_t* new_fluid_settings(void); +FLUIDSYNTH_API void delete_fluid_settings(fluid_settings_t* settings); + +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); + +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(fluid_settings_t* settings, const char *name, char** str); + +FLUIDSYNTH_API +char* fluid_settings_getstr_default(fluid_settings_t* settings, const char *name); + +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 +double fluid_settings_getnum_default(fluid_settings_t* settings, const char *name); + +FLUIDSYNTH_API +void 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); + +FLUIDSYNTH_API +void 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, char *name, 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, 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/fluidsynth/sfont.h b/libs/fluidsynth/fluidsynth/sfont.h new file mode 100644 index 0000000000..30aebfd126 --- /dev/null +++ b/libs/fluidsynth/fluidsynth/sfont.h @@ -0,0 +1,281 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/** + * @file sfont.h + * @brief SoundFont plugins + * + * It is possible to add new SoundFont loaders to the + * synthesizer. The API uses a couple of "interfaces" (structures + * with callback functions): #fluid_sfloader_t, #fluid_sfont_t, and + * #fluid_preset_t. 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 structure. The important callback function in + * this structure is "load", which should try to load a file and + * returns a #fluid_sfont_t structure, 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 structure. + * + * The #fluid_preset_t structure contains some functions to obtain + * information from the preset (name, bank, number). The most + * important callback is the noteon function. The noteon function + * 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 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. + */ + +/** + * 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 */ +}; + + +/** + * SoundFont loader structure. + */ +struct _fluid_sfloader_t { + void* data; /**< User defined data pointer */ + + /** + * The free method should free the memory allocated for the loader in + * addition to any private data. + * @param loader SoundFont loader + * @return Should return 0 if no error occured, non-zero otherwise + */ + int (*free)(fluid_sfloader_t* loader); + + /** + * 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 occured. + */ + fluid_sfont_t* (*load)(fluid_sfloader_t* loader, const char* filename); +}; + +/** + * Virtual SoundFont instance structure. + */ +struct _fluid_sfont_t { + void* data; /**< User defined data */ + unsigned int id; /**< SoundFont ID */ + + /** + * 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. + */ + int (*free)(fluid_sfont_t* sfont); + + /** + * Method to return the name of a virtual SoundFont. + * @param sfont Virtual SoundFont + * @return The name of the virtual SoundFont. + */ + char* (*get_name)(fluid_sfont_t* sfont); + + /** + * Get a virtual SoundFont preset by bank and program numbers. + * @param sfont Virtual SoundFont + * @param bank MIDI bank number (0-16384) + * @param prenum MIDI preset number (0-127) + * @return Should return an allocated virtual preset or NULL if it could not + * be found. + */ + fluid_preset_t* (*get_preset)(fluid_sfont_t* sfont, unsigned int bank, unsigned int prenum); + + /** + * Start virtual SoundFont preset iteration method. + * @param sfont Virtual SoundFont + * + * Starts/re-starts virtual preset iteration in a SoundFont. + */ + void (*iteration_start)(fluid_sfont_t* sfont); + + /** + * Virtual SoundFont preset iteration function. + * @param sfont Virtual SoundFont + * @param preset Caller supplied preset to fill in with current preset information + * @return 0 when no more presets are available, 1 otherwise + * + * Should store preset information to the caller supplied \a preset structure + * and advance the internal iteration state to the next preset for subsequent + * calls. + */ + int (*iteration_next)(fluid_sfont_t* sfont, fluid_preset_t* preset); +}; + +#define fluid_sfont_get_id(_sf) ((_sf)->id) + +/** + * Virtual SoundFont preset. + */ +struct _fluid_preset_t { + void* data; /**< User supplied data */ + fluid_sfont_t* sfont; /**< Parent virtual SoundFont */ + + /** + * Method to free a virtual SoundFont preset. + * @param preset Virtual SoundFont preset + * @return Should return 0 + */ + int (*free)(fluid_preset_t* preset); + + /** + * 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). + */ + char* (*get_name)(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 + */ + int (*get_banknum)(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 + */ + int (*get_num)(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. + */ + int (*noteon)(fluid_preset_t* preset, fluid_synth_t* synth, int chan, int key, int vel); + + /** + * 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 */ + 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; /**< Values: #FLUID_SAMPLETYPE_MONO, FLUID_SAMPLETYPE_RIGHT, FLUID_SAMPLETYPE_LEFT, FLUID_SAMPLETYPE_ROM */ + int valid; /**< Should be TRUE if sample data is valid, FALSE otherwise (in which case it will not be synthesized) */ + short* data; /**< Pointer to the sample's data */ + + 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 (use #fluid_sample_refcount to access this field) */ + + /** + * 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); + + void* userdata; /**< User defined data */ +}; + + +#define fluid_sample_refcount(_sample) ((_sample)->refcount) /**< Get the reference count of a sample. Should only be called from within synthesis context (noteon method for example) */ + + +#define FLUID_SAMPLETYPE_MONO 1 /**< Flag for #fluid_sample_t \a sampletype field for mono samples */ +#define FLUID_SAMPLETYPE_RIGHT 2 /**< Flag for #fluid_sample_t \a sampletype field for right samples of a stereo pair */ +#define FLUID_SAMPLETYPE_LEFT 4 /**< Flag for #fluid_sample_t \a sampletype field for left samples of a stereo pair */ +#define FLUID_SAMPLETYPE_LINKED 8 /**< Flag for #fluid_sample_t \a sampletype field, not used currently */ +#define FLUID_SAMPLETYPE_ROM 0x8000 /**< Flag for #fluid_sample_t \a sampletype field, ROM sample, causes sample to be ignored */ + + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_SFONT_H */ diff --git a/libs/fluidsynth/fluidsynth/synth.h b/libs/fluidsynth/fluidsynth/synth.h new file mode 100644 index 0000000000..f62e60cd1b --- /dev/null +++ b/libs/fluidsynth/fluidsynth/synth.h @@ -0,0 +1,315 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/** + * @file synth.h + * @brief Embeddable SoundFont synthesizer + * + * You create a new synthesizer with new_fluid_synth() and you destroy + * if with delete_fluid_synth(). Use the settings 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 described below 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(), ... + */ + +#define FLUID_SYNTH_CHANNEL_INFO_NAME_SIZE 32 /**< Length of channel info name field (including zero terminator) */ + +/** + * Channel information structure for fluid_synth_get_channel_info(). + * @since 1.1.1 + */ +struct _fluid_synth_channel_info_t +{ + int assigned : 1; /**< TRUE if a preset is assigned, FALSE otherwise */ + /* Reserved flag bits (at the least 31) */ + int sfont_id; /**< ID of parent SoundFont */ + int bank; /**< MIDI bank number (0-16383) */ + int program; /**< MIDI program number (0-127) */ + char name[FLUID_SYNTH_CHANNEL_INFO_NAME_SIZE]; /**< Channel preset name */ + char reserved[32]; /**< Reserved data for future expansion */ +}; + +FLUIDSYNTH_API fluid_synth_t* new_fluid_synth(fluid_settings_t* settings); +FLUIDSYNTH_API int delete_fluid_synth(fluid_synth_t* synth); +FLUIDSYNTH_API fluid_settings_t* fluid_synth_get_settings(fluid_synth_t* synth); + + +/* MIDI channel messages */ + +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_bank_select(fluid_synth_t* synth, int chan, unsigned int bank); +FLUIDSYNTH_API int fluid_synth_sfont_select(fluid_synth_t* synth, int chan, unsigned int sfont_id); +FLUIDSYNTH_API +int fluid_synth_program_select(fluid_synth_t* synth, int chan, unsigned int sfont_id, + unsigned int bank_num, unsigned int preset_num); +FLUIDSYNTH_API int +fluid_synth_program_select_by_sfont_name (fluid_synth_t* synth, int chan, + const char *sfont_name, unsigned int bank_num, + unsigned int preset_num); +FLUIDSYNTH_API +int fluid_synth_get_program(fluid_synth_t* synth, int chan, unsigned int* sfont_id, + unsigned int* bank_num, unsigned int* preset_num); +FLUIDSYNTH_API int fluid_synth_unset_program (fluid_synth_t *synth, int chan); +FLUIDSYNTH_API int fluid_synth_get_channel_info (fluid_synth_t *synth, int chan, + fluid_synth_channel_info_t *info); +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); + +enum fluid_midi_channel_type +{ + CHANNEL_TYPE_MELODIC = 0, + CHANNEL_TYPE_DRUM = 1 +}; + +int fluid_synth_set_channel_type(fluid_synth_t* synth, int chan, int type); + + +/* Low level access */ +FLUIDSYNTH_API fluid_preset_t* fluid_synth_get_channel_preset(fluid_synth_t* synth, int chan); +FLUIDSYNTH_API int fluid_synth_start(fluid_synth_t* synth, unsigned int id, + fluid_preset_t* preset, int audio_chan, + int midi_chan, int key, int vel); +FLUIDSYNTH_API int fluid_synth_stop(fluid_synth_t* synth, unsigned int id); + + +/* SoundFont management */ + +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, unsigned int id); +FLUIDSYNTH_API int fluid_synth_sfunload(fluid_synth_t* synth, unsigned int id, int reset_presets); +FLUIDSYNTH_API int fluid_synth_add_sfont(fluid_synth_t* synth, fluid_sfont_t* sfont); +FLUIDSYNTH_API void 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, unsigned 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); + + +/* Reverb */ + +FLUIDSYNTH_API void fluid_synth_set_reverb(fluid_synth_t* synth, double roomsize, + double damping, double width, double level); +FLUIDSYNTH_API void fluid_synth_set_reverb_on(fluid_synth_t* synth, int on); +FLUIDSYNTH_API double fluid_synth_get_reverb_roomsize(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_reverb_damp(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_reverb_level(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_reverb_width(fluid_synth_t* synth); + +#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 */ + + +/* Chorus */ + +/** + * 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 */ +}; + +FLUIDSYNTH_API void fluid_synth_set_chorus(fluid_synth_t* synth, int nr, double level, + double speed, double depth_ms, int type); +FLUIDSYNTH_API void fluid_synth_set_chorus_on(fluid_synth_t* synth, int on); +FLUIDSYNTH_API int fluid_synth_get_chorus_nr(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_chorus_level(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_chorus_speed_Hz(fluid_synth_t* synth); +FLUIDSYNTH_API double fluid_synth_get_chorus_depth_ms(fluid_synth_t* synth); +FLUIDSYNTH_API int fluid_synth_get_chorus_type(fluid_synth_t* synth); /* see fluid_chorus_mod */ + +#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 */ + + +/* Audio and MIDI channels */ + +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); + + +/* Synthesis parameters */ + +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 */ +}; + +#define FLUID_INTERP_DEFAULT FLUID_INTERP_4THORDER /**< Default interpolation method from #fluid_interp. */ +#define FLUID_INTERP_HIGHEST FLUID_INTERP_7THORDER /**< Highest interpolation method from #fluid_interp. */ + + +/* Generator interface */ + +FLUIDSYNTH_API +int fluid_synth_set_gen(fluid_synth_t* synth, int chan, int param, float value); +FLUIDSYNTH_API int fluid_synth_set_gen2 (fluid_synth_t* synth, int chan, + int param, float value, + int absolute, int normalized); +FLUIDSYNTH_API float fluid_synth_get_gen(fluid_synth_t* synth, int chan, int param); + + +/* Tuning */ + +FLUIDSYNTH_API +int fluid_synth_create_key_tuning(fluid_synth_t* synth, int bank, int prog, + const char* name, const double* pitch); +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_create_octave_tuning(fluid_synth_t* synth, int bank, int prog, + const char* name, const double* pitch); +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_select_tuning(fluid_synth_t* synth, int chan, int bank, int prog); +FLUIDSYNTH_API +int fluid_synth_activate_tuning(fluid_synth_t* synth, int chan, int bank, int prog, + int apply); +FLUIDSYNTH_API int fluid_synth_reset_tuning(fluid_synth_t* synth, int chan); +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); + +/* Misc */ + +FLUIDSYNTH_API double fluid_synth_get_cpu_load(fluid_synth_t* synth); +FLUIDSYNTH_API char* fluid_synth_error(fluid_synth_t* synth); + + +/* + * Synthesizer plugin + * + * To create a synthesizer plugin, create the synthesizer as + * explained above. Once the synthesizer is created you can call + * any of the functions below to get the audio. + */ + +FLUIDSYNTH_API int fluid_synth_write_s16(fluid_synth_t* synth, int len, + void* lout, int loff, int lincr, + void* rout, int roff, int rincr); +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); +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 nin, float** in, + int nout, float** out); + +/** + * 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); + +/* Synthesizer's interface to handle SoundFont loaders */ + +FLUIDSYNTH_API void fluid_synth_add_sfloader(fluid_synth_t* synth, fluid_sfloader_t* loader); +FLUIDSYNTH_API fluid_voice_t* fluid_synth_alloc_voice(fluid_synth_t* synth, fluid_sample_t* sample, + int channum, int key, int vel); +FLUIDSYNTH_API void fluid_synth_start_voice(fluid_synth_t* synth, fluid_voice_t* voice); +FLUIDSYNTH_API void fluid_synth_get_voicelist(fluid_synth_t* synth, + fluid_voice_t* buf[], int bufsize, int ID); +FLUIDSYNTH_API int fluid_synth_handle_midi_event(void* data, fluid_midi_event_t* event); +FLUIDSYNTH_API void fluid_synth_set_midi_router(fluid_synth_t* synth, + fluid_midi_router_t* router); + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_SYNTH_H */ diff --git a/libs/fluidsynth/fluidsynth/types.h b/libs/fluidsynth/fluidsynth/types.h new file mode 100644 index 0000000000..e956d818db --- /dev/null +++ b/libs/fluidsynth/fluidsynth/types.h @@ -0,0 +1,68 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/** + * @file types.h + * @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_synth_channel_info_t fluid_synth_channel_info_t; /**< SoundFont channel info */ +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_handler_t; /**< Command handler */ +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 int fluid_istream_t; /**< Input stream descriptor */ +typedef int fluid_ostream_t; /**< Output stream descriptor */ + + +#ifdef __cplusplus +} +#endif + +#endif /* _FLUIDSYNTH_TYPES_H */ diff --git a/libs/fluidsynth/fluidsynth/voice.h b/libs/fluidsynth/fluidsynth/voice.h new file mode 100644 index 0000000000..fe7ad8c1ab --- /dev/null +++ b/libs/fluidsynth/fluidsynth/voice.h @@ -0,0 +1,64 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +/** + * @file voice.h + * @brief Synthesis voice manipulation functions. + * + * The interface to the synthesizer's voices. + * Examples on using them can be found in fluid_defsfont.c. + * Most of these functions should only be called from within synthesis context, + * such as the SoundFont loader's noteon method. + */ + + +FLUIDSYNTH_API void fluid_voice_update_param(fluid_voice_t* voice, int gen); + +/** + * 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 void fluid_voice_gen_set(fluid_voice_t* voice, int gen, float val); +FLUIDSYNTH_API float fluid_voice_gen_get(fluid_voice_t* voice, int gen); +FLUIDSYNTH_API void fluid_voice_gen_incr(fluid_voice_t* voice, int gen, float val); + +FLUIDSYNTH_API unsigned int fluid_voice_get_id(fluid_voice_t* voice); +FLUIDSYNTH_API int fluid_voice_is_playing(fluid_voice_t* voice); +FLUIDSYNTH_API int fluid_voice_optimize_sample(fluid_sample_t* s); + + +#ifdef __cplusplus +} +#endif +#endif /* _FLUIDSYNTH_VOICE_H */ + diff --git a/libs/fluidsynth/src/fluid_adsr_env.c b/libs/fluidsynth/src/fluid_adsr_env.c new file mode 100644 index 0000000000..1d31fdb5e6 --- /dev/null +++ b/libs/fluidsynth/src/fluid_adsr_env.c @@ -0,0 +1,38 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +void +fluid_adsr_env_set_data(fluid_adsr_env_t* env, + 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) +{ + 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/fluid_adsr_env.h b/libs/fluidsynth/src/fluid_adsr_env.h new file mode 100644 index 0000000000..31303a9ce9 --- /dev/null +++ b/libs/fluidsynth/src/fluid_adsr_env.h @@ -0,0 +1,162 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_t{ + 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_t 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; + int section; + fluid_real_t val; /* the current value of the envelope */ +}; + +/* For performance, all functions are inlined */ + +static FLUID_INLINE void +fluid_adsr_env_calc(fluid_adsr_env_t* env, int is_volenv) +{ + 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] + if (env->section == FLUID_VOICE_ENVDECAY && is_volenv) + 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; + } + + env->val = x; + env->count++; +} + +/* This one cannot be inlined since it is referenced in + the event queue */ +void +fluid_adsr_env_set_data(fluid_adsr_env_t* env, + 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); + +static inline void +fluid_adsr_env_reset(fluid_adsr_env_t* env) +{ + env->count = 0; + env->section = 0; + env->val = 0.0f; +} + +static inline fluid_real_t +fluid_adsr_env_get_val(fluid_adsr_env_t* env) +{ + return env->val; +} + +static inline void +fluid_adsr_env_set_val(fluid_adsr_env_t* env, fluid_real_t val) +{ + env->val = val; +} + +static inline fluid_adsr_env_section_t +fluid_adsr_env_get_section(fluid_adsr_env_t* env) +{ + return env->section; +} + +static 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 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/fluid_chan.c b/libs/fluidsynth/src/fluid_chan.c new file mode 100644 index 0000000000..c6eb723146 --- /dev/null +++ b/libs/fluidsynth/src/fluid_chan.c @@ -0,0 +1,291 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 prognum, banknum; + + chan->sostenuto_orderid = 0; + + 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 + http://www.midi.org/techspecs/rp15.php +*/ +void +fluid_channel_init_ctrl(fluid_channel_t* chan, int is_all_ctrl_off) +{ + int i; + + chan->key_pressure = 0; + 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; + chan->gen_abs[i] = 0; + } + + 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) { + continue; + } + + fluid_channel_set_cc (chan, i, 0); + } + } + else { + for (i = 0; i < 128; i++) { + fluid_channel_set_cc (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); + + /* 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 http://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 */ +int +delete_fluid_channel(fluid_channel_t* chan) +{ + if (chan->preset) delete_fluid_preset (chan->preset); + FLUID_FREE(chan); + return FLUID_OK; +} + +/* FIXME - Calls fluid_channel_init() potentially in synthesis context */ +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_preset_notify (chan->preset, FLUID_PRESET_UNSELECTED, chan->channum); + + if (chan->preset) { + fluid_sfont_t *sfont; + sfont = chan->preset->sfont; + delete_fluid_preset (chan->preset); + fluid_synth_sfont_unref (chan->synth, sfont); /* -- unref preset's SoundFont */ + } + + chan->preset = preset; + + 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: http://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; +} diff --git a/libs/fluidsynth/src/fluid_chan.h b/libs/fluidsynth/src/fluid_chan.h new file mode 100644 index 0000000000..85aa1ef00c --- /dev/null +++ b/libs/fluidsynth/src/fluid_chan.h @@ -0,0 +1,149 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +/* + * 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_mutex_t mutex; /* Lock for thread sensitive parameters */ + + fluid_synth_t* synth; /**< Parent synthesizer instance */ + int channum; /**< MIDI channel number */ + + int sfont_bank_prog; /**< SoundFont ID (bit 21-31), bank (bit 7-20), program (bit 0-6) */ + fluid_preset_t* preset; /**< Selected preset */ + + int key_pressure; /**< MIDI key pressure */ + int channel_pressure; /**< MIDI channel pressure */ + int pitch_bend; /**< Current pitch bend value */ + int pitch_wheel_sensitivity; /**< Current pitch wheel sensitivity */ + + int cc[128]; /**< MIDI controller values */ + + /* 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 interp_method; /**< Interpolation method (enum fluid_interp) */ + fluid_tuning_t* tuning; /**< Micro tuning */ + int tuning_bank; /**< Current tuning bank number */ + int tuning_prog; /**< Current tuning program number */ + + /* NRPN system */ + int nrpn_select; /* Generator ID of SoundFont NRPN message */ + int 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]; + + /* By default, the NRPN values are relative to the values of the + * generators set in the SoundFont. For example, if the NRPN + * specifies an attack of 100 msec then 100 msec will be added to the + * combined attack time of the sound font and the modulators. + * + * However, it is useful to be able to specify the generator value + * absolutely, completely ignoring the generators of the SoundFont + * and the values of modulators. The gen_abs field, is a boolean + * flag indicating whether the NRPN value is absolute or not. + */ + char gen_abs[GEN_LAST]; + + /* Drum channel flag, CHANNEL_TYPE_MELODIC, or CHANNEL_TYPE_DRUM. */ + int channel_type; + +}; + +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); +int 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); +fluid_preset_t* fluid_channel_get_preset(fluid_channel_t* chan); +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); +int fluid_channel_get_num(fluid_channel_t* chan); +void fluid_channel_set_interp_method(fluid_channel_t* chan, int new_method); +int fluid_channel_get_interp_method(fluid_channel_t* chan); + +#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) \ + ((chan)->key_pressure) +#define fluid_channel_set_key_pressure(chan, val) \ + ((chan)->key_pressure = (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_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, _a) { (_c)->gen[_n] = _v; (_c)->gen_abs[_n] = _a; } +#define fluid_channel_get_gen(_c, _n) ((_c)->gen[_n]) +#define fluid_channel_get_gen_abs(_c, _n) ((_c)->gen_abs[_n]) +#define fluid_channel_get_min_note_length_ticks(chan) \ + ((chan)->synth->min_note_length_ticks) + +#endif /* _FLUID_CHAN_H */ diff --git a/libs/fluidsynth/src/fluid_chorus.c b/libs/fluidsynth/src/fluid_chorus.c new file mode 100644 index 0000000000..4bead5ce2d --- /dev/null +++ b/libs/fluidsynth/src/fluid_chorus.c @@ -0,0 +1,506 @@ +/* + * August 24, 1998 + * Copyright (C) 1998 Juergen Mueller And Sundry Contributors + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Juergen Mueller And Sundry Contributors are not responsible for + * the consequences of using this software. + */ + +/* + + CHANGES + + - Adapted for fluidsynth, Peter Hanappe, March 2002 + + - Variable delay line implementation using bandlimited + interpolation, code reorganization: Markus Nentwig May 2002 + + */ + + +/* + * Chorus effect. + * + * Flow diagram scheme for n delays ( 1 <= n <= MAX_CHORUS ): + * + * * gain-in ___ + * ibuff -----+--------------------------------------------->| | + * | _________ | | + * | | | * level 1 | | + * +---->| delay 1 |----------------------------->| | + * | |_________| | | + * | /|\ | | + * : | | | + * : +-----------------+ +--------------+ | + | + * : | Delay control 1 |<--| mod. speed 1 | | | + * : +-----------------+ +--------------+ | | + * | _________ | | + * | | | * level n | | + * +---->| delay n |----------------------------->| | + * |_________| | | + * /|\ |___| + * | | + * +-----------------+ +--------------+ | * gain-out + * | Delay control n |<--| mod. speed n | | + * +-----------------+ +--------------+ +----->obuff + * + * + * The delay i is controlled by a sine or triangle modulation i ( 1 <= i <= n). + * + * The delay of each block is modulated between 0..depth ms + * + */ + + +/* Variable delay line implementation + * ================================== + * + * The modulated delay needs the value of the delayed signal between + * samples. A lowpass filter is used to obtain intermediate values + * between samples (bandlimited interpolation). The sample pulse + * train is convoluted with the impulse response of the low pass + * filter (sinc function). To make it work with a small number of + * samples, the sinc function is windowed (Hamming window). + * + */ + +#include "fluid_chorus.h" +#include "fluid_sys.h" + +#define MAX_CHORUS 99 +#define MAX_DELAY 100 +#define MAX_DEPTH 10 +#define MIN_SPEED_HZ 0.29 +#define MAX_SPEED_HZ 5 + +/* Length of one delay line in samples: + * Set through MAX_SAMPLES_LN2. + * For example: + * MAX_SAMPLES_LN2=12 + * => MAX_SAMPLES=pow(2,12)=4096 + * => MAX_SAMPLES_ANDMASK=4095 + */ +#define MAX_SAMPLES_LN2 12 + +#define MAX_SAMPLES (1 << (MAX_SAMPLES_LN2-1)) +#define MAX_SAMPLES_ANDMASK (MAX_SAMPLES-1) + + +/* Interpolate how many steps between samples? Must be power of two + For example: 8 => use a resolution of 256 steps between any two + samples +*/ +#define INTERPOLATION_SUBSAMPLES_LN2 8 +#define INTERPOLATION_SUBSAMPLES (1 << (INTERPOLATION_SUBSAMPLES_LN2-1)) +#define INTERPOLATION_SUBSAMPLES_ANDMASK (INTERPOLATION_SUBSAMPLES-1) + +/* Use how many samples for interpolation? Must be odd. '7' sounds + relatively clean, when listening to the modulated delay signal + alone. For a demo on aliasing try '1' With '3', the aliasing is + still quite pronounced for some input frequencies +*/ +#define INTERPOLATION_SAMPLES 5 + +/* 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 *chorusbuf; + int counter; + long phase[MAX_CHORUS]; + long modulation_period_samples; + int *lookup_tab; + fluid_real_t sample_rate; + + /* sinc lookup table */ + fluid_real_t sinc_table[INTERPOLATION_SAMPLES][INTERPOLATION_SUBSAMPLES]; +}; + +static void fluid_chorus_triangle(int *buf, int len, int depth); +static void fluid_chorus_sine(int *buf, int len, int depth); + + +fluid_chorus_t* +new_fluid_chorus(fluid_real_t sample_rate) +{ + int i; int ii; + 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; + + /* Lookup table for the SI function (impulse response of an ideal low pass) */ + + /* i: Offset in terms of whole samples */ + for (i = 0; i < INTERPOLATION_SAMPLES; i++){ + + /* ii: Offset in terms of fractional samples ('subsamples') */ + for (ii = 0; ii < INTERPOLATION_SUBSAMPLES; ii++){ + /* Move the origin into the center of the table */ + double i_shifted = ((double) i- ((double) INTERPOLATION_SAMPLES) / 2. + + (double) ii / (double) INTERPOLATION_SUBSAMPLES); + if (fabs(i_shifted) < 0.000001) { + /* sinc(0) cannot be calculated straightforward (limit needed + for 0/0) */ + chorus->sinc_table[i][ii] = (fluid_real_t)1.; + + } else { + chorus->sinc_table[i][ii] = (fluid_real_t)sin(i_shifted * M_PI) / (M_PI * i_shifted); + /* Hamming window */ + chorus->sinc_table[i][ii] *= (fluid_real_t)0.5 * (1.0 + cos(2.0 * M_PI * i_shifted / (fluid_real_t)INTERPOLATION_SAMPLES)); + }; + }; + }; + + /* allocate lookup tables */ + chorus->lookup_tab = FLUID_ARRAY(int, (int) (chorus->sample_rate / MIN_SPEED_HZ)); + if (chorus->lookup_tab == NULL) { + fluid_log(FLUID_PANIC, "chorus: Out of memory"); + goto error_recovery; + } + + /* allocate sample buffer */ + + chorus->chorusbuf = FLUID_ARRAY(fluid_real_t, MAX_SAMPLES); + if (chorus->chorusbuf == NULL) { + fluid_log(FLUID_PANIC, "chorus: Out of memory"); + goto error_recovery; + } + + if (fluid_chorus_init(chorus) != FLUID_OK){ + goto error_recovery; + }; + + return chorus; + + error_recovery: + delete_fluid_chorus(chorus); + return NULL; +} + +void +delete_fluid_chorus(fluid_chorus_t* chorus) +{ + if (chorus == NULL) { + return; + } + + if (chorus->chorusbuf != NULL) { + FLUID_FREE(chorus->chorusbuf); + } + + if (chorus->lookup_tab != NULL) { + FLUID_FREE(chorus->lookup_tab); + } + + FLUID_FREE(chorus); +} + +int +fluid_chorus_init(fluid_chorus_t* chorus) +{ + int i; + + for (i = 0; i < MAX_SAMPLES; i++) { + chorus->chorusbuf[i] = 0.0; + } + + /* initialize the chorus with the default settings */ + fluid_chorus_set (chorus, FLUID_CHORUS_SET_ALL, FLUID_CHORUS_DEFAULT_N, + FLUID_CHORUS_DEFAULT_LEVEL, FLUID_CHORUS_DEFAULT_SPEED, + FLUID_CHORUS_DEFAULT_DEPTH, FLUID_CHORUS_MOD_SINE); + return FLUID_OK; +} + +void +fluid_chorus_reset(fluid_chorus_t* chorus) +{ + fluid_chorus_init(chorus); +} + +/** + * 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.29-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, float level, + float speed, float depth_ms, int type) +{ + int modulation_depth_samples; + int i; + + if (set & FLUID_CHORUS_SET_NR) chorus->number_blocks = nr; + if (set & FLUID_CHORUS_SET_LEVEL) chorus->level = level; + if (set & FLUID_CHORUS_SET_SPEED) chorus->speed_Hz = speed; + if (set & FLUID_CHORUS_SET_DEPTH) chorus->depth_ms = depth_ms; + if (set & FLUID_CHORUS_SET_TYPE) chorus->type = type; + + 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; + } + /* Depth: Check for too high value through modulation_depth_samples. */ + + 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 > 10) { + fluid_log(FLUID_WARN, "chorus: level must be < 10. A reasonable level is << 1! " + "Setting it to 0.1."); + chorus->level = 0.1; + } + + /* The modulating LFO goes through a full period every x samples: */ + chorus->modulation_period_samples = chorus->sample_rate / chorus->speed_Hz; + + /* The variation in delay time is x: */ + modulation_depth_samples = (int) + (chorus->depth_ms / 1000.0 /* convert modulation depth in ms to s*/ + * chorus->sample_rate); + + if (modulation_depth_samples > MAX_SAMPLES) { + fluid_log(FLUID_WARN, "chorus: Too high depth. Setting it to max (%d).", MAX_SAMPLES); + modulation_depth_samples = MAX_SAMPLES; + } + + /* initialize LFO table */ + if (chorus->type == FLUID_CHORUS_MOD_SINE) { + fluid_chorus_sine(chorus->lookup_tab, chorus->modulation_period_samples, + modulation_depth_samples); + } else if (chorus->type == FLUID_CHORUS_MOD_TRIANGLE) { + fluid_chorus_triangle(chorus->lookup_tab, chorus->modulation_period_samples, + modulation_depth_samples); + } else { + fluid_log(FLUID_WARN, "chorus: Unknown modulation type. Using sinewave."); + chorus->type = FLUID_CHORUS_MOD_SINE; + fluid_chorus_sine(chorus->lookup_tab, chorus->modulation_period_samples, + modulation_depth_samples); + } + + for (i = 0; i < chorus->number_blocks; i++) { + /* Set the phase of the chorus blocks equally spaced */ + chorus->phase[i] = (int) ((double) chorus->modulation_period_samples + * (double) i / (double) chorus->number_blocks); + } + + /* Start of the circular buffer */ + chorus->counter = 0; +} + + +void fluid_chorus_processmix(fluid_chorus_t* chorus, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int sample_index; + int i; + fluid_real_t d_in, d_out; + + for (sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { + + d_in = in[sample_index]; + d_out = 0.0f; + +# if 0 + /* Debug: Listen to the chorus signal only */ + left_out[sample_index]=0; + right_out[sample_index]=0; +#endif + + /* Write the current sample into the circular buffer */ + chorus->chorusbuf[chorus->counter] = d_in; + + for (i = 0; i < chorus->number_blocks; i++) { + int ii; + /* Calculate the delay in subsamples for the delay line of chorus block nr. */ + + /* The value in the lookup table is so, that this expression + * will always be positive. It will always include a number of + * full periods of MAX_SAMPLES*INTERPOLATION_SUBSAMPLES to + * remain positive at all times. */ + int pos_subsamples = (INTERPOLATION_SUBSAMPLES * chorus->counter + - chorus->lookup_tab[chorus->phase[i]]); + + int pos_samples = pos_subsamples/INTERPOLATION_SUBSAMPLES; + + /* modulo divide by INTERPOLATION_SUBSAMPLES */ + pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK; + + for (ii = 0; ii < INTERPOLATION_SAMPLES; ii++){ + /* Add the delayed signal to the chorus sum d_out Note: The + * delay in the delay line moves backwards for increasing + * delay!*/ + + /* The & in chorusbuf[...] is equivalent to a division modulo + MAX_SAMPLES, only faster. */ + d_out += chorus->chorusbuf[pos_samples & MAX_SAMPLES_ANDMASK] + * chorus->sinc_table[ii][pos_subsamples]; + + pos_samples--; + }; + /* Cycle the phase of the modulating LFO */ + chorus->phase[i]++; + chorus->phase[i] %= (chorus->modulation_period_samples); + } /* foreach chorus block */ + + d_out *= chorus->level; + + /* Add the chorus sum d_out to output */ + left_out[sample_index] += d_out; + right_out[sample_index] += d_out; + + /* Move forward in circular buffer */ + chorus->counter++; + chorus->counter %= MAX_SAMPLES; + + } /* foreach sample */ +} + +/* Duplication of code ... (replaces sample data instead of mixing) */ +void fluid_chorus_processreplace(fluid_chorus_t* chorus, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int sample_index; + int i; + fluid_real_t d_in, d_out; + + for (sample_index = 0; sample_index < FLUID_BUFSIZE; sample_index++) { + + d_in = in[sample_index]; + d_out = 0.0f; + +# if 0 + /* Debug: Listen to the chorus signal only */ + left_out[sample_index]=0; + right_out[sample_index]=0; +#endif + + /* Write the current sample into the circular buffer */ + chorus->chorusbuf[chorus->counter] = d_in; + + for (i = 0; i < chorus->number_blocks; i++) { + int ii; + /* Calculate the delay in subsamples for the delay line of chorus block nr. */ + + /* The value in the lookup table is so, that this expression + * will always be positive. It will always include a number of + * full periods of MAX_SAMPLES*INTERPOLATION_SUBSAMPLES to + * remain positive at all times. */ + int pos_subsamples = (INTERPOLATION_SUBSAMPLES * chorus->counter + - chorus->lookup_tab[chorus->phase[i]]); + + int pos_samples = pos_subsamples / INTERPOLATION_SUBSAMPLES; + + /* modulo divide by INTERPOLATION_SUBSAMPLES */ + pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK; + + for (ii = 0; ii < INTERPOLATION_SAMPLES; ii++){ + /* Add the delayed signal to the chorus sum d_out Note: The + * delay in the delay line moves backwards for increasing + * delay!*/ + + /* The & in chorusbuf[...] is equivalent to a division modulo + MAX_SAMPLES, only faster. */ + d_out += chorus->chorusbuf[pos_samples & MAX_SAMPLES_ANDMASK] + * chorus->sinc_table[ii][pos_subsamples]; + + pos_samples--; + }; + /* Cycle the phase of the modulating LFO */ + chorus->phase[i]++; + chorus->phase[i] %= (chorus->modulation_period_samples); + } /* foreach chorus block */ + + d_out *= chorus->level; + + /* Store the chorus sum d_out to output */ + left_out[sample_index] = d_out; + right_out[sample_index] = d_out; + + /* Move forward in circular buffer */ + chorus->counter++; + chorus->counter %= MAX_SAMPLES; + + } /* foreach sample */ +} + +/* Purpose: + * + * Calculates a modulation waveform (sine) Its value ( modulo + * MAXSAMPLES) varies between 0 and depth*INTERPOLATION_SUBSAMPLES. + * Its period length is len. The waveform data will be used modulo + * MAXSAMPLES only. Since MAXSAMPLES is substracted from the waveform + * a couple of times here, the resulting (current position in + * buffer)-(waveform sample) will always be positive. + */ +static void +fluid_chorus_sine(int *buf, int len, int depth) +{ + int i; + double val; + + for (i = 0; i < len; i++) { + val = sin((double) i / (double)len * 2.0 * M_PI); + buf[i] = (int) ((1.0 + val) * (double) depth / 2.0 * (double) INTERPOLATION_SUBSAMPLES); + buf[i] -= 3* MAX_SAMPLES * INTERPOLATION_SUBSAMPLES; + // printf("%i %i\n",i,buf[i]); + } +} + +/* Purpose: + * Calculates a modulation waveform (triangle) + * See fluid_chorus_sine for comments. + */ +static void +fluid_chorus_triangle(int *buf, int len, int depth) +{ + int i=0; + int ii=len-1; + double val; + double val2; + + while (i <= ii){ + val = i * 2.0 / len * (double)depth * (double) INTERPOLATION_SUBSAMPLES; + val2= (int) (val + 0.5) - 3 * MAX_SAMPLES * INTERPOLATION_SUBSAMPLES; + buf[i++] = (int) val2; + buf[ii--] = (int) val2; + } +} diff --git a/libs/fluidsynth/src/fluid_chorus.h b/libs/fluidsynth/src/fluid_chorus.h new file mode 100644 index 0000000000..3422fa94b2 --- /dev/null +++ b/libs/fluidsynth/src/fluid_chorus.h @@ -0,0 +1,61 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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; + +/** Flags for fluid_chorus_set() */ +typedef enum +{ + FLUID_CHORUS_SET_NR = 1 << 0, + FLUID_CHORUS_SET_LEVEL = 1 << 1, + FLUID_CHORUS_SET_SPEED = 1 << 2, + FLUID_CHORUS_SET_DEPTH = 1 << 3, + FLUID_CHORUS_SET_TYPE = 1 << 4, +} fluid_chorus_set_t; + +/** Value for fluid_chorus_set() which sets all chorus parameters. */ +#define FLUID_CHORUS_SET_ALL 0x1F + +/* + * chorus + */ +fluid_chorus_t* new_fluid_chorus(fluid_real_t sample_rate); +void delete_fluid_chorus(fluid_chorus_t* chorus); +int fluid_chorus_init(fluid_chorus_t* chorus); +void fluid_chorus_reset(fluid_chorus_t* chorus); + +void fluid_chorus_set(fluid_chorus_t* chorus, int set, int nr, float level, + float speed, float depth_ms, int type); + +void fluid_chorus_processmix(fluid_chorus_t* chorus, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); +void fluid_chorus_processreplace(fluid_chorus_t* chorus, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); + + + +#endif /* _FLUID_CHORUS_H */ diff --git a/libs/fluidsynth/src/fluid_conv.c b/libs/fluidsynth/src/fluid_conv.c new file mode 100644 index 0000000000..1a790cfbfb --- /dev/null +++ b/libs/fluidsynth/src/fluid_conv.c @@ -0,0 +1,320 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + + +/* conversion tables */ +fluid_real_t fluid_ct2hz_tab[FLUID_CENTS_HZ_SIZE]; +fluid_real_t fluid_cb2amp_tab[FLUID_CB_AMP_SIZE]; +fluid_real_t fluid_atten2amp_tab[FLUID_ATTEN_AMP_SIZE]; +fluid_real_t fluid_posbp_tab[128]; +fluid_real_t fluid_concave_tab[128]; +fluid_real_t fluid_convex_tab[128]; +fluid_real_t fluid_pan_tab[FLUID_PAN_SIZE]; + +/* + * void fluid_synth_init + * + * Does all the initialization for this module. + */ +void +fluid_conversion_config(void) +{ + int i; + double x; + + for (i = 0; i < FLUID_CENTS_HZ_SIZE; i++) { + fluid_ct2hz_tab[i] = (fluid_real_t) pow(2.0, (double) i / 1200.0); + } + + /* centibels to amplitude conversion + * Note: SF2.01 section 8.1.3: Initial attenuation range is + * between 0 and 144 dB. Therefore a negative attenuation is + * not allowed. + */ + for (i = 0; i < FLUID_CB_AMP_SIZE; i++) { + fluid_cb2amp_tab[i] = (fluid_real_t) pow(10.0, (double) i / -200.0); + } + + /* NOTE: EMU8k and EMU10k devices don't conform to the SoundFont + * specification in regards to volume attenuation. The below calculation + * is an approx. equation for generating a table equivelant to the + * cb_to_amp_table[] in tables.c of the TiMidity++ source, which I'm told + * was generated from device testing. By the spec this should be centibels. + */ + for (i = 0; i < FLUID_ATTEN_AMP_SIZE; i++) { + fluid_atten2amp_tab[i] = (fluid_real_t) pow(10.0, (double) i / FLUID_ATTEN_POWER_FACTOR); + } + + /* initialize the conversion tables (see fluid_mod.c + fluid_mod_get_value cases 4 and 8) */ + + /* concave unipolar positive transform curve */ + fluid_concave_tab[0] = 0.0; + fluid_concave_tab[127] = 1.0; + + /* convex unipolar positive transform curve */ + fluid_convex_tab[0] = 0; + fluid_convex_tab[127] = 1.0; + x = log10(128.0 / 127.0); + + /* There seems to be an error in the specs. The equations are + implemented according to the pictures on SF2.01 page 73. */ + + for (i = 1; i < 127; i++) { + x = -20.0 / 96.0 * log((i * i) / (127.0 * 127.0)) / log(10.0); + fluid_convex_tab[i] = (fluid_real_t) (1.0 - x); + fluid_concave_tab[127 - i] = (fluid_real_t) x; + } + + /* initialize the pan conversion table */ + x = PI / 2.0 / (FLUID_PAN_SIZE - 1.0); + for (i = 0; i < FLUID_PAN_SIZE; i++) { + fluid_pan_tab[i] = (fluid_real_t) sin(i * x); + } +} + +/* + * fluid_ct2hz + */ +fluid_real_t +fluid_ct2hz_real(fluid_real_t cents) +{ + if (cents < 0) + return (fluid_real_t) 1.0; + else if (cents < 900) { + return (fluid_real_t) 6.875 * fluid_ct2hz_tab[(int) (cents + 300)]; + } else if (cents < 2100) { + return (fluid_real_t) 13.75 * fluid_ct2hz_tab[(int) (cents - 900)]; + } else if (cents < 3300) { + return (fluid_real_t) 27.5 * fluid_ct2hz_tab[(int) (cents - 2100)]; + } else if (cents < 4500) { + return (fluid_real_t) 55.0 * fluid_ct2hz_tab[(int) (cents - 3300)]; + } else if (cents < 5700) { + return (fluid_real_t) 110.0 * fluid_ct2hz_tab[(int) (cents - 4500)]; + } else if (cents < 6900) { + return (fluid_real_t) 220.0 * fluid_ct2hz_tab[(int) (cents - 5700)]; + } else if (cents < 8100) { + return (fluid_real_t) 440.0 * fluid_ct2hz_tab[(int) (cents - 6900)]; + } else if (cents < 9300) { + return (fluid_real_t) 880.0 * fluid_ct2hz_tab[(int) (cents - 8100)]; + } else if (cents < 10500) { + return (fluid_real_t) 1760.0 * fluid_ct2hz_tab[(int) (cents - 9300)]; + } else if (cents < 11700) { + return (fluid_real_t) 3520.0 * fluid_ct2hz_tab[(int) (cents - 10500)]; + } else if (cents < 12900) { + return (fluid_real_t) 7040.0 * fluid_ct2hz_tab[(int) (cents - 11700)]; + } else if (cents < 14100) { + return (fluid_real_t) 14080.0 * fluid_ct2hz_tab[(int) (cents - 12900)]; + } else { + return (fluid_real_t) 1.0; /* some loony trying to make you deaf */ + } +} + +/* + * 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 960, 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_atten2amp + * + * in: a value between 0 and 1440, 0 is no attenuation + * out: a value between 1 and 0 + * + * Note: Volume attenuation is supposed to be centibels but EMU8k/10k don't + * follow this. Thats the reason for separate fluid_cb2amp and fluid_atten2amp. + */ +fluid_real_t +fluid_atten2amp(fluid_real_t atten) +{ + if (atten < 0) return 1.0; + else if (atten >= FLUID_ATTEN_AMP_SIZE) return 0.0; + else return fluid_atten2amp_tab[(int) atten]; +} + +/* + * 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.) { + tc = (fluid_real_t) -12000.0f; + } + if (tc > 5000.0f) { + tc = (fluid_real_t) 5000.0f; + } + return (fluid_real_t) pow(2.0, (double) tc / 1200.0); +} + +/* + * 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.){return (fluid_real_t) 0.0;}; + if (tc<-12000.){tc=(fluid_real_t) -12000.0;}; + if (tc>8000.){tc=(fluid_real_t) 8000.0;}; + return (fluid_real_t) pow(2.0, (double) tc / 1200.0); +} + +/* + * fluid_tc2sec + */ +fluid_real_t +fluid_tc2sec(fluid_real_t tc) +{ + /* No range checking here! */ + return (fluid_real_t) pow(2.0, (double) tc / 1200.0); +} + +/* + * 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.){return (fluid_real_t) 0.0;}; + if (tc<-12000.){tc=(fluid_real_t) -12000.0;}; + if (tc>8000.){tc=(fluid_real_t) 8000.0;}; + return (fluid_real_t) pow(2.0, (double) tc / 1200.0); +} + +/* + * fluid_act2hz + * + * Convert from absolute cents to Hertz + */ +fluid_real_t +fluid_act2hz(fluid_real_t c) +{ + return (fluid_real_t) (8.176 * pow(2.0, (double) c / 1200.0)); +} + +/* + * fluid_hz2ct + * + * Convert from Hertz to cents + */ +fluid_real_t +fluid_hz2ct(fluid_real_t f) +{ + return (fluid_real_t) (6900 + 1200 * log(f / 440.0) / log(2.0)); +} + +/* + * fluid_pan + */ +fluid_real_t +fluid_pan(fluid_real_t c, int left) +{ + if (left) { + c = -c; + } + if (c < -500) { + return (fluid_real_t) 0.0; + } else if (c > 500) { + return (fluid_real_t) 1.0; + } else { + return fluid_pan_tab[(int) (c + 500)]; + } +} + +/* + * fluid_concave + */ +fluid_real_t +fluid_concave(fluid_real_t val) +{ + if (val < 0) { + return 0; + } else if (val > 127) { + return 1; + } + return fluid_concave_tab[(int) val]; +} + +/* + * fluid_convex + */ +fluid_real_t +fluid_convex(fluid_real_t val) +{ + if (val < 0) { + return 0; + } else if (val > 127) { + return 1; + } + return fluid_convex_tab[(int) val]; +} diff --git a/libs/fluidsynth/src/fluid_conv.h b/libs/fluidsynth/src/fluid_conv.h new file mode 100644 index 0000000000..29793c3359 --- /dev/null +++ b/libs/fluidsynth/src/fluid_conv.h @@ -0,0 +1,63 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +#define FLUID_CENTS_HZ_SIZE 1200 +#define FLUID_VEL_CB_SIZE 128 +#define FLUID_CB_AMP_SIZE 961 +#define FLUID_ATTEN_AMP_SIZE 1441 +#define FLUID_PAN_SIZE 1002 + +/* EMU 8k/10k don't follow spec in regards to volume attenuation. + * This factor is used in the equation pow (10.0, cb / FLUID_ATTEN_POWER_FACTOR). + * By the standard this should be -200.0. */ +/* 07/11/2008 modified by S. Christian Collins for increased velocity sensitivity. Now it equals the response of EMU10K1 programming.*/ +#define FLUID_ATTEN_POWER_FACTOR (-200.0) /* was (-531.509)*/ + +void fluid_conversion_config(void); + +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_atten2amp(fluid_real_t atten); +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); +fluid_real_t fluid_act2hz(fluid_real_t c); +fluid_real_t fluid_hz2ct(fluid_real_t c); +fluid_real_t fluid_pan(fluid_real_t c, int left); +fluid_real_t fluid_concave(fluid_real_t val); +fluid_real_t fluid_convex(fluid_real_t val); + +extern fluid_real_t fluid_ct2hz_tab[FLUID_CENTS_HZ_SIZE]; +extern fluid_real_t fluid_vel2cb_tab[FLUID_VEL_CB_SIZE]; +extern fluid_real_t fluid_cb2amp_tab[FLUID_CB_AMP_SIZE]; +extern fluid_real_t fluid_posbp_tab[128]; +extern fluid_real_t fluid_concave_tab[128]; +extern fluid_real_t fluid_convex_tab[128]; +extern fluid_real_t fluid_pan_tab[FLUID_PAN_SIZE]; + + +#endif /* _FLUID_CONV_H */ diff --git a/libs/fluidsynth/src/fluid_defsfont.c b/libs/fluidsynth/src/fluid_defsfont.c new file mode 100644 index 0000000000..c395218411 --- /dev/null +++ b/libs/fluidsynth/src/fluid_defsfont.c @@ -0,0 +1,3432 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" +/* Todo: Get rid of that 'include' */ +#include "fluid_sys.h" + +/*************************************************************** + * + * SFONT LOADER + */ + +fluid_sfloader_t* new_fluid_defsfloader(fluid_settings_t* settings) +{ + fluid_sfloader_t* loader; + + loader = FLUID_NEW(fluid_sfloader_t); + if (loader == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + loader->data = settings; + loader->free = delete_fluid_defsfloader; + loader->load = fluid_defsfloader_load; + + return loader; +} + +int delete_fluid_defsfloader(fluid_sfloader_t* loader) +{ + if (loader) { + FLUID_FREE(loader); + } + return FLUID_OK; +} + +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(loader->data); + + if (defsfont == NULL) { + return NULL; + } + + if (fluid_defsfont_load(defsfont, filename) == FLUID_FAILED) { + delete_fluid_defsfont(defsfont); + return NULL; + } + + sfont = FLUID_NEW(fluid_sfont_t); + if (sfont == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + sfont->data = defsfont; + sfont->free = fluid_defsfont_sfont_delete; + sfont->get_name = fluid_defsfont_sfont_get_name; + sfont->get_preset = fluid_defsfont_sfont_get_preset; + sfont->iteration_start = fluid_defsfont_sfont_iteration_start; + sfont->iteration_next = fluid_defsfont_sfont_iteration_next; + + return sfont; +} + + + +/*************************************************************** + * + * PUBLIC INTERFACE + */ + +int fluid_defsfont_sfont_delete(fluid_sfont_t* sfont) +{ + if (delete_fluid_defsfont(sfont->data) != 0) { + return -1; + } + FLUID_FREE(sfont); + return 0; +} + +char* fluid_defsfont_sfont_get_name(fluid_sfont_t* sfont) +{ + return fluid_defsfont_get_name((fluid_defsfont_t*) sfont->data); +} + +#if 0 +fluid_sample_t* fluid_defsfont_get_sample(fluid_defsfont_t* sfont, char *s) +{ + /* This function is here just to avoid an ABI/SONAME bump, see ticket #98. Should never be used. */ + return NULL; +} +#endif + +fluid_preset_t* +fluid_defsfont_sfont_get_preset(fluid_sfont_t* sfont, unsigned int bank, unsigned int prenum) +{ + fluid_preset_t* preset = NULL; + fluid_defpreset_t* defpreset; + fluid_defsfont_t* defsfont = sfont->data; + + defpreset = fluid_defsfont_get_preset(defsfont, bank, prenum); + + if (defpreset == NULL) { + return NULL; + } + + if (defsfont->preset_stack_size > 0) { + defsfont->preset_stack_size--; + preset = defsfont->preset_stack[defsfont->preset_stack_size]; + } + if (!preset) + preset = FLUID_NEW(fluid_preset_t); + if (!preset) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + preset->sfont = sfont; + preset->data = defpreset; + preset->free = fluid_defpreset_preset_delete; + preset->get_name = fluid_defpreset_preset_get_name; + preset->get_banknum = fluid_defpreset_preset_get_banknum; + preset->get_num = fluid_defpreset_preset_get_num; + preset->noteon = fluid_defpreset_preset_noteon; + preset->notify = NULL; + + return preset; +} + +void fluid_defsfont_sfont_iteration_start(fluid_sfont_t* sfont) +{ + fluid_defsfont_iteration_start((fluid_defsfont_t*) sfont->data); +} + +int fluid_defsfont_sfont_iteration_next(fluid_sfont_t* sfont, fluid_preset_t* preset) +{ + preset->free = fluid_defpreset_preset_delete; + preset->get_name = fluid_defpreset_preset_get_name; + preset->get_banknum = fluid_defpreset_preset_get_banknum; + preset->get_num = fluid_defpreset_preset_get_num; + preset->noteon = fluid_defpreset_preset_noteon; + preset->notify = NULL; + + return fluid_defsfont_iteration_next((fluid_defsfont_t*) sfont->data, preset); +} + +int fluid_defpreset_preset_delete(fluid_preset_t* preset) +{ + fluid_defpreset_t* defpreset = preset ? preset->data : NULL; + fluid_defsfont_t* sfont = defpreset ? defpreset->sfont : NULL; + + if (sfont && sfont->preset_stack_size < sfont->preset_stack_capacity) { + sfont->preset_stack[sfont->preset_stack_size] = preset; + sfont->preset_stack_size++; + } + else + FLUID_FREE(preset); + + return 0; +} + +char* fluid_defpreset_preset_get_name(fluid_preset_t* preset) +{ + return fluid_defpreset_get_name((fluid_defpreset_t*) preset->data); +} + +int fluid_defpreset_preset_get_banknum(fluid_preset_t* preset) +{ + return fluid_defpreset_get_banknum((fluid_defpreset_t*) preset->data); +} + +int fluid_defpreset_preset_get_num(fluid_preset_t* preset) +{ + return fluid_defpreset_get_num((fluid_defpreset_t*) preset->data); +} + +int fluid_defpreset_preset_noteon(fluid_preset_t* preset, fluid_synth_t* synth, + int chan, int key, int vel) +{ + return fluid_defpreset_noteon((fluid_defpreset_t*) preset->data, synth, chan, key, vel); +} + + + + +/*************************************************************** + * + * CACHED SAMPLEDATA LOADER + */ + +typedef struct _fluid_cached_sampledata_t { + struct _fluid_cached_sampledata_t *next; + + char* filename; + time_t modification_time; + int num_references; + int mlock; + + const short* sampledata; + unsigned int samplesize; +} fluid_cached_sampledata_t; + +static fluid_cached_sampledata_t* all_cached_sampledata = NULL; +static fluid_mutex_t cached_sampledata_mutex = FLUID_MUTEX_INIT; + +static int fluid_get_file_modification_time(char *filename, time_t *modification_time) +{ +#if defined(WIN32) || defined(__OS2__) + *modification_time = 0; + return FLUID_OK; +#else + struct stat buf; + + if (stat(filename, &buf) == -1) { + return FLUID_FAILED; + } + + *modification_time = buf.st_mtime; + return FLUID_OK; +#endif +} + +static int fluid_cached_sampledata_load(char *filename, unsigned int samplepos, + unsigned int samplesize, short **sampledata, int try_mlock) +{ + fluid_file fd = NULL; + short *loaded_sampledata = NULL; + fluid_cached_sampledata_t* cached_sampledata = NULL; + time_t modification_time; + + fluid_mutex_lock(cached_sampledata_mutex); + + if (fluid_get_file_modification_time(filename, &modification_time) == FLUID_FAILED) { + FLUID_LOG(FLUID_WARN, "Unable to read modificaton time of soundfont file."); + modification_time = 0; + } + + for (cached_sampledata = all_cached_sampledata; cached_sampledata; cached_sampledata = cached_sampledata->next) { + if (strcmp(filename, cached_sampledata->filename)) + continue; + if (cached_sampledata->modification_time != modification_time) + continue; + if (cached_sampledata->samplesize != samplesize) { + FLUID_LOG(FLUID_ERR, "Cached size of soundfont doesn't match actual size of soundfont (cached: %u. actual: %u)", + cached_sampledata->samplesize, samplesize); + continue; + } + + if (try_mlock && !cached_sampledata->mlock) { + if (fluid_mlock(cached_sampledata->sampledata, samplesize) != 0) + FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible."); + else + cached_sampledata->mlock = try_mlock; + } + + cached_sampledata->num_references++; + loaded_sampledata = (short*) cached_sampledata->sampledata; + goto success_exit; + } + + fd = FLUID_FOPEN(filename, "rb"); + if (fd == NULL) { + FLUID_LOG(FLUID_ERR, "Can't open soundfont file"); + goto error_exit; + } + if (FLUID_FSEEK(fd, samplepos, SEEK_SET) == -1) { + perror("error"); + FLUID_LOG(FLUID_ERR, "Failed to seek position in data file"); + goto error_exit; + } + + + loaded_sampledata = (short*) FLUID_MALLOC(samplesize); + if (loaded_sampledata == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + goto error_exit; + } + if (FLUID_FREAD(loaded_sampledata, 1, samplesize, fd) < samplesize) { + FLUID_LOG(FLUID_ERR, "Failed to read sample data"); + goto error_exit; + } + + FLUID_FCLOSE(fd); + fd = NULL; + + + cached_sampledata = (fluid_cached_sampledata_t*) FLUID_MALLOC(sizeof(fluid_cached_sampledata_t)); + if (cached_sampledata == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory."); + goto error_exit; + } + + /* Lock the memory to disable paging. It's okay if this fails. It + probably means that the user doesn't have to required permission. */ + cached_sampledata->mlock = 0; + if (try_mlock) { + if (fluid_mlock(loaded_sampledata, samplesize) != 0) + FLUID_LOG(FLUID_WARN, "Failed to pin the sample data to RAM; swapping is possible."); + else + cached_sampledata->mlock = try_mlock; + } + + /* If this machine is big endian, the sample have to byte swapped */ + if (FLUID_IS_BIG_ENDIAN) { + unsigned char* cbuf; + unsigned char hi, lo; + unsigned int i, j; + short s; + cbuf = (unsigned char*)loaded_sampledata; + for (i = 0, j = 0; j < samplesize; i++) { + lo = cbuf[j++]; + hi = cbuf[j++]; + s = (hi << 8) | lo; + loaded_sampledata[i] = s; + } + } + + cached_sampledata->filename = (char*) FLUID_MALLOC(strlen(filename) + 1); + if (cached_sampledata->filename == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory."); + goto error_exit; + } + + sprintf(cached_sampledata->filename, "%s", filename); + cached_sampledata->modification_time = modification_time; + cached_sampledata->num_references = 1; + cached_sampledata->sampledata = loaded_sampledata; + cached_sampledata->samplesize = samplesize; + + cached_sampledata->next = all_cached_sampledata; + all_cached_sampledata = cached_sampledata; + + + success_exit: + fluid_mutex_unlock(cached_sampledata_mutex); + *sampledata = loaded_sampledata; + return FLUID_OK; + + error_exit: + if (fd != NULL) { + FLUID_FCLOSE(fd); + } + if (loaded_sampledata != NULL) { + FLUID_FREE(loaded_sampledata); + } + + if (cached_sampledata != NULL) { + if (cached_sampledata->filename != NULL) { + FLUID_FREE(cached_sampledata->filename); + } + FLUID_FREE(cached_sampledata); + } + + fluid_mutex_unlock(cached_sampledata_mutex); + *sampledata = NULL; + return FLUID_FAILED; +} + +static int fluid_cached_sampledata_unload(const short *sampledata) +{ + fluid_cached_sampledata_t* prev = NULL; + fluid_cached_sampledata_t* cached_sampledata; + + fluid_mutex_lock(cached_sampledata_mutex); + cached_sampledata = all_cached_sampledata; + + while (cached_sampledata != NULL) { + if (sampledata == cached_sampledata->sampledata) { + + cached_sampledata->num_references--; + + if (cached_sampledata->num_references == 0) { + if (cached_sampledata->mlock) + fluid_munlock(cached_sampledata->sampledata, cached_sampledata->samplesize); + FLUID_FREE((short*) cached_sampledata->sampledata); + FLUID_FREE(cached_sampledata->filename); + + if (prev != NULL) { + prev->next = cached_sampledata->next; + } else { + all_cached_sampledata = cached_sampledata->next; + } + + FLUID_FREE(cached_sampledata); + } + + goto success_exit; + } + + prev = cached_sampledata; + cached_sampledata = cached_sampledata->next; + } + + FLUID_LOG(FLUID_ERR, "Trying to free sampledata not found in cache."); + goto error_exit; + + success_exit: + fluid_mutex_unlock(cached_sampledata_mutex); + return FLUID_OK; + + error_exit: + fluid_mutex_unlock(cached_sampledata_mutex); + return FLUID_FAILED; +} + + + + +/*************************************************************** + * + * SFONT + */ + +/* + * new_fluid_defsfont + */ +fluid_defsfont_t* new_fluid_defsfont(fluid_settings_t* settings) +{ + fluid_defsfont_t* sfont; + int i; + + sfont = FLUID_NEW(fluid_defsfont_t); + if (sfont == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + sfont->filename = NULL; + sfont->samplepos = 0; + sfont->samplesize = 0; + sfont->sample = NULL; + sfont->sampledata = NULL; + sfont->preset = NULL; + fluid_settings_getint(settings, "synth.lock-memory", &sfont->mlock); + + /* Initialise preset cache, so we don't have to call malloc on program changes. + Usually, we have at most one preset per channel plus one temporarily used, + so optimise for that case. */ + fluid_settings_getint(settings, "synth.midi-channels", &sfont->preset_stack_capacity); + sfont->preset_stack_capacity++; + sfont->preset_stack_size = 0; + sfont->preset_stack = FLUID_ARRAY(fluid_preset_t*, sfont->preset_stack_capacity); + if (!sfont->preset_stack) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(sfont); + return NULL; + } + + for (i = 0; i < sfont->preset_stack_capacity; i++) { + sfont->preset_stack[i] = FLUID_NEW(fluid_preset_t); + if (!sfont->preset_stack[i]) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + delete_fluid_defsfont(sfont); + return NULL; + } + sfont->preset_stack_size++; + } + + return sfont; +} + +/* + * delete_fluid_defsfont + */ +int delete_fluid_defsfont(fluid_defsfont_t* sfont) +{ + fluid_list_t *list; + fluid_defpreset_t* preset; + fluid_sample_t* sample; + + /* Check that no samples are currently used */ + for (list = sfont->sample; list; list = fluid_list_next(list)) { + sample = (fluid_sample_t*) fluid_list_get(list); + if (fluid_sample_refcount(sample) != 0) { + return -1; + } + } + + if (sfont->filename != NULL) { + FLUID_FREE(sfont->filename); + } + + for (list = sfont->sample; list; list = fluid_list_next(list)) { + delete_fluid_sample((fluid_sample_t*) fluid_list_get(list)); + } + + if (sfont->sample) { + delete_fluid_list(sfont->sample); + } + + if (sfont->sampledata != NULL) { + fluid_cached_sampledata_unload(sfont->sampledata); + } + + while (sfont->preset_stack_size > 0) + FLUID_FREE(sfont->preset_stack[--sfont->preset_stack_size]); + FLUID_FREE(sfont->preset_stack); + + preset = sfont->preset; + while (preset != NULL) { + sfont->preset = preset->next; + delete_fluid_defpreset(preset); + preset = sfont->preset; + } + + FLUID_FREE(sfont); + return FLUID_OK; +} + +/* + * fluid_defsfont_get_name + */ +char* fluid_defsfont_get_name(fluid_defsfont_t* sfont) +{ + return sfont->filename; +} + + +/* + * fluid_defsfont_load + */ +int fluid_defsfont_load(fluid_defsfont_t* sfont, const char* file) +{ + SFData* sfdata; + fluid_list_t *p; + SFPreset* sfpreset; + SFSample* sfsample; + fluid_sample_t* sample; + fluid_defpreset_t* preset = NULL; + + sfont->filename = FLUID_MALLOC(1 + FLUID_STRLEN(file)); + if (sfont->filename == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + FLUID_STRCPY(sfont->filename, file); + + /* The actual loading is done in the sfont and sffile files */ + sfdata = sfload_file(file); + if (sfdata == NULL) { + FLUID_LOG(FLUID_ERR, "Couldn't load soundfont file"); + return FLUID_FAILED; + } + + /* Keep track of the position and size of the sample data because + it's loaded separately (and might be unoaded/reloaded in future) */ + sfont->samplepos = sfdata->samplepos; + sfont->samplesize = sfdata->samplesize; + + /* load sample data in one block */ + if (fluid_defsfont_load_sampledata(sfont) != FLUID_OK) + goto err_exit; + + /* Create all the sample headers */ + p = sfdata->sample; + while (p != NULL) { + sfsample = (SFSample *) p->data; + + sample = new_fluid_sample(); + if (sample == NULL) goto err_exit; + + if (fluid_sample_import_sfont(sample, sfsample, sfont) != FLUID_OK) + goto err_exit; + + /* Store reference to FluidSynth sample in SFSample for later IZone fixups */ + sfsample->fluid_sample = sample; + + fluid_defsfont_add_sample(sfont, sample); + fluid_voice_optimize_sample(sample); + p = fluid_list_next(p); + } + + /* Load all the presets */ + p = sfdata->preset; + while (p != NULL) { + sfpreset = (SFPreset *) p->data; + preset = new_fluid_defpreset(sfont); + if (preset == NULL) goto err_exit; + + if (fluid_defpreset_import_sfont(preset, sfpreset, sfont) != FLUID_OK) + goto err_exit; + + fluid_defsfont_add_preset(sfont, preset); + p = fluid_list_next(p); + } + sfont_close (sfdata); + + return FLUID_OK; + +err_exit: + sfont_close (sfdata); + if (preset != NULL) + delete_fluid_defpreset(preset); + return FLUID_FAILED; +} + +/* fluid_defsfont_add_sample + * + * Add a sample to the SoundFont + */ +int fluid_defsfont_add_sample(fluid_defsfont_t* sfont, fluid_sample_t* sample) +{ + sfont->sample = fluid_list_append(sfont->sample, sample); + return FLUID_OK; +} + +/* fluid_defsfont_add_preset + * + * Add a preset to the SoundFont + */ +int fluid_defsfont_add_preset(fluid_defsfont_t* sfont, fluid_defpreset_t* preset) +{ + fluid_defpreset_t *cur, *prev; + if (sfont->preset == NULL) { + preset->next = NULL; + sfont->preset = preset; + } else { + /* sort them as we go along. very basic sorting trick. */ + cur = sfont->preset; + prev = NULL; + while (cur != NULL) { + if ((preset->bank < cur->bank) + || ((preset->bank == cur->bank) && (preset->num < cur->num))) { + if (prev == NULL) { + preset->next = cur; + sfont->preset = preset; + } else { + preset->next = cur; + prev->next = preset; + } + return FLUID_OK; + } + prev = cur; + cur = cur->next; + } + preset->next = NULL; + prev->next = preset; + } + return FLUID_OK; +} + +/* + * fluid_defsfont_load_sampledata + */ +int +fluid_defsfont_load_sampledata(fluid_defsfont_t* sfont) +{ + return fluid_cached_sampledata_load(sfont->filename, sfont->samplepos, + sfont->samplesize, &sfont->sampledata, sfont->mlock); +} + +/* + * fluid_defsfont_get_preset + */ +fluid_defpreset_t* fluid_defsfont_get_preset(fluid_defsfont_t* sfont, unsigned int bank, unsigned int num) +{ + fluid_defpreset_t* preset = sfont->preset; + while (preset != NULL) { + if ((preset->bank == bank) && ((preset->num == num))) { + return preset; + } + preset = preset->next; + } + return NULL; +} + +/* + * fluid_defsfont_iteration_start + */ +void fluid_defsfont_iteration_start(fluid_defsfont_t* sfont) +{ + sfont->iter_cur = sfont->preset; +} + +/* + * fluid_defsfont_iteration_next + */ +int fluid_defsfont_iteration_next(fluid_defsfont_t* sfont, fluid_preset_t* preset) +{ + if (sfont->iter_cur == NULL) { + return 0; + } + + preset->data = (void*) sfont->iter_cur; + sfont->iter_cur = fluid_defpreset_next(sfont->iter_cur); + return 1; +} + +/*************************************************************** + * + * PRESET + */ + +/* + * new_fluid_defpreset + */ +fluid_defpreset_t* +new_fluid_defpreset(fluid_defsfont_t* sfont) +{ + fluid_defpreset_t* preset = FLUID_NEW(fluid_defpreset_t); + if (preset == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + preset->next = NULL; + preset->sfont = sfont; + preset->name[0] = 0; + preset->bank = 0; + preset->num = 0; + preset->global_zone = NULL; + preset->zone = NULL; + return preset; +} + +/* + * delete_fluid_defpreset + */ +int +delete_fluid_defpreset(fluid_defpreset_t* preset) +{ + int err = FLUID_OK; + fluid_preset_zone_t* zone; + if (preset->global_zone != NULL) { + if (delete_fluid_preset_zone(preset->global_zone) != FLUID_OK) { + err = FLUID_FAILED; + } + preset->global_zone = NULL; + } + zone = preset->zone; + while (zone != NULL) { + preset->zone = zone->next; + if (delete_fluid_preset_zone(zone) != FLUID_OK) { + err = FLUID_FAILED; + } + zone = preset->zone; + } + FLUID_FREE(preset); + return err; +} + +int +fluid_defpreset_get_banknum(fluid_defpreset_t* preset) +{ + return preset->bank; +} + +int +fluid_defpreset_get_num(fluid_defpreset_t* preset) +{ + return preset->num; +} + +char* +fluid_defpreset_get_name(fluid_defpreset_t* preset) +{ + return preset->name; +} + +/* + * fluid_defpreset_next + */ +fluid_defpreset_t* +fluid_defpreset_next(fluid_defpreset_t* preset) +{ + return preset->next; +} + + +/* + * fluid_defpreset_noteon + */ +int +fluid_defpreset_noteon(fluid_defpreset_t* preset, 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_sample_t* sample; + fluid_voice_t* voice; + fluid_mod_t * mod; + fluid_mod_t * mod_list[FLUID_NUM_MOD]; /* list for 'sorting' preset modulators */ + int mod_list_count; + int i; + + global_preset_zone = fluid_defpreset_get_global_zone(preset); + + /* run thru all the zones of this preset */ + preset_zone = fluid_defpreset_get_zone(preset); + while (preset_zone != NULL) { + + /* check if the note falls into the key and velocity range of this + preset */ + if (fluid_preset_zone_inside_range(preset_zone, 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 */ + inst_zone = fluid_inst_get_zone(inst); + while (inst_zone != NULL) { + + /* make sure this instrument zone has a valid sample */ + 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; + } + + /* check if the note falls into the key and velocity range of this + instrument */ + + if (fluid_inst_zone_inside_range(inst_zone, key, vel) && (sample != NULL)) { + + /* this is a good zone. allocate a new synthesis process and + initialize it */ + + voice = fluid_synth_alloc_voice(synth, sample, chan, key, vel); + 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 */ + + /* global instrument zone, modulators: Put them all into a + * list. */ + + mod_list_count = 0; + + if (global_inst_zone){ + mod = global_inst_zone->mod; + while (mod){ + mod_list[mod_list_count++] = mod; + mod = mod->next; + } + } + + /* local instrument zone, modulators. + * Replace modulators with the same definition in the list: + * SF 2.01 page 69, 'bullet' 8 + */ + mod = inst_zone->mod; + + while (mod){ + + /* 'Identical' modulators will be deleted by setting their + * list entry to NULL. The list length is known, NULL + * entries will be ignored later. SF2.01 section 9.5.1 + * page 69, 'bullet' 3 defines 'identical'. */ + + for (i = 0; i < mod_list_count; i++){ + if (mod_list[i] && fluid_mod_test_identity(mod,mod_list[i])){ + mod_list[i] = NULL; + } + } + + /* Finally add the new modulator to to the list. */ + mod_list[mod_list_count++] = mod; + mod = mod->next; + } + + /* Add instrument modulators (global / local) to the voice. */ + for (i = 0; i < mod_list_count; i++){ + + mod = mod_list[i]; + + if (mod != NULL){ /* disabled modulators CANNOT be skipped. */ + + /* Instrument modulators -supersede- existing (default) + * modulators. SF 2.01 page 69, 'bullet' 6 */ + fluid_voice_add_mod(voice, mod, FLUID_VOICE_OVERWRITE); + } + } + + /* 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 */ + if ((i != GEN_STARTADDROFS) + && (i != GEN_ENDADDROFS) + && (i != GEN_STARTLOOPADDROFS) + && (i != GEN_ENDLOOPADDROFS) + && (i != GEN_STARTADDRCOARSEOFS) + && (i != GEN_ENDADDRCOARSEOFS) + && (i != GEN_STARTLOOPADDRCOARSEOFS) + && (i != GEN_KEYNUM) + && (i != GEN_VELOCITY) + && (i != GEN_ENDLOOPADDRCOARSEOFS) + && (i != GEN_SAMPLEMODE) + && (i != GEN_EXCLUSIVECLASS) + && (i != 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. + */ + } + } /* if available at preset level */ + } /* for all generators */ + + + /* Global preset zone, modulators: put them all into a + * list. */ + mod_list_count = 0; + if (global_preset_zone){ + mod = global_preset_zone->mod; + while (mod){ + mod_list[mod_list_count++] = mod; + mod = mod->next; + } + } + + /* Process the modulators of the local preset zone. Kick + * out all identical modulators from the global preset zone + * (SF 2.01 page 69, second-last bullet) */ + + mod = preset_zone->mod; + while (mod){ + for (i = 0; i < mod_list_count; i++){ + if (mod_list[i] && fluid_mod_test_identity(mod,mod_list[i])){ + mod_list[i] = NULL; + } + } + + /* Finally add the new modulator to the list. */ + mod_list[mod_list_count++] = mod; + mod = mod->next; + } + + /* Add preset modulators (global / local) to the voice. */ + for (i = 0; i < mod_list_count; i++){ + mod = mod_list[i]; + if ((mod != NULL) && (mod->amount != 0)) { /* disabled modulators can be skipped. */ + + /* Preset modulators -add- to existing instrument / + * default modulators. SF2.01 page 70 first bullet on + * page */ + fluid_voice_add_mod(voice, mod, FLUID_VOICE_ADD); + } + } + + /* 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) + */ + } + + inst_zone = fluid_inst_zone_next(inst_zone); + } + } + 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* preset, fluid_preset_zone_t* zone) +{ + preset->global_zone = zone; + return FLUID_OK; +} + +/* + * fluid_defpreset_import_sfont + */ +int +fluid_defpreset_import_sfont(fluid_defpreset_t* preset, + SFPreset* sfpreset, + fluid_defsfont_t* sfont) +{ + fluid_list_t *p; + SFZone* sfzone; + fluid_preset_zone_t* zone; + int count; + char zone_name[256]; + if ((sfpreset->name != NULL) && (FLUID_STRLEN(sfpreset->name) > 0)) { + FLUID_STRCPY(preset->name, sfpreset->name); + } else { + FLUID_SPRINTF(preset->name, "Bank%d,Preset%d", sfpreset->bank, sfpreset->prenum); + } + preset->bank = sfpreset->bank; + preset->num = sfpreset->prenum; + p = sfpreset->zone; + count = 0; + while (p != NULL) { + sfzone = (SFZone *) p->data; + FLUID_SPRINTF(zone_name, "%s/%d", preset->name, count); + zone = new_fluid_preset_zone(zone_name); + if (zone == NULL) { + return FLUID_FAILED; + } + if (fluid_preset_zone_import_sfont(zone, sfzone, sfont) != 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(preset, zone); + } else if (fluid_defpreset_add_zone(preset, zone) != FLUID_OK) { + return FLUID_FAILED; + } + p = fluid_list_next(p); + count++; + } + return FLUID_OK; +} + +/* + * fluid_defpreset_add_zone + */ +int +fluid_defpreset_add_zone(fluid_defpreset_t* preset, fluid_preset_zone_t* zone) +{ + if (preset->zone == NULL) { + zone->next = NULL; + preset->zone = zone; + } else { + zone->next = preset->zone; + preset->zone = zone; + } + return FLUID_OK; +} + +/* + * fluid_defpreset_get_zone + */ +fluid_preset_zone_t* +fluid_defpreset_get_zone(fluid_defpreset_t* preset) +{ + return preset->zone; +} + +/* + * fluid_defpreset_get_global_zone + */ +fluid_preset_zone_t* +fluid_defpreset_get_global_zone(fluid_defpreset_t* preset) +{ + return preset->global_zone; +} + +/* + * fluid_preset_zone_next + */ +fluid_preset_zone_t* +fluid_preset_zone_next(fluid_preset_zone_t* preset) +{ + return preset->next; +} + +/* + * new_fluid_preset_zone + */ +fluid_preset_zone_t* +new_fluid_preset_zone(char *name) +{ + int size; + 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; + size = 1 + FLUID_STRLEN(name); + zone->name = FLUID_MALLOC(size); + if (zone->name == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(zone); + return NULL; + } + FLUID_STRCPY(zone->name, name); + zone->inst = NULL; + zone->keylo = 0; + zone->keyhi = 128; + zone->vello = 0; + zone->velhi = 128; + + /* 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_set_default_values(&zone->gen[0]); + zone->mod = NULL; /* list of modulators */ + return zone; +} + +/*************************************************************** + * + * PRESET_ZONE + */ + +/* + * delete_fluid_preset_zone + */ +int +delete_fluid_preset_zone(fluid_preset_zone_t* zone) +{ + fluid_mod_t *mod, *tmp; + + mod = zone->mod; + while (mod) /* delete the modulators */ + { + tmp = mod; + mod = mod->next; + fluid_mod_delete (tmp); + } + + if (zone->name) FLUID_FREE (zone->name); + if (zone->inst) delete_fluid_inst (zone->inst); + FLUID_FREE(zone); + return FLUID_OK; +} + +/* + * fluid_preset_zone_import_sfont + */ +int +fluid_preset_zone_import_sfont(fluid_preset_zone_t* zone, SFZone *sfzone, fluid_defsfont_t* sfont) +{ + fluid_list_t *r; + SFGen* sfgen; + int count; + for (count = 0, r = sfzone->gen; r != NULL; count++) { + sfgen = (SFGen *) r->data; + switch (sfgen->id) { + case GEN_KEYRANGE: + zone->keylo = (int) sfgen->amount.range.lo; + zone->keyhi = (int) sfgen->amount.range.hi; + break; + case GEN_VELRANGE: + zone->vello = (int) sfgen->amount.range.lo; + zone->velhi = (int) sfgen->amount.range.hi; + break; + default: + /* FIXME: some generators have an unsigne word amount value but i don't know which ones */ + zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; + zone->gen[sfgen->id].flags = GEN_SET; + break; + } + r = fluid_list_next(r); + } + if ((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) { + zone->inst = (fluid_inst_t*) new_fluid_inst(); + if (zone->inst == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return FLUID_FAILED; + } + if (fluid_inst_import_sfont(zone->inst, (SFInst *) sfzone->instsamp->data, sfont) != FLUID_OK) { + return FLUID_FAILED; + } + } + + /* Import the modulators (only SF2.1 and higher) */ + for (count = 0, r = sfzone->mod; r != NULL; count++) { + + SFMod* mod_src = (SFMod *)r->data; + fluid_mod_t * mod_dest = fluid_mod_new(); + int type; + + 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 *** */ + mod_dest->src1 = mod_src->src & 127; /* index of source 1, seven-bit value, SF2.01 section 8.2, page 50 */ + mod_dest->flags1 = 0; + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + if (mod_src->src & (1<<7)){ + mod_dest->flags1 |= FLUID_MOD_CC; + } else { + mod_dest->flags1 |= FLUID_MOD_GC; + } + + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if (mod_src->src & (1<<8)){ + mod_dest->flags1 |= FLUID_MOD_NEGATIVE; + } else { + mod_dest->flags1 |= FLUID_MOD_POSITIVE; + } + + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if (mod_src->src & (1<<9)){ + mod_dest->flags1 |= FLUID_MOD_BIPOLAR; + } else { + mod_dest->flags1 |= FLUID_MOD_UNIPOLAR; + } + + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type=(mod_src->src) >> 10; + type &= 63; /* type is a 6-bit value */ + if (type == 0){ + mod_dest->flags1 |= FLUID_MOD_LINEAR; + } else if (type == 1){ + mod_dest->flags1 |= FLUID_MOD_CONCAVE; + } else if (type == 2){ + mod_dest->flags1 |= FLUID_MOD_CONVEX; + } else if (type == 3){ + mod_dest->flags1 |= FLUID_MOD_SWITCH; + } else { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount=0; + } + + /* *** Dest *** */ + mod_dest->dest = mod_src->dest; /* index of controlled generator */ + + /* *** Amount source *** */ + mod_dest->src2 = mod_src->amtsrc & 127; /* index of source 2, seven-bit value, SF2.01 section 8.2, p.50 */ + mod_dest->flags2 = 0; + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + if (mod_src->amtsrc & (1<<7)){ + mod_dest->flags2 |= FLUID_MOD_CC; + } else { + mod_dest->flags2 |= FLUID_MOD_GC; + } + + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if (mod_src->amtsrc & (1<<8)){ + mod_dest->flags2 |= FLUID_MOD_NEGATIVE; + } else { + mod_dest->flags2 |= FLUID_MOD_POSITIVE; + } + + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if (mod_src->amtsrc & (1<<9)){ + mod_dest->flags2 |= FLUID_MOD_BIPOLAR; + } else { + mod_dest->flags2 |= FLUID_MOD_UNIPOLAR; + } + + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type = (mod_src->amtsrc) >> 10; + type &= 63; /* type is a 6-bit value */ + if (type == 0){ + mod_dest->flags2 |= FLUID_MOD_LINEAR; + } else if (type == 1){ + mod_dest->flags2 |= FLUID_MOD_CONCAVE; + } else if (type == 2){ + mod_dest->flags2 |= FLUID_MOD_CONVEX; + } else if (type == 3){ + mod_dest->flags2 |= FLUID_MOD_SWITCH; + } else { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount=0; + } + + /* *** 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){ + zone->mod = mod_dest; + } else { + fluid_mod_t * last_mod = zone->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 */ + + return FLUID_OK; +} + +/* + * fluid_preset_zone_get_inst + */ +fluid_inst_t* +fluid_preset_zone_get_inst(fluid_preset_zone_t* zone) +{ + return zone->inst; +} + +/* + * fluid_preset_zone_inside_range + */ +int +fluid_preset_zone_inside_range(fluid_preset_zone_t* zone, int key, int vel) +{ + return ((zone->keylo <= key) && + (zone->keyhi >= key) && + (zone->vello <= vel) && + (zone->velhi >= vel)); +} + +/*************************************************************** + * + * 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 + */ +int +delete_fluid_inst(fluid_inst_t* inst) +{ + fluid_inst_zone_t* zone; + int err = FLUID_OK; + if (inst->global_zone != NULL) { + if (delete_fluid_inst_zone(inst->global_zone) != FLUID_OK) { + err = FLUID_FAILED; + } + inst->global_zone = NULL; + } + zone = inst->zone; + while (zone != NULL) { + inst->zone = zone->next; + if (delete_fluid_inst_zone(zone) != FLUID_OK) { + err = FLUID_FAILED; + } + zone = inst->zone; + } + FLUID_FREE(inst); + return err; +} + +/* + * 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 + */ +int +fluid_inst_import_sfont(fluid_inst_t* inst, SFInst *sfinst, fluid_defsfont_t* sfont) +{ + fluid_list_t *p; + SFZone* sfzone; + fluid_inst_zone_t* zone; + char zone_name[256]; + int count; + + p = sfinst->zone; + if ((sfinst->name != NULL) && (FLUID_STRLEN(sfinst->name) > 0)) { + FLUID_STRCPY(inst->name, sfinst->name); + } else { + FLUID_STRCPY(inst->name, ""); + } + + count = 0; + while (p != NULL) { + + sfzone = (SFZone *) p->data; + FLUID_SPRINTF(zone_name, "%s/%d", inst->name, count); + + zone = new_fluid_inst_zone(zone_name); + if (zone == NULL) { + return FLUID_FAILED; + } + + if (fluid_inst_zone_import_sfont(zone, sfzone, sfont) != FLUID_OK) { + delete_fluid_inst_zone(zone); + return FLUID_FAILED; + } + + if ((count == 0) && (fluid_inst_zone_get_sample(zone) == NULL)) { + fluid_inst_set_global_zone(inst, zone); + + } else if (fluid_inst_add_zone(inst, zone) != FLUID_OK) { + return FLUID_FAILED; + } + + p = fluid_list_next(p); + count++; + } + return FLUID_OK; +} + +/* + * 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) +{ + int size; + 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; + size = 1 + FLUID_STRLEN(name); + zone->name = FLUID_MALLOC(size); + if (zone->name == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + FLUID_FREE(zone); + return NULL; + } + FLUID_STRCPY(zone->name, name); + zone->sample = NULL; + zone->keylo = 0; + zone->keyhi = 128; + zone->vello = 0; + zone->velhi = 128; + + /* Flag the generators as unused. + * This also sets the generator values to default, but they will be overwritten anyway, if used.*/ + fluid_gen_set_default_values(&zone->gen[0]); + zone->mod=NULL; /* list of modulators */ + return zone; +} + +/* + * delete_fluid_inst_zone + */ +int +delete_fluid_inst_zone(fluid_inst_zone_t* zone) +{ + fluid_mod_t *mod, *tmp; + + mod = zone->mod; + while (mod) /* delete the modulators */ + { + tmp = mod; + mod = mod->next; + fluid_mod_delete (tmp); + } + + if (zone->name) FLUID_FREE (zone->name); + FLUID_FREE(zone); + return FLUID_OK; +} + +/* + * 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* zone, SFZone *sfzone, fluid_defsfont_t* sfont) +{ + fluid_list_t *r; + SFGen* sfgen; + int count; + + for (count = 0, r = sfzone->gen; r != NULL; count++) { + sfgen = (SFGen *) r->data; + switch (sfgen->id) { + case GEN_KEYRANGE: + zone->keylo = (int) sfgen->amount.range.lo; + zone->keyhi = (int) sfgen->amount.range.hi; + break; + case GEN_VELRANGE: + zone->vello = (int) sfgen->amount.range.lo; + zone->velhi = (int) sfgen->amount.range.hi; + break; + default: + /* FIXME: some generators have an unsigned word amount value but + i don't know which ones */ + zone->gen[sfgen->id].val = (fluid_real_t) sfgen->amount.sword; + zone->gen[sfgen->id].flags = GEN_SET; + break; + } + r = fluid_list_next(r); + } + + /* FIXME */ +/* if (zone->gen[GEN_EXCLUSIVECLASS].flags == GEN_SET) { */ +/* FLUID_LOG(FLUID_DBG, "ExclusiveClass=%d\n", (int) zone->gen[GEN_EXCLUSIVECLASS].val); */ +/* } */ + + /* fixup sample pointer */ + if ((sfzone->instsamp != NULL) && (sfzone->instsamp->data != NULL)) + zone->sample = ((SFSample *)(sfzone->instsamp->data))->fluid_sample; + + /* Import the modulators (only SF2.1 and higher) */ + for (count = 0, r = sfzone->mod; r != NULL; count++) { + SFMod* mod_src = (SFMod *) r->data; + int type; + fluid_mod_t* mod_dest; + + mod_dest = fluid_mod_new(); + 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 *** */ + mod_dest->src1 = mod_src->src & 127; /* index of source 1, seven-bit value, SF2.01 section 8.2, page 50 */ + mod_dest->flags1 = 0; + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + if (mod_src->src & (1<<7)){ + mod_dest->flags1 |= FLUID_MOD_CC; + } else { + mod_dest->flags1 |= FLUID_MOD_GC; + } + + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if (mod_src->src & (1<<8)){ + mod_dest->flags1 |= FLUID_MOD_NEGATIVE; + } else { + mod_dest->flags1 |= FLUID_MOD_POSITIVE; + } + + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if (mod_src->src & (1<<9)){ + mod_dest->flags1 |= FLUID_MOD_BIPOLAR; + } else { + mod_dest->flags1 |= FLUID_MOD_UNIPOLAR; + } + + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type = (mod_src->src) >> 10; + type &= 63; /* type is a 6-bit value */ + if (type == 0){ + mod_dest->flags1 |= FLUID_MOD_LINEAR; + } else if (type == 1){ + mod_dest->flags1 |= FLUID_MOD_CONCAVE; + } else if (type == 2){ + mod_dest->flags1 |= FLUID_MOD_CONVEX; + } else if (type == 3){ + mod_dest->flags1 |= FLUID_MOD_SWITCH; + } else { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount = 0; + } + + /* *** Dest *** */ + mod_dest->dest=mod_src->dest; /* index of controlled generator */ + + /* *** Amount source *** */ + mod_dest->src2=mod_src->amtsrc & 127; /* index of source 2, seven-bit value, SF2.01 section 8.2, page 50 */ + mod_dest->flags2 = 0; + + /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ + if (mod_src->amtsrc & (1<<7)){ + mod_dest->flags2 |= FLUID_MOD_CC; + } else { + mod_dest->flags2 |= FLUID_MOD_GC; + } + + /* Bit 8: D flag SF 2.01 section 8.2.2 page 51*/ + if (mod_src->amtsrc & (1<<8)){ + mod_dest->flags2 |= FLUID_MOD_NEGATIVE; + } else { + mod_dest->flags2 |= FLUID_MOD_POSITIVE; + } + + /* Bit 9: P flag SF 2.01 section 8.2.3 page 51*/ + if (mod_src->amtsrc & (1<<9)){ + mod_dest->flags2 |= FLUID_MOD_BIPOLAR; + } else { + mod_dest->flags2 |= FLUID_MOD_UNIPOLAR; + } + + /* modulator source types: SF2.01 section 8.2.1 page 52 */ + type=(mod_src->amtsrc) >> 10; + type &= 63; /* type is a 6-bit value */ + if (type == 0){ + mod_dest->flags2 |= FLUID_MOD_LINEAR; + } else if (type == 1){ + mod_dest->flags2 |= FLUID_MOD_CONCAVE; + } else if (type == 2){ + mod_dest->flags2 |= FLUID_MOD_CONVEX; + } else if (type == 3){ + mod_dest->flags2 |= FLUID_MOD_SWITCH; + } else { + /* This shouldn't happen - unknown type! + * Deactivate the modulator by setting the amount to 0. */ + mod_dest->amount = 0; + } + + /* *** 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){ + zone->mod=mod_dest; + } else { + fluid_mod_t * last_mod=zone->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 */ + return FLUID_OK; +} + +/* + * fluid_inst_zone_get_sample + */ +fluid_sample_t* +fluid_inst_zone_get_sample(fluid_inst_zone_t* zone) +{ + return zone->sample; +} + +/* + * fluid_inst_zone_inside_range + */ +int +fluid_inst_zone_inside_range(fluid_inst_zone_t* zone, int key, int vel) +{ + return ((zone->keylo <= key) && + (zone->keyhi >= key) && + (zone->vello <= vel) && + (zone->velhi >= vel)); +} + +/*************************************************************** + * + * SAMPLE + */ + +/* + * new_fluid_sample + */ +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; + } + + memset(sample, 0, sizeof(fluid_sample_t)); + sample->valid = 1; + + return sample; +} + +/* + * delete_fluid_sample + */ +int +delete_fluid_sample(fluid_sample_t* sample) +{ + FLUID_FREE(sample); + return FLUID_OK; +} + +/* + * 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* sfont) +{ + FLUID_STRCPY(sample->name, sfsample->name); + sample->data = sfont->sampledata; + sample->start = sfsample->start; + sample->end = sfsample->start + sfsample->end; + sample->loopstart = sfsample->start + sfsample->loopstart; + sample->loopend = sfsample->start + sfsample->loopend; + sample->samplerate = sfsample->samplerate; + sample->origpitch = sfsample->origpitch; + sample->pitchadj = sfsample->pitchadj; + sample->sampletype = sfsample->sampletype; + + if (sample->sampletype & FLUID_SAMPLETYPE_ROM) { + sample->valid = 0; + FLUID_LOG(FLUID_WARN, "Ignoring sample %s: can't use ROM samples", sample->name); + } + if (sample->end - sample->start < 8) { + sample->valid = 0; + FLUID_LOG(FLUID_WARN, "Ignoring sample %s: too few sample data points", sample->name); + } else { +/* if (sample->loopstart < sample->start + 8) { */ +/* FLUID_LOG(FLUID_WARN, "Fixing sample %s: at least 8 data points required before loop start", sample->name); */ +/* sample->loopstart = sample->start + 8; */ +/* } */ +/* if (sample->loopend > sample->end - 8) { */ +/* FLUID_LOG(FLUID_WARN, "Fixing sample %s: at least 8 data points required after loop end", sample->name); */ +/* sample->loopend = sample->end - 8; */ +/* } */ + } + return FLUID_OK; +} + + + +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ + + + +/*=================================sfload.c======================== + Borrowed from Smurf SoundFont Editor by Josh Green + =================================================================*/ + +/* + functions for loading data from sfont files, with appropriate byte swapping + on big endian machines. Sfont IDs are not swapped because the ID read is + equivalent to the matching ID list in memory regardless of LE/BE machine +*/ + +#if FLUID_IS_BIG_ENDIAN + +#define READCHUNK(var,fd) G_STMT_START { \ + if (!safe_fread(var, 8, fd)) \ + return(FAIL); \ + ((SFChunk *)(var))->size = GUINT32_FROM_LE(((SFChunk *)(var))->size); \ +} G_STMT_END + +#define READD(var,fd) G_STMT_START { \ + unsigned int _temp; \ + if (!safe_fread(&_temp, 4, fd)) \ + return(FAIL); \ + var = GINT32_FROM_LE(_temp); \ +} G_STMT_END + +#define READW(var,fd) G_STMT_START { \ + unsigned short _temp; \ + if (!safe_fread(&_temp, 2, fd)) \ + return(FAIL); \ + var = GINT16_FROM_LE(_temp); \ +} G_STMT_END + +#else + +#define READCHUNK(var,fd) G_STMT_START { \ + if (!safe_fread(var, 8, fd)) \ + return(FAIL); \ + ((SFChunk *)(var))->size = GUINT32_FROM_LE(((SFChunk *)(var))->size); \ +} G_STMT_END + +#define READD(var,fd) G_STMT_START { \ + unsigned int _temp; \ + if (!safe_fread(&_temp, 4, fd)) \ + return(FAIL); \ + var = GINT32_FROM_LE(_temp); \ +} G_STMT_END + +#define READW(var,fd) G_STMT_START { \ + unsigned short _temp; \ + if (!safe_fread(&_temp, 2, fd)) \ + return(FAIL); \ + var = GINT16_FROM_LE(_temp); \ +} G_STMT_END + +#endif + + +#define READID(var,fd) G_STMT_START { \ + if (!safe_fread(var, 4, fd)) \ + return(FAIL); \ +} G_STMT_END + +#define READSTR(var,fd) G_STMT_START { \ + if (!safe_fread(var, 20, fd)) \ + return(FAIL); \ + (*var)[20] = '\0'; \ +} G_STMT_END + +#define READB(var,fd) G_STMT_START { \ + if (!safe_fread(&var, 1, fd)) \ + return(FAIL); \ +} G_STMT_END + +#define FSKIP(size,fd) G_STMT_START { \ + if (!safe_fseek(fd, size, SEEK_CUR)) \ + return(FAIL); \ +} G_STMT_END + +#define FSKIPW(fd) G_STMT_START { \ + if (!safe_fseek(fd, 2, SEEK_CUR)) \ + return(FAIL); \ +} G_STMT_END + +/* removes and advances a fluid_list_t pointer */ +#define SLADVREM(list, item) G_STMT_START { \ + fluid_list_t *_temp = item; \ + item = fluid_list_next(item); \ + list = fluid_list_remove_link(list, _temp); \ + delete1_fluid_list(_temp); \ +} G_STMT_END + +static int chunkid (unsigned int id); +static int load_body (unsigned int size, SFData * sf, FILE * fd); +static int read_listchunk (SFChunk * chunk, FILE * fd); +static int process_info (int size, SFData * sf, FILE * fd); +static int process_sdta (unsigned int size, SFData * sf, FILE * fd); +static int pdtahelper (unsigned int expid, unsigned int reclen, SFChunk * chunk, + int * size, FILE * fd); +static int process_pdta (int size, SFData * sf, FILE * fd); +static int load_phdr (int size, SFData * sf, FILE * fd); +static int load_pbag (int size, SFData * sf, FILE * fd); +static int load_pmod (int size, SFData * sf, FILE * fd); +static int load_pgen (int size, SFData * sf, FILE * fd); +static int load_ihdr (int size, SFData * sf, FILE * fd); +static int load_ibag (int size, SFData * sf, FILE * fd); +static int load_imod (int size, SFData * sf, FILE * fd); +static int load_igen (int size, SFData * sf, FILE * fd); +static int load_shdr (unsigned int size, SFData * sf, FILE * fd); +static int fixup_pgen (SFData * sf); +static int fixup_igen (SFData * sf); +static int fixup_sample (SFData * sf); + +char idlist[] = { + "RIFFLISTsfbkINFOsdtapdtaifilisngINAMiromiverICRDIENGIPRD" + "ICOPICMTISFTsnamsmplphdrpbagpmodpgeninstibagimodigenshdr" +}; + +static unsigned int sdtachunk_size; + +/* sound font file load functions */ +static int +chunkid (unsigned int id) +{ + unsigned int i; + unsigned int *p; + + p = (unsigned int *) & idlist; + for (i = 0; i < sizeof (idlist) / sizeof (int); i++, p += 1) + if (*p == id) + return (i + 1); + + return (UNKN_ID); +} + +SFData * +sfload_file (const char * fname) +{ + SFData *sf = NULL; + FILE *fd; + int fsize = 0; + int err = FALSE; + + if (!(fd = fopen (fname, "rb"))) + { + FLUID_LOG (FLUID_ERR, _("Unable to open file \"%s\""), fname); + return (NULL); + } + + if (!(sf = FLUID_NEW (SFData))) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + fclose(fd); + err = TRUE; + } + + if (!err) + { + memset (sf, 0, sizeof (SFData)); /* zero sfdata */ + sf->fname = FLUID_STRDUP (fname); /* copy file name */ + sf->sffd = fd; + } + + /* get size of file */ + if (!err && fseek (fd, 0L, SEEK_END) == -1) + { /* seek to end of file */ + err = TRUE; + FLUID_LOG (FLUID_ERR, _("Seek to end of file failed")); + } + if (!err && (fsize = ftell (fd)) == -1) + { /* position = size */ + err = TRUE; + FLUID_LOG (FLUID_ERR, _("Get end of file position failed")); + } + if (!err) + rewind (fd); + + if (!err && !load_body (fsize, sf, fd)) + err = TRUE; /* load the sfont */ + + if (err) + { + if (sf) + sfont_close (sf); + return (NULL); + } + + return (sf); +} + +static int +load_body (unsigned int size, SFData * sf, FILE * fd) +{ + SFChunk chunk; + + READCHUNK (&chunk, fd); /* load RIFF chunk */ + if (chunkid (chunk.id) != RIFF_ID) { /* error if not RIFF */ + FLUID_LOG (FLUID_ERR, _("Not a RIFF file")); + return (FAIL); + } + + READID (&chunk.id, fd); /* load file ID */ + if (chunkid (chunk.id) != SFBK_ID) { /* error if not SFBK_ID */ + FLUID_LOG (FLUID_ERR, _("Not a SoundFont file")); + return (FAIL); + } + + if (chunk.size != size - 8) { + gerr (ErrCorr, _("SoundFont file size mismatch")); + return (FAIL); + } + + /* Process INFO block */ + if (!read_listchunk (&chunk, fd)) + return (FAIL); + if (chunkid (chunk.id) != INFO_ID) + return (gerr (ErrCorr, _("Invalid ID found when expecting INFO chunk"))); + if (!process_info (chunk.size, sf, fd)) + return (FAIL); + + /* Process sample chunk */ + if (!read_listchunk (&chunk, fd)) + return (FAIL); + if (chunkid (chunk.id) != SDTA_ID) + return (gerr (ErrCorr, + _("Invalid ID found when expecting SAMPLE chunk"))); + if (!process_sdta (chunk.size, sf, fd)) + return (FAIL); + + /* process HYDRA chunk */ + if (!read_listchunk (&chunk, fd)) + return (FAIL); + if (chunkid (chunk.id) != PDTA_ID) + return (gerr (ErrCorr, _("Invalid ID found when expecting HYDRA chunk"))); + if (!process_pdta (chunk.size, sf, fd)) + return (FAIL); + + if (!fixup_pgen (sf)) + return (FAIL); + if (!fixup_igen (sf)) + return (FAIL); + if (!fixup_sample (sf)) + return (FAIL); + + /* sort preset list by bank, preset # */ + sf->preset = fluid_list_sort (sf->preset, + (fluid_compare_func_t) sfont_preset_compare_func); + + return (OK); +} + +static int +read_listchunk (SFChunk * chunk, FILE * fd) +{ + READCHUNK (chunk, fd); /* read list chunk */ + if (chunkid (chunk->id) != LIST_ID) /* error if ! list chunk */ + return (gerr (ErrCorr, _("Invalid chunk id in level 0 parse"))); + READID (&chunk->id, fd); /* read id string */ + chunk->size -= 4; + return (OK); +} + +static int +process_info (int size, SFData * sf, FILE * fd) +{ + SFChunk chunk; + unsigned char id; + char *item; + unsigned short ver; + + while (size > 0) + { + READCHUNK (&chunk, fd); + size -= 8; + + id = chunkid (chunk.id); + + if (id == IFIL_ID) + { /* sound font version chunk? */ + if (chunk.size != 4) + return (gerr (ErrCorr, + _("Sound font version info chunk has invalid size"))); + + READW (ver, fd); + sf->version.major = ver; + READW (ver, fd); + 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 (FAIL); + } + + if (sf->version.major > 2) { + FLUID_LOG (FLUID_WARN, + _("Sound font version is %d.%d which is newer than" + " what this version of FLUID Synth was designed for (v2.0x)"), + sf->version.major, + sf->version.minor); + return (FAIL); + } + } + else if (id == IVER_ID) + { /* ROM version chunk? */ + if (chunk.size != 4) + return (gerr (ErrCorr, + _("ROM version info chunk has invalid size"))); + + READW (ver, fd); + sf->romver.major = ver; + READW (ver, fd); + sf->romver.minor = ver; + } + else if (id != UNKN_ID) + { + if ((id != ICMT_ID && chunk.size > 256) || (chunk.size > 65536) + || (chunk.size % 2)) + return (gerr (ErrCorr, + _("INFO sub chunk %.4s has invalid chunk size" + " of %d bytes"), &chunk.id, chunk.size)); + + /* alloc for chunk id and da chunk */ + if (!(item = FLUID_MALLOC (chunk.size + 1))) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return (FAIL); + } + + /* attach to INFO list, sfont_close will cleanup if FAIL occurs */ + sf->info = fluid_list_append (sf->info, item); + + *(unsigned char *) item = id; + if (!safe_fread (&item[1], chunk.size, fd)) + return (FAIL); + + /* force terminate info item (don't forget uint8 info ID) */ + *(item + chunk.size) = '\0'; + } + else + return (gerr (ErrCorr, _("Invalid chunk id in INFO chunk"))); + size -= chunk.size; + } + + if (size < 0) + return (gerr (ErrCorr, _("INFO chunk size mismatch"))); + + return (OK); +} + +static int +process_sdta (unsigned int size, SFData * sf, FILE * fd) +{ + SFChunk chunk; + + if (size == 0) + return (OK); /* no sample data? */ + + /* read sub chunk */ + READCHUNK (&chunk, fd); + size -= 8; + + if (chunkid (chunk.id) != SMPL_ID) + return (gerr (ErrCorr, + _("Expected SMPL chunk found invalid id instead"))); + + /* 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) + return (gerr (ErrCorr, _("SDTA chunk size mismatch"))); + + /* sample data follows */ + sf->samplepos = ftell (fd); + + /* used in fixup_sample() to check validity of sample headers */ + sdtachunk_size = chunk.size; + sf->samplesize = chunk.size; + + FSKIP (size, fd); + + return (OK); +} + +static int +pdtahelper (unsigned int expid, unsigned int reclen, SFChunk * chunk, + int * size, FILE * fd) +{ + unsigned int id; + char *expstr; + + expstr = CHNKIDSTR (expid); /* in case we need it */ + + READCHUNK (chunk, fd); + *size -= 8; + + if ((id = chunkid (chunk->id)) != expid) + return (gerr (ErrCorr, _("Expected" + " PDTA sub-chunk \"%.4s\" found invalid id instead"), expstr)); + + if (chunk->size % reclen) /* valid chunk size? */ + return (gerr (ErrCorr, + _("\"%.4s\" chunk size is not a multiple of %d bytes"), expstr, + reclen)); + if ((*size -= chunk->size) < 0) + return (gerr (ErrCorr, + _("\"%.4s\" chunk size exceeds remaining PDTA chunk size"), expstr)); + return (OK); +} + +static int +process_pdta (int size, SFData * sf, FILE * fd) +{ + SFChunk chunk; + + if (!pdtahelper (PHDR_ID, SFPHDRSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_phdr (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (PBAG_ID, SFBAGSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_pbag (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (PMOD_ID, SFMODSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_pmod (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (PGEN_ID, SFGENSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_pgen (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (IHDR_ID, SFIHDRSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_ihdr (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (IBAG_ID, SFBAGSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_ibag (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (IMOD_ID, SFMODSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_imod (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (IGEN_ID, SFGENSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_igen (chunk.size, sf, fd)) + return (FAIL); + + if (!pdtahelper (SHDR_ID, SFSHDRSIZE, &chunk, &size, fd)) + return (FAIL); + if (!load_shdr (chunk.size, sf, fd)) + return (FAIL); + + return (OK); +} + +/* preset header loader */ +static int +load_phdr (int size, SFData * sf, FILE * fd) +{ + int i, i2; + SFPreset *p, *pr = NULL; /* ptr to current & previous preset */ + unsigned short zndx, pzndx = 0; + + if (size % SFPHDRSIZE || size == 0) + return (gerr (ErrCorr, _("Preset header chunk size is invalid"))); + + i = size / SFPHDRSIZE - 1; + if (i == 0) + { /* at least one preset + term record */ + FLUID_LOG (FLUID_WARN, _("File contains no presets")); + FSKIP (SFPHDRSIZE, fd); + return (OK); + } + + for (; i > 0; i--) + { /* load all preset headers */ + p = FLUID_NEW (SFPreset); + sf->preset = fluid_list_append (sf->preset, p); + p->zone = NULL; /* In case of failure, sfont_close can cleanup */ + READSTR (&p->name, fd); /* possible read failure ^ */ + READW (p->prenum, fd); + READW (p->bank, fd); + READW (zndx, fd); + READD (p->libr, fd); + READD (p->genre, fd); + READD (p->morph, fd); + + if (pr) + { /* not first preset? */ + if (zndx < pzndx) + return (gerr (ErrCorr, _("Preset header indices not monotonic"))); + i2 = zndx - pzndx; + while (i2--) + { + pr->zone = fluid_list_prepend (pr->zone, NULL); + } + } + else if (zndx > 0) /* 1st preset, warn if ofs >0 */ + FLUID_LOG (FLUID_WARN, _("%d preset zones not referenced, discarding"), zndx); + pr = p; /* update preset ptr */ + pzndx = zndx; + } + + FSKIP (24, fd); + READW (zndx, fd); /* Read terminal generator index */ + FSKIP (12, fd); + + if (zndx < pzndx) + return (gerr (ErrCorr, _("Preset header indices not monotonic"))); + i2 = zndx - pzndx; + while (i2--) + { + pr->zone = fluid_list_prepend (pr->zone, NULL); + } + + return (OK); +} + +/* preset bag loader */ +static int +load_pbag (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2; + SFZone *z, *pz = NULL; + unsigned short genndx, modndx; + unsigned short pgenndx = 0, pmodndx = 0; + unsigned short i; + + if (size % SFBAGSIZE || size == 0) /* size is multiple of SFBAGSIZE? */ + return (gerr (ErrCorr, _("Preset bag chunk size is invalid"))); + + p = sf->preset; + while (p) + { /* traverse through presets */ + p2 = ((SFPreset *) (p->data))->zone; + while (p2) + { /* traverse preset's zones */ + if ((size -= SFBAGSIZE) < 0) + return (gerr (ErrCorr, _("Preset bag chunk size mismatch"))); + z = FLUID_NEW (SFZone); + p2->data = z; + z->gen = NULL; /* Init gen and mod before possible failure, */ + z->mod = NULL; /* to ensure proper cleanup (sfont_close) */ + READW (genndx, fd); /* possible read failure ^ */ + READW (modndx, fd); + z->instsamp = NULL; + + if (pz) + { /* if not first zone */ + if (genndx < pgenndx) + return (gerr (ErrCorr, + _("Preset bag generator indices not monotonic"))); + if (modndx < pmodndx) + return (gerr (ErrCorr, + _("Preset bag modulator indices not monotonic"))); + 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 */ + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + size -= SFBAGSIZE; + if (size != 0) + return (gerr (ErrCorr, _("Preset bag chunk size mismatch"))); + + READW (genndx, fd); + READW (modndx, fd); + + 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 (OK); + } + + if (genndx < pgenndx) + return (gerr (ErrCorr, _("Preset bag generator indices not monotonic"))); + if (modndx < pmodndx) + return (gerr (ErrCorr, _("Preset bag modulator indices not monotonic"))); + 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 (OK); +} + +/* preset modulator loader */ +static int +load_pmod (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2, *p3; + SFMod *m; + + p = sf->preset; + while (p) + { /* traverse through all presets */ + p2 = ((SFPreset *) (p->data))->zone; + while (p2) + { /* traverse this preset's zones */ + p3 = ((SFZone *) (p2->data))->mod; + while (p3) + { /* load zone's modulators */ + if ((size -= SFMODSIZE) < 0) + return (gerr (ErrCorr, + _("Preset modulator chunk size mismatch"))); + m = FLUID_NEW (SFMod); + p3->data = m; + READW (m->src, fd); + READW (m->dest, fd); + READW (m->amount, fd); + READW (m->amtsrc, fd); + READW (m->trans, fd); + p3 = fluid_list_next (p3); + } + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + /* + If there isn't even a terminal record + Hmmm, the specs say there should be one, but.. + */ + if (size == 0) + return (OK); + + size -= SFMODSIZE; + if (size != 0) + return (gerr (ErrCorr, _("Preset modulator chunk size mismatch"))); + FSKIP (SFMODSIZE, fd); /* terminal mod */ + + return (OK); +} + +/* ------------------------------------------------------------------- + * 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 + * ------------------------------------------------------------------- */ +static int +load_pgen (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2, *p3, *dup, **hz = NULL; + SFZone *z; + SFGen *g; + SFGenAmount genval; + unsigned short genid; + int level, skip, drop, gzone, discarded; + + p = sf->preset; + while (p) + { /* traverse through all presets */ + gzone = FALSE; + discarded = FALSE; + p2 = ((SFPreset *) (p->data))->zone; + if (p2) + hz = &p2; + while (p2) + { /* traverse preset's zones */ + level = 0; + z = (SFZone *) (p2->data); + p3 = z->gen; + while (p3) + { /* load zone's generators */ + dup = NULL; + skip = FALSE; + drop = FALSE; + if ((size -= SFGENSIZE) < 0) + return (gerr (ErrCorr, + _("Preset generator chunk size mismatch"))); + + READW (genid, fd); + + if (genid == Gen_KeyRange) + { /* nothing precedes */ + if (level == 0) + { + level = 1; + READB (genval.range.lo, fd); + READB (genval.range.hi, fd); + } + else + skip = TRUE; + } + else if (genid == Gen_VelRange) + { /* only KeyRange precedes */ + if (level <= 1) + { + level = 2; + READB (genval.range.lo, fd); + READB (genval.range.hi, fd); + } + else + skip = TRUE; + } + else if (genid == Gen_Instrument) + { /* inst is last gen */ + level = 3; + READW (genval.uword, fd); + ((SFZone *) (p2->data))->instsamp = GINT_TO_POINTER (genval.uword + 1); + break; /* break out of generator loop */ + } + else + { + level = 2; + if (gen_validp (genid)) + { /* generator valid? */ + READW (genval.sword, fd); + dup = gen_inlist (genid, z->gen); + } + else + skip = TRUE; + } + + if (!skip) + { + if (!dup) + { /* if gen ! dup alloc new */ + g = FLUID_NEW (SFGen); + p3->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 (fd); + } + + if (!drop) + p3 = fluid_list_next (p3); /* next gen */ + else + SLADVREM (z->gen, p3); /* drop place holder */ + + } /* generator loop */ + + if (level == 3) + SLADVREM (z->gen, p3); /* zone has inst? */ + else + { /* congratulations its a global zone */ + if (!gzone) + { /* Prior global zones? */ + gzone = TRUE; + + /* if global zone is not 1st zone, relocate */ + if (*hz != p2) + { + void* save = p2->data; + FLUID_LOG (FLUID_WARN, + _("Preset \"%s\": Global zone is not first zone"), + ((SFPreset *) (p->data))->name); + SLADVREM (*hz, p2); + *hz = fluid_list_prepend (*hz, save); + continue; + } + } + else + { /* previous global zone exists, discard */ + FLUID_LOG (FLUID_WARN, + _("Preset \"%s\": Discarding invalid global zone"), + ((SFPreset *) (p->data))->name); + sfont_zone_delete (sf, hz, (SFZone *) (p2->data)); + } + } + + while (p3) + { /* Kill any zones following an instrument */ + discarded = TRUE; + if ((size -= SFGENSIZE) < 0) + return (gerr (ErrCorr, + _("Preset generator chunk size mismatch"))); + FSKIP (SFGENSIZE, fd); + SLADVREM (z->gen, p3); + } + + p2 = fluid_list_next (p2); /* next zone */ + } + if (discarded) + FLUID_LOG(FLUID_WARN, + _("Preset \"%s\": Some invalid generators were discarded"), + ((SFPreset *) (p->data))->name); + p = fluid_list_next (p); + } + + /* in case there isn't a terminal record */ + if (size == 0) + return (OK); + + size -= SFGENSIZE; + if (size != 0) + return (gerr (ErrCorr, _("Preset generator chunk size mismatch"))); + FSKIP (SFGENSIZE, fd); /* terminal gen */ + + return (OK); +} + +/* instrument header loader */ +static int +load_ihdr (int size, SFData * sf, FILE * fd) +{ + int i, i2; + SFInst *p, *pr = NULL; /* ptr to current & previous instrument */ + unsigned short zndx, pzndx = 0; + + if (size % SFIHDRSIZE || size == 0) /* chunk size is valid? */ + return (gerr (ErrCorr, _("Instrument header has invalid size"))); + + size = size / SFIHDRSIZE - 1; + if (size == 0) + { /* at least one preset + term record */ + FLUID_LOG (FLUID_WARN, _("File contains no instruments")); + FSKIP (SFIHDRSIZE, fd); + return (OK); + } + + for (i = 0; i < size; i++) + { /* load all instrument headers */ + p = FLUID_NEW (SFInst); + sf->inst = fluid_list_append (sf->inst, p); + p->zone = NULL; /* For proper cleanup if fail (sfont_close) */ + READSTR (&p->name, fd); /* Possible read failure ^ */ + READW (zndx, fd); + + if (pr) + { /* not first instrument? */ + if (zndx < pzndx) + return (gerr (ErrCorr, + _("Instrument header indices not monotonic"))); + i2 = zndx - pzndx; + while (i2--) + pr->zone = fluid_list_prepend (pr->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; + pr = p; /* update instrument ptr */ + } + + FSKIP (20, fd); + READW (zndx, fd); + + if (zndx < pzndx) + return (gerr (ErrCorr, _("Instrument header indices not monotonic"))); + i2 = zndx - pzndx; + while (i2--) + pr->zone = fluid_list_prepend (pr->zone, NULL); + + return (OK); +} + +/* instrument bag loader */ +static int +load_ibag (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2; + SFZone *z, *pz = NULL; + unsigned short genndx, modndx, pgenndx = 0, pmodndx = 0; + int i; + + if (size % SFBAGSIZE || size == 0) /* size is multiple of SFBAGSIZE? */ + return (gerr (ErrCorr, _("Instrument bag chunk size is invalid"))); + + p = sf->inst; + while (p) + { /* traverse through inst */ + p2 = ((SFInst *) (p->data))->zone; + while (p2) + { /* load this inst's zones */ + if ((size -= SFBAGSIZE) < 0) + return (gerr (ErrCorr, _("Instrument bag chunk size mismatch"))); + z = FLUID_NEW (SFZone); + p2->data = z; + z->gen = NULL; /* In case of failure, */ + z->mod = NULL; /* sfont_close can clean up */ + READW (genndx, fd); /* READW = possible read failure */ + READW (modndx, fd); + z->instsamp = NULL; + + if (pz) + { /* if not first zone */ + if (genndx < pgenndx) + return (gerr (ErrCorr, + _("Instrument generator indices not monotonic"))); + if (modndx < pmodndx) + return (gerr (ErrCorr, + _("Instrument modulator indices not monotonic"))); + 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; + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + size -= SFBAGSIZE; + if (size != 0) + return (gerr (ErrCorr, _("Instrument chunk size mismatch"))); + + READW (genndx, fd); + READW (modndx, fd); + + 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 (OK); + } + + if (genndx < pgenndx) + return (gerr (ErrCorr, _("Instrument generator indices not monotonic"))); + if (modndx < pmodndx) + return (gerr (ErrCorr, _("Instrument modulator indices not monotonic"))); + 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 (OK); +} + +/* instrument modulator loader */ +static int +load_imod (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2, *p3; + SFMod *m; + + p = sf->inst; + while (p) + { /* traverse through all inst */ + p2 = ((SFInst *) (p->data))->zone; + while (p2) + { /* traverse this inst's zones */ + p3 = ((SFZone *) (p2->data))->mod; + while (p3) + { /* load zone's modulators */ + if ((size -= SFMODSIZE) < 0) + return (gerr (ErrCorr, + _("Instrument modulator chunk size mismatch"))); + m = FLUID_NEW (SFMod); + p3->data = m; + READW (m->src, fd); + READW (m->dest, fd); + READW (m->amount, fd); + READW (m->amtsrc, fd); + READW (m->trans, fd); + p3 = fluid_list_next (p3); + } + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + /* + If there isn't even a terminal record + Hmmm, the specs say there should be one, but.. + */ + if (size == 0) + return (OK); + + size -= SFMODSIZE; + if (size != 0) + return (gerr (ErrCorr, _("Instrument modulator chunk size mismatch"))); + FSKIP (SFMODSIZE, fd); /* terminal mod */ + + return (OK); +} + +/* load instrument generators (see load_pgen for loading rules) */ +static int +load_igen (int size, SFData * sf, FILE * fd) +{ + fluid_list_t *p, *p2, *p3, *dup, **hz = NULL; + SFZone *z; + SFGen *g; + SFGenAmount genval; + unsigned short genid; + int level, skip, drop, gzone, discarded; + + p = sf->inst; + while (p) + { /* traverse through all instruments */ + gzone = FALSE; + discarded = FALSE; + p2 = ((SFInst *) (p->data))->zone; + if (p2) + hz = &p2; + while (p2) + { /* traverse this instrument's zones */ + level = 0; + z = (SFZone *) (p2->data); + p3 = z->gen; + while (p3) + { /* load zone's generators */ + dup = NULL; + skip = FALSE; + drop = FALSE; + if ((size -= SFGENSIZE) < 0) + return (gerr (ErrCorr, _("IGEN chunk size mismatch"))); + + READW (genid, fd); + + if (genid == Gen_KeyRange) + { /* nothing precedes */ + if (level == 0) + { + level = 1; + READB (genval.range.lo, fd); + READB (genval.range.hi, fd); + } + else + skip = TRUE; + } + else if (genid == Gen_VelRange) + { /* only KeyRange precedes */ + if (level <= 1) + { + level = 2; + READB (genval.range.lo, fd); + READB (genval.range.hi, fd); + } + else + skip = TRUE; + } + else if (genid == Gen_SampleId) + { /* sample is last gen */ + level = 3; + READW (genval.uword, fd); + ((SFZone *) (p2->data))->instsamp = GINT_TO_POINTER (genval.uword + 1); + break; /* break out of generator loop */ + } + else + { + level = 2; + if (gen_valid (genid)) + { /* gen valid? */ + READW (genval.sword, fd); + dup = gen_inlist (genid, z->gen); + } + else + skip = TRUE; + } + + if (!skip) + { + if (!dup) + { /* if gen ! dup alloc new */ + g = FLUID_NEW (SFGen); + p3->data = g; + g->id = genid; + } + else + { + g = (SFGen *) (dup->data); + drop = TRUE; + } + g->amount = genval; + } + else + { /* skip this generator */ + discarded = TRUE; + drop = TRUE; + FSKIPW (fd); + } + + if (!drop) + p3 = fluid_list_next (p3); /* next gen */ + else + SLADVREM (z->gen, p3); + + } /* generator loop */ + + if (level == 3) + SLADVREM (z->gen, p3); /* zone has sample? */ + else + { /* its a global zone */ + if (!gzone) + { + gzone = TRUE; + + /* if global zone is not 1st zone, relocate */ + if (*hz != p2) + { + void* save = p2->data; + FLUID_LOG (FLUID_WARN, + _("Instrument \"%s\": Global zone is not first zone"), + ((SFPreset *) (p->data))->name); + SLADVREM (*hz, p2); + *hz = fluid_list_prepend (*hz, save); + continue; + } + } + else + { /* previous global zone exists, discard */ + FLUID_LOG (FLUID_WARN, + _("Instrument \"%s\": Discarding invalid global zone"), + ((SFInst *) (p->data))->name); + sfont_zone_delete (sf, hz, (SFZone *) (p2->data)); + } + } + + while (p3) + { /* Kill any zones following a sample */ + discarded = TRUE; + if ((size -= SFGENSIZE) < 0) + return (gerr (ErrCorr, + _("Instrument generator chunk size mismatch"))); + FSKIP (SFGENSIZE, fd); + SLADVREM (z->gen, p3); + } + + p2 = fluid_list_next (p2); /* next zone */ + } + if (discarded) + FLUID_LOG(FLUID_WARN, + _("Instrument \"%s\": Some invalid generators were discarded"), + ((SFInst *) (p->data))->name); + p = fluid_list_next (p); + } + + /* for those non-terminal record cases, grr! */ + if (size == 0) + return (OK); + + size -= SFGENSIZE; + if (size != 0) + return (gerr (ErrCorr, _("IGEN chunk size mismatch"))); + FSKIP (SFGENSIZE, fd); /* terminal gen */ + + return (OK); +} + +/* sample header loader */ +static int +load_shdr (unsigned int size, SFData * sf, FILE * fd) +{ + unsigned int i; + SFSample *p; + + if (size % SFSHDRSIZE || size == 0) /* size is multiple of SHDR size? */ + return (gerr (ErrCorr, _("Sample header has invalid size"))); + + size = size / SFSHDRSIZE - 1; + if (size == 0) + { /* at least one sample + term record? */ + FLUID_LOG (FLUID_WARN, _("File contains no samples")); + FSKIP (SFSHDRSIZE, fd); + return (OK); + } + + /* load all sample headers */ + for (i = 0; i < size; i++) + { + p = FLUID_NEW (SFSample); + sf->sample = fluid_list_append (sf->sample, p); + READSTR (&p->name, fd); + READD (p->start, fd); + READD (p->end, fd); /* - end, loopstart and loopend */ + READD (p->loopstart, fd); /* - will be checked and turned into */ + READD (p->loopend, fd); /* - offsets in fixup_sample() */ + READD (p->samplerate, fd); + READB (p->origpitch, fd); + READB (p->pitchadj, fd); + FSKIPW (fd); /* skip sample link */ + READW (p->sampletype, fd); + p->samfile = 0; + } + + FSKIP (SFSHDRSIZE, fd); /* skip terminal shdr */ + + return (OK); +} + +/* "fixup" (inst # -> inst ptr) instrument references in preset list */ +static int +fixup_pgen (SFData * sf) +{ + fluid_list_t *p, *p2, *p3; + SFZone *z; + int i; + + p = sf->preset; + while (p) + { + p2 = ((SFPreset *) (p->data))->zone; + while (p2) + { /* traverse this preset's zones */ + z = (SFZone *) (p2->data); + if ((i = GPOINTER_TO_INT (z->instsamp))) + { /* load instrument # */ + p3 = fluid_list_nth (sf->inst, i - 1); + if (!p3) + return (gerr (ErrCorr, + _("Preset %03d %03d: Invalid instrument reference"), + ((SFPreset *) (p->data))->bank, + ((SFPreset *) (p->data))->prenum)); + z->instsamp = p3; + } + else + z->instsamp = NULL; + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + return (OK); +} + +/* "fixup" (sample # -> sample ptr) sample references in instrument list */ +static int +fixup_igen (SFData * sf) +{ + fluid_list_t *p, *p2, *p3; + SFZone *z; + int i; + + p = sf->inst; + while (p) + { + p2 = ((SFInst *) (p->data))->zone; + while (p2) + { /* traverse instrument's zones */ + z = (SFZone *) (p2->data); + if ((i = GPOINTER_TO_INT (z->instsamp))) + { /* load sample # */ + p3 = fluid_list_nth (sf->sample, i - 1); + if (!p3) + return (gerr (ErrCorr, + _("Instrument \"%s\": Invalid sample reference"), + ((SFInst *) (p->data))->name)); + z->instsamp = p3; + } + p2 = fluid_list_next (p2); + } + p = fluid_list_next (p); + } + + return (OK); +} + +/* convert sample end, loopstart and loopend to offsets and check if valid */ +static int +fixup_sample (SFData * sf) +{ + fluid_list_t *p; + SFSample *sam; + + p = sf->sample; + while (p) + { + sam = (SFSample *) (p->data); + + /* if sample is not a ROM sample and end is over the sample data chunk + or sam start is greater than 4 less than the end (at least 4 samples) */ + if ((!(sam->sampletype & FLUID_SAMPLETYPE_ROM) + && sam->end > sdtachunk_size) || sam->start > (sam->end - 4)) + { + FLUID_LOG (FLUID_WARN, _("Sample '%s' start/end file positions are invalid," + " disabling and will not be saved"), sam->name); + + /* disable sample by setting all sample markers to 0 */ + sam->start = sam->end = sam->loopstart = sam->loopend = 0; + + return (OK); + } + else if (sam->loopend > sam->end || sam->loopstart >= sam->loopend + || sam->loopstart <= sam->start) + { /* loop is fowled?? (cluck cluck :) */ + /* can pad loop by 8 samples and ensure at least 4 for loop (2*8+4) */ + if ((sam->end - sam->start) >= 20) + { + sam->loopstart = sam->start + 8; + sam->loopend = sam->end - 8; + } + else + { /* loop is fowled, sample is tiny (can't pad 8 samples) */ + sam->loopstart = sam->start + 1; + sam->loopend = sam->end - 1; + } + } + + /* convert sample end, loopstart, loopend to offsets from sam->start */ + sam->end -= sam->start + 1; /* marks last sample, contrary to SF spec. */ + sam->loopstart -= sam->start; + sam->loopend -= sam->start; + + p = fluid_list_next (p); + } + + return (OK); +} + +/*=================================sfont.c======================== + Smurf SoundFont Editor + ================================================================*/ + + +/* optimum chunk area sizes (could be more optimum) */ +#define PRESET_CHUNK_OPTIMUM_AREA 256 +#define INST_CHUNK_OPTIMUM_AREA 256 +#define SAMPLE_CHUNK_OPTIMUM_AREA 256 +#define ZONE_CHUNK_OPTIMUM_AREA 256 +#define MOD_CHUNK_OPTIMUM_AREA 256 +#define GEN_CHUNK_OPTIMUM_AREA 256 + +unsigned short badgen[] = { Gen_Unused1, Gen_Unused2, Gen_Unused3, Gen_Unused4, + Gen_Reserved1, Gen_Reserved2, Gen_Reserved3, 0 +}; + +unsigned short badpgen[] = { Gen_StartAddrOfs, Gen_EndAddrOfs, Gen_StartLoopAddrOfs, + Gen_EndLoopAddrOfs, Gen_StartAddrCoarseOfs, Gen_EndAddrCoarseOfs, + Gen_StartLoopAddrCoarseOfs, Gen_Keynum, Gen_Velocity, + Gen_EndLoopAddrCoarseOfs, Gen_SampleModes, Gen_ExclusiveClass, + Gen_OverrideRootKey, 0 +}; + +/* close SoundFont file and delete a SoundFont structure */ +void +sfont_close (SFData * sf) +{ + fluid_list_t *p, *p2; + + if (sf->sffd) + fclose (sf->sffd); + + if (sf->fname) + free (sf->fname); + + p = sf->info; + while (p) + { + free (p->data); + p = fluid_list_next (p); + } + delete_fluid_list(sf->info); + sf->info = NULL; + + p = sf->preset; + while (p) + { /* loop over presets */ + p2 = ((SFPreset *) (p->data))->zone; + while (p2) + { /* loop over preset's zones */ + sfont_free_zone (p2->data); + p2 = fluid_list_next (p2); + } /* free preset's zone list */ + delete_fluid_list (((SFPreset *) (p->data))->zone); + FLUID_FREE (p->data); /* free preset chunk */ + p = fluid_list_next (p); + } + delete_fluid_list (sf->preset); + sf->preset = NULL; + + p = sf->inst; + while (p) + { /* loop over instruments */ + p2 = ((SFInst *) (p->data))->zone; + while (p2) + { /* loop over inst's zones */ + sfont_free_zone (p2->data); + p2 = fluid_list_next (p2); + } /* free inst's zone list */ + delete_fluid_list (((SFInst *) (p->data))->zone); + FLUID_FREE (p->data); + p = fluid_list_next (p); + } + delete_fluid_list (sf->inst); + sf->inst = NULL; + + p = sf->sample; + while (p) + { + FLUID_FREE (p->data); + p = fluid_list_next (p); + } + delete_fluid_list (sf->sample); + sf->sample = NULL; + + FLUID_FREE (sf); +} + +/* free all elements of a zone (Preset or Instrument) */ +void +sfont_free_zone (SFZone * zone) +{ + fluid_list_t *p; + + if (!zone) + return; + + p = zone->gen; + while (p) + { /* Free gen chunks for this zone */ + if (p->data) + FLUID_FREE (p->data); + p = fluid_list_next (p); + } + delete_fluid_list (zone->gen); /* free genlist */ + + p = zone->mod; + while (p) + { /* Free mod chunks for this zone */ + if (p->data) + FLUID_FREE (p->data); + p = fluid_list_next (p); + } + delete_fluid_list (zone->mod); /* free modlist */ + + FLUID_FREE (zone); /* free zone chunk */ +} + +/* preset sort function, first by bank, then by preset # */ +int +sfont_preset_compare_func (void* a, void* b) +{ + int aval, bval; + + aval = (int) (((SFPreset *) a)->bank) << 16 | ((SFPreset *) a)->prenum; + bval = (int) (((SFPreset *) b)->bank) << 16 | ((SFPreset *) b)->prenum; + + return (aval - bval); +} + +/* delete zone from zone list */ +void +sfont_zone_delete (SFData * sf, fluid_list_t ** zlist, SFZone * zone) +{ + *zlist = fluid_list_remove (*zlist, (void*) zone); + sfont_free_zone (zone); +} + +/* Find generator in gen list */ +fluid_list_t * +gen_inlist (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 */ +int +gen_valid (int gen) +{ /* is generator id valid? */ + int i = 0; + + if (gen > Gen_MaxValid) + return (FALSE); + while (badgen[i] && badgen[i] != gen) + i++; + return (badgen[i] == 0); +} + +/* check validity of preset generator */ +int +gen_validp (int gen) +{ /* is preset generator valid? */ + int i = 0; + + if (!gen_valid (gen)) + return (FALSE); + while (badpgen[i] && badpgen[i] != (unsigned short) gen) + i++; + return (badpgen[i] == 0); +} + +/*================================util.c===========================*/ + +/* Logging function, returns FAIL to use as a return value in calling funcs */ +int +gerr (int ev, char * fmt, ...) +{ + va_list args; + + va_start (args, fmt); + vprintf(fmt, args); + va_end (args); + + printf("\n"); + + return (FAIL); +} + +int +safe_fread (void *buf, int count, FILE * fd) +{ + if (fread (buf, count, 1, fd) != 1) + { /* size_t = count, nmemb = 1 */ + if (feof (fd)) + gerr (ErrEof, _("EOF while attemping to read %d bytes"), count); + else + FLUID_LOG (FLUID_ERR, _("File read failed")); + return (FAIL); + } + return (OK); +} + +int +safe_fseek (FILE * fd, long ofs, int whence) +{ + if (fseek (fd, ofs, whence) == -1) { + FLUID_LOG (FLUID_ERR, _("File seek failed with offset = %ld and whence = %d"), ofs, whence); + return (FAIL); + } + return (OK); +} diff --git a/libs/fluidsynth/src/fluid_defsfont.h b/libs/fluidsynth/src/fluid_defsfont.h new file mode 100644 index 0000000000..29f3fd9e86 --- /dev/null +++ b/libs/fluidsynth/src/fluid_defsfont.h @@ -0,0 +1,530 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_list.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 + +/* Sound Font structure defines */ + +typedef struct _SFVersion +{ /* version structure */ + unsigned short major; + unsigned short minor; +} +SFVersion; + +typedef 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 */ +} +SFMod; + +typedef 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; +} +SFGenAmount; + +typedef struct _SFGen +{ /* Generator structure */ + unsigned short id; /* generator ID */ + SFGenAmount amount; /* generator value */ +} +SFGen; + +typedef struct _SFZone +{ /* Sample/instrument zone structure */ + fluid_list_t *instsamp; /* instrument/sample pointer for zone */ + fluid_list_t *gen; /* list of generators */ + fluid_list_t *mod; /* list of modulators */ +} +SFZone; + +typedef struct _SFSample +{ /* Sample structure */ + char name[21]; /* Name of sample */ + unsigned char samfile; /* Loaded sfont/sample buffer = 0/1 */ + 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) */ +} +SFSample; + +typedef struct _SFInst +{ /* Instrument structure */ + char name[21]; /* Name of instrument */ + fluid_list_t *zone; /* list of instrument zones */ +} +SFInst; + +typedef struct _SFPreset +{ /* Preset structure */ + char name[21]; /* preset name */ + unsigned short prenum; /* preset number */ + unsigned short bank; /* bank number */ + unsigned int libr; /* Not used (preserved) */ + unsigned int genre; /* Not used (preserved) */ + unsigned int morph; /* Not used (preserved) */ + fluid_list_t *zone; /* list of preset zones */ +} +SFPreset; + +/* NOTE: sffd is also used to determine if sound font is new (NULL) */ +typedef struct _SFData +{ /* Sound font data structure */ + SFVersion version; /* sound font version */ + SFVersion romver; /* ROM version */ + unsigned int samplepos; /* position within sffd of the sample chunk */ + unsigned int samplesize; /* length within sffd of the sample chunk */ + char *fname; /* file name */ + FILE *sffd; /* loaded sfont file descriptor */ + 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 */ +} +SFData; + +/* sf file chunk IDs */ +enum +{ UNKN_ID, RIFF_ID, LIST_ID, SFBK_ID, + INFO_ID, SDTA_ID, PDTA_ID, /* info/sample/preset */ + + IFIL_ID, ISNG_ID, INAM_ID, IROM_ID, /* info ids (1st byte of info strings) */ + IVER_ID, ICRD_ID, IENG_ID, IPRD_ID, /* more info ids */ + ICOP_ID, ICMT_ID, ISFT_ID, /* and yet more info ids */ + + SNAM_ID, SMPL_ID, /* sample ids */ + PHDR_ID, PBAG_ID, PMOD_ID, PGEN_ID, /* preset ids */ + IHDR_ID, IBAG_ID, IMOD_ID, IGEN_ID, /* instrument ids */ + SHDR_ID /* sample info */ +}; + +/* generator types */ +typedef enum +{ Gen_StartAddrOfs, Gen_EndAddrOfs, Gen_StartLoopAddrOfs, + Gen_EndLoopAddrOfs, Gen_StartAddrCoarseOfs, Gen_ModLFO2Pitch, + Gen_VibLFO2Pitch, Gen_ModEnv2Pitch, Gen_FilterFc, Gen_FilterQ, + Gen_ModLFO2FilterFc, Gen_ModEnv2FilterFc, Gen_EndAddrCoarseOfs, + Gen_ModLFO2Vol, Gen_Unused1, Gen_ChorusSend, Gen_ReverbSend, Gen_Pan, + Gen_Unused2, Gen_Unused3, Gen_Unused4, + Gen_ModLFODelay, Gen_ModLFOFreq, Gen_VibLFODelay, Gen_VibLFOFreq, + Gen_ModEnvDelay, Gen_ModEnvAttack, Gen_ModEnvHold, Gen_ModEnvDecay, + Gen_ModEnvSustain, Gen_ModEnvRelease, Gen_Key2ModEnvHold, + Gen_Key2ModEnvDecay, Gen_VolEnvDelay, Gen_VolEnvAttack, + Gen_VolEnvHold, Gen_VolEnvDecay, Gen_VolEnvSustain, Gen_VolEnvRelease, + Gen_Key2VolEnvHold, Gen_Key2VolEnvDecay, Gen_Instrument, + Gen_Reserved1, Gen_KeyRange, Gen_VelRange, + Gen_StartLoopAddrCoarseOfs, Gen_Keynum, Gen_Velocity, + Gen_Attenuation, Gen_Reserved2, Gen_EndLoopAddrCoarseOfs, + Gen_CoarseTune, Gen_FineTune, Gen_SampleId, Gen_SampleModes, + Gen_Reserved3, Gen_ScaleTune, Gen_ExclusiveClass, Gen_OverrideRootKey, + Gen_Dummy +} +Gen_Type; + +#define Gen_MaxValid Gen_Dummy - 1 /* maximum valid generator */ +#define Gen_Count Gen_Dummy /* count of generators */ +#define GenArrSize sizeof(SFGenAmount)*Gen_Count /* gen array size */ + +/* generator unit type */ +typedef enum +{ + None, /* No unit type */ + Unit_Smpls, /* in samples */ + Unit_32kSmpls, /* in 32k samples */ + Unit_Cent, /* in cents (1/100th of a semitone) */ + Unit_HzCent, /* in Hz Cents */ + Unit_TCent, /* in Time Cents */ + Unit_cB, /* in centibels (1/100th of a decibel) */ + Unit_Percent, /* in percentage */ + Unit_Semitone, /* in semitones */ + Unit_Range /* a range of values */ +} +Gen_Unit; + +/* global data */ + +extern unsigned short badgen[]; /* list of bad generators */ +extern unsigned short badpgen[]; /* list of bad preset generators */ + +/* functions */ +void sfont_init_chunks (void); + +void sfont_close (SFData * sf); +void sfont_free_zone (SFZone * zone); +int sfont_preset_compare_func (void* a, void* b); + +void sfont_zone_delete (SFData * sf, fluid_list_t ** zlist, SFZone * zone); + +fluid_list_t *gen_inlist (int gen, fluid_list_t * genlist); +int gen_valid (int gen); +int gen_validp (int gen); + + +/*-----------------------------------sffile.h----------------------------*/ +/* + File structures and routines (used to be in sffile.h) +*/ + +#define CHNKIDSTR(id) &idlist[(id - 1) * 4] + +/* sfont file chunk sizes */ +#define SFPHDRSIZE 38 +#define SFBAGSIZE 4 +#define SFMODSIZE 10 +#define SFGENSIZE 4 +#define SFIHDRSIZE 22 +#define SFSHDRSIZE 46 + +/* sfont file data structures */ +typedef struct _SFChunk +{ /* RIFF file chunk structure */ + unsigned int id; /* chunk id */ + unsigned int size; /* size of the following chunk */ +} +SFChunk; + +typedef struct _SFPhdr +{ + unsigned char name[20]; /* preset name */ + unsigned short preset; /* preset number */ + unsigned short bank; /* bank number */ + unsigned short pbagndx; /* index into preset bag */ + unsigned int library; /* just for preserving them */ + unsigned int genre; /* Not used */ + unsigned int morphology; /* Not used */ +} +SFPhdr; + +typedef struct _SFBag +{ + unsigned short genndx; /* index into generator list */ + unsigned short modndx; /* index into modulator list */ +} +SFBag; + +typedef struct _SFIhdr +{ + char name[20]; /* Name of instrument */ + unsigned short ibagndx; /* Instrument bag index */ +} +SFIhdr; + +typedef struct _SFShdr +{ /* Sample header loading struct */ + char name[20]; /* Sample name */ + unsigned int start; /* Offset to start of sample */ + unsigned int end; /* Offset to end of sample */ + unsigned int loopstart; /* Offset to start of loop */ + unsigned int loopend; /* Offset to end of loop */ + unsigned int samplerate; /* Sample rate recorded at */ + unsigned char origpitch; /* root midi key number */ + signed char pitchadj; /* pitch correction in cents */ + unsigned short samplelink; /* Not used */ + unsigned short sampletype; /* 1 mono,2 right,4 left,linked 8,0x8000=ROM */ +} +SFShdr; + +/* data */ +extern char idlist[]; + +/* functions */ +SFData *sfload_file (const char * fname); + + + +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ + +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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. + */ + +#include + + +/*-----------------------------------util.h----------------------------*/ +/* + Utility functions (formerly in util.h) + */ +#define FAIL 0 +#define OK 1 + +enum +{ ErrWarn, ErrFatal, ErrStatus, ErrCorr, ErrEof, ErrMem, Errno, + ErrRead, ErrWrite +}; + +#define ErrMax ErrWrite +#define ErrnoStart Errno +#define ErrnoEnd ErrWrite + +int gerr (int ev, char * fmt, ...); +int safe_fread (void *buf, int count, FILE * fd); +int safe_fwrite (void *buf, int count, FILE * fd); +int safe_fseek (FILE * fd, long ofs, int whence); + + +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ +/********************************************************************************/ + + + +/*************************************************************** + * + * 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; + +/* + + Public interface + + */ + +fluid_sfloader_t* new_fluid_defsfloader(fluid_settings_t* settings); +int delete_fluid_defsfloader(fluid_sfloader_t* loader); +fluid_sfont_t* fluid_defsfloader_load(fluid_sfloader_t* loader, const char* filename); + + +int fluid_defsfont_sfont_delete(fluid_sfont_t* sfont); +char* fluid_defsfont_sfont_get_name(fluid_sfont_t* sfont); +fluid_preset_t* fluid_defsfont_sfont_get_preset(fluid_sfont_t* sfont, unsigned int bank, unsigned int prenum); +void fluid_defsfont_sfont_iteration_start(fluid_sfont_t* sfont); +int fluid_defsfont_sfont_iteration_next(fluid_sfont_t* sfont, fluid_preset_t* preset); + + +int fluid_defpreset_preset_delete(fluid_preset_t* preset); +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); + + +/* + * fluid_defsfont_t + */ +struct _fluid_defsfont_t +{ + 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 */ + short* sampledata; /* the sample data, loaded in ram */ + fluid_list_t* sample; /* the samples in this soundfont */ + fluid_defpreset_t* preset; /* the presets of this soundfont */ + int mlock; /* Should we try memlock (avoid swapping)? */ + + fluid_preset_t iter_preset; /* preset interface used in the iteration */ + fluid_defpreset_t* iter_cur; /* the current preset in the iteration */ + + fluid_preset_t** preset_stack; /* List of presets that are available to use */ + int preset_stack_capacity; /* Length of preset_stack array */ + int preset_stack_size; /* Current number of items in the stack */ +}; + + +fluid_defsfont_t* new_fluid_defsfont(fluid_settings_t* settings); +int delete_fluid_defsfont(fluid_defsfont_t* sfont); +int fluid_defsfont_load(fluid_defsfont_t* sfont, const char* file); +char* fluid_defsfont_get_name(fluid_defsfont_t* sfont); +fluid_defpreset_t* fluid_defsfont_get_preset(fluid_defsfont_t* sfont, unsigned int bank, unsigned int prenum); +void fluid_defsfont_iteration_start(fluid_defsfont_t* sfont); +int fluid_defsfont_iteration_next(fluid_defsfont_t* sfont, fluid_preset_t* preset); +int fluid_defsfont_load_sampledata(fluid_defsfont_t* sfont); +int fluid_defsfont_add_sample(fluid_defsfont_t* sfont, fluid_sample_t* sample); +int fluid_defsfont_add_preset(fluid_defsfont_t* sfont, fluid_defpreset_t* preset); + + +/* + * fluid_preset_t + */ +struct _fluid_defpreset_t +{ + fluid_defpreset_t* next; + fluid_defsfont_t* sfont; /* the soundfont this preset belongs to */ + 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 */ +}; + +fluid_defpreset_t* new_fluid_defpreset(fluid_defsfont_t* sfont); +int delete_fluid_defpreset(fluid_defpreset_t* preset); +fluid_defpreset_t* fluid_defpreset_next(fluid_defpreset_t* preset); +int fluid_defpreset_import_sfont(fluid_defpreset_t* preset, SFPreset* sfpreset, fluid_defsfont_t* sfont); +int fluid_defpreset_set_global_zone(fluid_defpreset_t* preset, fluid_preset_zone_t* zone); +int fluid_defpreset_add_zone(fluid_defpreset_t* preset, fluid_preset_zone_t* zone); +fluid_preset_zone_t* fluid_defpreset_get_zone(fluid_defpreset_t* preset); +fluid_preset_zone_t* fluid_defpreset_get_global_zone(fluid_defpreset_t* preset); +int fluid_defpreset_get_banknum(fluid_defpreset_t* preset); +int fluid_defpreset_get_num(fluid_defpreset_t* preset); +char* fluid_defpreset_get_name(fluid_defpreset_t* preset); +int fluid_defpreset_noteon(fluid_defpreset_t* preset, 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; + int keylo; + int keyhi; + int vello; + int velhi; + fluid_gen_t gen[GEN_LAST]; + fluid_mod_t * mod; /* List of modulators */ +}; + +fluid_preset_zone_t* new_fluid_preset_zone(char* name); +int delete_fluid_preset_zone(fluid_preset_zone_t* zone); +fluid_preset_zone_t* fluid_preset_zone_next(fluid_preset_zone_t* preset); +int fluid_preset_zone_import_sfont(fluid_preset_zone_t* zone, SFZone* sfzone, fluid_defsfont_t* sfont); +int fluid_preset_zone_inside_range(fluid_preset_zone_t* zone, int key, int vel); +fluid_inst_t* fluid_preset_zone_get_inst(fluid_preset_zone_t* zone); + +/* + * fluid_inst_t + */ +struct _fluid_inst_t +{ + char name[21]; + fluid_inst_zone_t* global_zone; + fluid_inst_zone_t* zone; +}; + +fluid_inst_t* new_fluid_inst(void); +int delete_fluid_inst(fluid_inst_t* inst); +int fluid_inst_import_sfont(fluid_inst_t* inst, SFInst *sfinst, fluid_defsfont_t* sfont); +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; + int keylo; + int keyhi; + int vello; + int velhi; + fluid_gen_t gen[GEN_LAST]; + fluid_mod_t * mod; /* List of modulators */ +}; + +fluid_inst_zone_t* new_fluid_inst_zone(char* name); +int 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* zone, SFZone *sfzone, fluid_defsfont_t* sfont); +int fluid_inst_zone_inside_range(fluid_inst_zone_t* zone, int key, int vel); +fluid_sample_t* fluid_inst_zone_get_sample(fluid_inst_zone_t* zone); + + + +fluid_sample_t* new_fluid_sample(void); +int delete_fluid_sample(fluid_sample_t* sample); +int fluid_sample_import_sfont(fluid_sample_t* sample, SFSample* sfsample, fluid_defsfont_t* sfont); +int fluid_sample_in_rom(fluid_sample_t* sample); + + +#endif /* _FLUID_SFONT_H */ diff --git a/libs/fluidsynth/src/fluid_event.c b/libs/fluidsynth/src/fluid_event.c new file mode 100644 index 0000000000..b3b0608345 --- /dev/null +++ b/libs/fluidsynth/src/fluid_event.c @@ -0,0 +1,781 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_priv.h" +#include "fluidsynth_priv.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; +} + +/** + * 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) +{ + + if (evt == NULL) { + return; + } + + 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; +} + +/** + * Set source of a sequencer event (DOCME). + * @param evt Sequencer event structure + * @param src DOCME + */ +void +fluid_event_set_source(fluid_event_t* evt, short src) +{ + evt->src = src; +} + +/** + * Set destination of a sequencer event (DOCME). + * @param evt Sequencer event structure + * @param dest DOCME + */ +void +fluid_event_set_dest(fluid_event_t* evt, short dest) +{ + evt->dest = dest; +} + +/** + * Set a sequencer event to be a timer event. + * @param evt Sequencer event structure + * @param data DOCME + */ +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) + */ +void +fluid_event_noteon(fluid_event_t* evt, int channel, short key, short vel) +{ + 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. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * @param key MIDI note number (0-127) + * @param vel MIDI velocity value (0-127) + * @param duration Duration of note (DOCME units?) + */ +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, short 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 an any control change event. + * @param evt Sequencer event structure + * @param channel MIDI channel number + * DOCME + */ +void +fluid_event_any_control_change(fluid_event_t* evt, int channel) +{ + evt->type = FLUID_SEQ_ANYCONTROLCHANGE; + evt->channel = channel; +} + +/** + * 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 (DOCME units?) + */ +void +fluid_event_pitch_wheelsens(fluid_event_t* evt, int channel, short value) +{ + evt->type = FLUID_SEQ_PITCHWHHELSENS; + 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, short 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, short 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-16383 DOCME is that true?) + */ +void +fluid_event_control_change(fluid_event_t* evt, int channel, short control, short 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, short 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, short 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, short 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, short 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 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, short 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 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; +} + + + +/* + * 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; +} + +/** + * Get the time field from a sequencer event structure. + * @param evt Sequencer event structure + * @return Time value (DOCME units?) + */ +unsigned int fluid_event_get_time(fluid_event_t* evt) +{ + return evt->time; +} + +/** + * Get the source field from a sequencer event structure. + * @param evt Sequencer event structure + * @return DOCME + */ +short fluid_event_get_source(fluid_event_t* evt) +{ + return evt->src; +} + +/** + * Get the dest field from a sequencer event structure. + * @param evt Sequencer event structure + * @return DOCME + */ +short 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 channel number (DOCME 0-15 or more?) + */ +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_PITCHWHHELSENS, #FLUID_SEQ_MODULATION, #FLUID_SEQ_SUSTAIN, + * #FLUID_SEQ_CONTROLCHANGE, #FLUID_SEQ_PAN, #FLUID_SEQ_VOLUME, + * #FLUID_SEQ_REVERBSEND, #FLUID_SEQ_CHORUSSEND. + */ +short 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 (DOCME units?) + * + * 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. + */ +short +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; +} + + + +/********************/ +/* heap management */ +/********************/ + +fluid_evt_heap_t* +_fluid_evt_heap_init(int nbEvents) +{ +#ifdef HEAP_WITH_DYNALLOC + + int i; + fluid_evt_heap_t* heap; + fluid_evt_entry *tmp; + + heap = FLUID_NEW(fluid_evt_heap_t); + if (heap == NULL) { + fluid_log(FLUID_PANIC, "sequencer: Out of memory\n"); + return NULL; + } + + heap->freelist = NULL; + fluid_mutex_init(heap->mutex); + + /* LOCK */ + fluid_mutex_lock(heap->mutex); + + /* Allocate the event entries */ + for (i = 0; i < nbEvents; i++) { + tmp = FLUID_NEW(fluid_evt_entry); + tmp->next = heap->freelist; + heap->freelist = tmp; + } + + /* UNLOCK */ + fluid_mutex_unlock(heap->mutex); + + +#else + int i; + fluid_evt_heap_t* heap; + int siz = 2*sizeof(fluid_evt_entry *) + sizeof(fluid_evt_entry)*nbEvents; + + heap = (fluid_evt_heap_t *)FLUID_MALLOC(siz); + if (heap == NULL) { + fluid_log(FLUID_PANIC, "sequencer: Out of memory\n"); + return NULL; + } + FLUID_MEMSET(heap, 0, siz); + + /* link all heap events */ + { + fluid_evt_entry *tmp = &(heap->pool); + for (i = 0 ; i < nbEvents - 1 ; i++) + tmp[i].next = &(tmp[i+1]); + tmp[nbEvents-1].next = NULL; + + /* set head & tail */ + heap->tail = &(tmp[nbEvents-1]); + heap->head = &(heap->pool); + } +#endif + return (heap); +} + +void +_fluid_evt_heap_free(fluid_evt_heap_t* heap) +{ +#ifdef HEAP_WITH_DYNALLOC + fluid_evt_entry *tmp, *next; + + /* LOCK */ + fluid_mutex_lock(heap->mutex); + + tmp = heap->freelist; + while (tmp) { + next = tmp->next; + FLUID_FREE(tmp); + tmp = next; + } + + /* UNLOCK */ + fluid_mutex_unlock(heap->mutex); + fluid_mutex_destroy(heap->mutex); + + FLUID_FREE(heap); + +#else + FLUID_FREE(heap); +#endif +} + +fluid_evt_entry* +_fluid_seq_heap_get_free(fluid_evt_heap_t* heap) +{ +#ifdef HEAP_WITH_DYNALLOC + fluid_evt_entry* evt = NULL; + + /* LOCK */ + fluid_mutex_lock(heap->mutex); + +#if !defined(MACOS9) + if (heap->freelist == NULL) { + heap->freelist = FLUID_NEW(fluid_evt_entry); + if (heap->freelist != NULL) { + heap->freelist->next = NULL; + } + } +#endif + + evt = heap->freelist; + + if (evt != NULL) { + heap->freelist = heap->freelist->next; + evt->next = NULL; + } + + /* UNLOCK */ + fluid_mutex_unlock(heap->mutex); + + return evt; + +#else + fluid_evt_entry* evt; + if (heap->head == NULL) return NULL; + + /* take from head of the heap */ + /* critical - should threadlock ? */ + evt = heap->head; + heap->head = heap->head->next; + + return evt; +#endif +} + +void +_fluid_seq_heap_set_free(fluid_evt_heap_t* heap, fluid_evt_entry* evt) +{ +#ifdef HEAP_WITH_DYNALLOC + + /* LOCK */ + fluid_mutex_lock(heap->mutex); + + evt->next = heap->freelist; + heap->freelist = evt; + + /* UNLOCK */ + fluid_mutex_unlock(heap->mutex); + +#else + /* append to the end of the heap */ + /* critical - should threadlock ? */ + heap->tail->next = evt; + heap->tail = evt; + evt->next = NULL; +#endif +} diff --git a/libs/fluidsynth/src/fluid_event_priv.h b/libs/fluidsynth/src/fluid_event_priv.h new file mode 100644 index 0000000000..c0a4c1af4e --- /dev/null +++ b/libs/fluidsynth/src/fluid_event_priv.h @@ -0,0 +1,83 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" +#include "fluid_sys.h" + +/* Private data for event */ +/* ?? should be optimized in size, using unions */ +struct _fluid_event_t { + unsigned int time; + int type; + short src; + short dest; + int channel; + short key; + short vel; + short control; + short value; + short id; //?? unused ? + int pitch; + unsigned int duration; + void* data; +}; + +unsigned int fluid_event_get_time(fluid_event_t* evt); +void fluid_event_set_time(fluid_event_t* evt, unsigned int time); + +void fluid_event_clear(fluid_event_t* evt); + +/* private data for sorter + heap */ +enum fluid_evt_entry_type { + FLUID_EVT_ENTRY_INSERT = 0, + FLUID_EVT_ENTRY_REMOVE +}; + +typedef struct _fluid_evt_entry fluid_evt_entry; +struct _fluid_evt_entry { + fluid_evt_entry *next; + short entryType; + fluid_event_t evt; +}; + +#define HEAP_WITH_DYNALLOC 1 +/* #undef HEAP_WITH_DYNALLOC */ + +typedef struct _fluid_evt_heap_t { +#ifdef HEAP_WITH_DYNALLOC + fluid_evt_entry* freelist; + fluid_mutex_t mutex; +#else + fluid_evt_entry* head; + fluid_evt_entry* tail; + fluid_evt_entry pool; +#endif +} fluid_evt_heap_t; + +fluid_evt_heap_t* _fluid_evt_heap_init(int nbEvents); +void _fluid_evt_heap_free(fluid_evt_heap_t* heap); +fluid_evt_entry* _fluid_seq_heap_get_free(fluid_evt_heap_t* heap); +void _fluid_seq_heap_set_free(fluid_evt_heap_t* heap, fluid_evt_entry* evt); + +#endif /* _FLUID_EVENT_PRIV_H */ diff --git a/libs/fluidsynth/src/fluid_event_queue.h b/libs/fluidsynth/src/fluid_event_queue.h new file mode 100644 index 0000000000..a5c24da754 --- /dev/null +++ b/libs/fluidsynth/src/fluid_event_queue.h @@ -0,0 +1,195 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_QUEUE_H +#define _FLUID_EVENT_QUEUE_H + +#include "fluid_sys.h" +#include "fluid_midi.h" +#include "fluid_ringbuffer.h" + +/** + * Type of queued event. + */ +enum fluid_event_queue_elem +{ + FLUID_EVENT_QUEUE_ELEM_MIDI, /**< MIDI event. Uses midi field of event value */ + FLUID_EVENT_QUEUE_ELEM_UPDATE_GAIN, /**< Update synthesizer gain. No payload value */ + FLUID_EVENT_QUEUE_ELEM_POLYPHONY, /**< Synth polyphony event. No payload value */ + FLUID_EVENT_QUEUE_ELEM_GEN, /**< Generator event. Uses gen field of event value */ + FLUID_EVENT_QUEUE_ELEM_PRESET, /**< Preset set event. Uses preset field of event value */ + FLUID_EVENT_QUEUE_ELEM_STOP_VOICES, /**< Stop voices event. Uses ival field of event value */ + FLUID_EVENT_QUEUE_ELEM_FREE_PRESET, /**< Free preset return event. Uses pval field of event value */ + FLUID_EVENT_QUEUE_ELEM_SET_TUNING, /**< Set tuning event. Uses set_tuning field of event value */ + FLUID_EVENT_QUEUE_ELEM_REPL_TUNING, /**< Replace tuning event. Uses repl_tuning field of event value */ + FLUID_EVENT_QUEUE_ELEM_UNREF_TUNING /**< Unref tuning return event. Uses unref_tuning field of event value */ +}; + +/** + * SoundFont generator set event structure. + */ +typedef struct +{ + int channel; /**< MIDI channel number */ + int param; /**< FluidSynth generator ID */ + float value; /**< Value for the generator (absolute or relative) */ + int absolute; /**< 1 if value is absolute, 0 if relative */ +} fluid_event_gen_t; + +/** + * Preset channel assignment event structure. + */ +typedef struct +{ + int channel; /**< MIDI channel number */ + fluid_preset_t *preset; /**< Preset to assign (synth thread owns) */ +} fluid_event_preset_t; + +/** + * Tuning assignment event structure. + */ +typedef struct +{ + char apply; /**< TRUE to set tuning in realtime */ + int channel; /**< MIDI channel number */ + fluid_tuning_t *tuning; /**< Tuning to assign */ +} fluid_event_set_tuning_t; + +/** + * Tuning replacement event structure. + */ +typedef struct +{ + char apply; /**< TRUE if tuning change should be applied in realtime */ + fluid_tuning_t *old_tuning; /**< Old tuning pointer to replace */ + fluid_tuning_t *new_tuning; /**< New tuning to assign */ +} fluid_event_repl_tuning_t; + +/** + * Tuning unref event structure. + */ +typedef struct +{ + fluid_tuning_t *tuning; /**< Tuning to unref */ + int count; /**< Number of times to unref */ +} fluid_event_unref_tuning_t; + +/** + * Structure for an integer parameter sent to a MIDI channel (bank or SoundFont ID for example). + */ +typedef struct +{ + int channel; + int val; +} fluid_event_channel_int_t; + +/** + * Event queue element structure. + */ +typedef struct +{ + char type; /**< fluid_event_queue_elem */ + + union + { + fluid_midi_event_t midi; /**< If type == FLUID_EVENT_QUEUE_ELEM_MIDI */ + fluid_event_gen_t gen; /**< If type == FLUID_EVENT_QUEUE_ELEM_GEN */ + fluid_event_preset_t preset; /**< If type == FLUID_EVENT_QUEUE_ELEM_PRESET */ + fluid_event_set_tuning_t set_tuning; /**< If type == FLUID_EVENT_QUEUE_ELEM_SET_TUNING */ + fluid_event_repl_tuning_t repl_tuning; /**< If type == FLUID_EVENT_QUEUE_ELEM_REPL_TUNING */ + fluid_event_unref_tuning_t unref_tuning; /**< If type == FLUID_EVENT_QUEUE_ELEM_UNREF_TUNING */ + double dval; /**< A floating point payload value */ + int ival; /**< An integer payload value */ + void *pval; /**< A pointer payload value */ + }; +} fluid_event_queue_elem_t; + +typedef struct _fluid_ringbuffer_t fluid_event_queue_t; + +static FLUID_INLINE fluid_event_queue_t * +fluid_event_queue_new (int count) +{ + return (fluid_event_queue_t *) new_fluid_ringbuffer(count, sizeof(fluid_event_queue_elem_t)); +} + +static FLUID_INLINE void fluid_event_queue_free (fluid_event_queue_t *queue) +{ + delete_fluid_ringbuffer(queue); +} + +/** + * Get pointer to next input array element in queue. + * @param queue Lockless queue instance + * @return Pointer to array element in queue to store data to or NULL if queue is full + * + * This function along with fluid_queue_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 fluid_event_queue_elem_t * +fluid_event_queue_get_inptr (fluid_event_queue_t *queue) +{ + return (fluid_event_queue_elem_t *) fluid_ringbuffer_get_inptr(queue, 0); +} + +/** + * Advance the input queue index to complete a "push" operation. + * @param queue Lockless queue instance + * + * This function along with fluid_queue_get_inptr() form a queue "push" + * operation and is split into 2 functions to avoid element copy. + */ +static FLUID_INLINE void +fluid_event_queue_next_inptr (fluid_event_queue_t *queue) +{ + fluid_ringbuffer_next_inptr(queue, 1); +} + +/** + * 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_queue_next_outptr() is called. + * + * This function along with fluid_queue_next_outptr() form a queue "pop" + * operation and is split into 2 functions to avoid an element copy. + */ +static FLUID_INLINE fluid_event_queue_elem_t * +fluid_event_queue_get_outptr (fluid_event_queue_t *queue) +{ + return (fluid_event_queue_elem_t *) fluid_ringbuffer_get_outptr(queue); +} + +/** + * Advance the output queue index to complete a "pop" operation. + * @param queue Lockless queue instance + * + * This function along with fluid_queue_get_outptr() form a queue "pop" + * operation and is split into 2 functions to avoid an element copy. + */ +static FLUID_INLINE void +fluid_event_queue_next_outptr (fluid_event_queue_t *queue) +{ + fluid_ringbuffer_next_outptr(queue); +} + +#endif /* _FLUID_EVENT_QUEUE_H */ diff --git a/libs/fluidsynth/src/fluid_gen.c b/libs/fluidsynth/src/fluid_gen.c new file mode 100644 index 0000000000..0f1413eab2 --- /dev/null +++ b/libs/fluidsynth/src/fluid_gen.c @@ -0,0 +1,149 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + + +/* See SFSpec21 $8.1.3 */ +fluid_gen_info_t fluid_gen_info[] = { + /* number/name init 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 } +}; + + +/** + * Set an array of generators to their default values. + * @param gen Array of generators (should be #GEN_LAST in size). + * @return Always returns 0 + */ +int +fluid_gen_set_default_values(fluid_gen_t* gen) +{ + int i; + + for (i = 0; i < GEN_LAST; i++) { + gen[i].flags = GEN_UNUSED; + gen[i].mod = 0.0; + gen[i].nrpn = 0.0; + gen[i].val = fluid_gen_info[i].def; + } + + return FLUID_OK; +} + + +/* fluid_gen_init + * + * Set an array of generators to their initial value + */ +int +fluid_gen_init(fluid_gen_t* gen, fluid_channel_t* channel) +{ + int i; + + fluid_gen_set_default_values(gen); + + for (i = 0; i < GEN_LAST; i++) { + gen[i].nrpn = fluid_channel_get_gen(channel, i); + + /* This is an extension to the SoundFont standard. More + * documentation is available at the fluid_synth_set_gen2() + * function. */ + if (fluid_channel_get_gen_abs(channel, i)) { + gen[i].flags = GEN_ABS_NRPN; + } + } + + return FLUID_OK; +} + +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) +{ + fluid_real_t value = (float) data - 8192.0f; + fluid_clip(value, -8192, 8192); + return value * (float) fluid_gen_info[gen].nrpn_scale; +} diff --git a/libs/fluidsynth/src/fluid_gen.h b/libs/fluidsynth/src/fluid_gen.h new file mode 100644 index 0000000000..f54d0490fc --- /dev/null +++ b/libs/fluidsynth/src/fluid_gen.h @@ -0,0 +1,44 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 init; /* Does the generator need to be initialized (cfr. fluid_voice_init()) */ + 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_set_default_values()) */ +} fluid_gen_info_t; + +#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); +int fluid_gen_init(fluid_gen_t* gen, fluid_channel_t* channel); + + +#endif /* _FLUID_GEN_H */ diff --git a/libs/fluidsynth/src/fluid_hash.c b/libs/fluidsynth/src/fluid_hash.c new file mode 100644 index 0000000000..9d5a92009e --- /dev/null +++ b/libs/fluidsynth/src/fluid_hash.c @@ -0,0 +1,1310 @@ +/* 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 + * September 8, 2009 from glib 2.18.4 + */ + +/* + * MT safe + */ + +#include "fluidsynth_priv.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 guint 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 = sizeof (primes) / sizeof (primes[0]); + +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 preceeds 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 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 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; + 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); + FLUID_MEMSET (hashtable->nodes, 0, hashtable->size * sizeof (fluid_hashnode_t *)); + + 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 (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 (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 (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 (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 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 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/fluid_hash.h b/libs/fluidsynth/src/fluid_hash.h new file mode 100644 index 0000000000..3beff0607e --- /dev/null +++ b/libs/fluidsynth/src/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 + * 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; + volatile int 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/fluid_iir_filter.c b/libs/fluidsynth/src/fluid_iir_filter.c new file mode 100644 index 0000000000..5b474692ae --- /dev/null +++ b/libs/fluidsynth/src/fluid_iir_filter.c @@ -0,0 +1,301 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 lowpass filter with variable cutoff frequency and quality factor. + * 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, dsp_b0, dsp_b1, dsp_b2: Filter coefficients + * + * A couple of variables are used internally, their results are discarded: + * - dsp_i: Index through the output buffer + * - dsp_phase_fractional: The fractional part of dsp_phase + * - dsp_coeff: A table of four coefficients, depending on the fractional phase. + * Used to interpolate between samples. + * - dsp_process_buffer: Holds the processed signal between stages + * - 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) +{ + /* 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 (fabs (dsp_hist1) < 1e-20) 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 && fabs(dsp_b02) > 0.001) { + fluid_real_t compensate = old_b02 / dsp_b02; + dsp_centernode *= compensate; + 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"); +} + + +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->filter_startup = 1; +} + +void +fluid_iir_filter_set_fres(fluid_iir_filter_t* iir_filter, + fluid_real_t fres) +{ + iir_filter->fres = fres; + iir_filter->last_fres = -1.; +} + + +void +fluid_iir_filter_set_q_dB(fluid_iir_filter_t* iir_filter, + fluid_real_t q_dB) +{ + /* The 'sound font' Q is defined in dB. The filter needs a linear + q. Convert. */ + iir_filter->q_lin = (fluid_real_t) (pow(10.0f, q_dB / 20.0f)); + + /* 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_real_t) (1.0 / sqrt(iir_filter->q_lin)); + + /* The synthesis loop will have to recalculate the filter coefficients. */ + iir_filter->last_fres = -1.; + +} + + +static inline void +fluid_iir_filter_calculate_coefficients(fluid_iir_filter_t* iir_filter, + int transition_samples, + fluid_real_t output_rate) +{ + + /* + * 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 / ((float) output_rate))); + fluid_real_t sin_coeff = (fluid_real_t) sin(omega); + fluid_real_t cos_coeff = (fluid_real_t) 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; */ + + 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 b1_temp = (1.0f - cos_coeff) * a0_inv * iir_filter->filter_gain; + /* both b0 -and- b2 */ + fluid_real_t b02_temp = b1_temp * 0.5f; + + 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 (fabs(iir_filter->b02) > 0.0001) { + fluid_real_t quota = b02_temp / iir_filter->b02; + iir_filter->compensate_incr = quota < 0.5 || quota > 2; + } + /* 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 frequence 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) + fres = 5; + + /* if filter enabled and there is a significant frequency change.. */ + if ((abs (fres - iir_filter->last_fres) > 0.01)) + { + /* 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/fluid_iir_filter.h b/libs/fluidsynth/src/fluid_iir_filter.h new file mode 100644 index 0000000000..7dc5de14a5 --- /dev/null +++ b/libs/fluidsynth/src/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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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; + + +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_set_q_dB(fluid_iir_filter_t* iir_filter, + fluid_real_t q_dB); + +void fluid_iir_filter_set_fres(fluid_iir_filter_t* iir_filter, + fluid_real_t fres); + +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 +{ + /* 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/fluid_lfo.c b/libs/fluidsynth/src/fluid_lfo.c new file mode 100644 index 0000000000..ff178e0012 --- /dev/null +++ b/libs/fluidsynth/src/fluid_lfo.c @@ -0,0 +1,13 @@ +#include "fluid_lfo.h" + +void +fluid_lfo_set_incr(fluid_lfo_t* lfo, fluid_real_t increment) +{ + lfo->increment = increment; +} + +void +fluid_lfo_set_delay(fluid_lfo_t* lfo, unsigned int delay) +{ + lfo->delay = delay; +} diff --git a/libs/fluidsynth/src/fluid_lfo.h b/libs/fluidsynth/src/fluid_lfo.h new file mode 100644 index 0000000000..e9440cf526 --- /dev/null +++ b/libs/fluidsynth/src/fluid_lfo.h @@ -0,0 +1,72 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.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 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 +void fluid_lfo_set_incr(fluid_lfo_t* lfo, fluid_real_t increment); +void fluid_lfo_set_delay(fluid_lfo_t* lfo, unsigned int delay); + +static inline fluid_real_t +fluid_lfo_get_val(fluid_lfo_t* lfo) +{ + return lfo->val; +} + +static 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/fluid_list.c b/libs/fluidsynth/src/fluid_list.c new file mode 100644 index 0000000000..dd47a39e0e --- /dev/null +++ b/libs/fluidsynth/src/fluid_list.c @@ -0,0 +1,268 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_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; + while (list) { + next = list->next; + FLUID_FREE(list); + list = next; + } +} + +void +delete1_fluid_list(fluid_list_t *list) +{ + if (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 (void *a, void *b) +{ + if (a && b) return FLUID_STRCMP ((char *)a, (char *)b); + if (!a && !b) return 0; + if (a) return -1; + return 1; +} diff --git a/libs/fluidsynth/src/fluid_list.h b/libs/fluidsynth/src/fluid_list.h new file mode 100644 index 0000000000..bdc32918b8 --- /dev/null +++ b/libs/fluidsynth/src/fluid_list.h @@ -0,0 +1,62 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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)(void* a, 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_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 (void *a, void *b); + +#endif /* _FLUID_LIST_H */ diff --git a/libs/fluidsynth/src/fluid_midi.c b/libs/fluidsynth/src/fluid_midi.c new file mode 100644 index 0000000000..1ee3dd24cb --- /dev/null +++ b/libs/fluidsynth/src/fluid_midi.c @@ -0,0 +1,1942 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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); + +/* 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. + */ +static char* fluid_file_read_full(fluid_file fp, size_t* length); +#define READ_FULL_INITIAL_BUFLEN 1024 + + +/*************************************************************** + * + * MIDIFILE + */ + +/** + * 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; + + 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 = 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 %d bytes", 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 %d bytes; expected %d", n, + 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) +{ + if (mf == NULL) { + return; + } + FLUID_FREE(mf); + return; +} + +/* + * Gets the next byte in a MIDI file, taking into account previous running status. + * + * returns FLUID_FAILED 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 FLUID_FAILED; + } + 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[15]; + if (fluid_midi_file_read(mf, mthd, 14) != 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 ((mthd[12]) < 0) { + mf->uses_smpte = 1; + mf->smpte_fps = -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 = (mthd[12] << 8) | (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) +{ + int i; + int len = (int) FLUID_STRLEN(s); + for (i = 0; i < len; i++) { + if (!fluid_isascii(s[i])) { + return 0; + } + } + return 1; +} + +/* + * fluid_getlength + */ +long +fluid_getlength(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, + "An non-ascii track header found, corrupt file"); + return FLUID_FAILED; + + } else if (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) + fluid_midi_file_skip(mf, mf->tracklen - mf->trackpos); + + fluid_player_add_track(player, track); + + } 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: + 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 + * @return Always returns #FLUID_OK + */ +int +delete_fluid_midi_event(fluid_midi_event_t *evt) +{ + fluid_midi_event_t *temp; + + while (evt) { + temp = evt->next; + + /* Dynamic SYSEX event? - free (param2 indicates if dynamic) */ + if (evt->type == MIDI_SYSEX && evt->paramptr && evt->param2) + FLUID_FREE (evt->paramptr); + + FLUID_FREE(evt); + evt = temp; + } + return FLUID_OK; +} + +/** + * 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(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(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(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(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(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(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(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(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 + * @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 + * + * NOTE: Unlike the other event assignment functions, this one sets evt->type. + */ +int +fluid_midi_event_set_sysex(fluid_midi_event_t *evt, void *data, int size, int dynamic) +{ + evt->type = MIDI_SYSEX; + evt->paramptr = data; + evt->param1 = size; + evt->param2 = dynamic; + return FLUID_OK; +} + +/****************************************************** + * + * 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 + */ +int +delete_fluid_track(fluid_track_t *track) +{ + if (track->name != NULL) { + FLUID_FREE(track->name); + } + if (track->first != NULL) { + delete_fluid_midi_event(track->first); + } + FLUID_FREE(track); + return FLUID_OK; +} + +/* + * fluid_track_set_name + */ +int +fluid_track_set_name(fluid_track_t *track, char *name) +{ + int 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_name + */ +char * +fluid_track_get_name(fluid_track_t *track) +{ + return track->name; +} + +/* + * 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; +} + +#if 0 +/* + * fluid_track_count_events + */ +static int +fluid_track_count_events(fluid_track_t *track, int *on, int *off) +{ + fluid_midi_event_t *evt = track->first; + while (evt != NULL) { + if (evt->type == NOTE_ON) { + (*on)++; + } else if (evt->type == NOTE_OFF) { + (*off)++; + } + evt = evt->next; + } + return FLUID_OK; +} +#endif + +/* + * 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_first_event + */ +fluid_midi_event_t * +fluid_track_first_event(fluid_track_t *track) +{ + track->cur = track->first; + return track->cur; +} + +/* + * 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 + */ +int +fluid_track_send_events(fluid_track_t *track, + fluid_synth_t *synth, + fluid_player_t *player, + unsigned int ticks) +{ + int status = FLUID_OK; + fluid_midi_event_t *event; + + while (1) { + + event = track->cur; + if (event == NULL) { + return status; + } + + /* 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 status; + } + + track->ticks += event->dtime; + + if (!player || event->type == MIDI_EOT) { + } + else if (event->type == MIDI_SET_TEMPO) { + fluid_player_set_midi_tempo(player, event->param1); + } + else { + if (player->playback_callback) + player->playback_callback(player->playback_userdata, event); + } + + fluid_track_next_event(track); + + } + return status; +} + +/****************************************************** + * + * fluid_player + */ + +/** + * 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; + } + player->status = FLUID_PLAYER_READY; + 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; + player->send_program_change = 1; + player->miditempo = 480000; + player->deltatime = 4.0; + player->cur_msec = 0; + player->cur_ticks = 0; + fluid_player_set_playback_callback(player, fluid_synth_handle_midi_event, synth); + + player->use_system_timer = fluid_settings_str_equal(synth->settings, + "player.timing-source", "system"); + + fluid_settings_getint(synth->settings, "player.reset-synth", &i); + player->reset_synth_between_songs = i; + + return player; +} + +/** + * Delete a MIDI player instance. + * @param player MIDI player instance + * @return Always returns #FLUID_OK + */ +int +delete_fluid_player(fluid_player_t *player) +{ + fluid_list_t *q; + fluid_playlist_item* pi; + + if (player == NULL) { + return FLUID_OK; + } + fluid_player_stop(player); + fluid_player_reset(player); + + 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); + return FLUID_OK; +} + +/** + * 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, + NULL, NULL); + 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, NULL, NULL); +} + + +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; + } + } + /* player->current_file = NULL; */ + /* player->status = FLUID_PLAYER_READY; */ + /* player->loop = 1; */ + player->ntracks = 0; + player->division = 0; + player->send_program_change = 1; + player->miditempo = 480000; + 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; + } +} + +/* + * fluid_player_count_tracks + */ +int +fluid_player_count_tracks(fluid_player_t *player) +{ + return player->ntracks; +} + +/* + * fluid_player_get_track + */ +fluid_track_t * +fluid_player_get_track(fluid_player_t *player, int i) +{ + if ((i >= 0) && (i < MAX_NUMBER_OF_TRACKS)) { + return player->track[i]; + } else { + return NULL; + } +} + +/** + * Change the MIDI callback function. This is usually set to + * fluid_synth_handle_midi_event, but can optionally be changed + * to a user-defined function instead, for intercepting all MIDI + * messages sent to the synth. You can also use a midi router as + * the callback function to modify the MIDI messages before sending + * them to the synth. + * @param player MIDI player instance + * @param handler Pointer to callback function + * @param handler_data Parameter sent to the callback function + * @returns FLUID_OK + * @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 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); + if (buffer == NULL) + { + FLUID_FCLOSE(fp); + return FLUID_FAILED; + } + buffer_owned = 1; + FLUID_FCLOSE(fp); + } + 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_set_midi_tempo(player, player->miditempo); // 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; +} + +static 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; + } +} + +static 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 */ + 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; + + if (player->reset_synth_between_songs) { + fluid_synth_system_reset(player->synth); + } + + 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_player_t *player; + fluid_synth_t *synth; + player = (fluid_player_t *) data; + synth = player->synth; + + loadnextfile = player->currentfile == NULL ? 1 : 0; + do { + if (loadnextfile) { + loadnextfile = 0; + fluid_player_playlist_load(player, msec); + if (player->currentfile == NULL) { + return 0; + } + } + + player->cur_msec = msec; + player->cur_ticks = (player->start_ticks + + (int) ((double) (player->cur_msec - player->start_msec) + / player->deltatime)); + + for (i = 0; i < player->ntracks; i++) { + if (!fluid_track_eot(player->track[i])) { + status = FLUID_PLAYER_PLAYING; + if (fluid_track_send_events(player->track[i], synth, player, + player->cur_ticks) != FLUID_OK) { + /* */ + } + } + } + + if (status == FLUID_PLAYER_DONE) { + FLUID_LOG(FLUID_DBG, "%s: %d: Duration=%.3f sec", __FILE__, + __LINE__, (msec - player->begin_msec) / 1000.0); + loadnextfile = 1; + } + } while (loadnextfile); + + player->status = 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 + */ +int +fluid_player_play(fluid_player_t *player) +{ + if (player->status == FLUID_PLAYER_PLAYING) { + return FLUID_OK; + } + + if (player->playlist == NULL) { + return FLUID_OK; + } + + player->status = FLUID_PLAYER_PLAYING; + + if (player->use_system_timer) { + player->system_timer = new_fluid_timer((int) player->deltatime, + fluid_player_callback, (void *) player, TRUE, FALSE, TRUE); + if (player->system_timer == NULL) { + return FLUID_FAILED; + } + } else { + player->sample_timer = new_fluid_sample_timer(player->synth, + fluid_player_callback, (void *) player); + + if (player->sample_timer == NULL) { + return FLUID_FAILED; + } + } + return FLUID_OK; +} + +/** + * Stops a MIDI player. + * @param player MIDI player instance + * @return Always returns #FLUID_OK + */ +int +fluid_player_stop(fluid_player_t *player) +{ + if (player->system_timer != NULL) { + delete_fluid_timer(player->system_timer); + } + if (player->sample_timer != NULL) { + delete_fluid_sample_timer(player->synth, player->sample_timer); + } + player->status = FLUID_PLAYER_DONE; + player->sample_timer = NULL; + player->system_timer = NULL; + 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 player->status; +} + +/** + * Enable looping of a MIDI player + * @param player MIDI player instance + * @param loop Times left to loop the playlist. -1 means loop infinitely. + * @return Always returns #FLUID_OK + * @since 1.1.0 + * + * For example, if you want to loop the playlist twice, set loop to 2 + * and call this function before you start the player. + */ +int fluid_player_set_loop(fluid_player_t *player, int loop) +{ + player->loop = loop; + 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 + */ +int fluid_player_set_midi_tempo(fluid_player_t *player, int tempo) +{ + player->miditempo = tempo; + player->deltatime = (double) tempo / player->division / 1000.0; /* in milliseconds */ + 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); + + 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 + */ +int +fluid_player_set_bpm(fluid_player_t *player, int bpm) +{ + return fluid_player_set_midi_tempo(player, (int) ((double) 60 * 1e6 / bpm)); +} + +/** + * Wait for a MIDI player to terminate (when done playing). + * @param player MIDI player instance + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + */ +int +fluid_player_join(fluid_player_t *player) +{ + if (player->system_timer) { + return fluid_timer_join(player->system_timer); + } else if (player->sample_timer) { + /* Busy-wait loop, since there's no thread to wait for... */ + while (player->status != FLUID_PLAYER_DONE) { +#if defined(WIN32) + Sleep(10); +#else + usleep(10000); +#endif + } + } + return FLUID_OK; +} + +/************************************************************************ + * 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 + */ +int +delete_fluid_midi_parser(fluid_midi_parser_t *parser) +{ + FLUID_FREE(parser); + return FLUID_OK; +} + +/** + * 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. + */ +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) { + if (c == MIDI_SYSTEM_RESET) { + parser->event.type = c; + parser->status = 0; /* clear the status */ + return &parser->event; + } + + return NULL; + } + + /* 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->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/fluid_midi.h b/libs/fluidsynth/src/fluid_midi.h new file mode 100644 index 0000000000..90fcef7c91 --- /dev/null +++ b/libs/fluidsynth/src/fluid_midi.h @@ -0,0 +1,382 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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); +int 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 + +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 = 0x45, + 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_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_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 */ + +/** + * 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 resonse (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 */ + +enum fluid_driver_status +{ + FLUID_MIDI_READY, + FLUID_MIDI_LISTENING, + FLUID_MIDI_DONE +}; + +/*************************************************************** + * + * TYPE DEFINITIONS & FUNCTION DECLARATIONS + */ + +/* From ctype.h */ +#define fluid_isascii(c) (((c) & ~0x7f) == 0) + + + +/* + * 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; + +fluid_track_t* new_fluid_track(int num); +int delete_fluid_track(fluid_track_t* track); +int fluid_track_set_name(fluid_track_t* track, char* name); +char* fluid_track_get_name(fluid_track_t* track); +int fluid_track_add_event(fluid_track_t* track, fluid_midi_event_t* evt); +fluid_midi_event_t* fluid_track_first_event(fluid_track_t* track); +fluid_midi_event_t* fluid_track_next_event(fluid_track_t* track); +int fluid_track_get_duration(fluid_track_t* track); +int fluid_track_reset(fluid_track_t* track); + +int fluid_track_send_events(fluid_track_t* track, + fluid_synth_t* synth, + fluid_player_t* player, + unsigned int ticks); + +#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; + +/* + * fluid_player + */ +struct _fluid_player_t { + int status; + 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 send_program_change; /* should we ignore the program changes? */ + 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. */ + int start_ticks; /* the number of tempo ticks passed at the last tempo change */ + int cur_ticks; /* the number of tempo ticks passed */ + int begin_msec; /* the time (msec) of the beginning of the file */ + int start_msec; /* the start time of the last tempo change */ + int cur_msec; /* the current time */ + int miditempo; /* as indicated by MIDI SetTempo: n 24th of a usec per midi-clock. bravo! */ + double deltatime; /* milliseconds per midi tick. depends on set-tempo */ + 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 */ +}; + +int fluid_player_add_track(fluid_player_t* player, fluid_track_t* track); +int fluid_player_callback(void* data, unsigned int msec); +int fluid_player_count_tracks(fluid_player_t* player); +fluid_track_t* fluid_player_get_track(fluid_player_t* player, int i); +int fluid_player_reset(fluid_player_t* player); +int fluid_player_load(fluid_player_t* player, fluid_playlist_item *item); + +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; + +fluid_midi_file* new_fluid_midi_file(const char* buffer, size_t length); +void delete_fluid_midi_file(fluid_midi_file* mf); +int fluid_midi_file_read_mthd(fluid_midi_file* midifile); +int fluid_midi_file_load_tracks(fluid_midi_file* midifile, fluid_player_t* player); +int fluid_midi_file_read_track(fluid_midi_file* mf, fluid_player_t* player, int num); +int fluid_midi_file_read_event(fluid_midi_file* mf, fluid_track_t* track); +int fluid_midi_file_read_varlen(fluid_midi_file* mf); +int fluid_midi_file_getc(fluid_midi_file* mf); +int fluid_midi_file_push(fluid_midi_file* mf, int c); +int fluid_midi_file_read(fluid_midi_file* mf, void* buf, int len); +int fluid_midi_file_skip(fluid_midi_file* mf, int len); +int fluid_midi_file_eof(fluid_midi_file* mf); +int fluid_midi_file_read_tracklen(fluid_midi_file* mf); +int fluid_midi_file_eot(fluid_midi_file* mf); +int fluid_midi_file_get_division(fluid_midi_file* midifile); + + +#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. */ +}; + +int fluid_isasciistring(char* s); +long fluid_getlength(unsigned char *s); + + +#endif /* _FLUID_MIDI_H */ diff --git a/libs/fluidsynth/src/fluid_mod.c b/libs/fluidsynth/src/fluid_mod.c new file mode 100644 index 0000000000..5931aa52a6 --- /dev/null +++ b/libs/fluidsynth/src/fluid_mod.c @@ -0,0 +1,488 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +/* + * fluid_mod_clone + */ +void +fluid_mod_clone(fluid_mod_t* mod, 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 Modulator + * @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 Modulator + * @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 Modulator + * @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 Modulator + * @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 Modulator + * @return The primary source value (#fluid_mod_src or a MIDI CC controller value). + */ +int +fluid_mod_get_source1(fluid_mod_t* mod) +{ + return mod->src1; +} + +/** + * Get primary source flags from a modulator. + * @param mod Modulator + * @return The primary source flags (#fluid_mod_flags). + */ +int +fluid_mod_get_flags1(fluid_mod_t* mod) +{ + return mod->flags1; +} + +/** + * Get the secondary source value from a modulator. + * @param mod Modulator + * @return The secondary source value (#fluid_mod_src or a MIDI CC controller value). + */ +int +fluid_mod_get_source2(fluid_mod_t* mod) +{ + return mod->src2; +} + +/** + * Get secondary source flags from a modulator. + * @param mod Modulator + * @return The secondary source flags (#fluid_mod_flags). + */ +int +fluid_mod_get_flags2(fluid_mod_t* mod) +{ + return mod->flags2; +} + +/** + * Get destination effect from a modulator. + * @param mod Modulator + * @return Destination generator (#fluid_gen_type) + */ +int +fluid_mod_get_dest(fluid_mod_t* mod) +{ + return mod->dest; +} + +/** + * Get the scale amount from a modulator. + * @param mod Modulator + * @return Scale amount + */ +double +fluid_mod_get_amount(fluid_mod_t* mod) +{ + return (fluid_real_t) mod->amount; +} + + +/* + * fluid_mod_get_value + */ +fluid_real_t +fluid_mod_get_value(fluid_mod_t* mod, fluid_channel_t* chan, fluid_voice_t* voice) +{ + fluid_real_t v1 = 0.0, v2 = 1.0; + fluid_real_t range1 = 127.0, range2 = 127.0; + + if (chan == NULL) { + return 0.0f; + } + + /* '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 ((mod->src2 == FLUID_MOD_VELOCITY) && + (mod->src1 == FLUID_MOD_VELOCITY) && + (mod->flags1 == (FLUID_MOD_GC | FLUID_MOD_UNIPOLAR + | FLUID_MOD_NEGATIVE | FLUID_MOD_LINEAR)) && + (mod->flags2 == (FLUID_MOD_GC | FLUID_MOD_UNIPOLAR + | FLUID_MOD_POSITIVE | FLUID_MOD_SWITCH)) && + (mod->dest == GEN_FILTERFC)) { +// 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) { + if (mod->flags1 & FLUID_MOD_CC) { + v1 = fluid_channel_get_cc(chan, mod->src1); + } else { /* source 1 is one of the direct controllers */ + switch (mod->src1) { + case FLUID_MOD_NONE: /* SF 2.01 8.2.1 item 0: src enum=0 => value is 1 */ + v1 = range1; + break; + case FLUID_MOD_VELOCITY: + v1 = voice->vel; + break; + case FLUID_MOD_KEY: + v1 = voice->key; + break; + case FLUID_MOD_KEYPRESSURE: + v1 = fluid_channel_get_key_pressure (chan); + break; + case FLUID_MOD_CHANNELPRESSURE: + v1 = fluid_channel_get_channel_pressure (chan); + break; + case FLUID_MOD_PITCHWHEEL: + v1 = fluid_channel_get_pitch_bend (chan); + range1 = 0x4000; + break; + case FLUID_MOD_PITCHWHEELSENS: + v1 = fluid_channel_get_pitch_wheel_sensitivity (chan); + break; + default: + v1 = 0.0; + } + } + + /* transform the input value */ + switch (mod->flags1 & 0x0f) { + case 0: /* linear, unipolar, positive */ + v1 /= range1; + break; + case 1: /* linear, unipolar, negative */ + v1 = 1.0f - v1 / range1; + break; + case 2: /* linear, bipolar, positive */ + v1 = -1.0f + 2.0f * v1 / range1; + break; + case 3: /* linear, bipolar, negative */ + v1 = 1.0f - 2.0f * v1 / range1; + break; + case 4: /* concave, unipolar, positive */ + v1 = fluid_concave(v1); + break; + case 5: /* concave, unipolar, negative */ + v1 = fluid_concave(127 - v1); + break; + case 6: /* concave, bipolar, positive */ + v1 = (v1 > 64)? fluid_concave(2 * (v1 - 64)) : -fluid_concave(2 * (64 - v1)); + break; + case 7: /* concave, bipolar, negative */ + v1 = (v1 > 64)? -fluid_concave(2 * (v1 - 64)) : fluid_concave(2 * (64 - v1)); + break; + case 8: /* convex, unipolar, positive */ + v1 = fluid_convex(v1); + break; + case 9: /* convex, unipolar, negative */ + v1 = fluid_convex(127 - v1); + break; + case 10: /* convex, bipolar, positive */ + v1 = (v1 > 64)? fluid_convex(2 * (v1 - 64)) : -fluid_convex(2 * (64 - v1)); + break; + case 11: /* convex, bipolar, negative */ + v1 = (v1 > 64)? -fluid_convex(2 * (v1 - 64)) : fluid_convex(2 * (64 - v1)); + break; + case 12: /* switch, unipolar, positive */ + v1 = (v1 >= 64)? 1.0f : 0.0f; + break; + case 13: /* switch, unipolar, negative */ + v1 = (v1 >= 64)? 0.0f : 1.0f; + break; + case 14: /* switch, bipolar, positive */ + v1 = (v1 >= 64)? 1.0f : -1.0f; + break; + case 15: /* switch, bipolar, negative */ + v1 = (v1 >= 64)? -1.0f : 1.0f; + break; + } + } 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) { + if (mod->flags2 & FLUID_MOD_CC) { + v2 = fluid_channel_get_cc(chan, mod->src2); + } else { + switch (mod->src2) { + case FLUID_MOD_NONE: /* SF 2.01 8.2.1 item 0: src enum=0 => value is 1 */ + v2 = range2; + break; + case FLUID_MOD_VELOCITY: + v2 = voice->vel; + break; + case FLUID_MOD_KEY: + v2 = voice->key; + break; + case FLUID_MOD_KEYPRESSURE: + v2 = fluid_channel_get_key_pressure (chan); + break; + case FLUID_MOD_CHANNELPRESSURE: + v2 = fluid_channel_get_channel_pressure (chan); + break; + case FLUID_MOD_PITCHWHEEL: + v2 = fluid_channel_get_pitch_bend (chan); + break; + case FLUID_MOD_PITCHWHEELSENS: + v2 = fluid_channel_get_pitch_wheel_sensitivity (chan); + break; + default: + v1 = 0.0f; + } + } + + /* transform the second input value */ + switch (mod->flags2 & 0x0f) { + case 0: /* linear, unipolar, positive */ + v2 /= range2; + break; + case 1: /* linear, unipolar, negative */ + v2 = 1.0f - v2 / range2; + break; + case 2: /* linear, bipolar, positive */ + v2 = -1.0f + 2.0f * v2 / range2; + break; + case 3: /* linear, bipolar, negative */ + v2 = -1.0f + 2.0f * v2 / range2; + break; + case 4: /* concave, unipolar, positive */ + v2 = fluid_concave(v2); + break; + case 5: /* concave, unipolar, negative */ + v2 = fluid_concave(127 - v2); + break; + case 6: /* concave, bipolar, positive */ + v2 = (v2 > 64)? fluid_concave(2 * (v2 - 64)) : -fluid_concave(2 * (64 - v2)); + break; + case 7: /* concave, bipolar, negative */ + v2 = (v2 > 64)? -fluid_concave(2 * (v2 - 64)) : fluid_concave(2 * (64 - v2)); + break; + case 8: /* convex, unipolar, positive */ + v2 = fluid_convex(v2); + break; + case 9: /* convex, unipolar, negative */ + v2 = 1.0f - fluid_convex(v2); + break; + case 10: /* convex, bipolar, positive */ + v2 = (v2 > 64)? -fluid_convex(2 * (v2 - 64)) : fluid_convex(2 * (64 - v2)); + break; + case 11: /* convex, bipolar, negative */ + v2 = (v2 > 64)? -fluid_convex(2 * (v2 - 64)) : fluid_convex(2 * (64 - v2)); + break; + case 12: /* switch, unipolar, positive */ + v2 = (v2 >= 64)? 1.0f : 0.0f; + break; + case 13: /* switch, unipolar, negative */ + v2 = (v2 >= 64)? 0.0f : 1.0f; + break; + case 14: /* switch, bipolar, positive */ + v2 = (v2 >= 64)? 1.0f : -1.0f; + break; + case 15: /* switch, bipolar, negative */ + v2 = (v2 >= 64)? -1.0f : 1.0f; + break; + } + } 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* +fluid_mod_new() +{ + 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 +fluid_mod_delete (fluid_mod_t *mod) +{ + FLUID_FREE(mod); +} + +/** + * 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 (fluid_mod_t *mod1, 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; +} + +/* debug function: Prints the contents of a modulator */ +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_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_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); +}; + + diff --git a/libs/fluidsynth/src/fluid_mod.h b/libs/fluidsynth/src/fluid_mod.h new file mode 100644 index 0000000000..81c9f76c70 --- /dev/null +++ b/libs/fluidsynth/src/fluid_mod.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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +void fluid_mod_clone(fluid_mod_t* mod, fluid_mod_t* src); +fluid_real_t fluid_mod_get_value(fluid_mod_t* mod, fluid_channel_t* chan, fluid_voice_t* voice); +void fluid_dump_modulator(fluid_mod_t * mod); + +#define fluid_mod_has_source(mod,cc,ctrl) \ +( ((((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))))) + +#define fluid_mod_has_dest(mod,gen) ((mod)->dest == gen) + + +#endif /* _FLUID_MOD_H */ diff --git a/libs/fluidsynth/src/fluid_phase.h b/libs/fluidsynth/src/fluid_phase.h new file mode 100644 index 0000000000..15f2fa7550 --- /dev/null +++ b/libs/fluidsynth/src/fluid_phase.h @@ -0,0 +1,117 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +/* + * phase + */ + +#define FLUID_INTERP_BITS 8 +#define FLUID_INTERP_BITS_MASK 0xff000000 +#define FLUID_INTERP_BITS_SHIFT 24 +#define FLUID_INTERP_MAX 256 + +#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 unsigned long long 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) = ((unsigned long long)(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) = (((unsigned long long)(b)) << 32) \ + | (uint32) (((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) \ + ((((unsigned long long)(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)((_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) -= (unsigned long long)(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/fluid_rev.c b/libs/fluidsynth/src/fluid_rev.c new file mode 100644 index 0000000000..166007da3f --- /dev/null +++ b/libs/fluidsynth/src/fluid_rev.c @@ -0,0 +1,544 @@ +/* + + Freeverb + + Written by Jezar at Dreampoint, June 2000 + http://www.dreampoint.co.uk + This code is public domain + + Translated to C by Peter Hanappe, Mai 2001 +*/ + +#include "fluid_rev.h" + +/*************************************************************** + * + * REVERB + */ + +/* Denormalising: + * + * 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. + */ + +# if defined(WITH_FLOATX) +# define zap_almost_zero(sample) (((*(unsigned int*)&(sample))&0x7f800000) < 0x08000000)?0.0f:(sample) +# else +/* 1e-20 was chosen as an arbitrary (small) threshold. */ +#define zap_almost_zero(sample) fabs(sample)<1e-10 ? 0 : sample; +#endif + +/* 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. + */ + + +//#define DC_OFFSET 0 +#define DC_OFFSET 1e-8 +//#define DC_OFFSET 0.001f +typedef struct _fluid_allpass fluid_allpass; +typedef struct _fluid_comb fluid_comb; + +struct _fluid_allpass { + fluid_real_t feedback; + fluid_real_t *buffer; + int bufsize; + int bufidx; +}; + +void fluid_allpass_init(fluid_allpass* allpass); +void fluid_allpass_setfeedback(fluid_allpass* allpass, fluid_real_t val); +fluid_real_t fluid_allpass_getfeedback(fluid_allpass* allpass); + +static void +fluid_allpass_setbuffer(fluid_allpass* allpass, int size) +{ + allpass->bufidx = 0; + allpass->buffer = FLUID_ARRAY(fluid_real_t,size); + allpass->bufsize = size; +} + +static void +fluid_allpass_release(fluid_allpass* allpass) +{ + FLUID_FREE(allpass->buffer); +} + +void +fluid_allpass_init(fluid_allpass* allpass) +{ + int i; + int len = allpass->bufsize; + fluid_real_t* buf = allpass->buffer; + for (i = 0; i < len; i++) { + buf[i] = DC_OFFSET; /* this is not 100 % correct. */ + } +} + +void +fluid_allpass_setfeedback(fluid_allpass* allpass, fluid_real_t val) +{ + allpass->feedback = val; +} + +fluid_real_t +fluid_allpass_getfeedback(fluid_allpass* allpass) +{ + return allpass->feedback; +} + +#define fluid_allpass_process(_allpass, _input) \ +{ \ + fluid_real_t output; \ + fluid_real_t bufout; \ + bufout = _allpass.buffer[_allpass.bufidx]; \ + output = bufout-_input; \ + _allpass.buffer[_allpass.bufidx] = _input + (bufout * _allpass.feedback); \ + if (++_allpass.bufidx >= _allpass.bufsize) { \ + _allpass.bufidx = 0; \ + } \ + _input = output; \ +} + +/* fluid_real_t fluid_allpass_process(fluid_allpass* allpass, fluid_real_t input) */ +/* { */ +/* fluid_real_t output; */ +/* fluid_real_t bufout; */ +/* bufout = allpass->buffer[allpass->bufidx]; */ +/* undenormalise(bufout); */ +/* output = -input + bufout; */ +/* allpass->buffer[allpass->bufidx] = input + (bufout * allpass->feedback); */ +/* if (++allpass->bufidx >= allpass->bufsize) { */ +/* allpass->bufidx = 0; */ +/* } */ +/* return output; */ +/* } */ + +struct _fluid_comb { + fluid_real_t feedback; + fluid_real_t filterstore; + fluid_real_t damp1; + fluid_real_t damp2; + fluid_real_t *buffer; + int bufsize; + int bufidx; +}; + +void fluid_comb_setbuffer(fluid_comb* comb, int size); +void fluid_comb_release(fluid_comb* comb); +void fluid_comb_init(fluid_comb* comb); +void fluid_comb_setdamp(fluid_comb* comb, fluid_real_t val); +fluid_real_t fluid_comb_getdamp(fluid_comb* comb); +void fluid_comb_setfeedback(fluid_comb* comb, fluid_real_t val); +fluid_real_t fluid_comb_getfeedback(fluid_comb* comb); + +void +fluid_comb_setbuffer(fluid_comb* comb, int size) +{ + comb->filterstore = 0; + comb->bufidx = 0; + comb->buffer = FLUID_ARRAY(fluid_real_t,size); + comb->bufsize = size; +} + +void +fluid_comb_release(fluid_comb* comb) +{ + FLUID_FREE(comb->buffer); +} + +void +fluid_comb_init(fluid_comb* comb) +{ + int i; + fluid_real_t* buf = comb->buffer; + int len = comb->bufsize; + for (i = 0; i < len; i++) { + buf[i] = DC_OFFSET; /* This is not 100 % correct. */ + } +} + +void +fluid_comb_setdamp(fluid_comb* comb, fluid_real_t val) +{ + comb->damp1 = val; + comb->damp2 = 1 - val; +} + +fluid_real_t +fluid_comb_getdamp(fluid_comb* comb) +{ + return comb->damp1; +} + +void +fluid_comb_setfeedback(fluid_comb* comb, fluid_real_t val) +{ + comb->feedback = val; +} + +fluid_real_t +fluid_comb_getfeedback(fluid_comb* comb) +{ + return comb->feedback; +} + +#define fluid_comb_process(_comb, _input, _output) \ +{ \ + fluid_real_t _tmp = _comb.buffer[_comb.bufidx]; \ + _comb.filterstore = (_tmp * _comb.damp2) + (_comb.filterstore * _comb.damp1); \ + _comb.buffer[_comb.bufidx] = _input + (_comb.filterstore * _comb.feedback); \ + if (++_comb.bufidx >= _comb.bufsize) { \ + _comb.bufidx = 0; \ + } \ + _output += _tmp; \ +} + +/* fluid_real_t fluid_comb_process(fluid_comb* comb, fluid_real_t input) */ +/* { */ +/* fluid_real_t output; */ + +/* output = comb->buffer[comb->bufidx]; */ +/* undenormalise(output); */ +/* comb->filterstore = (output * comb->damp2) + (comb->filterstore * comb->damp1); */ +/* undenormalise(comb->filterstore); */ +/* comb->buffer[comb->bufidx] = input + (comb->filterstore * comb->feedback); */ +/* if (++comb->bufidx >= comb->bufsize) { */ +/* comb->bufidx = 0; */ +/* } */ + +/* return output; */ +/* } */ + +#define numcombs 8 +#define numallpasses 4 +#define fixedgain 0.015f +#define scalewet 3.0f +#define scaledamp 1.0f +#define scaleroom 0.28f +#define offsetroom 0.7f +#define initialroom 0.5f +#define initialdamp 0.2f +#define initialwet 1 +#define initialdry 0 +#define initialwidth 1 +#define stereospread 23 + +/* + These values assume 44.1KHz sample rate + they will probably be OK for 48KHz sample rate + but would need scaling for 96KHz (or other) sample rates. + The values were obtained by listening tests. +*/ +#define combtuningL1 1116 +#define combtuningR1 (1116 + stereospread) +#define combtuningL2 1188 +#define combtuningR2 (1188 + stereospread) +#define combtuningL3 1277 +#define combtuningR3 (1277 + stereospread) +#define combtuningL4 1356 +#define combtuningR4 (1356 + stereospread) +#define combtuningL5 1422 +#define combtuningR5 (1422 + stereospread) +#define combtuningL6 1491 +#define combtuningR6 (1491 + stereospread) +#define combtuningL7 1557 +#define combtuningR7 (1557 + stereospread) +#define combtuningL8 1617 +#define combtuningR8 (1617 + stereospread) +#define allpasstuningL1 556 +#define allpasstuningR1 (556 + stereospread) +#define allpasstuningL2 441 +#define allpasstuningR2 (441 + stereospread) +#define allpasstuningL3 341 +#define allpasstuningR3 (341 + stereospread) +#define allpasstuningL4 225 +#define allpasstuningR4 (225 + stereospread) + +struct _fluid_revmodel_t { + fluid_real_t roomsize; + fluid_real_t damp; + fluid_real_t wet, wet1, wet2; + fluid_real_t width; + fluid_real_t gain; + /* + The following are all declared inline + to remove the need for dynamic allocation + with its subsequent error-checking messiness + */ + /* Comb filters */ + fluid_comb combL[numcombs]; + fluid_comb combR[numcombs]; + /* Allpass filters */ + fluid_allpass allpassL[numallpasses]; + fluid_allpass allpassR[numallpasses]; +}; + +static void fluid_revmodel_update(fluid_revmodel_t* rev); +static void fluid_revmodel_init(fluid_revmodel_t* rev); +void fluid_set_revmodel_buffers(fluid_revmodel_t* rev, fluid_real_t sample_rate); + +fluid_revmodel_t* +new_fluid_revmodel(fluid_real_t sample_rate) +{ + fluid_revmodel_t* rev; + rev = FLUID_NEW(fluid_revmodel_t); + if (rev == NULL) { + return NULL; + } + + fluid_set_revmodel_buffers(rev, sample_rate); + + /* Set default values */ + fluid_allpass_setfeedback(&rev->allpassL[0], 0.5f); + fluid_allpass_setfeedback(&rev->allpassR[0], 0.5f); + fluid_allpass_setfeedback(&rev->allpassL[1], 0.5f); + fluid_allpass_setfeedback(&rev->allpassR[1], 0.5f); + fluid_allpass_setfeedback(&rev->allpassL[2], 0.5f); + fluid_allpass_setfeedback(&rev->allpassR[2], 0.5f); + fluid_allpass_setfeedback(&rev->allpassL[3], 0.5f); + fluid_allpass_setfeedback(&rev->allpassR[3], 0.5f); + + rev->gain = fixedgain; + fluid_revmodel_set(rev,FLUID_REVMODEL_SET_ALL,initialroom,initialdamp,initialwidth,initialwet); + + return rev; +} + +void +delete_fluid_revmodel(fluid_revmodel_t* rev) +{ + int i; + for (i = 0; i < numcombs;i++) { + fluid_comb_release(&rev->combL[i]); + fluid_comb_release(&rev->combR[i]); + } + for (i = 0; i < numallpasses; i++) { + fluid_allpass_release(&rev->allpassL[i]); + fluid_allpass_release(&rev->allpassR[i]); + } + + FLUID_FREE(rev); +} + +void +fluid_set_revmodel_buffers(fluid_revmodel_t* rev, fluid_real_t sample_rate) { + + float srfactor = sample_rate/44100.0f; + + fluid_comb_setbuffer(&rev->combL[0], combtuningL1*srfactor); + fluid_comb_setbuffer(&rev->combR[0], combtuningR1*srfactor); + fluid_comb_setbuffer(&rev->combL[1], combtuningL2*srfactor); + fluid_comb_setbuffer(&rev->combR[1], combtuningR2*srfactor); + fluid_comb_setbuffer(&rev->combL[2], combtuningL3*srfactor); + fluid_comb_setbuffer(&rev->combR[2], combtuningR3*srfactor); + fluid_comb_setbuffer(&rev->combL[3], combtuningL4*srfactor); + fluid_comb_setbuffer(&rev->combR[3], combtuningR4*srfactor); + fluid_comb_setbuffer(&rev->combL[4], combtuningL5*srfactor); + fluid_comb_setbuffer(&rev->combR[4], combtuningR5*srfactor); + fluid_comb_setbuffer(&rev->combL[5], combtuningL6*srfactor); + fluid_comb_setbuffer(&rev->combR[5], combtuningR6*srfactor); + fluid_comb_setbuffer(&rev->combL[6], combtuningL7*srfactor); + fluid_comb_setbuffer(&rev->combR[6], combtuningR7*srfactor); + fluid_comb_setbuffer(&rev->combL[7], combtuningL8*srfactor); + fluid_comb_setbuffer(&rev->combR[7], combtuningR8*srfactor); + fluid_allpass_setbuffer(&rev->allpassL[0], allpasstuningL1*srfactor); + fluid_allpass_setbuffer(&rev->allpassR[0], allpasstuningR1*srfactor); + fluid_allpass_setbuffer(&rev->allpassL[1], allpasstuningL2*srfactor); + fluid_allpass_setbuffer(&rev->allpassR[1], allpasstuningR2*srfactor); + fluid_allpass_setbuffer(&rev->allpassL[2], allpasstuningL3*srfactor); + fluid_allpass_setbuffer(&rev->allpassR[2], allpasstuningR3*srfactor); + fluid_allpass_setbuffer(&rev->allpassL[3], allpasstuningL4*srfactor); + fluid_allpass_setbuffer(&rev->allpassR[3], allpasstuningR4*srfactor); + + /* Clear all buffers */ + fluid_revmodel_init(rev); +} + + +static void +fluid_revmodel_init(fluid_revmodel_t* rev) +{ + int i; + for (i = 0; i < numcombs;i++) { + fluid_comb_init(&rev->combL[i]); + fluid_comb_init(&rev->combR[i]); + } + for (i = 0; i < numallpasses; i++) { + fluid_allpass_init(&rev->allpassL[i]); + fluid_allpass_init(&rev->allpassR[i]); + } +} + +void +fluid_revmodel_reset(fluid_revmodel_t* rev) +{ + fluid_revmodel_init(rev); +} + +void +fluid_revmodel_processreplace(fluid_revmodel_t* rev, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k = 0; + fluid_real_t outL, outR, input; + + for (k = 0; k < FLUID_BUFSIZE; k++) { + + outL = outR = 0; + + /* The original Freeverb code expects a stereo signal and 'input' + * is set to the sum of the left and right input sample. Since + * this code works on a mono signal, 'input' is set to twice the + * input sample. */ + input = (2.0f * in[k] + DC_OFFSET) * rev->gain; + + /* Accumulate comb filters in parallel */ + for (i = 0; i < numcombs; i++) { + fluid_comb_process(rev->combL[i], input, outL); + fluid_comb_process(rev->combR[i], input, outR); + } + /* Feed through allpasses in series */ + for (i = 0; i < numallpasses; i++) { + fluid_allpass_process(rev->allpassL[i], outL); + fluid_allpass_process(rev->allpassR[i], outR); + } + + /* Remove the DC offset */ + outL -= DC_OFFSET; + outR -= DC_OFFSET; + + /* Calculate output REPLACING anything already there */ + left_out[k] = outL * rev->wet1 + outR * rev->wet2; + right_out[k] = outR * rev->wet1 + outL * rev->wet2; + } +} + +void +fluid_revmodel_processmix(fluid_revmodel_t* rev, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k = 0; + fluid_real_t outL, outR, input; + + for (k = 0; k < FLUID_BUFSIZE; k++) { + + outL = outR = 0; + + /* The original Freeverb code expects a stereo signal and 'input' + * is set to the sum of the left and right input sample. Since + * this code works on a mono signal, 'input' is set to twice the + * input sample. */ + input = (2.0f * in[k] + DC_OFFSET) * rev->gain; + + /* Accumulate comb filters in parallel */ + for (i = 0; i < numcombs; i++) { + fluid_comb_process(rev->combL[i], input, outL); + fluid_comb_process(rev->combR[i], input, outR); + } + /* Feed through allpasses in series */ + for (i = 0; i < numallpasses; i++) { + fluid_allpass_process(rev->allpassL[i], outL); + fluid_allpass_process(rev->allpassR[i], outR); + } + + /* Remove the DC offset */ + outL -= DC_OFFSET; + outR -= DC_OFFSET; + + /* Calculate output MIXING with anything already there */ + left_out[k] += outL * rev->wet1 + outR * rev->wet2; + right_out[k] += outR * rev->wet1 + outL * rev->wet2; + } +} + +static void +fluid_revmodel_update(fluid_revmodel_t* rev) +{ + /* Recalculate internal values after parameter change */ + int i; + + rev->wet1 = rev->wet * (rev->width / 2.0f + 0.5f); + rev->wet2 = rev->wet * ((1.0f - rev->width) / 2.0f); + + for (i = 0; i < numcombs; i++) { + fluid_comb_setfeedback(&rev->combL[i], rev->roomsize); + fluid_comb_setfeedback(&rev->combR[i], rev->roomsize); + } + + for (i = 0; i < numcombs; i++) { + fluid_comb_setdamp(&rev->combL[i], rev->damp); + fluid_comb_setdamp(&rev->combR[i], rev->damp); + } +} + +/** + * Set one or more reverb parameters. + * @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 + */ +void +fluid_revmodel_set(fluid_revmodel_t* rev, int set, float roomsize, + float damping, float width, float level) +{ + if (set & FLUID_REVMODEL_SET_ROOMSIZE) + rev->roomsize = (roomsize * scaleroom) + offsetroom; + + if (set & FLUID_REVMODEL_SET_DAMPING) + rev->damp = damping * scaledamp; + + if (set & FLUID_REVMODEL_SET_WIDTH) + rev->width = width; + + if (set & FLUID_REVMODEL_SET_LEVEL) + { + fluid_clip(level, 0.0f, 1.0f); + rev->wet = level * scalewet; + } + + fluid_revmodel_update (rev); +} + +void +fluid_revmodel_samplerate_change(fluid_revmodel_t* rev, fluid_real_t sample_rate) { + int i; + for (i = 0; i < numcombs;i++) { + fluid_comb_release(&rev->combL[i]); + fluid_comb_release(&rev->combR[i]); + } + for (i = 0; i < numallpasses; i++) { + fluid_allpass_release(&rev->allpassL[i]); + fluid_allpass_release(&rev->allpassR[i]); + } + fluid_set_revmodel_buffers(rev, sample_rate); +} diff --git a/libs/fluidsynth/src/fluid_rev.h b/libs/fluidsynth/src/fluid_rev.h new file mode 100644 index 0000000000..f977352cf5 --- /dev/null +++ b/libs/fluidsynth/src/fluid_rev.h @@ -0,0 +1,73 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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; + + +/** Flags for fluid_revmodel_set() */ +typedef enum +{ + FLUID_REVMODEL_SET_ROOMSIZE = 1 << 0, + FLUID_REVMODEL_SET_DAMPING = 1 << 1, + FLUID_REVMODEL_SET_WIDTH = 1 << 2, + FLUID_REVMODEL_SET_LEVEL = 1 << 3 +} fluid_revmodel_set_t; + +/** Value for fluid_revmodel_set() which sets all reverb parameters. */ +#define FLUID_REVMODEL_SET_ALL 0x0F + +/* + * reverb preset + */ +typedef struct _fluid_revmodel_presets_t { + 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); +void delete_fluid_revmodel(fluid_revmodel_t* rev); + +void fluid_revmodel_processmix(fluid_revmodel_t* rev, fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out); + +void fluid_revmodel_processreplace(fluid_revmodel_t* rev, 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, float roomsize, + float damping, float width, float level); + +void fluid_revmodel_samplerate_change(fluid_revmodel_t* rev, fluid_real_t sample_rate); + +#endif /* _FLUID_REV_H */ diff --git a/libs/fluidsynth/src/fluid_ringbuffer.c b/libs/fluidsynth/src/fluid_ringbuffer.c new file mode 100644 index 0000000000..f6c06dd76d --- /dev/null +++ b/libs/fluidsynth/src/fluid_ringbuffer.c @@ -0,0 +1,89 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * 2009-05-28 + */ + +#include "fluid_ringbuffer.h" +#include "fluidsynth_priv.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, int 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_FREE (queue); + FLUID_LOG (FLUID_ERR, "Out of memory"); + 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; + 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_FREE (queue->array); + FLUID_FREE (queue); +} diff --git a/libs/fluidsynth/src/fluid_ringbuffer.h b/libs/fluidsynth/src/fluid_ringbuffer.h new file mode 100644 index 0000000000..bd43f8a250 --- /dev/null +++ b/libs/fluidsynth/src/fluid_ringbuffer.h @@ -0,0 +1,128 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 */ + int count; /**< Current count of elements */ + int in; /**< Index in queue to store next pushed element */ + int out; /**< Index in queue of next popped element */ + int elementsize; /**< Size of each element */ + void* userdata; +}; + +typedef struct _fluid_ringbuffer_t fluid_ringbuffer_t; + + +fluid_ringbuffer_t *new_fluid_ringbuffer (int count, int elementsize); +void delete_fluid_ringbuffer (fluid_ringbuffer_t *queue); + +/** + * Get pointer to next input array element in queue. + * @param queue Lockless queue instance + * @param count 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/fluid_rvoice.c b/libs/fluidsynth/src/fluid_rvoice.c new file mode 100644 index 0000000000..ba8da98333 --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice.c @@ -0,0 +1,664 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +/** + * @return -1 if voice has finished, 0 if it's currently quiet, 1 otherwise + */ +static 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_atten2amp (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_atten2amp (voice->dsp.attenuation) + * fluid_cb2amp (960.0f * (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_atten2amp (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 (!voice->dsp.check_sample_sanity_flag){ + return; + } + +#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); + 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_dsp_float.c). + */ +int +fluid_rvoice_write (fluid_rvoice_t* voice, fluid_real_t *dsp_buf) +{ + int ticks = voice->envlfo.ticks; + int count; + + /******************* 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(voice, 0); + } + + voice->envlfo.ticks += FLUID_BUFSIZE; + + /******************* vol env **********************/ + + fluid_adsr_env_calc(&voice->envlfo.volenv, 1); + 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, 0); + 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; + + /******************* phase **********************/ + + /* 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 + + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch + + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch + + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_pitch) + / voice->dsp.root_pitch_hz; + + 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->dsp.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. */ + voice->dsp.dsp_buf = dsp_buf; + + switch (voice->dsp.interp_method) + { + case FLUID_INTERP_NONE: + count = fluid_rvoice_dsp_interpolate_none (&voice->dsp); + break; + case FLUID_INTERP_LINEAR: + count = fluid_rvoice_dsp_interpolate_linear (&voice->dsp); + break; + case FLUID_INTERP_4THORDER: + default: + count = fluid_rvoice_dsp_interpolate_4th_order (&voice->dsp); + break; + case FLUID_INTERP_7THORDER: + count = fluid_rvoice_dsp_interpolate_7th_order (&voice->dsp); + 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 + + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_fc); + + fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); + + return count; +} + + +static 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 data down to buffers + * + * @param buffers Destination buffer(s) + * @param dsp_buf Mono sample source + * @param samplecount Number of samples to process (no FLUID_BUFSIZE restriction) + * @param dest_bufs Array of buffers to mixdown to + * @param dest_bufcount Length of dest_bufs + */ +void +fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t* buffers, + fluid_real_t* dsp_buf, int samplecount, + fluid_real_t** dest_bufs, int dest_bufcount) +{ + int bufcount = buffers->count; + int i, dsp_i; + if (!samplecount || !bufcount || !dest_bufcount) + return; + + for (i=0; i < bufcount; i++) { + fluid_real_t* buf = get_dest_buf(buffers, i, dest_bufs, dest_bufcount); + fluid_real_t* next_buf; + fluid_real_t amp = buffers->bufs[i].amp; + if (buf == NULL || amp == 0.0f) + continue; + + /* Optimization for centered stereo samples - we can save one + multiplication per sample */ + next_buf = (i+1 >= bufcount ? NULL : get_dest_buf(buffers, i+1, dest_bufs, dest_bufcount)); + if (next_buf && buffers->bufs[i+1].amp == amp) { + for (dsp_i = 0; dsp_i < samplecount; dsp_i++) { + fluid_real_t samp = amp * dsp_buf[dsp_i]; + buf[dsp_i] += samp; + next_buf[dsp_i] += samp; + } + i++; + } + else { + for (dsp_i = 0; dsp_i < samplecount; dsp_i++) + buf[dsp_i] += amp * dsp_buf[dsp_i]; + } + } +} + +/** + * 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[bufnum].amp = 0.0f; + buffers->bufs[bufnum].mapping = i; + } + buffers->count = bufnum+1; + return FLUID_OK; +} + + +void +fluid_rvoice_buffers_set_amp(fluid_rvoice_buffers_t* buffers, + unsigned int bufnum, fluid_real_t value) +{ + if (fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) + return; + buffers->bufs[bufnum].amp = value; +} + +void +fluid_rvoice_buffers_set_mapping(fluid_rvoice_buffers_t* buffers, + unsigned int bufnum, int mapping) +{ + if (fluid_rvoice_buffers_check_bufnum(buffers, bufnum) != FLUID_OK) + return; + buffers->bufs[bufnum].mapping = mapping; +} + + +void +fluid_rvoice_reset(fluid_rvoice_t* voice) +{ + 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 */ + + /* 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); + + /* 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; +} + + +void +fluid_rvoice_noteoff(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 equievalent 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) * pow (10.0, lfo / -200); + fluid_real_t env_value = - ((-200 * log (amp) / log (10.0) - lfo) / 960.0 - 1); + fluid_clip (env_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.volenv, env_value); + } + } + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE); + fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE); +} + + +void +fluid_rvoice_set_output_rate(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->dsp.output_rate = value; +} + +void +fluid_rvoice_set_interp_method(fluid_rvoice_t* voice, int value) +{ + voice->dsp.interp_method = value; +} + +void +fluid_rvoice_set_root_pitch_hz(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->dsp.root_pitch_hz = value; +} + +void +fluid_rvoice_set_pitch(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->dsp.pitch = value; +} + + +void +fluid_rvoice_set_attenuation(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->dsp.attenuation = value; +} + +void +fluid_rvoice_set_min_attenuation_cB(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->dsp.min_attenuation_cB = value; +} + +void +fluid_rvoice_set_viblfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.viblfo_to_pitch = value; +} + +void fluid_rvoice_set_modlfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.modlfo_to_pitch = value; +} + +void +fluid_rvoice_set_modlfo_to_vol(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.modlfo_to_vol = value; +} + +void +fluid_rvoice_set_modlfo_to_fc(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.modlfo_to_fc = value; +} + +void +fluid_rvoice_set_modenv_to_fc(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.modenv_to_fc = value; +} + +void +fluid_rvoice_set_modenv_to_pitch(fluid_rvoice_t* voice, fluid_real_t value) +{ + voice->envlfo.modenv_to_pitch = value; +} + +void +fluid_rvoice_set_synth_gain(fluid_rvoice_t* voice, fluid_real_t value) +{ + 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; +} + +void +fluid_rvoice_set_start(fluid_rvoice_t* voice, int value) +{ + voice->dsp.start = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +void +fluid_rvoice_set_end(fluid_rvoice_t* voice, int value) +{ + voice->dsp.end = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +void +fluid_rvoice_set_loopstart(fluid_rvoice_t* voice, int value) +{ + voice->dsp.loopstart = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +void fluid_rvoice_set_loopend(fluid_rvoice_t* voice, int value) +{ + voice->dsp.loopend = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + +void fluid_rvoice_set_samplemode(fluid_rvoice_t* voice, enum fluid_loop value) +{ + voice->dsp.samplemode = value; + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_CHECK; +} + + +void +fluid_rvoice_set_sample(fluid_rvoice_t* voice, fluid_sample_t* value) +{ + voice->dsp.sample = value; + if (value) { + voice->dsp.check_sample_sanity_flag |= FLUID_SAMPLESANITY_STARTUP; + } +} + +void +fluid_rvoice_voiceoff(fluid_rvoice_t* voice) +{ + 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/fluid_rvoice.h b/libs/fluidsynth/src/fluid_rvoice.h new file mode 100644 index 0000000000..4566cb1de3 --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice.h @@ -0,0 +1,200 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + * 0.00001 * 2 is approximately 0.00003 :) + */ +#define FLUID_NOISE_FLOOR 0.00003 + + +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 */ + int interp_method; + fluid_sample_t* sample; + int check_sample_sanity_flag; /* Flag that initiates, that sample-related parameters + have to be checked. */ + + /* 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) */ + enum fluid_loop samplemode; + + /* 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 */ + + int has_looped; /* Flag that is set as soon as the first loop is completed. */ + fluid_real_t attenuation; /* the attenuation in centibels */ + 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 *dsp_buf; /* buffer to store interpolated sample data to */ + + 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 */ + int is_looping; + +}; + +/* 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 { + fluid_real_t amp; + int mapping; /* Mapping to mixdown buffer index */ + } bufs[FLUID_RVOICE_MAX_BUFS]; +}; + + +/** + * 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_rvoice_buffers_t buffers; +}; + + +int fluid_rvoice_write(fluid_rvoice_t* voice, fluid_real_t *dsp_buf); + +void fluid_rvoice_buffers_mix(fluid_rvoice_buffers_t* buffers, + fluid_real_t* dsp_buf, int samplecount, + fluid_real_t** dest_bufs, int dest_bufcount); +void fluid_rvoice_buffers_set_amp(fluid_rvoice_buffers_t* buffers, + unsigned int bufnum, fluid_real_t value); +void fluid_rvoice_buffers_set_mapping(fluid_rvoice_buffers_t* buffers, + unsigned int bufnum, int mapping); + +/* Dynamic update functions */ + +void fluid_rvoice_noteoff(fluid_rvoice_t* voice, unsigned int min_ticks); +void fluid_rvoice_voiceoff(fluid_rvoice_t* voice); +void fluid_rvoice_reset(fluid_rvoice_t* voice); +void fluid_rvoice_set_output_rate(fluid_rvoice_t* voice, fluid_real_t output_rate); +void fluid_rvoice_set_interp_method(fluid_rvoice_t* voice, int interp_method); +void fluid_rvoice_set_root_pitch_hz(fluid_rvoice_t* voice, fluid_real_t root_pitch_hz); +void fluid_rvoice_set_pitch(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_synth_gain(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_attenuation(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_min_attenuation_cB(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_viblfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_modlfo_to_pitch(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_modlfo_to_vol(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_modlfo_to_fc(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_modenv_to_fc(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_modenv_to_pitch(fluid_rvoice_t* voice, fluid_real_t value); +void fluid_rvoice_set_start(fluid_rvoice_t* voice, int value); +void fluid_rvoice_set_end(fluid_rvoice_t* voice, int value); +void fluid_rvoice_set_loopstart(fluid_rvoice_t* voice, int value); +void fluid_rvoice_set_loopend(fluid_rvoice_t* voice, int value); +void fluid_rvoice_set_sample(fluid_rvoice_t* voice, fluid_sample_t* value); +void fluid_rvoice_set_samplemode(fluid_rvoice_t* voice, enum fluid_loop value); + +/* defined in fluid_rvoice_dsp.c */ + +void fluid_rvoice_dsp_config (void); +int fluid_rvoice_dsp_interpolate_none (fluid_rvoice_dsp_t *voice); +int fluid_rvoice_dsp_interpolate_linear (fluid_rvoice_dsp_t *voice); +int fluid_rvoice_dsp_interpolate_4th_order (fluid_rvoice_dsp_t *voice); +int fluid_rvoice_dsp_interpolate_7th_order (fluid_rvoice_dsp_t *voice); + +#endif diff --git a/libs/fluidsynth/src/fluid_rvoice_dsp.c b/libs/fluidsynth/src/fluid_rvoice_dsp.c new file mode 100644 index 0000000000..df7da5022d --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_dsp.c @@ -0,0 +1,675 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.h" +#include "fluid_phase.h" +#include "fluid_rvoice.h" +#include "fluid_sys.h" + +/* Purpose: + * + * Interpolates audio data (obtains values between the samples of the original + * waveform data). + * + * Variables loaded from the voice structure (assigned in fluid_voice_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) */ + +/* Linear interpolation table (2 coefficients centered on 1st) */ +static fluid_real_t interp_coeff_linear[FLUID_INTERP_MAX][2]; + +/* 4th order (cubic) interpolation table (4 coefficients centered on 2nd) */ +static fluid_real_t interp_coeff[FLUID_INTERP_MAX][4]; + +/* 7th order interpolation (7 coefficients centered on 3rd) */ +static fluid_real_t sinc_table7[FLUID_INTERP_MAX][7]; + + +#define SINC_INTERP_ORDER 7 /* 7th order constant */ + + +/* Initializes interpolation tables */ +void fluid_rvoice_dsp_config (void) +{ + int i, i2; + double x, v; + double i_shifted; + + /* Initialize the coefficients for the interpolation. The math comes + * from a mail, posted by Olli Niemitalo to the music-dsp mailing + * list (I found it in the music-dsp archives + * http://www.smartelectronix.com/musicdsp/). */ + + for (i = 0; i < FLUID_INTERP_MAX; i++) + { + x = (double) i / (double) FLUID_INTERP_MAX; + + interp_coeff[i][0] = (fluid_real_t)(x * (-0.5 + x * (1 - 0.5 * x))); + interp_coeff[i][1] = (fluid_real_t)(1.0 + x * x * (1.5 * x - 2.5)); + interp_coeff[i][2] = (fluid_real_t)(x * (0.5 + x * (2.0 - 1.5 * x))); + interp_coeff[i][3] = (fluid_real_t)(0.5 * x * x * (x - 1.0)); + + interp_coeff_linear[i][0] = (fluid_real_t)(1.0 - x); + interp_coeff_linear[i][1] = (fluid_real_t)x; + } + + /* i: Offset in terms of whole samples */ + for (i = 0; i < SINC_INTERP_ORDER; i++) + { /* i2: Offset in terms of fractional samples ('subsamples') */ + for (i2 = 0; i2 < FLUID_INTERP_MAX; i2++) + { + /* center on middle of table */ + i_shifted = (double)i - ((double)SINC_INTERP_ORDER / 2.0) + + (double)i2 / (double)FLUID_INTERP_MAX; + + /* sinc(0) cannot be calculated straightforward (limit needed for 0/0) */ + if (fabs (i_shifted) > 0.000001) + { + v = (fluid_real_t)sin (i_shifted * M_PI) / (M_PI * i_shifted); + /* Hamming window */ + v *= (fluid_real_t)0.5 * (1.0 + cos (2.0 * M_PI * i_shifted / (fluid_real_t)SINC_INTERP_ORDER)); + } + else v = 1.0; + + sinc_table7[FLUID_INTERP_MAX - i2 - 1][i] = v; + } + } + +#if 0 + for (i = 0; i < FLUID_INTERP_MAX; i++) + { + printf ("%d %0.3f %0.3f %0.3f %0.3f %0.3f %0.3f %0.3f\n", + i, sinc_table7[0][i], sinc_table7[1][i], sinc_table7[2][i], + sinc_table7[3][i], sinc_table7[4][i], sinc_table7[5][i], sinc_table7[6][i]); + } +#endif + + fluid_check_fpe("interpolation table calculation"); +} + +/* 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_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + fluid_real_t *dsp_buf = voice->dsp_buf; + 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; + int looping; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float (dsp_phase_incr, voice->phase_incr); + + /* voice is currently looping? */ + looping = voice->is_looping; + + 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 * dsp_data[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_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + fluid_real_t *dsp_buf = voice->dsp_buf; + 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; + short int point; + fluid_real_t *coeffs; + int looping; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float (dsp_phase_incr, voice->phase_incr); + + /* voice is currently looping? */ + looping = voice->is_looping; + + /* 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 = dsp_data[voice->loopstart]; /* loop start */ + else point = dsp_data[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] * dsp_data[dsp_phase_index] + + coeffs[1] * dsp_data[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] * dsp_data[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_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + fluid_real_t *dsp_buf = voice->dsp_buf; + 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; + short int start_point, end_point1, end_point2; + fluid_real_t *coeffs; + int looping; + + /* Convert playback "speed" floating point value to phase index/fract */ + fluid_phase_set_float (dsp_phase_incr, voice->phase_incr); + + /* voice is currently looping? */ + looping = voice->is_looping; + + /* 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 = dsp_data[voice->loopend - 1]; /* last point in loop (wrap around) */ + } + else + { + start_index = voice->start; + start_point = dsp_data[voice->start]; /* just duplicate the point */ + } + + /* get points off the end (loop start if looping, duplicate point if end) */ + if (looping) + { + end_point1 = dsp_data[voice->loopstart]; + end_point2 = dsp_data[voice->loopstart + 1]; + } + else + { + end_point1 = dsp_data[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] * dsp_data[dsp_phase_index] + + coeffs[2] * dsp_data[dsp_phase_index+1] + + coeffs[3] * dsp_data[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] * dsp_data[dsp_phase_index-1] + + coeffs[1] * dsp_data[dsp_phase_index] + + coeffs[2] * dsp_data[dsp_phase_index+1] + + coeffs[3] * dsp_data[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] * dsp_data[dsp_phase_index-1] + + coeffs[1] * dsp_data[dsp_phase_index] + + coeffs[2] * dsp_data[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] * dsp_data[dsp_phase_index-1] + + coeffs[1] * dsp_data[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 = dsp_data[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_phase_t dsp_phase = voice->phase; + fluid_phase_t dsp_phase_incr; + short int *dsp_data = voice->sample->data; + fluid_real_t *dsp_buf = voice->dsp_buf; + 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; + short int start_points[3]; + short int end_points[3]; + fluid_real_t *coeffs; + int looping; + + /* 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); + + /* voice is currently looping? */ + looping = voice->is_looping; + + /* 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] = dsp_data[voice->loopend - 1]; + start_points[1] = dsp_data[voice->loopend - 2]; + start_points[2] = dsp_data[voice->loopend - 3]; + } + else + { + start_index = voice->start; + start_points[0] = dsp_data[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] = dsp_data[voice->loopstart]; + end_points[1] = dsp_data[voice->loopstart + 1]; + end_points[2] = dsp_data[voice->loopstart + 2]; + } + else + { + end_points[0] = dsp_data[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] * (fluid_real_t)start_points[2] + + coeffs[1] * (fluid_real_t)start_points[1] + + coeffs[2] * (fluid_real_t)start_points[0] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)dsp_data[dsp_phase_index+2] + + coeffs[6] * (fluid_real_t)dsp_data[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] * (fluid_real_t)start_points[1] + + coeffs[1] * (fluid_real_t)start_points[0] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)dsp_data[dsp_phase_index+2] + + coeffs[6] * (fluid_real_t)dsp_data[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] * (fluid_real_t)start_points[0] + + coeffs[1] * (fluid_real_t)dsp_data[dsp_phase_index-2] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)dsp_data[dsp_phase_index+2] + + coeffs[6] * (fluid_real_t)dsp_data[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_real_t)dsp_data[dsp_phase_index-3] + + coeffs[1] * (fluid_real_t)dsp_data[dsp_phase_index-2] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)dsp_data[dsp_phase_index+2] + + coeffs[6] * (fluid_real_t)dsp_data[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_real_t)dsp_data[dsp_phase_index-3] + + coeffs[1] * (fluid_real_t)dsp_data[dsp_phase_index-2] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)dsp_data[dsp_phase_index+2] + + coeffs[6] * (fluid_real_t)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_real_t)dsp_data[dsp_phase_index-3] + + coeffs[1] * (fluid_real_t)dsp_data[dsp_phase_index-2] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)dsp_data[dsp_phase_index+1] + + coeffs[5] * (fluid_real_t)end_points[0] + + coeffs[6] * (fluid_real_t)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_real_t)dsp_data[dsp_phase_index-3] + + coeffs[1] * (fluid_real_t)dsp_data[dsp_phase_index-2] + + coeffs[2] * (fluid_real_t)dsp_data[dsp_phase_index-1] + + coeffs[3] * (fluid_real_t)dsp_data[dsp_phase_index] + + coeffs[4] * (fluid_real_t)end_points[0] + + coeffs[5] * (fluid_real_t)end_points[1] + + coeffs[6] * (fluid_real_t)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] = dsp_data[voice->loopend - 1]; + start_points[1] = dsp_data[voice->loopend - 2]; + start_points[2] = dsp_data[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/fluid_rvoice_event.c b/libs/fluidsynth/src/fluid_rvoice_event.c new file mode 100644 index 0000000000..65edb9dacb --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_event.c @@ -0,0 +1,293 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +#define EVENTFUNC_0(proc, type) \ + if (event->method == proc) { \ + proc((type) event->object); \ + return; } + +#define EVENTFUNC_R1(proc, type) \ + if (event->method == proc) { \ + if(event->intparam != 0) { FLUID_LOG(FLUID_DBG, "IR-mismatch"); } \ + proc((type) event->object, event->realparams[0]); \ + return; } + +#define EVENTFUNC_PTR(proc, type, type2) \ + if (event->method == proc) { \ + proc((type) event->object, (type2) event->ptr); \ + return; } + +#define EVENTFUNC_I1(proc, type) \ + if (event->method == proc) { \ + if(event->realparams[0] != 0.0f) { FLUID_LOG(FLUID_DBG, "IR-mismatch"); } \ + proc((type) event->object, event->intparam); \ + return; } + +#define EVENTFUNC_IR(proc, type) \ + if (event->method == proc) { \ + proc((type) event->object, event->intparam, event->realparams[0]); \ + return; } + +#define EVENTFUNC_ALL(proc, type) \ + if (event->method == proc) { \ + proc((type) event->object, event->intparam, event->realparams[0], \ + event->realparams[1], event->realparams[2], event->realparams[3], \ + event->realparams[4]); \ + return; } + +#define EVENTFUNC_R4(proc, type) \ + if (event->method == proc) { \ + proc((type) event->object, event->intparam, event->realparams[0], \ + event->realparams[1], event->realparams[2], event->realparams[3]); \ + return; } + +void +fluid_rvoice_event_dispatch(fluid_rvoice_event_t* event) +{ + EVENTFUNC_PTR(fluid_rvoice_mixer_add_voice, fluid_rvoice_mixer_t*, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_noteoff, fluid_rvoice_t*); + EVENTFUNC_0(fluid_rvoice_voiceoff, fluid_rvoice_t*); + EVENTFUNC_0(fluid_rvoice_reset, fluid_rvoice_t*); + + EVENTFUNC_ALL(fluid_adsr_env_set_data, fluid_adsr_env_t*); + + EVENTFUNC_I1(fluid_lfo_set_delay, fluid_lfo_t*); + EVENTFUNC_R1(fluid_lfo_set_incr, fluid_lfo_t*); + + EVENTFUNC_R1(fluid_iir_filter_set_fres, fluid_iir_filter_t*); + EVENTFUNC_R1(fluid_iir_filter_set_q_dB, fluid_iir_filter_t*); + + EVENTFUNC_IR(fluid_rvoice_buffers_set_mapping, fluid_rvoice_buffers_t*); + EVENTFUNC_IR(fluid_rvoice_buffers_set_amp, fluid_rvoice_buffers_t*); + + EVENTFUNC_R1(fluid_rvoice_set_modenv_to_pitch, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_output_rate, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_root_pitch_hz, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_synth_gain, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_pitch, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_attenuation, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_min_attenuation_cB, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_viblfo_to_pitch, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_modlfo_to_pitch, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_modlfo_to_vol, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_modlfo_to_fc, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_modenv_to_fc, fluid_rvoice_t*); + EVENTFUNC_R1(fluid_rvoice_set_modenv_to_pitch, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_interp_method, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_start, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_end, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_loopstart, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_loopend, fluid_rvoice_t*); + EVENTFUNC_I1(fluid_rvoice_set_samplemode, fluid_rvoice_t*); + EVENTFUNC_PTR(fluid_rvoice_set_sample, fluid_rvoice_t*, fluid_sample_t*); + + EVENTFUNC_R1(fluid_rvoice_mixer_set_samplerate, fluid_rvoice_mixer_t*); + EVENTFUNC_I1(fluid_rvoice_mixer_set_polyphony, fluid_rvoice_mixer_t*); + EVENTFUNC_I1(fluid_rvoice_mixer_set_reverb_enabled, fluid_rvoice_mixer_t*); + EVENTFUNC_I1(fluid_rvoice_mixer_set_chorus_enabled, fluid_rvoice_mixer_t*); + EVENTFUNC_I1(fluid_rvoice_mixer_set_mix_fx, fluid_rvoice_mixer_t*); + EVENTFUNC_0(fluid_rvoice_mixer_reset_fx, fluid_rvoice_mixer_t*); + EVENTFUNC_0(fluid_rvoice_mixer_reset_reverb, fluid_rvoice_mixer_t*); + EVENTFUNC_0(fluid_rvoice_mixer_reset_chorus, fluid_rvoice_mixer_t*); + EVENTFUNC_IR(fluid_rvoice_mixer_set_threads, fluid_rvoice_mixer_t*); + + EVENTFUNC_ALL(fluid_rvoice_mixer_set_chorus_params, fluid_rvoice_mixer_t*); + EVENTFUNC_R4(fluid_rvoice_mixer_set_reverb_params, fluid_rvoice_mixer_t*); + + FLUID_LOG(FLUID_ERR, "fluid_rvoice_event_dispatch: Unknown method %p to dispatch!", event->method); +} + + +/** + * 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(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, int intparam, + fluid_real_t realparam) +{ + fluid_rvoice_event_t* event; + fluid_rvoice_event_t local_event; + event = handler->is_threadsafe ? + fluid_ringbuffer_get_inptr(handler->queue, handler->queue_stored) : &local_event; + + if (event == NULL) { + FLUID_LOG(FLUID_WARN, "Ringbuffer full, try increasing polyphony!"); + return FLUID_FAILED; // Buffer full... + } + + event->method = method; + event->object = object; + event->intparam = intparam; + event->realparams[0] = realparam; + if (handler->is_threadsafe) + handler->queue_stored++; + else + fluid_rvoice_event_dispatch(event); + return FLUID_OK; +} + + +int +fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, void* ptr) +{ + fluid_rvoice_event_t* event; + fluid_rvoice_event_t local_event; + event = handler->is_threadsafe ? + fluid_ringbuffer_get_inptr(handler->queue, handler->queue_stored) : &local_event; + + if (event == NULL) { + FLUID_LOG(FLUID_WARN, "Ringbuffer full, try increasing polyphony!"); + return FLUID_FAILED; // Buffer full... + } + + event->method = method; + event->object = object; + event->ptr = ptr; + if (handler->is_threadsafe) + handler->queue_stored++; + else + fluid_rvoice_event_dispatch(event); + return FLUID_OK; +} + + +int +fluid_rvoice_eventhandler_push5(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, int intparam, + fluid_real_t r1, fluid_real_t r2, + fluid_real_t r3, fluid_real_t r4, fluid_real_t r5) +{ + fluid_rvoice_event_t* event; + fluid_rvoice_event_t local_event; + event = handler->is_threadsafe ? + fluid_ringbuffer_get_inptr(handler->queue, handler->queue_stored) : &local_event; + + if (event == NULL) { + FLUID_LOG(FLUID_WARN, "Ringbuffer full, try increasing polyphony!"); + return FLUID_FAILED; // Buffer full... + } + + event->method = method; + event->object = object; + event->intparam = intparam; + event->realparams[0] = r1; + event->realparams[1] = r2; + event->realparams[2] = r3; + event->realparams[3] = r4; + event->realparams[4] = r5; + if (handler->is_threadsafe) + handler->queue_stored++; + else + fluid_rvoice_event_dispatch(event); + return FLUID_OK; +} + + +static void +finished_voice_callback(void* userdata, fluid_rvoice_t* rvoice) +{ + fluid_rvoice_eventhandler_t* eventhandler = userdata; + 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 is_threadsafe, int queuesize, + int finished_voices_size, int bufs, int fx_bufs, fluid_real_t sample_rate) +{ + 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; + eventhandler->is_threadsafe = is_threadsafe; + 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, sample_rate); + if (eventhandler->mixer == NULL) + goto error_recovery; + fluid_rvoice_mixer_set_finished_voices_callback(eventhandler->mixer, + finished_voice_callback, eventhandler); + 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) +{ + if (handler == NULL) return; + 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/fluid_rvoice_event.h b/libs/fluidsynth/src/fluid_rvoice_event.h new file mode 100644 index 0000000000..e8693bcd21 --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_event.h @@ -0,0 +1,115 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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" + +#define EVENT_REAL_PARAMS (5) + +typedef struct _fluid_rvoice_event_t fluid_rvoice_event_t; +typedef struct _fluid_rvoice_eventhandler_t fluid_rvoice_eventhandler_t; + +struct _fluid_rvoice_event_t { + void* method; + void* object; + void* ptr; + int intparam; + fluid_real_t realparams[EVENT_REAL_PARAMS]; +}; + +void fluid_rvoice_event_dispatch(fluid_rvoice_event_t* event); + + +/** + * Bridge between the renderer thread and the midi state thread. + * If is_threadsafe is true, that means fluid_rvoice_eventhandler_fetch_all + * can be called in parallell with fluid_rvoice_eventhandler_push/flush + */ +struct _fluid_rvoice_eventhandler_t { + int is_threadsafe; /* False for optimal performance, true for atomic operations */ + fluid_ringbuffer_t* queue; /**< List of fluid_rvoice_event_t */ + int 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 is_threadsafe, int queuesize, int finished_voices_size, int bufs, + int fx_bufs, fluid_real_t sample_rate); + +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*); + +static FLUID_INLINE void +fluid_rvoice_eventhandler_flush(fluid_rvoice_eventhandler_t* handler) +{ + if (handler->queue_stored > 0) { + fluid_ringbuffer_next_inptr(handler->queue, handler->queue_stored); + handler->queue_stored = 0; + } +} + +/** + * @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(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, int intparam, + fluid_real_t realparam); + +int fluid_rvoice_eventhandler_push_ptr(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, void* ptr); + +int fluid_rvoice_eventhandler_push5(fluid_rvoice_eventhandler_t* handler, + void* method, void* object, int intparam, + fluid_real_t r1, fluid_real_t r2, + fluid_real_t r3, fluid_real_t r4, fluid_real_t r5); + +static FLUID_INLINE void +fluid_rvoice_eventhandler_add_rvoice(fluid_rvoice_eventhandler_t* handler, + fluid_rvoice_t* rvoice) +{ + if (handler->is_threadsafe) + fluid_rvoice_eventhandler_push_ptr(handler, fluid_rvoice_mixer_add_voice, + handler->mixer, rvoice); + else + fluid_rvoice_mixer_add_voice(handler->mixer, rvoice); +} + + + +#endif diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.c b/libs/fluidsynth/src/fluid_rvoice_mixer.c new file mode 100644 index 0000000000..cc633f510b --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_mixer.c @@ -0,0 +1,974 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.h" +//#include "fluid_ladspa.h" + +#define SYNTH_REVERB_CHANNEL 0 +#define SYNTH_CHORUS_CHANNEL 1 + +#define ENABLE_MIXER_THREADS 1 + +// 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 */ +#ifdef ENABLE_MIXER_THREADS + fluid_thread_t* thread; /**< Thread object */ +#endif + + fluid_rvoice_t** finished_voices; /* List of voices who have finished */ + int finished_voice_count; + + int ready; /**< Atomic: buffers are ready for mixing */ + + int buf_blocks; /**< Number of blocks allocated in the buffers */ + + int buf_count; + fluid_real_t** left_buf; + fluid_real_t** right_buf; + + int fx_buf_count; + 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 */ + fluid_chorus_t* chorus; /**< Chorus unit */ + 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? */ +}; + +struct _fluid_rvoice_mixer_t { + fluid_mixer_fx_t fx; + + fluid_mixer_buffers_t buffers; /**< Used by mixer only: own buffers */ + void (*remove_voice_callback)(void*, fluid_rvoice_t*); /**< Used by mixer only: Receive this callback every time a voice is removed */ + void* remove_voice_callback_userdata; + + 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 */ + +#ifdef LADSPA + fluid_LADSPA_FxUnit_t* LADSPA_FxUnit; /**< Used by mixer only: Effects unit for LADSPA support. Never created or freed */ +#endif + +#ifdef ENABLE_MIXER_THREADS +// int sleeping_threads; /**< Atomic: number of threads currently asleep */ +// int active_threads; /**< Atomic: number of threads in the thread loop */ + int threads_should_terminate; /**< Atomic: Set to TRUE when threads should terminate */ + int 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 +}; + +static FLUID_INLINE void +fluid_rvoice_mixer_process_fx(fluid_rvoice_mixer_t* mixer) +{ + int i; + fluid_profile_ref_var(prof_ref); + if (mixer->fx.with_reverb) { + if (mixer->fx.mix_fx_to_out) { + for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) + fluid_revmodel_processmix(mixer->fx.reverb, + &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i], + &mixer->buffers.left_buf[0][i], + &mixer->buffers.right_buf[0][i]); + } + else { + for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) + fluid_revmodel_processreplace(mixer->fx.reverb, + &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i], + &mixer->buffers.fx_left_buf[SYNTH_REVERB_CHANNEL][i], + &mixer->buffers.fx_right_buf[SYNTH_REVERB_CHANNEL][i]); + } + fluid_profile(FLUID_PROF_ONE_BLOCK_REVERB, prof_ref); + } + + if (mixer->fx.with_chorus) { + if (mixer->fx.mix_fx_to_out) { + for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) + fluid_chorus_processmix(mixer->fx.chorus, + &mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i], + &mixer->buffers.left_buf[0][i], + &mixer->buffers.right_buf[0][i]); + } + else { + for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) + fluid_chorus_processreplace(mixer->fx.chorus, + &mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i], + &mixer->buffers.fx_left_buf[SYNTH_CHORUS_CHANNEL][i], + &mixer->buffers.fx_right_buf[SYNTH_CHORUS_CHANNEL][i]); + } + fluid_profile(FLUID_PROF_ONE_BLOCK_CHORUS, prof_ref); + } + +#ifdef LADSPA + /* Run the signal through the LADSPA Fx unit */ + if (mixer->LADSPA_FxUnit) { + int j; + FLUID_DECLARE_VLA(fluid_real_t*, left_buf, mixer->buffers.buf_count); + FLUID_DECLARE_VLA(fluid_real_t*, right_buf, mixer->buffers.buf_count); + FLUID_DECLARE_VLA(fluid_real_t*, fx_left_buf, mixer->buffers.fx_buf_count); + FLUID_DECLARE_VLA(fluid_real_t*, fx_right_buf, mixer->buffers.fx_buf_count); + for (j=0; j < mixer->buffers.buf_count; j++) { + left_buf[j] = mixer->buffers.left_buf[j]; + right_buf[j] = mixer->buffers.right_buf[j]; + } + for (j=0; j < mixer->buffers.fx_buf_count; j++) { + fx_left_buf[j] = mixer->buffers.fx_left_buf[j]; + fx_right_buf[j] = mixer->buffers.fx_right_buf[j]; + } + for (i=0; i < mixer->current_blockcount * FLUID_BUFSIZE; i += FLUID_BUFSIZE) { + fluid_LADSPA_run(mixer->LADSPA_FxUnit, left_buf, right_buf, fx_left_buf, + fx_right_buf); + for (j=0; j < mixer->buffers.buf_count; j++) { + left_buf[j] += FLUID_BUFSIZE; + right_buf[j] += FLUID_BUFSIZE; + } + for (j=0; j < mixer->buffers.fx_buf_count; j++) { + fx_left_buf[j] += FLUID_BUFSIZE; + fx_right_buf[j] += FLUID_BUFSIZE; + } + } + fluid_check_fpe("LADSPA"); + } +#endif +} + +/** + * During rendering, rvoices might be finished. Set this callback + * for getting a callback any time the rvoice is finished. + */ +void fluid_rvoice_mixer_set_finished_voices_callback( + fluid_rvoice_mixer_t* mixer, + void (*func)(void*, fluid_rvoice_t*), + void* userdata) +{ + mixer->remove_voice_callback_userdata = userdata; + mixer->remove_voice_callback = func; +} + + + +/** + * 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. + * @return Number of samples written + */ +static int +fluid_mix_one(fluid_rvoice_t* rvoice, fluid_real_t** bufs, unsigned int bufcount, int blockcount) +{ + int i, result = 0; + + FLUID_DECLARE_VLA(fluid_real_t, local_buf, FLUID_BUFSIZE*blockcount); + + for (i=0; i < blockcount; i++) { + int s = fluid_rvoice_write(rvoice, &local_buf[FLUID_BUFSIZE*i]); + if (s == -1) { + s = FLUID_BUFSIZE; /* Voice is quiet, TODO: optimize away memset/mix */ + FLUID_MEMSET(&local_buf[FLUID_BUFSIZE*i], 0, FLUID_BUFSIZE*sizeof(fluid_real_t)); + } + result += s; + if (s < FLUID_BUFSIZE) { + break; + } + } + fluid_rvoice_buffers_mix(&rvoice->buffers, local_buf, result, bufs, bufcount); + + return result; +} + +/** + * 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* reverb_buf, *chorus_buf; + int i; + + /* Set up the reverb / chorus buffers only, when the effect is + * enabled on synth level. Nonexisting buffers are detected in the + * DSP loop. Not sending the reverb / chorus signal saves some time + * in that case. */ + reverb_buf = buffers->mixer->fx.with_reverb ? buffers->fx_left_buf[SYNTH_REVERB_CHANNEL] : NULL; + chorus_buf = buffers->mixer->fx.with_chorus ? buffers->fx_left_buf[SYNTH_CHORUS_CHANNEL] : NULL; + outbufs[buffers->buf_count*2 + SYNTH_REVERB_CHANNEL] = reverb_buf; + outbufs[buffers->buf_count*2 + SYNTH_CHORUS_CHANNEL] = chorus_buf; + + /* 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. + */ + + for (i = 0; i < buffers->buf_count; i++) { + outbufs[i*2] = buffers->left_buf[i]; + outbufs[i*2+1] = buffers->right_buf[i]; + } + return buffers->buf_count*2 + 2; +} + + +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]; + } + } + if (buffers->mixer->remove_voice_callback) + buffers->mixer->remove_voice_callback( + buffers->mixer->remove_voice_callback_userdata, v); + } + buffers->finished_voice_count = 0; +} + +static FLUID_INLINE void fluid_rvoice_mixer_process_finished_voices(fluid_rvoice_mixer_t* mixer) +{ +#ifdef 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 void +fluid_mixer_buffers_render_one(fluid_mixer_buffers_t* buffers, + fluid_rvoice_t* voice, fluid_real_t** bufs, + unsigned int bufcount) +{ + int s = fluid_mix_one(voice, bufs, bufcount, buffers->mixer->current_blockcount); + if (s < buffers->mixer->current_blockcount * FLUID_BUFSIZE) { + fluid_finish_rvoice(buffers, voice); + } +} +/* +static int fluid_mixer_buffers_replace_voice(fluid_mixer_buffers_t* buffers, + fluid_rvoice_t* voice) +{ + int i, retval=0; + int fvc = buffers->finished_voice_count; + for (i=0; i < fvc; i++) + if (buffers->finished_voices[i] == voice) { + fvc--; + if (i < fvc) + buffers->finished_voices[i] = buffers->finished_voices[fvc]; + retval++; + } + fvc = buffers->finished_voice_count; + return retval; +} +*/ + +int +fluid_rvoice_mixer_add_voice(fluid_rvoice_mixer_t* mixer, fluid_rvoice_t* voice) +{ + int i; + + if (mixer->active_voices < mixer->polyphony) { + mixer->rvoices[mixer->active_voices++] = voice; + return FLUID_OK; + } + + /* 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 FLUID_FAILED; + } + if (mixer->rvoices[i]->envlfo.volenv.section == FLUID_VOICE_ENVFINISHED) { + fluid_finish_rvoice(&mixer->buffers, mixer->rvoices[i]); + mixer->rvoices[i] = voice; + return FLUID_OK; + } + } + + /* This should never happen */ + FLUID_LOG(FLUID_ERR, "Trying to exceed polyphony in fluid_rvoice_mixer_add_voice"); + return FLUID_FAILED; +} + +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 + */ +int +fluid_rvoice_mixer_set_polyphony(fluid_rvoice_mixer_t* handler, int value) +{ + void* newptr; + 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; + +#ifdef 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 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_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); + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref); + } +} + + +static FLUID_INLINE void +fluid_mixer_buffers_zero(fluid_mixer_buffers_t* buffers) +{ + int i; + int size = buffers->mixer->current_blockcount * FLUID_BUFSIZE * sizeof(fluid_real_t); + /* TODO: Optimize by only zero out the buffers we actually use later on. */ + for (i=0; i < buffers->buf_count; i++) { + FLUID_MEMSET(buffers->left_buf[i], 0, size); + FLUID_MEMSET(buffers->right_buf[i], 0, size); + } + for (i=0; i < buffers->fx_buf_count; i++) { + FLUID_MEMSET(buffers->fx_left_buf[i], 0, size); + FLUID_MEMSET(buffers->fx_right_buf[i], 0, size); + } +} + + + +static int +fluid_mixer_buffers_init(fluid_mixer_buffers_t* buffers, fluid_rvoice_mixer_t* mixer) +{ + int i, samplecount; + + buffers->mixer = mixer; + buffers->buf_count = buffers->mixer->buffers.buf_count; + buffers->fx_buf_count = buffers->mixer->buffers.fx_buf_count; + buffers->buf_blocks = buffers->mixer->buffers.buf_blocks; + samplecount = FLUID_BUFSIZE * buffers->buf_blocks; + + + /* Left and right audio buffers */ + + buffers->left_buf = FLUID_ARRAY(fluid_real_t*, buffers->buf_count); + buffers->right_buf = FLUID_ARRAY(fluid_real_t*, buffers->buf_count); + + if ((buffers->left_buf == NULL) || (buffers->right_buf == NULL)) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + FLUID_MEMSET(buffers->left_buf, 0, buffers->buf_count * sizeof(fluid_real_t*)); + FLUID_MEMSET(buffers->right_buf, 0, buffers->buf_count * sizeof(fluid_real_t*)); + + for (i = 0; i < buffers->buf_count; i++) { + + buffers->left_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount); + buffers->right_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount); + + if ((buffers->left_buf[i] == NULL) || (buffers->right_buf[i] == NULL)) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + } + + /* Effects audio buffers */ + + buffers->fx_left_buf = FLUID_ARRAY(fluid_real_t*, buffers->fx_buf_count); + buffers->fx_right_buf = FLUID_ARRAY(fluid_real_t*, buffers->fx_buf_count); + + if ((buffers->fx_left_buf == NULL) || (buffers->fx_right_buf == NULL)) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + FLUID_MEMSET(buffers->fx_left_buf, 0, buffers->fx_buf_count * sizeof(fluid_real_t*)); + FLUID_MEMSET(buffers->fx_right_buf, 0, buffers->fx_buf_count * sizeof(fluid_real_t*)); + + for (i = 0; i < buffers->fx_buf_count; i++) { + buffers->fx_left_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount); + buffers->fx_right_buf[i] = FLUID_ARRAY(fluid_real_t, samplecount); + + if ((buffers->fx_left_buf[i] == NULL) || (buffers->fx_right_buf[i] == 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) + */ +void +fluid_rvoice_mixer_set_samplerate(fluid_rvoice_mixer_t* mixer, fluid_real_t samplerate) +{ + int i; + if (mixer->fx.chorus) + delete_fluid_chorus(mixer->fx.chorus); + mixer->fx.chorus = new_fluid_chorus(samplerate); + if (mixer->fx.reverb) + fluid_revmodel_samplerate_change(mixer->fx.reverb, samplerate); + for (i=0; i < mixer->active_voices; i++) + fluid_rvoice_set_output_rate(mixer->rvoices[i], samplerate); +} + + +/** + * @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, fluid_real_t sample_rate) +{ + 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->buffers.buf_count = buf_count; + mixer->buffers.fx_buf_count = fx_buf_count; + mixer->buffers.buf_blocks = FLUID_MIXER_MAX_BUFFERS_DEFAULT; + + /* allocate the reverb module */ + mixer->fx.reverb = new_fluid_revmodel(sample_rate); + mixer->fx.chorus = new_fluid_chorus(sample_rate); + if (mixer->fx.reverb == NULL) { + FLUID_LOG(FLUID_ERR, "Out of memory"); + delete_fluid_rvoice_mixer(mixer); + return NULL; + } + + if (!fluid_mixer_buffers_init(&mixer->buffers, mixer)) { + delete_fluid_rvoice_mixer(mixer); + return NULL; + } + +#ifdef 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) { + delete_fluid_rvoice_mixer(mixer); + return NULL; + } +#endif + + return mixer; +} + +static void +fluid_mixer_buffers_free(fluid_mixer_buffers_t* buffers) +{ + int i; + + FLUID_FREE(buffers->finished_voices); + + /* free all the sample buffers */ + if (buffers->left_buf != NULL) { + for (i = 0; i < buffers->buf_count; i++) { + if (buffers->left_buf[i] != NULL) { + FLUID_FREE(buffers->left_buf[i]); + } + } + FLUID_FREE(buffers->left_buf); + } + + if (buffers->right_buf != NULL) { + for (i = 0; i < buffers->buf_count; i++) { + if (buffers->right_buf[i] != NULL) { + FLUID_FREE(buffers->right_buf[i]); + } + } + FLUID_FREE(buffers->right_buf); + } + + if (buffers->fx_left_buf != NULL) { + for (i = 0; i < buffers->fx_buf_count; i++) { + if (buffers->fx_left_buf[i] != NULL) { + FLUID_FREE(buffers->fx_left_buf[i]); + } + } + FLUID_FREE(buffers->fx_left_buf); + } + + if (buffers->fx_right_buf != NULL) { + for (i = 0; i < buffers->fx_buf_count; i++) { + if (buffers->fx_right_buf[i] != NULL) { + FLUID_FREE(buffers->fx_right_buf[i]); + } + } + FLUID_FREE(buffers->fx_right_buf); + } +} + +void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t* mixer) +{ + if (!mixer) + return; + fluid_rvoice_mixer_set_threads(mixer, 0, 0); +#ifdef ENABLE_MIXER_THREADS + 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); + if (mixer->fx.reverb) + delete_fluid_revmodel(mixer->fx.reverb); + if (mixer->fx.chorus) + delete_fluid_chorus(mixer->fx.chorus); + FLUID_FREE(mixer->rvoices); + FLUID_FREE(mixer); +} + + +#ifdef LADSPA +void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t* mixer, + fluid_LADSPA_FxUnit_t* ladspa) +{ + mixer->LADSPA_FxUnit = ladspa; +} +#endif + +void fluid_rvoice_mixer_set_reverb_enabled(fluid_rvoice_mixer_t* mixer, int on) +{ + mixer->fx.with_reverb = on; +} + +void fluid_rvoice_mixer_set_chorus_enabled(fluid_rvoice_mixer_t* mixer, int on) +{ + mixer->fx.with_chorus = on; +} + +void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t* mixer, int on) +{ + mixer->fx.mix_fx_to_out = on; +} + +void fluid_rvoice_mixer_set_chorus_params(fluid_rvoice_mixer_t* mixer, int set, + int nr, double level, double speed, + double depth_ms, int type) +{ + fluid_chorus_set(mixer->fx.chorus, set, nr, level, speed, depth_ms, type); +} +void fluid_rvoice_mixer_set_reverb_params(fluid_rvoice_mixer_t* mixer, int set, + double roomsize, double damping, + double width, double level) +{ + fluid_revmodel_set(mixer->fx.reverb, set, roomsize, damping, width, level); +} + +void fluid_rvoice_mixer_reset_fx(fluid_rvoice_mixer_t* mixer) +{ + fluid_revmodel_reset(mixer->fx.reverb); + fluid_chorus_reset(mixer->fx.chorus); +} + +void fluid_rvoice_mixer_reset_reverb(fluid_rvoice_mixer_t* mixer) +{ + fluid_revmodel_reset(mixer->fx.reverb); +} + +void fluid_rvoice_mixer_reset_chorus(fluid_rvoice_mixer_t* mixer) +{ + fluid_chorus_reset(mixer->fx.chorus); +} + +int fluid_rvoice_mixer_get_bufs(fluid_rvoice_mixer_t* mixer, + fluid_real_t*** left, fluid_real_t*** right) +{ + *left = mixer->buffers.left_buf; + *right = mixer->buffers.right_buf; + return mixer->buffers.buf_count; +} + + +#ifdef 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 void +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; + + 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) { + fluid_mixer_buffers_zero(buffers); + bufcount = fluid_mixer_buffers_prepare(buffers, bufs); + hasValidData = 1; + } + // then render voice to buffers + fluid_mixer_buffers_render_one(buffers, rvoice, bufs, bufcount); + } + } + +} + +static void +fluid_mixer_buffers_mix(fluid_mixer_buffers_t* dest, fluid_mixer_buffers_t* src) +{ + int i,j; + int scount = dest->mixer->current_blockcount * FLUID_BUFSIZE; + int minbuf; + + minbuf = dest->buf_count; + if (minbuf > src->buf_count) + minbuf = src->buf_count; + for (i=0; i < minbuf; i++) { + for (j=0; j < scount; j++) { + dest->left_buf[i][j] += src->left_buf[i][j]; + dest->right_buf[i][j] += src->right_buf[i][j]; + } + } + + minbuf = dest->fx_buf_count; + if (minbuf > src->fx_buf_count) + minbuf = src->fx_buf_count; + for (i=0; i < minbuf; i++) { + for (j=0; j < scount; j++) { + dest->fx_left_buf[i][j] += src->fx_left_buf[i][j]; + dest->fx_right_buf[i][j] += src->fx_right_buf[i][j]; + } + } +} + + +/** + * Go through all threads and see if someone is finished for mixing + */ +static FLUID_INLINE int +fluid_mixer_mix_in(fluid_rvoice_mixer_t* mixer, int extra_threads) +{ + 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]); + hasmixed = 1; + break; + } + } + } while (hasmixed); + return result; +} + +static void +fluid_render_loop_multithread(fluid_rvoice_mixer_t* mixer) +{ + int i, bufcount; + //int scount = mixer->current_blockcount * FLUID_BUFSIZE; + 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); + 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)) { + // 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); + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICE, prof_ref); + //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", + // mixer->current_blockcount, test, mixer->active_voices, waits); +} + +#endif + +/** + * 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 + */ +void +fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t* mixer, int thread_count, + int prio_level) +{ +#ifdef ENABLE_MIXER_THREADS + char name[16]; + int i; + + // Kill all existing threads first + if (mixer->thread_count) { + 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; + } + + if (thread_count == 0) + return; + + // 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_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_atomic_int_set(&b->ready, THREAD_BUF_NODATA); + g_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; + } + +#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 > mixer->buffers.buf_blocks ? + mixer->buffers.buf_blocks : blockcount; + + // Zero buffers + fluid_mixer_buffers_zero(&mixer->buffers); + fluid_profile(FLUID_PROF_ONE_BLOCK_CLEAR, prof_ref); + +#ifdef ENABLE_MIXER_THREADS + if (mixer->thread_count > 0) + fluid_render_loop_multithread(mixer); + else +#endif + fluid_render_loop_singlethread(mixer); + fluid_profile(FLUID_PROF_ONE_BLOCK_VOICES, prof_ref); + + + // Process reverb & chorus + fluid_rvoice_mixer_process_fx(mixer); + + // Call the callback and pack active voice array + fluid_rvoice_mixer_process_finished_voices(mixer); + + return mixer->current_blockcount; +} diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.h b/libs/fluidsynth/src/fluid_rvoice_mixer.h new file mode 100644 index 0000000000..d4e41ca0a8 --- /dev/null +++ b/libs/fluidsynth/src/fluid_rvoice_mixer.h @@ -0,0 +1,74 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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; + +#define FLUID_MIXER_MAX_BUFFERS_DEFAULT (8192/FLUID_BUFSIZE) + + +void fluid_rvoice_mixer_set_finished_voices_callback( + fluid_rvoice_mixer_t* mixer, + void (*func)(void*, fluid_rvoice_t*), + void* userdata); + + +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); + +fluid_rvoice_mixer_t* new_fluid_rvoice_mixer(int buf_count, int fx_buf_count, + fluid_real_t sample_rate); + +void delete_fluid_rvoice_mixer(fluid_rvoice_mixer_t*); + +void fluid_rvoice_mixer_set_samplerate(fluid_rvoice_mixer_t* mixer, fluid_real_t samplerate); +void fluid_rvoice_mixer_set_reverb_enabled(fluid_rvoice_mixer_t* mixer, int on); +void fluid_rvoice_mixer_set_chorus_enabled(fluid_rvoice_mixer_t* mixer, int on); +void fluid_rvoice_mixer_set_mix_fx(fluid_rvoice_mixer_t* mixer, int on); +int fluid_rvoice_mixer_set_polyphony(fluid_rvoice_mixer_t* handler, int value); +int fluid_rvoice_mixer_add_voice(fluid_rvoice_mixer_t* mixer, fluid_rvoice_t* voice); +void fluid_rvoice_mixer_set_chorus_params(fluid_rvoice_mixer_t* mixer, int set, + int nr, double level, double speed, + double depth_ms, int type); +void fluid_rvoice_mixer_set_reverb_params(fluid_rvoice_mixer_t* mixer, int set, + double roomsize, double damping, + double width, double level); +void fluid_rvoice_mixer_reset_fx(fluid_rvoice_mixer_t* mixer); +void fluid_rvoice_mixer_reset_reverb(fluid_rvoice_mixer_t* mixer); +void fluid_rvoice_mixer_reset_chorus(fluid_rvoice_mixer_t* mixer); + +void fluid_rvoice_mixer_set_threads(fluid_rvoice_mixer_t* mixer, int thread_count, + int prio_level); + +#ifdef LADSPA +void fluid_rvoice_mixer_set_ladspa(fluid_rvoice_mixer_t* mixer, + fluid_LADSPA_FxUnit_t* ladspa); +#endif + +#endif + diff --git a/libs/fluidsynth/src/fluid_settings.c b/libs/fluidsynth/src/fluid_settings.c new file mode 100644 index 0000000000..2061c90f90 --- /dev/null +++ b/libs/fluidsynth/src/fluid_settings.c @@ -0,0 +1,1602 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.h" +#include "fluid_sys.h" +#include "fluid_hash.h" +#include "fluid_synth.h" +//#include "fluid_cmd.h" +//#include "fluid_adriver.h" +//#include "fluid_mdriver.h" +#include "fluid_settings.h" +#include "fluid_midi.h" + +/* Defined in fluid_filerenderer.c */ +extern void fluid_file_renderer_settings (fluid_settings_t* settings); + +/* 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 { + int type; /**< fluid_types_enum */ +} fluid_setting_node_t; + +typedef struct { + fluid_setting_node_t node; + char* value; + char* def; + int hints; + fluid_list_t* options; + fluid_str_update_t update; + void* data; +} fluid_str_setting_t; + +typedef struct { + fluid_setting_node_t node; + double value; + double def; + double min; + double max; + int hints; + fluid_num_update_t update; + void* data; +} fluid_num_setting_t; + +typedef struct { + fluid_setting_node_t node; + int value; + int def; + int min; + int max; + int hints; + fluid_int_update_t update; + void* data; +} fluid_int_setting_t; + +typedef struct { + fluid_setting_node_t node; + fluid_hashtable_t *hashtable; +} fluid_set_setting_t; + + +static fluid_str_setting_t* +new_fluid_str_setting(const char* value, char* def, int hints, fluid_str_update_t fun, void* data) +{ + fluid_str_setting_t* str; + + str = FLUID_NEW(fluid_str_setting_t); + + if (!str) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + str->node.type = FLUID_STR_TYPE; + str->value = value? FLUID_STRDUP(value) : NULL; + str->def = def? FLUID_STRDUP(def) : NULL; + str->hints = hints; + str->options = NULL; + str->update = fun; + str->data = data; + return str; +} + +static void +delete_fluid_str_setting(fluid_str_setting_t* str) +{ + if (!str) return; + + if (str->value) FLUID_FREE(str->value); + if (str->def) FLUID_FREE(str->def); + + if (str->options) { + fluid_list_t* list = str->options; + + while (list) { + FLUID_FREE (list->data); + list = fluid_list_next(list); + } + + delete_fluid_list(str->options); + } + + FLUID_FREE(str); +} + + +static fluid_num_setting_t* +new_fluid_num_setting(double min, double max, double def, + int hints, fluid_num_update_t fun, void* data) +{ + fluid_num_setting_t* setting; + + setting = FLUID_NEW(fluid_num_setting_t); + + if (!setting) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + setting->node.type = FLUID_NUM_TYPE; + setting->value = def; + setting->def = def; + setting->min = min; + setting->max = max; + setting->hints = hints; + setting->update = fun; + setting->data = data; + return setting; +} + +static void +delete_fluid_num_setting(fluid_num_setting_t* setting) +{ + if (setting) FLUID_FREE(setting); +} + +static fluid_int_setting_t* +new_fluid_int_setting(int min, int max, int def, + int hints, fluid_int_update_t fun, void* data) +{ + fluid_int_setting_t* setting; + + setting = FLUID_NEW(fluid_int_setting_t); + + if (!setting) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + setting->node.type = FLUID_INT_TYPE; + setting->value = def; + setting->def = def; + setting->min = min; + setting->max = max; + setting->hints = hints; + setting->update = fun; + setting->data = data; + return setting; +} + +static void +delete_fluid_int_setting(fluid_int_setting_t* setting) +{ + if (setting) FLUID_FREE(setting); +} + +static fluid_set_setting_t* +new_fluid_set_setting(void) +{ + fluid_set_setting_t* setting; + + setting = FLUID_NEW(fluid_set_setting_t); + + if (!setting) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + setting->node.type = FLUID_SET_TYPE; + setting->hashtable = new_fluid_hashtable_full(fluid_str_hash, fluid_str_equal, + fluid_settings_key_destroy_func, + fluid_settings_value_destroy_func); + if (!setting->hashtable) + { + FLUID_FREE (setting); + return NULL; + } + + return setting; +} + +static void +delete_fluid_set_setting(fluid_set_setting_t* setting) +{ + if (setting) + { + delete_fluid_hashtable(setting->hashtable); + FLUID_FREE(setting); + } +} + +/** + * 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((fluid_num_setting_t*) value); + break; + case FLUID_INT_TYPE: + delete_fluid_int_setting((fluid_int_setting_t*) value); + break; + case FLUID_STR_TYPE: + delete_fluid_str_setting((fluid_str_setting_t*) value); + break; + case FLUID_SET_TYPE: + delete_fluid_set_setting((fluid_set_setting_t*) value); + break; + } +} + +void +fluid_settings_init(fluid_settings_t* settings) +{ + fluid_return_if_fail (settings != NULL); + + fluid_synth_settings(settings); + //fluid_shell_settings(settings); + fluid_player_settings(settings); +#if 0 + fluid_file_renderer_settings(settings); + fluid_audio_driver_settings(settings); + fluid_midi_driver_settings(settings); +#endif +} + +static int +fluid_settings_tokenize(const char *s, char *buf, char **ptr) +{ + char *tokstr, *tok; + int n = 0; + + if (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 1 if the node exists, 0 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 0; + + for (n = 0; n < ntokens; n++) { + + node = fluid_hashtable_lookup(table, tokens[n]); + if (!node) return 0; + + table = (node->type == FLUID_SET_TYPE) ? ((fluid_set_setting_t *)node)->hashtable : NULL; + } + + if (value) *value = node; + + return 1; +} + +/** + * 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 1 if the value has been set, zero otherwise + */ +static int +fluid_settings_set(fluid_settings_t* settings, const char *name, void* 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) - 1; + if (num == 0) + return 0; + + for (n = 0; n < num; n++) { + + node = fluid_hashtable_lookup(table, tokens[n]); + + if (node) { + + if (node->type == FLUID_SET_TYPE) { + table = ((fluid_set_setting_t *)node)->hashtable; + } else { + /* path ends prematurely */ + FLUID_LOG(FLUID_WARN, "'%s' is not a node", name[n]); + return 0; + } + + } else { + /* create a new node */ + fluid_set_setting_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 0; + } + + fluid_hashtable_insert(table, dupname, setnode); + table = setnode->hashtable; + } + } + + dupname = FLUID_STRDUP (tokens[num]); + + if (!dupname) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return 0; + } + + fluid_hashtable_insert(table, dupname, value); + + return 1; +} + +/** returns 1 if the value has been registered correctly, 0 + otherwise */ +int +fluid_settings_register_str(fluid_settings_t* settings, char* name, char* def, int hints, + fluid_str_update_t fun, void* data) +{ + fluid_setting_node_t *node; + fluid_str_setting_t* setting; + int retval; + + 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)) { + setting = new_fluid_str_setting(def, def, hints, fun, data); + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_str_setting (setting); + } else { + /* if variable already exists, don't change its value. */ + if (node->type == FLUID_STR_TYPE) { + setting = (fluid_str_setting_t*) node; + setting->update = fun; + setting->data = data; + setting->def = def? FLUID_STRDUP(def) : NULL; + setting->hints = hints; + retval = 1; + } else { + FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name); + retval = 0; + } + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** returns 1 if the value has been register correctly, zero + otherwise */ +int +fluid_settings_register_num(fluid_settings_t* settings, char* name, double def, + double min, double max, int hints, + fluid_num_update_t fun, void* data) +{ + fluid_setting_node_t *node; + int retval; + + 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); + + /* 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)) { + /* insert a new setting */ + fluid_num_setting_t* setting; + setting = new_fluid_num_setting(min, max, def, hints, fun, data); + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_num_setting (setting); + } else { + if (node->type == FLUID_NUM_TYPE) { + /* update the existing setting but don't change its value */ + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + setting->update = fun; + setting->data = data; + setting->min = min; + setting->max = max; + setting->def = def; + setting->hints = hints; + retval = 1; + } else { + /* type mismatch */ + FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name); + retval = 0; + } + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** returns 1 if the value has been register correctly, zero + otherwise. */ +int +fluid_settings_register_int(fluid_settings_t* settings, char* name, int def, + int min, int max, int hints, + fluid_int_update_t fun, void* data) +{ + fluid_setting_node_t *node; + int retval; + + 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); + + /* 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)) { + /* insert a new setting */ + fluid_int_setting_t* setting; + setting = new_fluid_int_setting(min, max, def, hints, fun, data); + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_int_setting (setting); + } else { + if (node->type == FLUID_INT_TYPE) { + /* update the existing setting but don't change its value */ + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + setting->update = fun; + setting->data = data; + setting->min = min; + setting->max = max; + setting->def = def; + setting->hints = hints; + retval = 1; + } else { + /* type mismatch */ + FLUID_LOG(FLUID_WARN, "Type mismatch on setting '%s'", name); + retval = 0; + } + } + + 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, 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_return_val_if_fail (settings != NULL, FLUID_NO_TYPE); + fluid_return_val_if_fail (name != NULL, FLUID_NO_TYPE); + fluid_return_val_if_fail (name[0] != '\0', FLUID_NO_TYPE); + + fluid_rec_mutex_lock (settings->mutex); + type = fluid_settings_get (settings, name, &node) ? node->type : FLUID_NO_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 + * @return the hints associated to the named setting if it exists, zero otherwise + */ +int +fluid_settings_get_hints(fluid_settings_t* settings, const char *name) +{ + fluid_setting_node_t *node; + int hints = 0; + + 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)) { + if (node->type == FLUID_NUM_TYPE) { + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + hints = setting->hints; + } else if (node->type == FLUID_STR_TYPE) { + fluid_str_setting_t* setting = (fluid_str_setting_t*) node; + hints = setting->hints; + } else if (node->type == FLUID_INT_TYPE) { + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + hints = setting->hints; + } + } + + fluid_rec_mutex_unlock (settings->mutex); + + return hints; +} + +/** + * Ask whether the setting is changeable in real-time. + * + * @param settings a settings object + * @param name a setting's name + * @return non zero if the setting is changeable in real-time + */ +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)) { + if (node->type == FLUID_NUM_TYPE) { + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + isrealtime = setting->update != NULL; + } else if (node->type == FLUID_STR_TYPE) { + fluid_str_setting_t* setting = (fluid_str_setting_t*) node; + isrealtime = setting->update != NULL; + } else if (node->type == FLUID_INT_TYPE) { + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + 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 1 if the value has been set, 0 otherwise + */ +int +fluid_settings_setstr(fluid_settings_t* settings, const char *name, const char *str) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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)) { + if (node->type == FLUID_STR_TYPE) { + fluid_str_setting_t *setting = (fluid_str_setting_t *)node; + + if (setting->value) FLUID_FREE (setting->value); + setting->value = str ? FLUID_STRDUP (str) : NULL; + + /* Call under lock to keep update() synchronized with the current value */ + if (setting->update) (*setting->update)(setting->data, name, str); + retval = 1; + } + else if (node->type == FLUID_INT_TYPE) /* Handle yes/no for boolean values for backwards compatibility */ + { + fluid_int_setting_t *setting = (fluid_int_setting_t *)node; + + if (setting->hints & FLUID_HINT_TOGGLED) + { + if (FLUID_STRCMP (str, "yes") == 0) + { + setting->value = TRUE; + if (setting->update) (*setting->update)(setting->data, name, TRUE); + } + else if (FLUID_STRCMP (str, "no") == 0) + { + setting->value = FALSE; + if (setting->update) (*setting->update)(setting->data, name, FALSE); + } + } + } + } else { + /* insert a new setting */ + fluid_str_setting_t* setting; + setting = new_fluid_str_setting(str, NULL, 0, NULL, NULL); + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_str_setting (setting); + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * Copy the value of a string setting + * @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 1 if the value exists, 0 otherwise + * @since 1.1.0 + * + * Like fluid_settings_getstr() but is thread safe. A size of 256 should be + * more than sufficient for the string buffer. + */ +int +fluid_settings_copystr(fluid_settings_t* settings, const char *name, + char *str, int len) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (str != NULL, 0); + fluid_return_val_if_fail (len > 0, 0); + + str[0] = 0; + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get (settings, name, &node)) + { + if (node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = (fluid_str_setting_t *)node; + + if (setting->value) + { + FLUID_STRNCPY (str, setting->value, len); + str[len - 1] = 0; /* Force terminate, in case of truncation */ + } + + retval = 1; + } + else if (node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = (fluid_int_setting_t *)node; + + if (setting->hints & FLUID_HINT_TOGGLED) + { + FLUID_STRNCPY (str, setting->value ? "yes" : "no", len); + str[len - 1] = 0; /* Force terminate, in case of truncation */ + + retval = 1; + } + } + } + + 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 1 if the value exists and was successfully duplicated, 0 otherwise + * @since 1.1.0 + * + * Like fluid_settings_copystr() but allocates a new copy of the string. Caller + * owns the string and should free it with free() when done using it. + */ +int +fluid_settings_dupstr(fluid_settings_t* settings, const char *name, char** str) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (str != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node)) + { + if (node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = (fluid_str_setting_t *)node; + + if (setting->value) + { + *str = FLUID_STRDUP (setting->value); + if (!*str) FLUID_LOG (FLUID_ERR, "Out of memory"); + } + + if (!setting->value || *str) retval = 1; /* Don't set to 1 if out of memory */ + } + else if (node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = (fluid_int_setting_t *)node; + + 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 = 1; /* Don't set to 1 if out of memory */ + } + } + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * Get the value of a string setting + * @param settings a settings object + * @param name a setting's name + * @param str Location to store pointer to the settings string value + * @return 1 if the value exists, 0 otherwise + * @deprecated + * + * If the value does not exists, 'str' is set to NULL. Otherwise, 'str' will + * point to the value. The application does not own the returned value and it + * is valid only until a new value is assigned to the setting of the given name. + * + * NOTE: In a multi-threaded environment, caller must ensure that the setting + * being read by fluid_settings_getstr() is not assigned during the + * duration of callers use of the setting's value. Use fluid_settings_copystr() + * or fluid_settings_dupstr() which does not have this restriction. + */ +int +fluid_settings_getstr(fluid_settings_t* settings, const char *name, char** str) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (str != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node)) + { + if (node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = (fluid_str_setting_t *)node; + *str = setting->value; + retval = 1; + } + else if (node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = (fluid_int_setting_t *)node; + + if (setting->hints & FLUID_HINT_TOGGLED) + { + *str = setting->value ? "yes" : "no"; + retval = 1; + } + } + } + else *str = NULL; + + 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 1 if the value exists and is equal to 's', 0 otherwise + */ +int +fluid_settings_str_equal (fluid_settings_t* settings, const char *name, const char *s) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (s != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get (settings, name, &node)) + { + if (node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t *setting = (fluid_str_setting_t *)node; + 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 = (fluid_int_setting_t *)node; + + 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. Note that the returned string is + * not owned by the caller and should not be modified or freed. + * + * @param settings a settings object + * @param name a setting's name + * @return the default string value of the setting if it exists, NULL otherwise + */ +char* +fluid_settings_getstr_default(fluid_settings_t* settings, const char *name) +{ + fluid_setting_node_t *node; + char *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)) + { + if (node->type == FLUID_STR_TYPE) + { + fluid_str_setting_t* setting = (fluid_str_setting_t*) node; + retval = setting->def; + } + else if (node->type == FLUID_INT_TYPE) /* Handle boolean integers for backwards compatibility */ + { + fluid_int_setting_t *setting = (fluid_int_setting_t *)node; + + if (setting->hints & FLUID_HINT_TOGGLED) + retval = setting->def ? "yes" : "no"; + } + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * 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 1 if the setting exists and option was added, 0 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 = 0; + + 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_return_val_if_fail (s != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_STR_TYPE)) { + fluid_str_setting_t* setting = (fluid_str_setting_t*) node; + char* copy = FLUID_STRDUP(s); + setting->options = fluid_list_append(setting->options, copy); + setting->hints |= FLUID_HINT_OPTIONLIST; + retval = 1; + } + + 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 1 if the setting exists and option was removed, 0 otherwise + */ +int +fluid_settings_remove_option(fluid_settings_t* settings, const char *name, const char* s) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (s != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_STR_TYPE)) { + + fluid_str_setting_t* setting = (fluid_str_setting_t*) node; + 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 = 1; + 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 1 if the value has been set, 0 otherwise + */ +int +fluid_settings_setnum(fluid_settings_t* settings, const char *name, double val) +{ + fluid_setting_node_t *node; + fluid_num_setting_t* setting; + int retval = 0; + + 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)) { + if (node->type == FLUID_NUM_TYPE) { + setting = (fluid_num_setting_t*) node; + + if (val < setting->min) val = setting->min; + else if (val > setting->max) val = setting->max; + + setting->value = val; + + /* Call under lock to keep update() synchronized with the current value */ + if (setting->update) (*setting->update)(setting->data, name, val); + retval = 1; + } + } else { + /* insert a new setting */ + fluid_num_setting_t* setting; + setting = new_fluid_num_setting(-1e10, 1e10, 0.0f, 0, NULL, NULL); + setting->value = val; + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_num_setting (setting); + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * 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 1 if the value exists, 0 otherwise + */ +int +fluid_settings_getnum(fluid_settings_t* settings, const char *name, double* val) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (val != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_NUM_TYPE)) { + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + *val = setting->value; + retval = 1; + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * 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 + */ +void +fluid_settings_getnum_range(fluid_settings_t* settings, const char *name, + double* min, double* max) +{ + fluid_setting_node_t *node; + + fluid_return_if_fail (settings != NULL); + fluid_return_if_fail (name != NULL); + fluid_return_if_fail (name[0] != '\0'); + fluid_return_if_fail (min != NULL); + fluid_return_if_fail (max != NULL); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_NUM_TYPE)) { + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + *min = setting->min; + *max = setting->max; + } + + fluid_rec_mutex_unlock (settings->mutex); +} + +/** + * Get the default value of a named numeric (double) setting + * + * @param settings a settings object + * @param name a setting's name + * @return the default value if the named setting exists, 0.0f otherwise + */ +double +fluid_settings_getnum_default(fluid_settings_t* settings, const char *name) +{ + fluid_setting_node_t *node; + double retval = 0.0; + + fluid_return_val_if_fail (settings != NULL, 0.0); + fluid_return_val_if_fail (name != NULL, 0.0); + fluid_return_val_if_fail (name[0] != '\0', 0.0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_NUM_TYPE)) { + fluid_num_setting_t* setting = (fluid_num_setting_t*) node; + retval = setting->def; + } + + 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 1 if the value has been set, 0 otherwise + */ +int +fluid_settings_setint(fluid_settings_t* settings, const char *name, int val) +{ + fluid_setting_node_t *node; + fluid_int_setting_t* setting; + int retval = 0; + + 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)) { + if (node->type == FLUID_INT_TYPE) { + setting = (fluid_int_setting_t*) node; + + if (val < setting->min) val = setting->min; + else if (val > setting->max) val = setting->max; + + setting->value = val; + + /* Call under lock to keep update() synchronized with the current value */ + if (setting->update) (*setting->update)(setting->data, name, val); + retval = 1; + } + } else { + /* insert a new setting */ + fluid_int_setting_t* setting; + setting = new_fluid_int_setting(INT_MIN, INT_MAX, 0, 0, NULL, NULL); + setting->value = val; + retval = fluid_settings_set(settings, name, setting); + if (retval != 1) delete_fluid_int_setting (setting); + } + + fluid_rec_mutex_unlock (settings->mutex); + + return retval; +} + +/** + * 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 1 if the value exists, 0 otherwise + */ +int +fluid_settings_getint(fluid_settings_t* settings, const char *name, int* val) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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_return_val_if_fail (val != NULL, 0); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_INT_TYPE)) { + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + *val = setting->value; + retval = 1; + } + + 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 + */ +void +fluid_settings_getint_range(fluid_settings_t* settings, const char *name, + int* min, int* max) +{ + fluid_setting_node_t *node; + + fluid_return_if_fail (settings != NULL); + fluid_return_if_fail (name != NULL); + fluid_return_if_fail (name[0] != '\0'); + fluid_return_if_fail (min != NULL); + fluid_return_if_fail (max != NULL); + + fluid_rec_mutex_lock (settings->mutex); + + if (fluid_settings_get(settings, name, &node) + && (node->type == FLUID_INT_TYPE)) { + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + *min = setting->min; + *max = setting->max; + } + + fluid_rec_mutex_unlock (settings->mutex); +} + +/** + * Get the default value of an integer setting. + * + * @param settings a settings object + * @param name a setting's name + * @return the setting's default integer value it it exists, zero otherwise + */ +int +fluid_settings_getint_default(fluid_settings_t* settings, const char *name) +{ + fluid_setting_node_t *node; + int retval = 0; + + 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) + && (node->type == FLUID_INT_TYPE)) { + fluid_int_setting_t* setting = (fluid_int_setting_t*) node; + retval = setting->def; + } + + 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 \a 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) || node->type != FLUID_STR_TYPE) + { + fluid_rec_mutex_unlock (settings->mutex); /* -- unlock */ + return; + } + + setting = (fluid_str_setting_t*)node; + + /* 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, (char *)name, (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) && node->type == FLUID_STR_TYPE) + count = fluid_list_size (((fluid_str_setting_t *)node)->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 \a name or not a string setting). Free the string when finished with it. + * @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_str_setting_t *setting; + fluid_list_t *p, *newlist = NULL; + int 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) || node->type != FLUID_STR_TYPE) + { + fluid_rec_mutex_unlock (settings->mutex); /* -- unlock */ + return (NULL); + } + + setting = (fluid_str_setting_t*)node; + + /* Duplicate option list, count options and get total string length */ + for (p = setting->options, count = 0, len = 0; p; p = p->next, count++) + { + option = fluid_list_get (p); + + if (option) + { + newlist = fluid_list_append (newlist, option); + len += strlen (option); + } + } + + if (count > 1) len += (count - 1) * 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; + int pathlen; + char *s; + + pathlen = 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(((fluid_set_setting_t *)value)->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 \a 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; + int r; + + 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) + { + r = fluid_settings_get (settings, (char *)(p->data), &node); + if (r && node) (*func) (data, (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 */ +} diff --git a/libs/fluidsynth/src/fluid_settings.h b/libs/fluidsynth/src/fluid_settings.h new file mode 100644 index 0000000000..0eb1f97286 --- /dev/null +++ b/libs/fluidsynth/src/fluid_settings.h @@ -0,0 +1,56 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + + +/** returns 1 if the option was added, 0 otherwise */ +int fluid_settings_add_option(fluid_settings_t* settings, const char* name, const char* s); + +/** returns 1 if the option was added, 0 otherwise */ +int fluid_settings_remove_option(fluid_settings_t* settings, const char* name, const char* s); + + +typedef int (*fluid_num_update_t)(void* data, const char* name, double value); +typedef int (*fluid_str_update_t)(void* data, const char* name, const char* value); +typedef int (*fluid_int_update_t)(void* data, const char* name, int value); + +/** returns 0 if the value has been registered correctly, non-zero + otherwise */ +int fluid_settings_register_str(fluid_settings_t* settings, char* name, char* def, int hints, + fluid_str_update_t fun, void* data); + +/** returns 0 if the value has been registered correctly, non-zero + otherwise */ +int fluid_settings_register_num(fluid_settings_t* settings, char* name, double def, + double min, double max, int hints, + fluid_num_update_t fun, void* data); + +/** returns 0 if the value has been registered correctly, non-zero + otherwise */ +int fluid_settings_register_int(fluid_settings_t* settings, char* name, int def, + int min, int max, int hints, + fluid_int_update_t fun, void* data); + + +#endif /* _FLUID_SETTINGS_H */ diff --git a/libs/fluidsynth/src/fluid_sfont.h b/libs/fluidsynth/src/fluid_sfont.h new file mode 100644 index 0000000000..51e941e440 --- /dev/null +++ b/libs/fluidsynth/src/fluid_sfont.h @@ -0,0 +1,68 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + + +/* + * 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 delete_fluid_sfont(_sf) ( ((_sf) && (_sf)->free)? (*(_sf)->free)(_sf) : 0) +#define fluid_sfont_get_name(_sf) (*(_sf)->get_name)(_sf) +#define fluid_sfont_get_preset(_sf,_bank,_prenum) (*(_sf)->get_preset)(_sf,_bank,_prenum) +#define fluid_sfont_iteration_start(_sf) (*(_sf)->iteration_start)(_sf) +#define fluid_sfont_iteration_next(_sf,_pr) (*(_sf)->iteration_next)(_sf,_pr) +#define fluid_sfont_get_data(_sf) (_sf)->data +#define fluid_sfont_set_data(_sf,_p) { (_sf)->data = (void*) (_p); } + + +#define delete_fluid_preset(_preset) \ + { if ((_preset) && (_preset)->free) { (*(_preset)->free)(_preset); }} + +#define fluid_preset_get_data(_preset) (_preset)->data +#define fluid_preset_set_data(_preset,_p) { (_preset)->data = (void*) (_p); } +#define fluid_preset_get_name(_preset) (*(_preset)->get_name)(_preset) +#define fluid_preset_get_banknum(_preset) (*(_preset)->get_banknum)(_preset) +#define fluid_preset_get_num(_preset) (*(_preset)->get_num)(_preset) + +#define fluid_preset_noteon(_preset,_synth,_ch,_key,_vel) \ + (*(_preset)->noteon)(_preset,_synth,_ch,_key,_vel) + +#define fluid_preset_notify(_preset,_reason,_chan) \ + { if ((_preset) && (_preset)->notify) { (*(_preset)->notify)(_preset,_reason,_chan); }} + + +#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); + + + +#endif /* _PRIV_FLUID_SFONT_H */ diff --git a/libs/fluidsynth/src/fluid_synth.c b/libs/fluidsynth/src/fluid_synth.c new file mode 100644 index 0000000000..a12260c7b0 --- /dev/null +++ b/libs/fluidsynth/src/fluid_synth.c @@ -0,0 +1,5053 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + +#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_hash.h" +#include "fluid_defsfont.h" + +#ifdef TRAP_ON_FPE +#define _GNU_SOURCE +#include + +/* seems to not be declared in fenv.h */ +extern int feenableexcept (int excepts); +#endif + +static void fluid_synth_init(void); + +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_update_device_id (fluid_synth_t *synth, char *name, + int value); +static int fluid_synth_update_overflow (fluid_synth_t *synth, char *name, + fluid_real_t value); +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_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_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 fluid_preset_t* +fluid_synth_get_preset(fluid_synth_t* synth, unsigned int sfontnum, + unsigned int banknum, unsigned int prognum); +static fluid_preset_t* +fluid_synth_get_preset_by_sfont_name(fluid_synth_t* synth, const char *sfontname, + unsigned int banknum, unsigned int prognum); + +static void fluid_synth_update_presets(fluid_synth_t* synth); +static int fluid_synth_update_sample_rate(fluid_synth_t* synth, + char* name, double value); +static int fluid_synth_update_gain(fluid_synth_t* synth, + char* name, double value); +static void fluid_synth_update_gain_LOCAL(fluid_synth_t* synth); +static int fluid_synth_update_polyphony(fluid_synth_t* synth, + char* name, int value); +static int fluid_synth_update_polyphony_LOCAL(fluid_synth_t* synth, int new_polyphony); +static void init_dither(void); +static inline int roundi (float x); +static int fluid_synth_render_blocks(fluid_synth_t* synth, int blockcount); +//static void fluid_synth_core_thread_func (void* data); +//static FLUID_INLINE void fluid_synth_process_event_queue_LOCAL +// (fluid_synth_t *synth, fluid_event_queue_t *queue); +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 fluid_sfont_info_t *new_fluid_sfont_info (fluid_synth_t *synth, + fluid_sfont_t *sfont); +static int fluid_synth_sfunload_callback(void* data, unsigned int msec); +static void fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t* synth, + int chan, int key); +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, int absolute); +static void fluid_synth_stop_LOCAL (fluid_synth_t *synth, unsigned int id); + + + +/*************************************************************** + * + * GLOBAL + */ + +/* has the synth module been initialized? */ +static int fluid_synth_initialized = 0; +static void fluid_synth_init(void); +static void init_dither(void); + +/* 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. + */ + +fluid_mod_t default_vel2att_mod; /* SF2.01 section 8.4.1 */ +fluid_mod_t default_vel2filter_mod; /* SF2.01 section 8.4.2 */ +fluid_mod_t default_at2viblfo_mod; /* SF2.01 section 8.4.3 */ +fluid_mod_t default_mod2viblfo_mod; /* SF2.01 section 8.4.4 */ +fluid_mod_t default_att_mod; /* SF2.01 section 8.4.5 */ +fluid_mod_t default_pan_mod; /* SF2.01 section 8.4.6 */ +fluid_mod_t default_expr_mod; /* SF2.01 section 8.4.7 */ +fluid_mod_t default_reverb_mod; /* SF2.01 section 8.4.8 */ +fluid_mod_t default_chorus_mod; /* SF2.01 section 8.4.9 */ +fluid_mod_t default_pitch_bend_mod; /* SF2.01 section 8.4.10 */ + +/* reverb presets */ +static 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 }, + { NULL, 0.0f, 0.0f, 0.0f, 0.0f } +}; + + +/*************************************************************** + * + * INITIALIZATION & UTILITIES + */ + +static void fluid_synth_register_overflow(fluid_settings_t* settings, + fluid_num_update_t update_func, + void* update_data) +{ + fluid_settings_register_num(settings, "synth.overflow.percussion", + 4000, -10000, 10000, 0, update_func, update_data); + fluid_settings_register_num(settings, "synth.overflow.sustained", + -1000, -10000, 10000, 0, update_func, update_data); + fluid_settings_register_num(settings, "synth.overflow.released", + -2000, -10000, 10000, 0, update_func, update_data); + fluid_settings_register_num(settings, "synth.overflow.age", + 1000, -10000, 10000, 0, update_func, update_data); + fluid_settings_register_num(settings, "synth.overflow.volume", + 500, -10000, 10000, 0, update_func, update_data); +} + +void fluid_synth_settings(fluid_settings_t* settings) +{ + fluid_settings_register_int(settings, "synth.verbose", 0, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.dump", 0, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.reverb.active", 1, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.chorus.active", 1, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.ladspa.active", 0, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.lock-memory", 1, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_str(settings, "midi.portname", "", 0, NULL, NULL); + + fluid_settings_register_str(settings, "synth.default-soundfont", + DEFAULT_SOUNDFONT, 0, NULL, NULL); + + fluid_settings_register_int(settings, "synth.polyphony", + 256, 1, 65535, 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.midi-channels", + 16, 16, 256, 0, NULL, NULL); + fluid_settings_register_num(settings, "synth.gain", + 0.2f, 0.0f, 10.0f, + 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.audio-channels", + 1, 1, 128, 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.audio-groups", + 1, 1, 128, 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.effects-channels", + 2, 2, 2, 0, NULL, NULL); + fluid_settings_register_num(settings, "synth.sample-rate", + 44100.0f, 8000.0f, 96000.0f, + 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.device-id", + 0, 0, 126, 0, NULL, NULL); + fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 256, 0, NULL, NULL); + + fluid_settings_register_int(settings, "synth.min-note-length", 10, 0, 65535, 0, NULL, NULL); + + fluid_settings_register_int(settings, "synth.threadsafe-api", 1, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + fluid_settings_register_int(settings, "synth.parallel-render", 1, 0, 1, + FLUID_HINT_TOGGLED, NULL, NULL); + + fluid_synth_register_overflow(settings, NULL, NULL); + + fluid_settings_register_str(settings, "synth.midi-bank-select", "gs", 0, NULL, NULL); + 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"); + +} + +/** + * 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; +} + +#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_synth_api_exit(synth); \ + return fail_value; \ + } \ + +#define FLUID_API_RETURN(return_value) \ + do { fluid_synth_api_exit(synth); \ + return return_value; } while (0) + +/* + * void fluid_synth_init + * + * Does all the initialization for this module. + */ +static void +fluid_synth_init(void) +{ + fluid_synth_initialized++; + +#ifdef TRAP_ON_FPE + /* Turn on floating point exception traps */ + feenableexcept (FE_DIVBYZERO | FE_UNDERFLOW | FE_OVERFLOW | FE_INVALID); +#endif + + fluid_conversion_config(); + + fluid_rvoice_dsp_config(); + + fluid_sys_config(); + + init_dither(); + + + /* 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, 960.0); /* 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, 1, /* 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, 7, /* 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, 960.0); /* Amount: 960 */ + + + + /* SF2.01 page 55 section 8.4.6 MIDI continuous controller 10 to Pan Position */ + fluid_mod_set_source1(&default_pan_mod, 10, /* 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 syas: "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, 11, /* 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, 960.0); /* Amount: 960 */ + + + + /* SF2.01 page 55 section 8.4.8: MIDI continuous controller 91 to Reverb send */ + fluid_mod_set_source1(&default_reverb_mod, 91, /* 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, 93, /* 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 ... */ + 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 */ + ); + fluid_mod_set_dest(&default_pitch_bend_mod, GEN_PITCH); /* Destination: Initial pitch */ + fluid_mod_set_amount(&default_pitch_bend_mod, 12700.0); /* Amount: 12700 cents */ +} + +static FLUID_INLINE unsigned int fluid_synth_get_ticks(fluid_synth_t* synth) +{ + if (synth->eventhandler->is_threadsafe) + return fluid_atomic_int_get(&synth->ticks_since_start); + else + return synth->ticks_since_start; +} + +static FLUID_INLINE void fluid_synth_add_ticks(fluid_synth_t* synth, int val) +{ + if (synth->eventhandler->is_threadsafe) + fluid_atomic_int_add((int*) &synth->ticks_since_start, val); + else + 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; + } + result->starttick = fluid_synth_get_ticks(synth); + result->isfinished = 0; + result->data = data; + result->callback = callback; + result->next = synth->sample_timers; + synth->sample_timers = result; + return result; +} + +int delete_fluid_sample_timer(fluid_synth_t* synth, fluid_sample_timer_t* timer) +{ + fluid_sample_timer_t** ptr = &synth->sample_timers; + while (*ptr) { + if (*ptr == timer) { + *ptr = timer->next; + FLUID_FREE(timer); + return FLUID_OK; + } + ptr = &((*ptr)->next); + } + FLUID_LOG(FLUID_ERR,"delete_fluid_sample_timer failed, no timer found"); + return FLUID_FAILED; +} + + +/*************************************************************** + * + * FLUID SYNTH + */ + +static FLUID_INLINE void +fluid_synth_update_mixer(fluid_synth_t* synth, void* 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(synth->eventhandler, method, + synth->eventhandler->mixer, + intparam, realparam); +} + + +/** + * Create new FluidSynth instance. + * @param settings Configuration parameters to use (used directly). + * @return New FluidSynth instance or NULL on error + * + * NOTE: The settings parameter is used directly and should not be modified + * or freed independently. + */ +fluid_synth_t* +new_fluid_synth(fluid_settings_t *settings) +{ + fluid_synth_t* synth; + fluid_sfloader_t* loader; + double gain; + int i, nbuf; + + /* initialize all the conversion tables and other stuff */ + if (fluid_synth_initialized == 0) { + 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)); + + 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.dump", &synth->dump); + + fluid_settings_getint(settings, "synth.polyphony", &synth->polyphony); + fluid_settings_getnum(settings, "synth.sample-rate", &synth->sample_rate); + 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_getnum(settings, "synth.gain", &gain); + synth->gain = gain; + fluid_settings_getint(settings, "synth.device-id", &synth->device_id); + fluid_settings_getint(settings, "synth.cpu-cores", &synth->cores); + + /* register the callbacks */ + fluid_settings_register_num(settings, "synth.sample-rate", + 44100.0f, 8000.0f, 96000.0f, 0, + (fluid_num_update_t) fluid_synth_update_sample_rate, synth); + fluid_settings_register_num(settings, "synth.gain", + 0.2f, 0.0f, 10.0f, 0, + (fluid_num_update_t) fluid_synth_update_gain, synth); + fluid_settings_register_int(settings, "synth.polyphony", + synth->polyphony, 1, 65535, 0, + (fluid_int_update_t) fluid_synth_update_polyphony, + synth); + fluid_settings_register_int(settings, "synth.device-id", + synth->device_id, 126, 0, 0, + (fluid_int_update_t) fluid_synth_update_device_id, synth); + + fluid_synth_register_overflow(settings, + (fluid_num_update_t) fluid_synth_update_overflow, 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; + } + + + /* The number of buffers is determined by the higher number of nr + * groups / nr audio channels. If LADSPA is unused, they should be + * the same. */ + nbuf = synth->audio_channels; + if (synth->audio_groups > nbuf) { + nbuf = synth->audio_groups; + } + + /* as soon as the synth is created it starts playing. */ + synth->state = FLUID_SYNTH_PLAYING; + synth->sfont_info = NULL; + synth->sfont_hash = new_fluid_hashtable (NULL, NULL); + synth->noteid = 0; + synth->ticks_since_start = 0; + synth->tuning = NULL; + fluid_private_init(synth->tuning_iter); + + /* Allocate event queue for rvoice mixer */ + fluid_settings_getint(settings, "synth.parallel-render", &i); + /* In an overflow situation, a new voice takes about 50 spaces in the queue! */ + synth->eventhandler = new_fluid_rvoice_eventhandler(i, synth->polyphony*64, + synth->polyphony, nbuf, synth->effects_channels, synth->sample_rate); + + if (synth->eventhandler == NULL) + goto error_recovery; + +#ifdef LADSPA + /* Create and initialize the Fx unit.*/ + synth->LADSPA_FxUnit = new_fluid_LADSPA_FxUnit(synth); + fluid_rvoice_mixer_set_ladspa(synth->eventhandler->mixer, synth->LADSPA_FxUnit); +#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; + } + 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; + } + for (i = 0; i < synth->nvoice; i++) { + synth->voice[i] = new_fluid_voice(synth->sample_rate); + if (synth->voice[i] == NULL) { + goto error_recovery; + } + } + + fluid_synth_set_sample_rate(synth, synth->sample_rate); + + fluid_synth_update_overflow(synth, "", 0.0f); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_polyphony, + synth->polyphony, 0.0f); + fluid_synth_set_reverb_on(synth, synth->with_reverb); + fluid_synth_set_chorus_on(synth, synth->with_chorus); + + synth->cur = FLUID_BUFSIZE; + synth->curmax = 0; + synth->dither_index = 0; + + synth->reverb_roomsize = FLUID_REVERB_DEFAULT_ROOMSIZE; + synth->reverb_damping = FLUID_REVERB_DEFAULT_DAMP; + synth->reverb_width = FLUID_REVERB_DEFAULT_WIDTH; + synth->reverb_level = FLUID_REVERB_DEFAULT_LEVEL; + + fluid_rvoice_eventhandler_push5(synth->eventhandler, + fluid_rvoice_mixer_set_reverb_params, + synth->eventhandler->mixer, + FLUID_REVMODEL_SET_ALL, synth->reverb_roomsize, + synth->reverb_damping, synth->reverb_width, + synth->reverb_level, 0.0f); + + /* Initialize multi-core variables if multiple cores enabled */ + if (synth->cores > 1) + { + int prio_level = 0; + fluid_settings_getint (synth->settings, "audio.realtime-prio", &prio_level); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_threads, + synth->cores-1, prio_level); + } + + synth->bank_select = FLUID_BANK_STYLE_GS; + if (fluid_settings_str_equal (settings, "synth.midi-bank-select", "gm") == 1) + synth->bank_select = FLUID_BANK_STYLE_GM; + else if (fluid_settings_str_equal (settings, "synth.midi-bank-select", "gs") == 1) + synth->bank_select = FLUID_BANK_STYLE_GS; + else if (fluid_settings_str_equal (settings, "synth.midi-bank-select", "xg") == 1) + synth->bank_select = FLUID_BANK_STYLE_XG; + else if (fluid_settings_str_equal (settings, "synth.midi-bank-select", "mma") == 1) + 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 + * @return FLUID_OK + * + * NOTE: Other users of a synthesizer instance, such as audio and MIDI drivers, + * should be deleted prior to freeing the FluidSynth instance. + */ +int +delete_fluid_synth(fluid_synth_t* synth) +{ + int i, k; + fluid_list_t *list; + fluid_sfont_info_t* sfont_info; +// fluid_event_queue_t* queue; + fluid_sfloader_t* loader; + + if (synth == NULL) { + return FLUID_OK; + } + + fluid_profiling_print(); + + /* 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; + fluid_voice_unlock_rvoice(voice); + fluid_voice_overflow_rvoice_finished(voice); + if (fluid_voice_is_playing(voice)) + fluid_voice_off(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); + + if (synth->eventhandler) + delete_fluid_rvoice_eventhandler(synth->eventhandler); + + /* delete all the SoundFonts */ + for (list = synth->sfont_info; list; list = fluid_list_next (list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + delete_fluid_sfont (sfont_info->sfont); + FLUID_FREE (sfont_info); + } + + delete_fluid_list(synth->sfont_info); + + + /* Delete the SoundFont info hash */ + if (synth->sfont_hash) delete_fluid_hashtable (synth->sfont_hash); + + + /* 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); + + + if (synth->channel != NULL) { + for (i = 0; i < synth->midi_channels; i++) { + if (synth->channel[i] != NULL) { + delete_fluid_channel(synth->channel[i]); + } + } + FLUID_FREE(synth->channel); + } + + if (synth->voice != NULL) { + for (i = 0; i < synth->nvoice; i++) { + if (synth->voice[i] != NULL) { + 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++) { + if (synth->tuning[i][k] != NULL) { + 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 Fx unit */ + fluid_LADSPA_shutdown(synth->LADSPA_FxUnit); + FLUID_FREE(synth->LADSPA_FxUnit); +#endif + + fluid_rec_mutex_destroy(synth->mutex); + + FLUID_FREE(synth); + + return FLUID_OK; +} + +/** + * 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. + */ +/* FIXME - The error messages are not thread-safe, yet. They are still stored + * in a global message buffer (see fluid_sys.c). */ +char* +fluid_synth_error(fluid_synth_t* synth) +{ + return fluid_error(); +} + +/** + * Send a note-on 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) + * @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); + + 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]; + + /* make 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 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); + + + return fluid_preset_noteon(channel->preset, synth, chan, key, vel); +} + +/** + * Send 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); + + 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) +{ + fluid_voice_t* voice; + int status = FLUID_FAILED; + int i; + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + if (_ON(voice) && (voice->chan == chan) && (voice->key == 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", + voice->chan, voice->key, 0, voice->id, + (fluid_curtime() - synth->start) / 1000.0f, + used_voices); + } /* if verbose */ + + fluid_voice_noteoff(voice); + status = FLUID_OK; + } /* if voice on */ + } /* for all voices */ + return status; +} + +/* Damp 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_voice_t* voice; + int i; + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + + if ((voice->chan == chan) && _SUSTAINED(voice)) + fluid_voice_release(voice); + } + + return FLUID_OK; +} + +/* Damp 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_voice_t* voice; + int i; + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + + if ((voice->chan == chan) && _HELD_BY_SOSTENUTO(voice)) + fluid_voice_release(voice); + } + + return FLUID_OK; +} + + +/** + * Send a MIDI controller event 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 val MIDI controller value (0-127) + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_cc(fluid_synth_t* synth, int chan, int num, int val) +{ + int result; + 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); + + if (synth->verbose) + FLUID_LOG(FLUID_INFO, "cc\t%d\t%d\t%d", chan, num, val); + + fluid_channel_set_cc (synth->channel[chan], num, val); + result = fluid_synth_cc_LOCAL (synth, chan, num); + FLUID_API_RETURN(result); +} + +/* Local synthesis thread variant of MIDI CC set function. */ +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 SUSTAIN_SWITCH: + /* 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: + /* 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: + fluid_channel_set_bank_msb (chan, value & 0x7F); + break; + case BANK_SELECT_LSB: + fluid_channel_set_bank_lsb (chan, value & 0x7F); + break; + case ALL_NOTES_OFF: + fluid_synth_all_notes_off_LOCAL (synth, channum); + break; + case ALL_SOUND_OFF: + fluid_synth_all_sounds_off_LOCAL (synth, channum); + break; + case ALL_CTRL_OFF: + fluid_channel_init_ctrl (chan, 1); + fluid_synth_modulate_voices_all_LOCAL (synth, channum); + break; + case DATA_ENTRY_MSB: + { + 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, FALSE); + } + + 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 (+/- 50 cents, 8192 = center) */ + fluid_synth_set_gen_LOCAL (synth, channum, GEN_FINETUNE, + (data - 8192) / 8192.0 * 50.0, FALSE); + 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, FALSE); + 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: + fluid_channel_set_cc (chan, NRPN_LSB, 0); + chan->nrpn_select = 0; + chan->nrpn_active = 1; + break; + case NRPN_LSB: + /* 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: + case RPN_LSB: + chan->nrpn_active = 0; + break; + default: + 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); + + *pval = fluid_channel_get_cc (synth->channel[chan], num); + FLUID_API_RETURN(FLUID_OK); +} + +/* + * Handler for synth.device-id setting. + */ +static int +fluid_synth_update_device_id (fluid_synth_t *synth, char *name, int value) +{ + fluid_synth_api_enter(synth); + synth->device_id = value; + fluid_synth_api_exit(synth); + return 0; +} + +/** + * 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 + */ +/* SYSEX format (0xF0 and 0xF7 not passed to this function): + * Non-realtime: 0xF0 0x7E [BODY] 0xF7 + * Realtime: 0xF0 0x7F [BODY] 0xF7 + * Tuning messages: 0xF0 0x7E/0x7F 0x08 [BODY] 0xF7 + */ +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) + && 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); + } + 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]; + int note, frac, frac2; + uint8 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; + FLUID_STRNCPY (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[5] * 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; +} + +/** + * Turn off all notes on a MIDI channel (put 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 + 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 +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 (_PLAYING(voice) && ((-1 == chan) || (chan == voice->chan))) + fluid_voice_noteoff(voice); + } + return FLUID_OK; +} + +/** + * Immediately stop all notes on a 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 + 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 (_PLAYING(voice) && ((-1 == chan) || (chan == voice->chan))) + 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 and + * resets controllers. + * @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) +{ + fluid_voice_t* voice; + int i; + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + + if (_PLAYING(voice)) + fluid_voice_off(voice); + } + + for (i = 0; i < synth->midi_channels; i++) + fluid_channel_reset(synth->channel[i]); + + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_reset_fx, 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 (voice->chan == 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 (voice->chan == 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); + + 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 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); + + 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) +{ + fluid_return_val_if_fail (ppitch_bend != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + *ppitch_bend = fluid_channel_get_pitch_bend (synth->channel[chan]); + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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); + + 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) +{ + fluid_return_val_if_fail (pval != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + *pval = fluid_channel_get_pitch_wheel_sensitivity (synth->channel[chan]); + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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. + * + * NOTE: The returned preset has been allocated, caller owns it and should + * free it when finished using it. + */ +static fluid_preset_t* +fluid_synth_get_preset(fluid_synth_t* synth, unsigned int sfontnum, + unsigned int banknum, unsigned int prognum) +{ + fluid_preset_t *preset = NULL; + fluid_sfont_info_t *sfont_info; + fluid_list_t *list; + + /* 128 indicates an "unset" operation" */ + if (prognum == FLUID_UNSET_PROGRAM) return NULL; + + for (list = synth->sfont_info; list; list = fluid_list_next (list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + + if (fluid_sfont_get_id (sfont_info->sfont) == sfontnum) + { + preset = fluid_sfont_get_preset (sfont_info->sfont, + banknum - sfont_info->bankofs, prognum); + if (preset) sfont_info->refcount++; /* Add reference to SoundFont */ + break; + } + } + + return preset; +} + +/* Get a preset by SoundFont name, bank and program. + * Returns preset pointer or NULL. + * + * NOTE: The returned preset has been allocated, caller owns it and should + * free it when finished using it. + */ +static fluid_preset_t* +fluid_synth_get_preset_by_sfont_name(fluid_synth_t* synth, const char *sfontname, + unsigned int banknum, unsigned int prognum) +{ + fluid_preset_t *preset = NULL; + fluid_sfont_info_t *sfont_info; + fluid_list_t *list; + + for (list = synth->sfont_info; list; list = fluid_list_next (list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + + if (FLUID_STRCMP (fluid_sfont_get_name (sfont_info->sfont), sfontname) == 0) + { + preset = fluid_sfont_get_preset (sfont_info->sfont, + banknum - sfont_info->bankofs, prognum); + if (preset) sfont_info->refcount++; /* Add reference to SoundFont */ + break; + } + } + + return preset; +} + +/* Find a preset by bank and program numbers. + * Returns preset pointer or NULL. + * + * NOTE: The returned preset has been allocated, caller owns it and should + * free it when finished using it. */ +fluid_preset_t* +fluid_synth_find_preset(fluid_synth_t* synth, unsigned int banknum, + unsigned int prognum) +{ + fluid_preset_t *preset = NULL; + fluid_sfont_info_t *sfont_info; + fluid_list_t *list; + + for (list = synth->sfont_info; list; list = fluid_list_next (list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + + preset = fluid_sfont_get_preset (sfont_info->sfont, + banknum - sfont_info->bankofs, prognum); + if (preset) + { + sfont_info->refcount++; /* Add reference to SoundFont */ + break; + } + } + + return preset; +} + +/** + * 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 + */ +/* FIXME - Currently not real-time safe, due to preset allocation and mutex lock, + * and may be called from within synthesis context. */ + +/* 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_return_val_if_fail (prognum >= 0 && prognum <= 128, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(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 (subst_bank == DRUM_INST_BANK) { + subst_prog = 0; + 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 + */ +int +fluid_synth_bank_select(fluid_synth_t* synth, int chan, unsigned int bank) +{ + fluid_return_val_if_fail (bank <= 16383, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + fluid_channel_set_sfont_bank_prog (synth->channel[chan], -1, bank, -1); + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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 + */ +int +fluid_synth_sfont_select(fluid_synth_t* synth, int chan, unsigned int sfont_id) +{ + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + fluid_channel_set_sfont_bank_prog(synth->channel[chan], sfont_id, -1, -1); + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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) +{ + int result; + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + result = fluid_synth_program_change (synth, chan, FLUID_UNSET_PROGRAM); + FLUID_API_RETURN(result); +} + +/** + * 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, unsigned int* sfont_id, + unsigned int* bank_num, unsigned int* preset_num) +{ + 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); + + channel = synth->channel[chan]; + fluid_channel_get_sfont_bank_prog(channel, (int *)sfont_id, (int *)bank_num, + (int *)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; + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * 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, unsigned int sfont_id, + unsigned int bank_num, unsigned int preset_num) +{ + fluid_preset_t* preset = NULL; + fluid_channel_t* channel; + int result; + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + channel = synth->channel[chan]; + + /* ++ Allocate preset */ + 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); +} + +/** + * 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, unsigned int bank_num, + unsigned 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); + + channel = synth->channel[chan]; + + /* ++ Allocate preset */ + 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); + } +} + +/* Handler for synth.gain setting. */ +static int +fluid_synth_update_sample_rate(fluid_synth_t* synth, char* name, double value) +{ + fluid_synth_set_sample_rate(synth, (float) value); + return 0; +} + +/** + * Set sample rate of the synth. + * NOTE: This function is currently experimental and should only be + * used when no voices or notes are active, and before any rendering calls. + * @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) +{ + int i; + fluid_return_if_fail (synth != NULL); + fluid_synth_api_enter(synth); + fluid_clip (sample_rate, 8000.0f, 96000.0f); + synth->sample_rate = sample_rate; + + fluid_settings_getint(synth->settings, "synth.min-note-length", &i); + synth->min_note_length_ticks = (unsigned int) (i*synth->sample_rate/1000.0f); + + for (i=0; i < synth->polyphony; i++) + fluid_voice_set_output_rate(synth->voice[i], sample_rate); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_samplerate, + 0, sample_rate); + fluid_synth_api_exit(synth); +} + + +/* Handler for synth.gain setting. */ +static int +fluid_synth_update_gain(fluid_synth_t* synth, char* name, double value) +{ + fluid_synth_set_gain(synth, (float) value); + return 0; +} + +/** + * 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 (_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 int +fluid_synth_update_polyphony(fluid_synth_t* synth, char* name, int value) +{ + fluid_synth_set_polyphony(synth, value); + return 0; +} + +/** + * 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->sample_rate); + if (synth->voice[i] == NULL) + return FLUID_FAILED; + } + 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 (_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); +} + +/** + * Get current number of active voices. + * @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. + */ +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 this number of frames at a time. Defaults to 64 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. + * @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_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 audio buffers. + * @param synth FluidSynth instance + * @param len Count of audio frames to synthesize + * @param left Array of floats to store left channel of audio (len in size) + * @param right Array of floats to store right channel of audio (len in size) + * @param fx_left Not currently used + * @param fx_right Not currently used + * @return FLUID_OK on success, FLUID_FAIL otherwise + * + * NOTE: Should only be called from synthesis thread. + */ +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; + fluid_real_t** right_in; + double time = fluid_utime(); + int i, num, available, count; +#ifdef WITH_FLOAT + int bytes; +#endif + float cpu_load; + + if (!synth->eventhandler->is_threadsafe) + fluid_synth_api_enter(synth); + + /* 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); + + 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] + synth->cur, bytes); + FLUID_MEMCPY(right[i], right_in[i] + synth->cur, bytes); +#else //WITH_FLOAT + int j; + for (j = 0; j < num; j++) { + left[i][j] = (float) left_in[i][j + synth->cur]; + right[i][j] = (float) right_in[i][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); + + 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], bytes); + FLUID_MEMCPY(right[i] + count, right_in[i], bytes); +#else //WITH_FLOAT + int j; + for (j = 0; j < num; j++) { + left[i][j + count] = (float) left_in[i][j]; + right[i][j + count] = (float) right_in[i][j]; + } +#endif //WITH_FLOAT + } + + count += num; + } + + synth->cur = num; + + time = fluid_utime() - time; + cpu_load = 0.5 * (synth->cpu_load + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set (&synth->cpu_load, cpu_load); + + if (!synth->eventhandler->is_threadsafe) + fluid_synth_api_exit(synth); + + return FLUID_OK; +} + +/** + * Synthesize floating point audio to audio buffers. + * @param synth FluidSynth instance + * @param len Count of audio frames to synthesize + * @param nin Ignored + * @param in Ignored + * @param nout Count of arrays in 'out' + * @param out Array of arrays to store audio to + * @return FLUID_OK on success, FLUID_FAIL otherwise + * + * This function implements the default interface defined in fluidsynth/audio.h. + * NOTE: Should only be called from synthesis thread. + */ +/* + * FIXME: Currently if nout != 2 memory allocation will occur! + */ +int +fluid_synth_process(fluid_synth_t* synth, int len, int nin, float** in, + int nout, float** out) +{ + if (nout==2) { + return fluid_synth_write_float(synth, len, out[0], 0, 1, out[1], 0, 1); + } + else { + float **left, **right; + int i; + left = FLUID_ARRAY(float*, nout/2); + right = FLUID_ARRAY(float*, nout/2); + for(i=0; ieventhandler->is_threadsafe) + fluid_synth_api_enter(synth); + + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); + l = synth->cur; + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + + for (i = 0, j = loff, k = roff; i < len; i++, l++, j += lincr, k += rincr) { + /* fill up the buffers as needed */ + if (l >= synth->curmax) { + int blocksleft = (len-i+FLUID_BUFSIZE-1) / FLUID_BUFSIZE; + synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + + l = 0; + } + + left_out[j] = (float) left_in[0][l]; + right_out[k] = (float) right_in[0][l]; + } + + synth->cur = l; + + time = fluid_utime() - time; + cpu_load = 0.5 * (synth->cpu_load + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set (&synth->cpu_load, cpu_load); + + if (!synth->eventhandler->is_threadsafe) + fluid_synth_api_exit(synth); + fluid_profile(FLUID_PROF_WRITE, prof_ref); + + return FLUID_OK; +} + +#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 inline int +roundi (float x) +{ + if (x >= 0.0f) + return (int)(x+0.5f); + else + return (int)(x-0.5f); +} + +/** + * 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_FAIL 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: 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) +{ + int i, j, k, cur; + signed short* left_out = (signed short*) lout; + signed short* right_out = (signed short*) rout; + fluid_real_t** left_in; + fluid_real_t** right_in; + fluid_real_t left_sample; + fluid_real_t right_sample; + double time = fluid_utime(); + int di; + //double prof_ref_on_block; + float cpu_load; + fluid_profile_ref_var (prof_ref); + + if (!synth->eventhandler->is_threadsafe) + fluid_synth_api_enter(synth); + + fluid_rvoice_mixer_set_mix_fx(synth->eventhandler->mixer, 1); + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + + cur = synth->cur; + di = synth->dither_index; + + for (i = 0, j = loff, k = roff; i < len; i++, cur++, j += lincr, k += rincr) { + + /* fill up the buffers as needed */ + if (cur >= synth->curmax) { + int blocksleft = (len-i+FLUID_BUFSIZE-1) / FLUID_BUFSIZE; + //prof_ref_on_block = fluid_profile_ref(); + synth->curmax = FLUID_BUFSIZE * fluid_synth_render_blocks(synth, blocksleft); + fluid_rvoice_mixer_get_bufs(synth->eventhandler->mixer, &left_in, &right_in); + cur = 0; + + //fluid_profile(FLUID_PROF_ONE_BLOCK, prof_ref_on_block); + } + + left_sample = roundi (left_in[0][cur] * 32766.0f + rand_table[0][di]); + right_sample = roundi (right_in[0][cur] * 32766.0f + rand_table[1][di]); + + di++; + if (di >= DITHER_SIZE) di = 0; + + /* digital clipping */ + if (left_sample > 32767.0f) left_sample = 32767.0f; + if (left_sample < -32768.0f) left_sample = -32768.0f; + if (right_sample > 32767.0f) right_sample = 32767.0f; + if (right_sample < -32768.0f) right_sample = -32768.0f; + + left_out[j] = (signed short) left_sample; + right_out[k] = (signed short) right_sample; + } + + synth->cur = cur; + synth->dither_index = di; /* keep dither buffer continous */ + + fluid_profile(FLUID_PROF_WRITE, prof_ref); + + time = fluid_utime() - time; + cpu_load = 0.5 * (synth->cpu_load + time * synth->sample_rate / len / 10000.0); + fluid_atomic_float_set (&synth->cpu_load, cpu_load); + + if (!synth->eventhandler->is_threadsafe) + fluid_synth_api_exit(synth); + + return 0; +} + +/** + * 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, float* lin, float* rin, + void* lout, int loff, int lincr, + void* rout, int roff, int rincr) +{ + int i, j, k; + signed short* left_out = (signed short*) lout; + signed short* right_out = (signed short*) rout; + fluid_real_t left_sample; + fluid_real_t right_sample; + 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_sample = roundi (lin[i] * 32766.0f + rand_table[0][di]); + right_sample = roundi (rin[i] * 32766.0f + rand_table[1][di]); + + di++; + if (di >= DITHER_SIZE) di = 0; + + /* digital clipping */ + if (left_sample > 32767.0f) left_sample = 32767.0f; + if (left_sample < -32768.0f) left_sample = -32768.0f; + if (right_sample > 32767.0f) right_sample = 32767.0f; + if (right_sample < -32768.0f) right_sample = -32768.0f; + + left_out[j] = (signed short) left_sample; + right_out[k] = (signed short) right_sample; + } + + *dither_index = di; /* keep dither buffer continous */ + + fluid_profile(FLUID_PROF_WRITE, prof_ref); +} + +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_off(synth->voice[j]); + break; + } + else if (synth->voice[j]->overflow_rvoice == fv) { + fluid_voice_overflow_rvoice_finished(synth->voice[j]); + 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; + 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); + + for (i=0; i < blockcount; i++) { + fluid_sample_timer_process(synth); + fluid_synth_add_ticks(synth, FLUID_BUFSIZE); + 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); + return blockcount; +} + + +static int fluid_synth_update_overflow (fluid_synth_t *synth, char *name, + fluid_real_t value) +{ + double d; + fluid_synth_api_enter(synth); + + fluid_settings_getnum(synth->settings, "synth.overflow.percussion", &d); + synth->overflow.percussion = d; + fluid_settings_getnum(synth->settings, "synth.overflow.released", &d); + synth->overflow.released = d; + fluid_settings_getnum(synth->settings, "synth.overflow.sustained", &d); + synth->overflow.sustained = d; + fluid_settings_getnum(synth->settings, "synth.overflow.volume", &d); + synth->overflow.volume = d; + fluid_settings_getnum(synth->settings, "synth.overflow.age", &d); + synth->overflow.age = d; + + FLUID_API_RETURN(0); +} + + +/* Selects a voice for killing. */ +static fluid_voice_t* +fluid_synth_free_voice_by_kill_LOCAL(fluid_synth_t* synth) +{ + int i; + fluid_real_t best_prio = OVERFLOW_PRIO_CANNOT_KILL-1; + fluid_real_t 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 ", + voice->id, best_voice_index, voice->chan, voice->key); + 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) +{ + int i, k; + fluid_voice_t* voice = NULL; + fluid_channel_t* channel = NULL; + unsigned int ticks; + + fluid_return_val_if_fail (sample != NULL, NULL); + FLUID_API_ENTRY_CHAN(NULL); + + /* 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); + FLUID_API_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); + } + + if (chan >= 0) { + channel = synth->channel[chan]; + } + + if (fluid_voice_init (voice, sample, channel, key, vel, + synth->storeid, ticks, synth->gain) != FLUID_OK) { + FLUID_LOG(FLUID_WARN, "Failed to initialize voice"); + FLUID_API_RETURN(NULL); + } + + /* add the default modulators to the synthesis process. */ + fluid_voice_add_mod(voice, &default_vel2att_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.1 */ + fluid_voice_add_mod(voice, &default_vel2filter_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.2 */ + fluid_voice_add_mod(voice, &default_at2viblfo_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.3 */ + fluid_voice_add_mod(voice, &default_mod2viblfo_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.4 */ + fluid_voice_add_mod(voice, &default_att_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.5 */ + fluid_voice_add_mod(voice, &default_pan_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.6 */ + fluid_voice_add_mod(voice, &default_expr_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.7 */ + fluid_voice_add_mod(voice, &default_reverb_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.8 */ + fluid_voice_add_mod(voice, &default_chorus_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.9 */ + fluid_voice_add_mod(voice, &default_pitch_bend_mod, FLUID_VOICE_DEFAULT); /* SF2.01 $8.4.10 */ + + FLUID_API_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 = _GEN(new_voice,GEN_EXCLUSIVECLASS); + fluid_voice_t* existing_voice; + 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++) { + 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 (_PLAYING(existing_voice) + && existing_voice->chan == new_voice->chan + && (int)_GEN (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 */ + if (synth->eventhandler->is_threadsafe) + fluid_voice_lock_rvoice(voice); + fluid_rvoice_eventhandler_add_rvoice(synth->eventhandler, voice->rvoice); + fluid_synth_api_exit(synth); +} + +/** + * Add a SoundFont loader interface. + * @param synth FluidSynth instance + * @param loader Loader API structure, used directly and should remain allocated + * as long as the synth instance is used. + * + * 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) +{ + gboolean sfont_already_loaded; + + fluid_return_if_fail (synth != NULL); + fluid_return_if_fail (loader != NULL); + fluid_synth_api_enter(synth); + sfont_already_loaded = synth->sfont_info != NULL; + if (!sfont_already_loaded) + 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 SoundFont instance + * @param filename File to load + * @param reset_presets TRUE to re-assign presets for all MIDI channels + * @return SoundFont ID on success, FLUID_FAILED on error + */ +int +fluid_synth_sfload(fluid_synth_t* synth, const char* filename, int reset_presets) +{ + fluid_sfont_info_t *sfont_info; + fluid_sfont_t *sfont; + fluid_list_t *list; + fluid_sfloader_t *loader; + unsigned 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); + + /* 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_info = new_fluid_sfont_info (synth, sfont); + + if (!sfont_info) + { + delete_fluid_sfont (sfont); + FLUID_API_RETURN(FLUID_FAILED); + } + + sfont->id = sfont_id = ++synth->sfont_id; + synth->sfont_info = fluid_list_prepend(synth->sfont_info, sfont_info); /* prepend to list */ + fluid_hashtable_insert (synth->sfont_hash, sfont, sfont_info); /* Hash sfont->sfont_info */ + + /* reset the presets for all channels if requested */ + if (reset_presets) fluid_synth_program_reset(synth); + + FLUID_API_RETURN((int)sfont_id); + } + } + + FLUID_LOG(FLUID_ERR, "Failed to load SoundFont \"%s\"", filename); + FLUID_API_RETURN(FLUID_FAILED); +} + +/* Create a new SoundFont info structure, free with FLUID_FREE */ +static fluid_sfont_info_t * +new_fluid_sfont_info (fluid_synth_t *synth, fluid_sfont_t *sfont) +{ + fluid_sfont_info_t *sfont_info; + + sfont_info = FLUID_NEW (fluid_sfont_info_t); + + if (!sfont_info) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + sfont_info->sfont = sfont; + sfont_info->synth = synth; + sfont_info->refcount = 1; /* Start with refcount of 1 for owning synth */ + sfont_info->bankofs = 0; + + return (sfont_info); +} + +/** + * Unload a SoundFont. + * @param synth SoundFont instance + * @param id ID of SoundFont to unload + * @param reset_presets TRUE to re-assign presets for all MIDI channels + * @return FLUID_OK on success, FLUID_FAILED on error + */ +int +fluid_synth_sfunload(fluid_synth_t* synth, unsigned int id, int reset_presets) +{ + fluid_sfont_info_t *sfont_info = 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_info; list; list = fluid_list_next(list)) { + sfont_info = (fluid_sfont_info_t*) fluid_list_get(list); + + if (fluid_sfont_get_id (sfont_info->sfont) == id) + { + synth->sfont_info = fluid_list_remove (synth->sfont_info, sfont_info); + 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_info list's reference to SoundFont */ + fluid_synth_sfont_unref (synth, sfont_info->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_sfont_info_t *sfont_info; + int refcount = 0; + + sfont_info = fluid_hashtable_lookup (synth->sfont_hash, sfont); + + if (sfont_info) + { + sfont_info->refcount--; /* -- Remove the sfont_info list's reference */ + refcount = sfont_info->refcount; + + if (refcount == 0) /* Remove SoundFont from hash if no more references */ + fluid_hashtable_remove (synth->sfont_hash, sfont_info->sfont); + } + + fluid_return_if_fail (sfont_info != NULL); /* Shouldn't happen, programming error if so */ + + if (refcount == 0) /* No more references? - Attempt delete */ + { + if (delete_fluid_sfont (sfont_info->sfont) == 0) /* SoundFont loader can block SoundFont unload */ + { + FLUID_FREE (sfont_info); + FLUID_LOG (FLUID_DBG, "Unloaded SoundFont"); + } /* spin off a timer thread to unload the sfont later (SoundFont loader blocked unload) */ + else new_fluid_timer (100, fluid_synth_sfunload_callback, sfont_info, TRUE, TRUE, FALSE); + } +} + +/* 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_info_t *sfont_info = (fluid_sfont_info_t *)data; + + if (delete_fluid_sfont (sfont_info->sfont) == 0) + { + FLUID_FREE (sfont_info); + 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 SoundFont 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, unsigned int id) +{ + char filename[1024]; + fluid_sfont_info_t *sfont_info, *old_sfont_info; + fluid_sfont_t* sfont; + fluid_sfloader_t* loader; + fluid_list_t *list; + int index; + + 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_info, index = 0; list; list = fluid_list_next (list), index++) { + old_sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + if (fluid_sfont_get_id (old_sfont_info->sfont) == id) break; + } + + if (!list) { + FLUID_LOG(FLUID_ERR, "No SoundFont with id = %d", id); + FLUID_API_RETURN(FLUID_FAILED); + } + + /* keep a copy of the SoundFont's filename */ + FLUID_STRCPY (filename, fluid_sfont_get_name (old_sfont_info->sfont)); + + if (fluid_synth_sfunload (synth, id, FALSE) != FLUID_OK) + FLUID_API_RETURN(FLUID_FAILED); + + /* 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_info = new_fluid_sfont_info (synth, sfont); + + if (!sfont_info) + { + delete_fluid_sfont (sfont); + FLUID_API_RETURN(FLUID_FAILED); + } + + synth->sfont_info = fluid_list_insert_at(synth->sfont_info, index, sfont_info); /* insert the sfont at the same index */ + fluid_hashtable_insert (synth->sfont_hash, sfont, sfont_info); /* Hash sfont->sfont_info */ + + /* reset the presets for all channels */ + fluid_synth_update_presets(synth); + FLUID_API_RETURN(sfont->id); + } + } + + FLUID_LOG(FLUID_ERR, "Failed to load SoundFont \"%s\"", filename); + FLUID_API_RETURN(FLUID_FAILED); +} + +/** + * Add a SoundFont. The SoundFont will be added to the top of the SoundFont stack. + * @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) +{ + fluid_sfont_info_t *sfont_info; + unsigned 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_info = new_fluid_sfont_info (synth, sfont); + if (!sfont_info) + FLUID_API_RETURN(FLUID_FAILED); + + sfont->id = sfont_id = ++synth->sfont_id; + synth->sfont_info = fluid_list_prepend (synth->sfont_info, sfont_info); /* prepend to list */ + fluid_hashtable_insert (synth->sfont_hash, sfont, sfont_info); /* Hash sfont->sfont_info */ + + /* 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 + * + * 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. + */ +void +fluid_synth_remove_sfont(fluid_synth_t* synth, fluid_sfont_t* sfont) +{ + fluid_sfont_info_t *sfont_info; + fluid_list_t *list; + + fluid_return_if_fail (synth != NULL); + fluid_return_if_fail (sfont != NULL); + fluid_synth_api_enter(synth); + + /* remove the SoundFont from the list */ + for (list = synth->sfont_info; list; list = fluid_list_next(list)) { + sfont_info = (fluid_sfont_info_t*) fluid_list_get(list); + + if (sfont_info->sfont == sfont) + { + synth->sfont_info = fluid_list_remove (synth->sfont_info, sfont_info); + + /* Remove from SoundFont hash regardless of refcount (SoundFont delete is up to caller) */ + fluid_hashtable_remove (synth->sfont_hash, sfont_info->sfont); + break; + } + } + + /* reset the presets for all channels */ + fluid_synth_program_reset (synth); + fluid_synth_api_exit(synth); +} + +/** + * 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_info); + 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_info, num); + if (list) sfont = ((fluid_sfont_info_t *)fluid_list_get (list))->sfont; + 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, unsigned 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_info; list; list = fluid_list_next(list)) { + sfont = ((fluid_sfont_info_t *)fluid_list_get (list))->sfont; + 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_info; list; list = fluid_list_next(list)) { + sfont = ((fluid_sfont_info_t *)fluid_list_get (list))->sfont; + 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 channel + * @deprecated fluid_synth_get_channel_info() should replace most use cases. + * + * 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 information on the currently selected preset on a MIDI channel. + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @param info Caller supplied structure to fill with preset information + * @return #FLUID_OK on success, #FLUID_FAILED otherwise + * @since 1.1.1 + */ +int +fluid_synth_get_channel_info (fluid_synth_t *synth, int chan, + fluid_synth_channel_info_t *info) +{ + fluid_channel_t *channel; + fluid_preset_t *preset; + char *name; + + if (info) + { + info->assigned = FALSE; + info->name[0] = '\0'; + } + + fluid_return_val_if_fail (info != NULL, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + channel = synth->channel[chan]; + preset = channel->preset; + + if (preset) + { + info->assigned = TRUE; + name = fluid_preset_get_name (preset); + + if (name) + { + strncpy (info->name, name, FLUID_SYNTH_CHANNEL_INFO_NAME_SIZE); + info->name[FLUID_SYNTH_CHANNEL_INFO_NAME_SIZE - 1] = '\0'; + } + else info->name[0] = '\0'; + + info->sfont_id = preset->sfont->id; + info->bank = fluid_preset_get_banknum (preset); + info->program = fluid_preset_get_num (preset); + } + else + { + info->assigned = FALSE; + fluid_channel_get_sfont_bank_prog (channel, &info->sfont_id, &info->bank, &info->program); + info->name[0] = '\0'; + } + + fluid_synth_api_exit(synth); + return FLUID_OK; +} + +/** + * Get list of 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 (_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 reverb, FALSE to disable + */ +void +fluid_synth_set_reverb_on(fluid_synth_t* synth, int on) +{ + fluid_return_if_fail (synth != NULL); + + fluid_atomic_int_set (&synth->with_reverb, on != 0); + fluid_synth_update_mixer(synth, fluid_rvoice_mixer_set_reverb_enabled, + on != 0, 0.0f); +} + +/** + * 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, int num) +{ + int i = 0; + while (revmodel_preset[i].name != NULL) { + if (i == num) { + fluid_synth_set_reverb (synth, revmodel_preset[i].roomsize, + revmodel_preset[i].damp, revmodel_preset[i].width, + revmodel_preset[i].level); + return FLUID_OK; + } + i++; + } + return FLUID_FAILED; +} + +/** + * Set reverb parameters. + * @param synth FluidSynth instance + * @param roomsize Reverb room size value (0.0-1.2) + * @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) + * + * NOTE: Not realtime safe and therefore should not be called from synthesis + * context at the risk of stalling audio output. + */ +void +fluid_synth_set_reverb(fluid_synth_t* synth, double roomsize, double damping, + double width, double level) +{ + fluid_synth_set_reverb_full (synth, FLUID_REVMODEL_SET_ALL, + roomsize, damping, width, level); +} + +/** + * Set one or more reverb parameters. + * @param synth FluidSynth instance + * @param set Flags indicating which parameters should be set (#fluid_revmodel_set_t) + * @param roomsize Reverb room size value (0.0-1.2) + * @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 + * + * NOTE: Not realtime safe and therefore should not be called from synthesis + * context at the risk of stalling audio output. + */ +int +fluid_synth_set_reverb_full(fluid_synth_t* synth, int set, double roomsize, + double damping, double width, double level) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + + if (!(set & FLUID_REVMODEL_SET_ALL)) + set = FLUID_REVMODEL_SET_ALL; + + /* Synth shadow values are set here so that they will be returned if querried */ + + fluid_synth_api_enter(synth); + + if (set & FLUID_REVMODEL_SET_ROOMSIZE) + fluid_atomic_float_set (&synth->reverb_roomsize, roomsize); + + if (set & FLUID_REVMODEL_SET_DAMPING) + fluid_atomic_float_set (&synth->reverb_damping, damping); + + if (set & FLUID_REVMODEL_SET_WIDTH) + fluid_atomic_float_set (&synth->reverb_width, width); + + if (set & FLUID_REVMODEL_SET_LEVEL) + fluid_atomic_float_set (&synth->reverb_level, level); + + fluid_rvoice_eventhandler_push5(synth->eventhandler, + fluid_rvoice_mixer_set_reverb_params, + synth->eventhandler->mixer, set, + roomsize, damping, width, level, 0.0f); + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Get reverb room size. + * @param synth FluidSynth instance + * @return Reverb room size (0.0-1.2) + */ +double +fluid_synth_get_reverb_roomsize(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + result = fluid_atomic_float_get (&synth->reverb_roomsize); + FLUID_API_RETURN(result); +} + +/** + * Get reverb damping. + * @param synth FluidSynth instance + * @return Reverb damping value (0.0-1.0) + */ +double +fluid_synth_get_reverb_damp(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->reverb_damping); + FLUID_API_RETURN(result); +} + +/** + * Get reverb level. + * @param synth FluidSynth instance + * @return Reverb level value (0.0-1.0) + */ +double +fluid_synth_get_reverb_level(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->reverb_level); + FLUID_API_RETURN(result); +} + +/** + * Get reverb width. + * @param synth FluidSynth instance + * @return Reverb width value (0.0-100.0) + */ +double +fluid_synth_get_reverb_width(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->reverb_width); + FLUID_API_RETURN(result); +} + +/** + * Enable or disable chorus effect. + * @param synth FluidSynth instance + * @param on TRUE to enable chorus, FALSE to disable + */ +void +fluid_synth_set_chorus_on(fluid_synth_t* synth, int on) +{ + fluid_return_if_fail (synth != NULL); + fluid_synth_api_enter(synth); + + fluid_atomic_int_set (&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); +} + +/** + * Set chorus parameters. + * @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.29-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_synth_set_chorus(fluid_synth_t* synth, int nr, double level, + double speed, double depth_ms, int type) +{ + fluid_synth_set_chorus_full (synth, FLUID_CHORUS_SET_ALL, nr, level, speed, + depth_ms, type); +} + +/** + * Set one or more chorus parameters. + * @param synth FluidSynth 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.29-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) + */ +int +fluid_synth_set_chorus_full(fluid_synth_t* synth, int set, int nr, double level, + double speed, double depth_ms, int type) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + + if (!(set & FLUID_CHORUS_SET_ALL)) + set = FLUID_CHORUS_SET_ALL; + + /* Synth shadow values are set here so that they will be returned if queried */ + fluid_synth_api_enter(synth); + + if (set & FLUID_CHORUS_SET_NR) + fluid_atomic_int_set (&synth->chorus_nr, nr); + + if (set & FLUID_CHORUS_SET_LEVEL) + fluid_atomic_float_set (&synth->chorus_level, level); + + if (set & FLUID_CHORUS_SET_SPEED) + fluid_atomic_float_set (&synth->chorus_speed, speed); + + if (set & FLUID_CHORUS_SET_DEPTH) + fluid_atomic_float_set (&synth->chorus_depth, depth_ms); + + if (set & FLUID_CHORUS_SET_TYPE) + fluid_atomic_int_set (&synth->chorus_type, type); + + fluid_rvoice_eventhandler_push5(synth->eventhandler, + fluid_rvoice_mixer_set_chorus_params, + synth->eventhandler->mixer, set, + nr, level, speed, depth_ms, type); + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Get chorus voice number (delay line count) value. + * @param synth FluidSynth instance + * @return Chorus voice count (0-99) + */ +int +fluid_synth_get_chorus_nr(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_int_get (&synth->chorus_nr); + FLUID_API_RETURN(result); +} + +/** + * Get chorus level. + * @param synth FluidSynth instance + * @return Chorus level value (0.0-10.0) + */ +double +fluid_synth_get_chorus_level(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->chorus_level); + FLUID_API_RETURN(result); +} + +/** + * Get chorus speed in Hz. + * @param synth FluidSynth instance + * @return Chorus speed in Hz (0.29-5.0) + */ +double +fluid_synth_get_chorus_speed_Hz(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->chorus_speed); + FLUID_API_RETURN(result); +} + +/** + * Get chorus depth. + * @param synth FluidSynth instance + * @return Chorus depth + */ +double +fluid_synth_get_chorus_depth_ms(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_float_get (&synth->chorus_depth); + FLUID_API_RETURN(result); +} + +/** + * Get chorus waveform type. + * @param synth FluidSynth instance + * @return Chorus waveform type (#fluid_chorus_mod) + */ +int +fluid_synth_get_chorus_type(fluid_synth_t* synth) +{ + double result; + fluid_return_val_if_fail (synth != NULL, 0.0); + fluid_synth_api_enter(synth); + + result = fluid_atomic_int_get (&synth->chorus_type); + FLUID_API_RETURN(result); +} + +/* + * 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... + */ +static void +fluid_synth_release_voice_on_same_note_LOCAL(fluid_synth_t* synth, int chan, + int key) +{ + int i; + fluid_voice_t* voice; + + synth->storeid = synth->noteid++; + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + if (_PLAYING(voice) + && (voice->chan == chan) + && (voice->key == key) + && (fluid_voice_get_id(voice) != synth->noteid)) { + /* Id of voices that was sustained by sostenuto */ + if(_HELD_BY_SOSTENUTO(voice)) + synth->storeid = voice->id; + /* Force the voice into release stage (pedaling is ignored) */ + fluid_voice_release(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 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; +// fluid_event_queue_t *queue; +// fluid_event_queue_elem_t *event; + + 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_event_queue_elem_t *event; + 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 (_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 well-tempered (normal) scale. + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * NOTE: Tuning is not applied in realtime to existing notes of the replaced + * tuning (if any), use fluid_synth_activate_key_tuning() instead to specify + * this behavior. + */ +int +fluid_synth_create_key_tuning(fluid_synth_t* synth, int bank, int prog, + const char* name, const double* pitch) +{ + return fluid_synth_activate_key_tuning (synth, bank, prog, name, pitch, FALSE); +} + +/** + * 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 well-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); +} + +/** + * Apply an octave tuning to 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) + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * NOTE: Tuning is not applied in realtime to existing notes of the replaced + * tuning (if any), use fluid_synth_activate_octave_tuning() instead to specify + * this behavior. + */ +int +fluid_synth_create_octave_tuning(fluid_synth_t* synth, int bank, int prog, + const char* name, const double* pitch) +{ + return fluid_synth_activate_octave_tuning (synth, bank, prog, name, pitch, FALSE); +} + +/** + * 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); +} + +/** + * Select 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 + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * NOTE: This function does NOT activate tuning in realtime, use + * fluid_synth_activate_tuning() instead to specify whether tuning change + * should cause existing notes to update. + * + * NOTE: Prior to version 1.1.0 it was an error to select a tuning that didn't + * already exist. Starting with 1.1.0, a default equal tempered scale will be + * created, if no tuning exists for the given bank and prog. + */ +int +fluid_synth_select_tuning(fluid_synth_t* synth, int chan, int bank, int prog) +{ + return fluid_synth_activate_tuning (synth, chan, bank, prog, FALSE); +} + +/** + * 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_event_queue_elem_t *event; + //fluid_event_queue_t *queue; + 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 (set it to the default well-tempered scale). + * @param synth FluidSynth instance + * @param chan MIDI channel number (0 to MIDI channel count - 1) + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * NOTE: This function does NOT activate tuning change in realtime, use + * fluid_synth_deactivate_tuning() instead to specify whether tuning change + * should cause existing notes to update. + */ +int +fluid_synth_reset_tuning(fluid_synth_t* synth, int chan) +{ + return fluid_synth_deactivate_tuning (synth, chan, FALSE); +} + +/** + * 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) + { + 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; +} + +/** + * Convenience function to set a string setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param str Value to assign to the setting + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_setstr(fluid_synth_t* synth, const char* name, const char* str) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + + return fluid_settings_setstr(synth->settings, name, str); +} + +/** + * Convenience function to duplicate a string setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param str Location to store a pointer to the newly allocated string value + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * The returned string is owned by the caller and should be freed with free() + * when finished with it. + */ +int +fluid_synth_dupstr(fluid_synth_t* synth, const char* name, char** str) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + fluid_return_val_if_fail (str != NULL, FLUID_FAILED); + + return fluid_settings_dupstr(synth->settings, name, str); +} + +/** + * Convenience function to set a floating point setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param val Value to assign to the setting + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_setnum(fluid_synth_t* synth, const char* name, double val) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + + return fluid_settings_setnum(synth->settings, name, val); +} + +/** + * Convenience function to get a floating point setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param val Location to store the current value of the setting + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_getnum(fluid_synth_t* synth, const char* name, double* val) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + + return fluid_settings_getnum(synth->settings, name, val); +} + +/** + * Convenience function to set an integer setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param val Value to assign to the setting + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_setint(fluid_synth_t* synth, const char* name, int val) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + + return fluid_settings_setint(synth->settings, name, val); +} + +/** + * Convenience function to get an integer setting of a synth. + * @param synth FluidSynth instance + * @param name Name of setting parameter + * @param val Location to store the current value of the setting + * @return FLUID_OK on success, FLUID_FAILED otherwise + */ +int +fluid_synth_getint(fluid_synth_t* synth, const char* name, int* val) +{ + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_return_val_if_fail (name != NULL, FLUID_FAILED); + + return fluid_settings_getint(synth->settings, name, val); +} + +/** + * Set a SoundFont generator (effect) value on a MIDI channel in real-time. + * @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 generator value to assign to the MIDI channel + * @return FLUID_OK on success, FLUID_FAILED otherwise + * + * Parameter numbers and ranges are described in the SoundFont 2.01 + * specification PDF, paragraph 8.1.3, page 48. See #fluid_gen_type. + */ +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, FALSE); + + 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, + int absolute) +{ + fluid_voice_t* voice; + int i; + + fluid_channel_set_gen (synth->channel[chan], param, value, absolute); + + for (i = 0; i < synth->polyphony; i++) { + voice = synth->voice[i]; + + if (voice->chan == chan) + fluid_voice_set_param (voice, param, value, absolute); + } +} + +/** + * Set a SoundFont generator (effect) value on a MIDI channel in real-time. + * @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 or absolute generator value to assign to the MIDI channel + * @param absolute 0 to assign a relative value, non-zero to assign an absolute value + * @param normalized 0 if value is specified in the native units of the generator, + * non-zero to take the value as a 0.0-1.0 range and apply it to the valid + * generator effect range (scaled and shifted as necessary). + * @return FLUID_OK on success, FLUID_FAILED otherwise + * @since 1.1.0 + * + * This function allows for setting all effect parameters in real time on a + * MIDI channel. Setting absolute to non-zero will cause the value to override + * any generator values set in the instruments played on the MIDI channel. + * See SoundFont 2.01 spec, paragraph 8.1.3, page 48 for details on SoundFont + * generator parameters and valid ranges. + */ +int +fluid_synth_set_gen2(fluid_synth_t* synth, int chan, int param, + float value, int absolute, int normalized) +{ + float v; + fluid_return_val_if_fail (param >= 0 && param < GEN_LAST, FLUID_FAILED); + FLUID_API_ENTRY_CHAN(FLUID_FAILED); + + v = normalized ? fluid_gen_scale(param, value) : value; + + fluid_synth_set_gen_LOCAL (synth, chan, param, v, absolute); + + FLUID_API_RETURN(FLUID_OK); +} + +/** + * Get generator value assigned to a MIDI channel. + * @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 generator value assigned to 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); +} + +/** + * Assign a MIDI router to a synth. + * @param synth FluidSynth instance + * @param router MIDI router to assign to the synth + * + * NOTE: This should only be done once and prior to using the synth. + */ +void +fluid_synth_set_midi_router(fluid_synth_t* synth, fluid_midi_router_t* router) +{ + fluid_return_if_fail (synth != NULL); + fluid_synth_api_enter(synth); + + synth->midi_router = router; + fluid_synth_api_exit(synth); +}; + +/** + * 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 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); + } + return FLUID_FAILED; +} + +/** + * Create and start voices using a preset and a MIDI note on event. + * @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; + 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); + 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 (_ON(voice) && (fluid_voice_get_id (voice) == id)) + fluid_voice_noteoff(voice); + } +} + +/** + * Offset the bank numbers of a loaded SoundFont. + * @param synth FluidSynth instance + * @param sfont_id ID of a loaded SoundFont + * @param offset Bank offset value to apply to all instruments + */ +int +fluid_synth_set_bank_offset(fluid_synth_t* synth, int sfont_id, int offset) +{ + fluid_sfont_info_t *sfont_info; + fluid_list_t *list; + + fluid_return_val_if_fail (synth != NULL, FLUID_FAILED); + fluid_synth_api_enter(synth); + + for (list = synth->sfont_info; list; list = fluid_list_next(list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + + if (fluid_sfont_get_id (sfont_info->sfont) == (unsigned int)sfont_id) + { + sfont_info->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_info_t *sfont_info; + fluid_list_t *list; + int offset = 0; + + fluid_return_val_if_fail (synth != NULL, 0); + fluid_synth_api_enter(synth); + + for (list = synth->sfont_info; list; list = fluid_list_next(list)) { + sfont_info = (fluid_sfont_info_t *)fluid_list_get (list); + + if (fluid_sfont_get_id (sfont_info->sfont) == (unsigned int)sfont_id) + { + offset = sfont_info->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 CHANNEL_TYPE_MELODIC, or CHANNEL_TYPE_DRUM + * @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); +} + diff --git a/libs/fluidsynth/src/fluid_synth.h b/libs/fluidsynth/src/fluid_synth.h new file mode 100644 index 0000000000..019a8e0d55 --- /dev/null +++ b/libs/fluidsynth/src/fluid_synth.h @@ -0,0 +1,237 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include "fluidsynth_priv.h" +#include "fluid_event_queue.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_sys.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 */ + +#if defined(WITH_FLOAT) +#define FLUID_SAMPLE_FORMAT FLUID_SAMPLE_FLOAT +#else +#define FLUID_SAMPLE_FORMAT FLUID_SAMPLE_DOUBLE +#endif + + +/*************************************************************** + * + * ENUM + */ +/*enum fluid_loop { + FLUID_UNLOOPED = 0, + FLUID_LOOP_DURING_RELEASE = 1, + FLUID_NOTUSED = 2, + FLUID_LOOP_UNTIL_RELEASE = 3 +};*/ + +/** + * 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 + +/** + * Structure used for sfont_info field in #fluid_synth_t for each loaded + * SoundFont with the SoundFont instance and additional fields. + */ +typedef struct _fluid_sfont_info_t { + fluid_sfont_t *sfont; /**< Loaded SoundFont */ + fluid_synth_t *synth; /**< Parent synth */ + int refcount; /**< SoundFont reference count (0 if no presets referencing it) */ + int bankofs; /**< Bank offset */ +} fluid_sfont_info_t; + +/* + * 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_FxUnit - same instance copied in rendering thread. Synchronising handled internally (I think...?). + * + */ + +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? */ + int dump; /**< Dump events to stdout to hook up a user interface? */ + 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 state; /**< the synthesizer state */ + unsigned int 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_info; /**< List of fluid_sfont_info_t for each loaded SoundFont (remains until SoundFont is unloaded) */ + fluid_hashtable_t *sfont_hash; /**< Hash of fluid_sfont_t->fluid_sfont_info_t (remains until SoundFont is deleted) */ + unsigned int sfont_id; /**< Incrementing ID assigned to each loaded 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; + fluid_rvoice_eventhandler_t* eventhandler; + + float reverb_roomsize; /**< Shadow of reverb roomsize */ + float reverb_damping; /**< Shadow of reverb damping */ + float reverb_width; /**< Shadow of reverb width */ + float reverb_level; /**< Shadow of reverb level */ + + int chorus_nr; /**< Shadow of chorus number */ + float chorus_level; /**< Shadow of chorus level */ + float chorus_speed; /**< Shadow of chorus speed */ + float chorus_depth; /**< Shadow of chorus depth */ + int chorus_type; /**< Shadow of chorus type */ + + 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) */ + + char outbuf[256]; /**< buffer for message output */ + float 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_midi_router_t* midi_router; /**< The midi router. Could be done nicer. */ + 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) */ + +#ifdef LADSPA + fluid_LADSPA_FxUnit_t* LADSPA_FxUnit; /**< Effects unit for LADSPA support */ +#endif +}; + +int fluid_synth_setstr(fluid_synth_t* synth, const char* name, const char* str); +int fluid_synth_dupstr(fluid_synth_t* synth, const char* name, char** str); +int fluid_synth_setnum(fluid_synth_t* synth, const char* name, double val); +int fluid_synth_getnum(fluid_synth_t* synth, const char* name, double* val); +int fluid_synth_setint(fluid_synth_t* synth, const char* name, int val); +int fluid_synth_getint(fluid_synth_t* synth, const char* name, int* val); + +fluid_preset_t* fluid_synth_find_preset(fluid_synth_t* synth, + unsigned int banknum, + unsigned int prognum); +void fluid_synth_sfont_unref (fluid_synth_t *synth, fluid_sfont_t *sfont); + + +int fluid_synth_all_notes_off(fluid_synth_t* synth, int chan); +int fluid_synth_all_sounds_off(fluid_synth_t* synth, int chan); +int fluid_synth_kill_voice(fluid_synth_t* synth, fluid_voice_t * voice); + +void fluid_synth_print_voice(fluid_synth_t* synth); + +void fluid_synth_dither_s16(int *dither_index, int len, float* lin, 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, int num); +int fluid_synth_set_reverb_full(fluid_synth_t* synth, int set, double roomsize, + double damping, double width, double level); + +int fluid_synth_reset_chorus(fluid_synth_t* synth); +int fluid_synth_set_chorus_full(fluid_synth_t* synth, int set, int nr, double level, + double speed, double depth_ms, int type); + +fluid_sample_timer_t* new_fluid_sample_timer(fluid_synth_t* synth, fluid_timer_callback_t callback, void* data); +int delete_fluid_sample_timer(fluid_synth_t* synth, fluid_sample_timer_t* timer); + +void fluid_synth_api_enter(fluid_synth_t* synth); +void fluid_synth_api_exit(fluid_synth_t* synth); + +void fluid_synth_process_event_queue(fluid_synth_t* synth); + +/* + * misc + */ + +void fluid_synth_settings(fluid_settings_t* settings); + +#endif /* _FLUID_SYNTH_H */ diff --git a/libs/fluidsynth/src/fluid_sys.c b/libs/fluidsynth/src/fluid_sys.c new file mode 100644 index 0000000000..600b04e88f --- /dev/null +++ b/libs/fluidsynth/src/fluid_sys.c @@ -0,0 +1,1296 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 WITH_READLINE +#include +#include +#endif + +#ifdef DBUS_SUPPORT +#include "fluid_rtkit.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 */ +#define WIN32_SOCKET_FLAG 0x40000000 + +/* 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; + 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 char fluid_errbuf[512]; /* buffer for error message */ + +static fluid_log_function_t fluid_log_function[LAST_LOG_LEVEL]; +static void* fluid_log_user_data[LAST_LOG_LEVEL]; +static int fluid_log_initialized = 0; + +static char* fluid_libname = "fluidsynth"; + + +void fluid_sys_config() +{ + fluid_log_config(); +} + + +unsigned int fluid_debug_flags = 0; + +#if DEBUG +/* + * fluid_debug + */ +int fluid_debug(int level, char * fmt, ...) +{ + if (fluid_debug_flags & level) { + fluid_log_function_t fun; + va_list args; + + va_start (args, fmt); + vsnprintf(fluid_errbuf, sizeof (fluid_errbuf), fmt, args); + va_end (args); + + fun = fluid_log_function[FLUID_DBG]; + if (fun != NULL) { + (*fun)(level, fluid_errbuf, fluid_log_user_data[FLUID_DBG]); + } + } + return 0; +} +#endif + +/** + * 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, char* message, void* data) +{ + FILE* out; + +#if defined(WIN32) + out = stdout; +#else + out = stderr; +#endif + + if (fluid_log_initialized == 0) { + fluid_log_config(); + } + + 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: +#if DEBUG + FLUID_FPRINTF(out, "%s: debug: %s\n", fluid_libname, message); +#endif + break; + default: + FLUID_FPRINTF(out, "%s: %s\n", fluid_libname, message); + break; + } + fflush(out); +} + +/* + * fluid_init_log + */ +void +fluid_log_config(void) +{ + if (fluid_log_initialized == 0) { + + fluid_log_initialized = 1; + + if (fluid_log_function[FLUID_PANIC] == NULL) { + fluid_set_log_function(FLUID_PANIC, fluid_default_log_function, NULL); + } + + if (fluid_log_function[FLUID_ERR] == NULL) { + fluid_set_log_function(FLUID_ERR, fluid_default_log_function, NULL); + } + + if (fluid_log_function[FLUID_WARN] == NULL) { + fluid_set_log_function(FLUID_WARN, fluid_default_log_function, NULL); + } + + if (fluid_log_function[FLUID_INFO] == NULL) { + fluid_set_log_function(FLUID_INFO, fluid_default_log_function, NULL); + } + + if (fluid_log_function[FLUID_DBG] == NULL) { + fluid_set_log_function(FLUID_DBG, fluid_default_log_function, NULL); + } + } +} + +/** + * 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, ...) +{ + fluid_log_function_t fun = NULL; + + va_list args; + va_start (args, fmt); + vsnprintf(fluid_errbuf, sizeof (fluid_errbuf), fmt, args); + va_end (args); + + if ((level >= 0) && (level < LAST_LOG_LEVEL)) { + fun = fluid_log_function[level]; + if (fun != NULL) { + (*fun)(level, fluid_errbuf, fluid_log_user_data[level]); + } + } + return FLUID_FAILED; +} + +/** + * 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 get's 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; +} + +/* + * fluid_error + */ +char* +fluid_error() +{ + return fluid_errbuf; +} + +/** + * 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 = fopen(filename, "rb"); + char id[4]; + + if (fp == NULL) { + return 0; + } + if (fread((void*) id, 1, 4, fp) != 4) { + fclose(fp); + return 0; + } + fclose(fp); + + return strncmp(id, "MThd", 4) == 0; +} + +/** + * Check if a file is a SoundFont file. + * @param filename Path to the file to check + * @return TRUE if it could be a SoundFont, FALSE otherwise + * + * The current implementation only checks for the "RIFF" header in the file. + * It is useful only to distinguish between SoundFont and MIDI files. + */ +int +fluid_is_soundfont(const char *filename) +{ + FILE* fp = fopen(filename, "rb"); + char id[4]; + + if (fp == NULL) { + return 0; + } + if (fread((void*) id, 1, 4, fp) != 4) { + fclose(fp); + return 0; + } + fclose(fp); + + return strncmp(id, "RIFF", 4) == 0; +} + +/** + * Get time in milliseconds to be used in relative timing operations. + * @return Unix time in milliseconds. + */ +unsigned int fluid_curtime(void) +{ + static glong initial_seconds = 0; + GTimeVal timeval; + + if (initial_seconds == 0) { + g_get_current_time (&timeval); + initial_seconds = timeval.tv_sec; + } + + g_get_current_time (&timeval); + + return (unsigned int)((timeval.tv_sec - initial_seconds) * 1000.0 + timeval.tv_usec / 1000.0); +} + +/** + * Get time in microseconds to be used in relative timing operations. + * @return Unix time in microseconds. + */ +double +fluid_utime (void) +{ + GTimeVal timeval; + + g_get_current_time (&timeval); + + return (timeval.tv_sec * 1000000.0 + timeval.tv_usec); +} + + +#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. http://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 + +fluid_profile_data_t fluid_profile_data[] = +{ + { FLUID_PROF_WRITE, "fluid_synth_write_* ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK, "fluid_synth_one_block ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK_CLEAR, "fluid_synth_one_block:clear ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK_VOICE, "fluid_synth_one_block:one voice ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK_VOICES, "fluid_synth_one_block:all voices", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK_REVERB, "fluid_synth_one_block:reverb ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_ONE_BLOCK_CHORUS, "fluid_synth_one_block:chorus ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_VOICE_NOTE, "fluid_voice:note ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_VOICE_RELEASE, "fluid_voice:release ", 1e10, 0.0, 0.0, 0}, + { FLUID_PROF_LAST, "last", 1e100, 0.0, 0.0, 0} +}; + + +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_PROF_LAST; 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); + } + } +} + + +#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; + GError *err = NULL; + + g_return_val_if_fail (func != NULL, NULL); + +#if OLD_GLIB_THREAD_API + /* Make sure g_thread_init has been called. + * FIXME - 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 + } +#if NEW_GLIB_THREAD_API + else thread = g_thread_try_new (name, (GThreadFunc)func, data, &err); +#else + 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); + 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 void +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) g_usleep (delay * 1000); + } + + FLUID_LOG (FLUID_DBG, "Timer thread finished"); + + if (timer->auto_destroy) + FLUID_FREE (timer); + + return; +} + +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 */ + + return timer; +} + +int +delete_fluid_timer (fluid_timer_t *timer) +{ + int 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); + + return FLUID_OK; +} + +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; +} + + +/*************************************************************** + * + * 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 WITH_READLINE + if (in == fluid_get_stdin ()) + { + char *line; + + line = readline (prompt); + + if (line == NULL) + return -1; + + snprintf(buf, len, "%s", line); + buf[len - 1] = 0; + + 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 & WIN32_SOCKET_FLAG)) + { + n = read(in, &c, 1); + if (n == -1) return -1; + } + else + { + n = recv(in & ~WIN32_SOCKET_FLAG, &c, 1, 0); + if (n == SOCKET_ERROR) return -1; + } +#endif + + if (n == 0) + { + *buf++ = 0; + return 0; + } + + 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, char* format, ...) +{ + char buf[4096]; + va_list args; + int len; + + va_start (args, format); + len = 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, strlen (buf)); +#else + { + int retval; + + /* Handle write differently depending on if its a socket or file descriptor */ + if (!(out & WIN32_SOCKET_FLAG)) + return write(out, buf, strlen (buf)); + + /* Socket */ + retval = send (out & ~WIN32_SOCKET_FLAG, buf, strlen (buf), 0); + + return retval != SOCKET_ERROR ? retval : -1; + } +#endif +} + +int fluid_server_socket_join(fluid_server_socket_t *server_socket) +{ + return fluid_thread_join (server_socket->thread); +} + + +#ifndef WIN32 // Not win32? + +#define SOCKET_ERROR -1 + +fluid_istream_t fluid_socket_get_istream (fluid_socket_t sock) +{ + return sock; +} + +fluid_ostream_t fluid_socket_get_ostream (fluid_socket_t sock) +{ + return sock; +} + +void fluid_socket_close(fluid_socket_t sock) +{ + if (sock != INVALID_SOCKET) + close (sock); +} + +static void +fluid_server_socket_run (void *data) +{ + fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data; + fluid_socket_t client_socket; +#ifdef IPV6 + struct sockaddr_in6 addr; + char straddr[INET6_ADDRSTRLEN]; +#else + struct sockaddr_in addr; + char straddr[INET_ADDRSTRLEN]; +#endif + socklen_t addrlen = sizeof (addr); + int retval; + 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"); + + server_socket->cont = 0; + return; + } else { +#ifdef IPV6 + inet_ntop(AF_INET6, &addr.sin6_addr, straddr, sizeof(straddr)); +#else + inet_ntop(AF_INET, &addr.sin_addr, straddr, sizeof(straddr)); +#endif + retval = server_socket->func (server_socket->data, client_socket, + straddr); + + if (retval != 0) + fluid_socket_close(client_socket); + } + } + + FLUID_LOG(FLUID_DBG, "Server closing"); +} + +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 + struct sockaddr_in6 addr; +#else + struct sockaddr_in addr; +#endif + fluid_socket_t sock; + + g_return_val_if_fail (func != NULL, NULL); +#ifdef IPV6 + sock = socket(AF_INET6, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + FLUID_LOG(FLUID_ERR, "Failed to create server socket"); + return NULL; + } + + FLUID_MEMSET((char *)&addr, 0, sizeof(struct sockaddr_in6)); + addr.sin6_family = AF_INET6; + addr.sin6_addr = in6addr_any; + addr.sin6_port = htons(port); +#else + + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) { + FLUID_LOG(FLUID_ERR, "Failed to create server socket"); + return NULL; + } + + FLUID_MEMSET((char *)&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); +#endif + if (bind(sock, (const struct sockaddr *) &addr, sizeof(addr)) == SOCKET_ERROR) { + FLUID_LOG(FLUID_ERR, "Failed to bind server socket"); + fluid_socket_close(sock); + return NULL; + } + + if (listen(sock, 10) == SOCKET_ERROR) { + FLUID_LOG(FLUID_ERR, "Failed listen on server socket"); + fluid_socket_close(sock); + 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); + 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); + return NULL; + } + + return server_socket; +} + +int delete_fluid_server_socket(fluid_server_socket_t* server_socket) +{ + server_socket->cont = 0; + if (server_socket->socket != INVALID_SOCKET) { + fluid_socket_close(server_socket->socket); + } + if (server_socket->thread) { + delete_fluid_thread(server_socket->thread); + } + FLUID_FREE(server_socket); + return FLUID_OK; +} + + +#else // Win32 is "special" + + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +fluid_istream_t fluid_socket_get_istream (fluid_socket_t sock) +{ + return sock | WIN32_SOCKET_FLAG; +} + +fluid_ostream_t fluid_socket_get_ostream (fluid_socket_t sock) +{ + return sock | WIN32_SOCKET_FLAG; +} + +void fluid_socket_close (fluid_socket_t sock) +{ + if (sock != INVALID_SOCKET) + closesocket (sock); +} + +static void fluid_server_socket_run (void *data) +{ + fluid_server_socket_t *server_socket = (fluid_server_socket_t *)data; + fluid_socket_t client_socket; +#ifdef IPV6 + struct sockaddr_in6 addr; + char straddr[INET6_ADDRSTRLEN]; +#else + struct sockaddr_in addr; + char straddr[INET_ADDRSTRLEN]; +#endif + 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: %ld", WSAGetLastError ()); + + server_socket->cont = 0; + return; + } + else + { +#ifdef IPV6 + 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); + if (r != 0) + fluid_socket_close (client_socket); + } + } + + FLUID_LOG (FLUID_DBG, "Server closing"); +} + +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 + struct sockaddr_in6 addr; +#else + struct sockaddr_in addr; +#endif + + fluid_socket_t sock; + WSADATA wsaData; + int retval; + + g_return_val_if_fail (func != NULL, NULL); + + // Win32 requires initialization of winsock + retval = WSAStartup (MAKEWORD (2,2), &wsaData); + + if (retval != 0) + { + FLUID_LOG(FLUID_ERR, "Server socket creation error: WSAStartup failed: %d", retval); + return NULL; + } +#ifdef IPV6 + sock = socket (AF_INET6, SOCK_STREAM, 0); + if (sock == INVALID_SOCKET) + { + FLUID_LOG (FLUID_ERR, "Failed to create server socket: %ld", WSAGetLastError ()); + WSACleanup (); + return NULL; + } + addr.sin6_family = AF_INET6; + addr.sin6_port = htons (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: %ld", WSAGetLastError ()); + WSACleanup (); + return NULL; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons (port); + addr.sin_addr.s_addr = htonl (INADDR_ANY); +#endif + retval = bind (sock, (struct sockaddr *)&addr, sizeof (addr)); + + if (retval == SOCKET_ERROR) + { + FLUID_LOG (FLUID_ERR, "Failed to bind server socket: %ld", WSAGetLastError ()); + fluid_socket_close (sock); + WSACleanup (); + return NULL; + } + + if (listen (sock, SOMAXCONN) == SOCKET_ERROR) + { + FLUID_LOG (FLUID_ERR, "Failed to listen on server socket: %ld", WSAGetLastError ()); + fluid_socket_close (sock); + WSACleanup (); + 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); + WSACleanup (); + 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); + WSACleanup (); + return NULL; + } + + return server_socket; +} + +int delete_fluid_server_socket(fluid_server_socket_t *server_socket) +{ + server_socket->cont = 0; + + if (server_socket->socket != INVALID_SOCKET) + fluid_socket_close (server_socket->socket); + + if (server_socket->thread) + delete_fluid_thread (server_socket->thread); + + FLUID_FREE (server_socket); + + WSACleanup (); // Should be called the same number of times as WSAStartup + + return FLUID_OK; +} + +#endif diff --git a/libs/fluidsynth/src/fluid_sys.h b/libs/fluidsynth/src/fluid_sys.h new file mode 100644 index 0000000000..4953515692 --- /dev/null +++ b/libs/fluidsynth/src/fluid_sys.h @@ -0,0 +1,448 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 + */ + + +/** + + 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 + + */ + +#ifndef _FLUID_SYS_H +#define _FLUID_SYS_H + +#include +#include "fluidsynth_priv.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") + + +void fluid_sys_config(void); +void fluid_log_config(void); +void fluid_time_config(void); + + +/* Misc */ + +#define fluid_return_val_if_fail g_return_val_if_fail +#define fluid_return_if_fail g_return_if_fail +#define FLUID_INLINE inline +#define FLUID_POINTER_TO_UINT GPOINTER_TO_UINT +#define FLUID_UINT_TO_POINTER GUINT_TO_POINTER +#define FLUID_POINTER_TO_INT GPOINTER_TO_INT +#define FLUID_INT_TO_POINTER GINT_TO_POINTER +#define FLUID_N_ELEMENTS(struct) (sizeof (struct) / sizeof (struct[0])) + +#define FLUID_IS_BIG_ENDIAN (G_BYTE_ORDER == G_BIG_ENDIAN) + +/* + * Utility functions + */ +char *fluid_strtok (char **str, char *delim); + + +/** + + Additional debugging system, separate from the log system. This + allows to print selected debug messages of a specific subsystem. + */ + +extern unsigned int fluid_debug_flags; + +#if DEBUG + +enum fluid_debug_level { + FLUID_DBG_DRIVER = 1 +}; + +int fluid_debug(int level, char * fmt, ...); + +#else +#define fluid_debug +#endif + + +#if defined(__OS2__) +#define INCL_DOS +#include + +typedef int socklen_t; +#endif + +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); + +int delete_fluid_timer(fluid_timer_t* timer); +int fluid_timer_join(fluid_timer_t* timer); +int fluid_timer_stop(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_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 32)) +#define OLD_GLIB_THREAD_API (GLIB_MAJOR_VERSION < 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 32)) + +/* 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) +{ + 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) +{ + g_cond_clear (cond); + g_free (cond); +} + +/* Thread private data */ + +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) + +#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) G_STMT_START { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_mutex_init (&(_m)); \ +} G_STMT_END; + +/* 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) G_STMT_START { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_rec_mutex_init (&(_m)); \ +} G_STMT_END; + +/* 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) G_STMT_START { \ + if (!g_thread_supported ()) g_thread_init (NULL); \ + g_static_private_init (&(_priv)); \ +} G_STMT_END; + +#endif + + +/* Atomic operations */ + +#define fluid_atomic_int_inc(_pi) g_atomic_int_inc(_pi) +#define fluid_atomic_int_add(_pi, _val) g_atomic_int_add(_pi, _val) +#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) +#else +#define fluid_atomic_int_exchange_and_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(volatile float *fptr, float val) +{ + sint32 ival; + memcpy (&ival, &val, 4); + fluid_atomic_int_set ((volatile int *)fptr, ival); +} + +static FLUID_INLINE float +fluid_atomic_float_get(volatile float *fptr) +{ + sint32 ival; + float fval; + ival = fluid_atomic_int_get ((volatile int *)fptr); + memcpy (&fval, &ival, 4); + return fval; +} + + +/* Threads */ + +typedef GThread fluid_thread_t; +typedef void (*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); + +/* Sockets and I/O */ + +fluid_istream_t fluid_get_stdin (void); +fluid_ostream_t fluid_get_stdout (void); +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, char* format, ...); + +/* The function should return 0 if no error occured, 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); +int 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); + + + +/* Profiling */ + + +/** + * Profile 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_PROF_LAST +}; + + +#if WITH_PROFILING + +void fluid_profiling_print(void); + + +/** Profiling data. Keep track of min/avg/max values to execute a + piece of code. */ +typedef struct _fluid_profile_data_t { + int num; + char* description; + double min, max, total; + unsigned int count; +} fluid_profile_data_t; + +extern fluid_profile_data_t fluid_profile_data[]; + +/** Macro to obtain a time refence 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() + +/** Macro to calculate the min/avg/max. Needs a time refence and a + profile number. */ +#define fluid_profile(_num,_ref) { \ + 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++; \ + _ref = _now; \ +} + + +#else + +/* No profiling */ +#define fluid_profiling_print() +#define fluid_profile_ref() 0 +#define fluid_profile_ref_var(name) +#define fluid_profile(_num,_ref) + +#endif + + + +/** + + 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 processsor. +*/ +#ifdef FPE_CHECK +#define fluid_check_fpe(expl) fluid_check_fpe_i386(expl) +#define fluid_clear_fpe() fluid_clear_fpe_i386() +#else +#define fluid_check_fpe(expl) +#define fluid_clear_fpe() +#endif + +unsigned int fluid_check_fpe_i386(char * explanation_in_case_of_fpe); +void fluid_clear_fpe_i386(void); + +#endif /* _FLUID_SYS_H */ diff --git a/libs/fluidsynth/src/fluid_tuning.c b/libs/fluidsynth/src/fluid_tuning.c new file mode 100644 index 0000000000..8977ed6728 --- /dev/null +++ b/libs/fluidsynth/src/fluid_tuning.c @@ -0,0 +1,174 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.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; + } + + tuning->name = NULL; + + if (name != NULL) { + tuning->name = FLUID_STRDUP(name); + } + + tuning->bank = bank; + tuning->prog = prog; + + for (i = 0; i < 128; i++) { + tuning->pitch[i] = i * 100.0; + } + + 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; + } + + if (tuning->name) + { + new_tuning->name = FLUID_STRDUP (tuning->name); + + if (!new_tuning->name) + { + FLUID_FREE (new_tuning); + FLUID_LOG (FLUID_PANIC, "Out of memory"); + return NULL; + } + } + else new_tuning->name = NULL; + + new_tuning->bank = tuning->bank; + new_tuning->prog = tuning->prog; + + for (i = 0; i < 128; i++) + new_tuning->pitch[i] = tuning->pitch[i]; + + new_tuning->refcount = 1; /* Start with a refcount of 1 */ + + return new_tuning; +} + +void +delete_fluid_tuning (fluid_tuning_t *tuning) +{ + if (tuning->name) 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; +} + +void fluid_tuning_set_name(fluid_tuning_t* tuning, char* name) +{ + if (tuning->name != NULL) { + FLUID_FREE(tuning->name); + tuning->name = NULL; + } + if (name != NULL) { + tuning->name = FLUID_STRDUP(name); + } +} + +char* fluid_tuning_get_name(fluid_tuning_t* tuning) +{ + return tuning->name; +} + +static void fluid_tuning_set_key(fluid_tuning_t* tuning, int key, double pitch) +{ + tuning->pitch[key] = pitch; +} + +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/fluid_tuning.h b/libs/fluidsynth/src/fluid_tuning.h new file mode 100644 index 0000000000..d974139276 --- /dev/null +++ b/libs/fluidsynth/src/fluid_tuning.h @@ -0,0 +1,68 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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: + + http://www.midi.org/about-midi/tuning.htm + http://www.midi.org/about-midi/tuning-scale.htm + http://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 */ + int 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); + +void fluid_tuning_set_name(fluid_tuning_t* tuning, 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/fluid_voice.c b/libs/fluidsynth/src/fluid_voice.c new file mode 100644 index 0000000000..e6efbac899 --- /dev/null +++ b/libs/fluidsynth/src/fluid_voice.c @@ -0,0 +1,1626 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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 "fluidsynth_priv.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" + +/* 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 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 { \ + if (voice->can_access_rvoice) proc(voice->rvoice); \ + else fluid_rvoice_eventhandler_push(voice->channel->synth->eventhandler, \ + proc, voice->rvoice, 0, 0.0f); \ + } while (0) + +#define UPDATE_RVOICE_PTR(proc, obj) \ + do { \ + if (voice->can_access_rvoice) proc(voice->rvoice, obj); \ + else fluid_rvoice_eventhandler_push_ptr(voice->channel->synth->eventhandler, \ + proc, voice->rvoice, obj); \ + } while (0) + + +#define UPDATE_RVOICE_GENERIC_R1(proc, obj, rarg) \ + do { \ + if (voice->can_access_rvoice) proc(obj, rarg); \ + else fluid_rvoice_eventhandler_push(voice->channel->synth->eventhandler, \ + proc, obj, 0, rarg); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_I1(proc, obj, iarg) \ + do { \ + if (voice->can_access_rvoice) proc(obj, iarg); \ + else fluid_rvoice_eventhandler_push(voice->channel->synth->eventhandler, \ + proc, obj, iarg, 0.0f); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_IR(proc, obj, iarg, rarg) \ + do { \ + if (voice->can_access_rvoice) proc(obj, iarg, rarg); \ + else fluid_rvoice_eventhandler_push(voice->channel->synth->eventhandler, \ + proc, obj, iarg, rarg); \ + } while (0) + +#define UPDATE_RVOICE_GENERIC_ALL(proc, obj, iarg, r1, r2, r3, r4, r5) \ + do { \ + if (voice->can_access_rvoice) proc(obj, iarg, r1, r2, r3, r4, r5); \ + else fluid_rvoice_eventhandler_push5(voice->channel->synth->eventhandler, \ + proc, obj, iarg, r1, r2, r3, r4, r5); \ + } while (0) + + +#define UPDATE_RVOICE_VOLENV(section, arg1, arg2, arg3, arg4, arg5) \ + do { \ + fluid_adsr_env_set_data(&voice->volenv, section, arg1, arg2, arg3, arg4, arg5) \ + UPDATE_RVOICE_GENERIC_ALL(fluid_adsr_env_set_data, &voice->rvoice->envlfo.volenv, section, arg1, arg2, arg3, arg4, arg5) \ + } while(0) + +#define UPDATE_RVOICE_MODENV(section, arg1, arg2, arg3, arg4, arg5) \ + UPDATE_RVOICE_GENERIC_ALL(fluid_adsr_env_set_data, &voice->rvoice->envlfo.modenv, section, arg1, arg2, arg3, arg4, arg5) + +#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_FILTER1(proc, arg1) UPDATE_RVOICE_GENERIC_R1(proc, &voice->rvoice->resonant_filter, arg1) + +#define UPDATE_RVOICE2(proc, iarg, rarg) UPDATE_RVOICE_GENERIC_IR(proc, voice->rvoice, iarg, rarg) +#define UPDATE_RVOICE_BUFFERS2(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 inline void +fluid_voice_update_volenv(fluid_voice_t* voice, + 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_adsr_env_set_data(&voice->volenv, section, count, coeff, increment, + min, max); + UPDATE_RVOICE_GENERIC_ALL(fluid_adsr_env_set_data, + &voice->rvoice->envlfo.volenv, section, count, + coeff, increment, min, max); +} + +static inline void +fluid_voice_update_modenv(fluid_voice_t* voice, + 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) +{ + UPDATE_RVOICE_GENERIC_ALL(fluid_adsr_env_set_data, + &voice->rvoice->envlfo.modenv, section, count, + coeff, increment, min, max); +} + +static inline void fluid_sample_null_ptr(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; +} + +static void fluid_voice_initialize_rvoice(fluid_voice_t* voice) +{ + 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, FLUID_VOICE_ENVSUSTAIN, + 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); + fluid_voice_update_volenv(voice, FLUID_VOICE_ENVFINISHED, + 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVSUSTAIN, + 0xffffffff, 1.0f, 0.0f, -1.0f, 2.0f); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVFINISHED, + 0xffffffff, 0.0f, 0.0f, -1.0f, 1.0f); +} + +/* + * new_fluid_voice + */ +fluid_voice_t* +new_fluid_voice(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->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"); + FLUID_FREE(voice->rvoice); + FLUID_FREE(voice); + return NULL; + } + + voice->status = FLUID_VOICE_CLEAN; + voice->chan = NO_CHANNEL; + voice->key = 0; + voice->vel = 0; + voice->channel = NULL; + voice->sample = NULL; + + /* Initialize both the rvoice and overflow_rvoice */ + voice->can_access_rvoice = 1; + voice->can_access_overflow_rvoice = 1; + fluid_voice_initialize_rvoice(voice); + fluid_voice_swap_rvoice(voice); + fluid_voice_initialize_rvoice(voice); + + fluid_voice_set_output_rate(voice, output_rate); + + return voice; +} + +/* + * delete_fluid_voice + */ +int +delete_fluid_voice(fluid_voice_t* voice) +{ + if (voice == NULL) { + return FLUID_OK; + } + if (!voice->can_access_rvoice || !voice->can_access_overflow_rvoice) { + /* stop rvoice before deleting voice! */ + return FLUID_FAILED; + } + FLUID_FREE(voice->overflow_rvoice); + FLUID_FREE(voice->rvoice); + FLUID_FREE(voice); + return FLUID_OK; +} + +/* fluid_voice_init + * + * Initialize the synthesis process + */ +int +fluid_voice_init(fluid_voice_t* voice, fluid_sample_t* sample, + 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->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->debug = 0; + voice->has_noteoff = 0; + UPDATE_RVOICE0(fluid_rvoice_reset); + + /* Increment the reference count of the sample to prevent the + unloading of the soundfont while this voice is playing, + once for us and once for the rvoice. */ + fluid_sample_incr_ref(sample); + UPDATE_RVOICE_PTR(fluid_rvoice_set_sample, sample); + fluid_sample_incr_ref(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.0000001){ + voice->synth_gain = 0.0000001; + } + UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, voice->synth_gain); + + /* Set up buffer mapping, should be done more flexible in the future. */ + i = channel->synth->audio_groups; + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_mapping, 2, i*2 + SYNTH_REVERB_CHANNEL); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_mapping, 3, i*2 + SYNTH_CHORUS_CHANNEL); + i = 2 * (voice->chan % i); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_mapping, 0, i); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_mapping, 1, i+1); + + return FLUID_OK; +} + + +/** + * Update sample rate. + * NOTE: If the voice is active, it will be turned off. + */ +int +fluid_voice_set_output_rate(fluid_voice_t* voice, fluid_real_t value) +{ + if (_PLAYING(voice)) + fluid_voice_off(voice); + + voice->output_rate = value; + UPDATE_RVOICE_R1(fluid_rvoice_set_output_rate, value); + /* Update the other rvoice as well */ + fluid_voice_swap_rvoice(voice); + UPDATE_RVOICE_R1(fluid_rvoice_set_output_rate, value); + fluid_voice_swap_rvoice(voice); + + return FLUID_FAILED; +} + + +/** + * 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(fluid_voice_t* voice, int num) +{ + /* This is an extension to the SoundFont standard. More + * documentation is available at the fluid_synth_set_gen2() + * function. */ + if (voice->gen[num].flags == GEN_ABS_NRPN) { + return (fluid_real_t) voice->gen[num].nrpn; + } else { + return (fluid_real_t) (voice->gen[num].val + voice->gen[num].mod + voice->gen[num].nrpn); + } +} + + +/** + * Synthesize a voice to a buffer. + * + * @param voice Voice to synthesize + * @param dsp_buf Audio buffer to synthesize to (#FLUID_BUFSIZE in length) + * @return Count of samples written to dsp_buf (can be 0) + * + * Panning, reverb and chorus are processed separately. The dsp interpolation + * routine is in (fluid_dsp_float.c). + */ +int +fluid_voice_write (fluid_voice_t* voice, fluid_real_t *dsp_buf) +{ + int result; + if (!voice->can_access_rvoice) + return 0; + + result = fluid_rvoice_write(voice->rvoice, dsp_buf); + + if (result == -1) + return 0; + + if ((result < FLUID_BUFSIZE) && _PLAYING(voice)) /* Voice finished by itself */ + fluid_voice_off(voice); + + return result; +} + + +/** + * Mix voice data to left/right (panning), reverb and chorus buffers. + * @param count Number of samples + * @param dsp_buf Source buffer + * @param voice Voice to mix + * @param left_buf Left audio buffer + * @param right_buf Right audio buffer + * @param reverb_buf Reverb buffer + * @param chorus_buf Chorus buffer + * + */ +void +fluid_voice_mix (fluid_voice_t *voice, int count, fluid_real_t* dsp_buf, + fluid_real_t* left_buf, fluid_real_t* right_buf, + fluid_real_t* reverb_buf, fluid_real_t* chorus_buf) +{ + fluid_rvoice_buffers_t buffers; + fluid_real_t* dest_buf[4] = {left_buf, right_buf, reverb_buf, chorus_buf}; + + fluid_rvoice_buffers_set_amp(&buffers, 0, voice->amp_left); + fluid_rvoice_buffers_set_amp(&buffers, 1, voice->amp_right); + fluid_rvoice_buffers_set_amp(&buffers, 2, voice->amp_reverb); + fluid_rvoice_buffers_set_amp(&buffers, 3, voice->amp_chorus); + + fluid_rvoice_buffers_mix(&buffers, dsp_buf, count, dest_buf, 4); + + fluid_check_fpe ("voice_mix"); +} + + + +/* + * 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); + + voice->ref = fluid_profile_ref(); + + voice->status = FLUID_VOICE_ON; + + /* Increment voice count */ + voice->channel->synth->active_voice_count++; +} + +void +fluid_voice_calculate_gen_pitch(fluid_voice_t* voice) +{ + fluid_tuning_t* tuning; + fluid_real_t x; + + /* The GEN_PITCH is a hack to fit the pitch bend controller into the + * modulator paradigm. Now the nominal pitch of the key is set. + * 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 _GEN(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)); + voice->gen[GEN_PITCH].val = voice->gen[GEN_SCALETUNE].val / 100.0f * + (fluid_tuning_get_pitch (tuning, voice->key) - x) + x; + } else { + voice->gen[GEN_PITCH].val = voice->gen[GEN_SCALETUNE].val + * (voice->key - voice->root_pitch / 100.0f) + voice->root_pitch; + } + +} + +/* + * 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; + + int list_of_generators_to_initialize[35] = { + 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, /* --- */ + -1}; /* end-of-list marker */ + + /* 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_set_default_values. + */ + + 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->channel, 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 (i = 0; list_of_generators_to_initialize[i] != -1; i++) { + fluid_voice_update_param(voice, list_of_generators_to_initialize[i]); + } + + /* 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 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. + */ + timecents = (_GEN(voice, gen_base) + _GEN(voice, gen_key2base) * (60.0 - voice->key)); + + /* Range checking */ + if (is_decay){ + /* SF 2.01 section 8.1.3 # 28, 36 */ + if (timecents > 8000.0) { + timecents = 8000.0; + } + } else { + /* SF 2.01 section 8.1.3 # 27, 35 */ + if (timecents > 5000) { + timecents = 5000.0; + } + /* SF 2.01 section 8.1.2 # 27, 35: + * The most negative number indicates no hold time + */ + if (timecents <= -32768.) { + return 0; + } + } + /* SF 2.01 section 8.1.3 # 27, 28, 35, 36 */ + if (timecents < -12000.0) { + timecents = -12000.0; + } + + 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.5); + + 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. _GEN(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) + * + * 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) +{ + double q_dB; + fluid_real_t x; + fluid_real_t y; + unsigned int count, z; + // Alternate attenuation scale used by EMU10K1 cards when setting the attenuation at the preset or instrument level within the SoundFont bank. + static const float ALT_ATTENUATION_SCALE = 0.4; + + switch (gen) { + + case GEN_PAN: + /* range checking is done in the fluid_pan function */ + voice->pan = _GEN(voice, GEN_PAN); + voice->amp_left = fluid_pan(voice->pan, 1) * voice->synth_gain / 32768.0f; + voice->amp_right = fluid_pan(voice->pan, 0) * voice->synth_gain / 32768.0f; + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 0, voice->amp_left); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 1, voice->amp_right); + break; + + case GEN_ATTENUATION: + voice->attenuation = ((fluid_real_t)(voice)->gen[GEN_ATTENUATION].val*ALT_ATTENUATION_SCALE) + + (fluid_real_t)(voice)->gen[GEN_ATTENUATION].mod + (fluid_real_t)(voice)->gen[GEN_ATTENUATION].nrpn; + + /* 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.0, 1440.0); + 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 = (_GEN(voice, GEN_PITCH) + + 100.0f * _GEN(voice, GEN_COARSETUNE) + + _GEN(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 = _GEN(voice, GEN_REVERBSEND) / 1000.0f; + fluid_clip(voice->reverb_send, 0.0, 1.0); + voice->amp_reverb = voice->reverb_send * voice->synth_gain / 32768.0f; + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 2, voice->amp_reverb); + break; + + case GEN_CHORUSSEND: + /* The generator unit is 'tenths of a percent'. */ + voice->chorus_send = _GEN(voice, GEN_CHORUSSEND) / 1000.0f; + fluid_clip(voice->chorus_send, 0.0, 1.0); + voice->amp_chorus = voice->chorus_send * voice->synth_gain / 32768.0f; + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 3, voice->amp_chorus); + 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(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(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] + */ + x = _GEN(voice, GEN_FILTERFC); + UPDATE_RVOICE_FILTER1(fluid_iir_filter_set_fres, x); + break; + + case GEN_FILTERQ: + /* The generator contains 'centibels' (1/10 dB) => divide by 10 to + * obtain dB */ + q_dB = _GEN(voice, GEN_FILTERQ) / 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; + UPDATE_RVOICE_FILTER1(fluid_iir_filter_set_q_dB, q_dB); + + break; + + case GEN_MODLFOTOPITCH: + x = _GEN(voice, GEN_MODLFOTOPITCH); + fluid_clip(x, -12000.0, 12000.0); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_pitch, x); + break; + + case GEN_MODLFOTOVOL: + x = _GEN(voice, GEN_MODLFOTOVOL); + fluid_clip(x, -960.0, 960.0); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_vol, x); + break; + + case GEN_MODLFOTOFILTERFC: + x = _GEN(voice, GEN_MODLFOTOFILTERFC); + fluid_clip(x, -12000, 12000); + UPDATE_RVOICE_R1(fluid_rvoice_set_modlfo_to_fc, x); + break; + + case GEN_MODLFODELAY: + x = _GEN(voice, 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 + */ + x = _GEN(voice, GEN_MODLFOFREQ); + fluid_clip(x, -16000.0f, 4500.0f); + x = (4.0f * FLUID_BUFSIZE * fluid_act2hz(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 + */ + x = _GEN(voice, GEN_VIBLFOFREQ); + fluid_clip(x, -16000.0f, 4500.0f); + x = 4.0f * FLUID_BUFSIZE * fluid_act2hz(x) / voice->output_rate; + UPDATE_RVOICE_ENVLFO_R1(fluid_lfo_set_incr, viblfo, x); + break; + + case GEN_VIBLFODELAY: + x = _GEN(voice,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: + x = _GEN(voice, GEN_VIBLFOTOPITCH); + fluid_clip(x, -12000.0, 12000.0); + 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. + * */ + x = _GEN(voice, GEN_KEYNUM); + if (x >= 0){ + voice->key = x; + } + 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. */ + x = _GEN(voice, GEN_VELOCITY); + if (x > 0) { + voice->vel = x; + } + break; + + case GEN_MODENVTOPITCH: + x = _GEN(voice, GEN_MODENVTOPITCH); + fluid_clip(x, -12000.0, 12000.0); + UPDATE_RVOICE_R1(fluid_rvoice_set_modenv_to_pitch, x); + break; + + case GEN_MODENVTOFILTERFC: + x = _GEN(voice,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.0, 12000.0); + 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) { + z = (voice->sample->start + + (int) _GEN(voice, GEN_STARTADDROFS) + + 32768 * (int) _GEN(voice, GEN_STARTADDRCOARSEOFS)); + 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) { + z = (voice->sample->end + + (int) _GEN(voice, GEN_ENDADDROFS) + + 32768 * (int) _GEN(voice, GEN_ENDADDRCOARSEOFS)); + 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) { + z = (voice->sample->loopstart + + (int) _GEN(voice, GEN_STARTLOOPADDROFS) + + 32768 * (int) _GEN(voice, GEN_STARTLOOPADDRCOARSEOFS)); + 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) { + z = (voice->sample->loopend + + (int) _GEN(voice, GEN_ENDLOOPADDROFS) + + 32768 * (int) _GEN(voice, GEN_ENDLOOPADDRCOARSEOFS)); + 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 */ + x = _GEN(voice, GEN_VOLENVDELAY); + fluid_clip(x, -12000.0f, 5000.0f); + count = NUM_BUFFERS_DELAY(x); + fluid_voice_update_volenv(voice, FLUID_VOICE_ENVDELAY, + count, 0.0f, 0.0f, -1.0f, 1.0f); + break; + + case GEN_VOLENVATTACK: /* SF2.01 section 8.1.3 # 34 */ + x = _GEN(voice, GEN_VOLENVATTACK); + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_ATTACK(x); + fluid_voice_update_volenv(voice, FLUID_VOICE_ENVATTACK, + count, 1.0f, count ? 1.0f / count : 0.0f, -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, 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 */ + y = 1.0f - 0.001f * _GEN(voice, GEN_VOLENVSUSTAIN); + fluid_clip(y, 0.0f, 1.0f); + count = calculate_hold_decay_buffers(voice, GEN_VOLENVDECAY, GEN_KEYTOVOLENVDECAY, 1); /* 1 for decay */ + fluid_voice_update_volenv(voice, FLUID_VOICE_ENVDECAY, + count, 1.0f, count ? -1.0f / count : 0.0f, y, 2.0f); + break; + + case GEN_VOLENVRELEASE: /* SF2.01 section 8.1.3 # 38 */ + x = _GEN(voice, GEN_VOLENVRELEASE); + fluid_clip(x, FLUID_MIN_VOLENVRELEASE, 8000.0f); + count = 1 + NUM_BUFFERS_RELEASE(x); + fluid_voice_update_volenv(voice, FLUID_VOICE_ENVRELEASE, + count, 1.0f, count ? -1.0f / count : 0.0f, 0.0f, 1.0f); + break; + + /* Modulation envelope */ + case GEN_MODENVDELAY: /* SF2.01 section 8.1.3 # 25 */ + x = _GEN(voice, GEN_MODENVDELAY); + fluid_clip(x, -12000.0f, 5000.0f); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVDELAY, + NUM_BUFFERS_DELAY(x), 0.0f, 0.0f, -1.0f, 1.0f); + break; + + case GEN_MODENVATTACK: /* SF2.01 section 8.1.3 # 26 */ + x = _GEN(voice, GEN_MODENVATTACK); + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_ATTACK(x); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVATTACK, + count, 1.0f, count ? 1.0f / count : 0.0f, -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, 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 */ + y = 1.0f - 0.001f * _GEN(voice, GEN_MODENVSUSTAIN); + fluid_clip(y, 0.0f, 1.0f); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVDECAY, + count, 1.0f, count ? -1.0f / count : 0.0f, y, 2.0f); + break; + + case GEN_MODENVRELEASE: /* SF 2.01 section 8.1.3 # 30 */ + x = _GEN(voice, GEN_MODENVRELEASE); + fluid_clip(x, -12000.0f, 8000.0f); + count = 1 + NUM_BUFFERS_RELEASE(x); + fluid_voice_update_modenv(voice, FLUID_VOICE_ENVRELEASE, + count, 1.0f, count ? -1.0f / count : 0.0f, 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 continous control and a channel control (pitch bend, ...) + * @param ctrl the control number + * + * 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 three steps: + * + * - first, we look for all the modulators that have the changed + * controller as a source. This will yield a list of generators that + * will be changed because of the controller event. + * + * - For every changed generator, calculate its new value. This is the + * sum of its original value plus the values of al the attached + * modulators. + * + * - For every changed generator, convert its value to the correct + * unit of the corresponding DSP parameter + */ +int fluid_voice_modulate(fluid_voice_t* voice, int cc, int ctrl) +{ + int i, k; + fluid_mod_t* mod; + int gen; + fluid_real_t modval; + +/* 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. */ + if (fluid_mod_has_source(mod, cc, ctrl)) { + + gen = fluid_mod_get_dest(mod); + modval = 0.0; + + /* step 2: for every changed modulator, calculate the modulation + * value of its associated generator */ + 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->channel, voice); + } + } + + fluid_gen_set_mod(&voice->gen[gen], modval); + + /* step 3: now that we have the new value of the generator, + * recalculate the parameter values that are derived from the + * generator */ + fluid_voice_update_param(voice, gen); + } + } + return FLUID_OK; +} + +/** + * Update all the modulators. This function is called after a + * ALL_CTRL_OFF MIDI message has been received (CC 121). + * + */ +int fluid_voice_modulate_all(fluid_voice_t* voice) +{ + fluid_mod_t* mod; + int i, k, gen; + fluid_real_t modval; + + /* Loop through all the modulators. + + FIXME: we should loop through the set of generators instead of + the set of modulators. We risk to call 'fluid_voice_update_param' + several times for the same generator if several modulators have + that generator as destination. It's not an error, just a wast of + energy (think polution, global warming, unhappy musicians, + ...) */ + + for (i = 0; i < voice->mod_count; i++) { + + mod = &voice->mod[i]; + gen = fluid_mod_get_dest(mod); + modval = 0.0; + + /* Accumulate the modulation values of all the modulators with + * destination 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->channel, voice); + } + } + + fluid_gen_set_mod(&voice->gen[gen], modval); + + /* Update the parameter values that are depend on the generator + * 'gen' */ + fluid_voice_update_param(voice, gen); + } + + return FLUID_OK; +} + +/* + 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 occured +} + +/* + * fluid_voice_noteoff + */ +int +fluid_voice_noteoff(fluid_voice_t* voice) +{ + fluid_channel_t* channel; + + fluid_profile(FLUID_PROF_VOICE_NOTE, voice->ref); + + 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); + + return FLUID_OK; +} + +/* + * 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 (!_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; +} + +/* + * Called by fluid_synth when the overflow rvoice can be reclaimed. + */ +void fluid_voice_overflow_rvoice_finished(fluid_voice_t* voice) +{ + voice->can_access_overflow_rvoice = 1; + fluid_sample_null_ptr(&voice->overflow_rvoice->dsp.sample); +} + + +/* + * fluid_voice_off + * + * Purpose: + * Turns off a voice, meaning that it is not processed + * anymore by the DSP loop. + */ +int +fluid_voice_off(fluid_voice_t* voice) +{ + fluid_profile(FLUID_PROF_VOICE_RELEASE, voice->ref); + + voice->chan = NO_CHANNEL; + UPDATE_RVOICE0(fluid_rvoice_voiceoff); + + if (voice->can_access_rvoice) + fluid_sample_null_ptr(&voice->rvoice->dsp.sample); + + voice->status = FLUID_VOICE_OFF; + voice->has_noteoff = 1; + + /* Decrement the reference count of the sample. */ + fluid_sample_null_ptr(&voice->sample); + + /* Decrement voice count */ + voice->channel->synth->active_voice_count--; + + return FLUID_OK; +} + +/** + * Adds a modulator to the voice. + * @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) +{ + int i; + + /* + * Some soundfonts come with a huge number of non-standard + * controllers, because they have been designed for one particular + * sound card. Discard them, maybe print a warning. + */ + + if (((mod->flags1 & FLUID_MOD_CC) == 0) + && ((mod->src1 != 0) /* SF2.01 section 8.2.1: Constant value */ + && (mod->src1 != 2) /* Note-on velocity */ + && (mod->src1 != 3) /* Note-on key number */ + && (mod->src1 != 10) /* Poly pressure */ + && (mod->src1 != 13) /* Channel pressure */ + && (mod->src1 != 14) /* Pitch wheel */ + && (mod->src1 != 16))) { /* Pitch wheel sensitivity */ + FLUID_LOG(FLUID_WARN, "Ignoring invalid controller, using non-CC source %i.", mod->src1); + return; + } + + if (mode == FLUID_VOICE_ADD) { + + /* if identical modulator exists, add them */ + for (i = 0; i < voice->mod_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 < voice->mod_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); + } +} + +/** + * Get the unique ID of the noteon-event. + * @param voice Voice instance + * @return Note on unique ID + * + * A SoundFont loader may store the voice processes it has created for + * real-time control during the operation of a voice (for example: parameter + * changes in SoundFont editor). The synth uses a pool of voices, which are + * 'recycled' and never deallocated. + * + * 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(fluid_voice_t* voice) +{ + return voice->id; +} + +/** + * Check if a voice is still playing. + * @param voice Voice instance + * @return TRUE if playing, FALSE otherwise + */ +int fluid_voice_is_playing(fluid_voice_t* voice) +{ + return _PLAYING(voice); +} + +/* + * 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_init, after + * all modulators have been run on the voice once. Also, + * voice->attenuation has to be initialized. + */ +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))) { + + fluid_real_t current_val = fluid_mod_get_value(mod, voice->channel, voice); + fluid_real_t v = fabs(mod->amount); + + if ((mod->src1 == FLUID_MOD_PITCHWHEEL) + || (mod->flags1 & FLUID_MOD_BIPOLAR) + || (mod->flags2 & FLUID_MOD_BIPOLAR) + || (mod->amount < 0)) { + /* Can this modulator produce a negative contribution? */ + v *= -1.0; + } else { + /* No negative value possible. But still, the minimum contribution is 0. */ + v = 0; + } + + /* For example: + * - current_val=100 + * - min_val=-4000 + * - possible_att_reduction_cB += 4100 + */ + if (current_val > v){ + possible_att_reduction_cB += (current_val - v); + } + } + } + + 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, int abs) +{ + voice->gen[gen].nrpn = nrpn_value; + voice->gen[gen].flags = (abs)? GEN_ABS_NRPN : GEN_SET; + fluid_voice_update_param(voice, gen); + return FLUID_OK; +} + +int fluid_voice_set_gain(fluid_voice_t* voice, fluid_real_t gain) +{ + /* avoid division by zero*/ + if (gain < 0.0000001){ + gain = 0.0000001; + } + + voice->synth_gain = gain; + voice->amp_left = fluid_pan(voice->pan, 1) * gain / 32768.0f; + voice->amp_right = fluid_pan(voice->pan, 0) * gain / 32768.0f; + voice->amp_reverb = voice->reverb_send * gain / 32768.0f; + voice->amp_chorus = voice->chorus_send * gain / 32768.0f; + + UPDATE_RVOICE_R1(fluid_rvoice_set_synth_gain, gain); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 0, voice->amp_left); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 1, voice->amp_right); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 2, voice->amp_reverb); + UPDATE_RVOICE_BUFFERS2(fluid_rvoice_buffers_set_amp, 3, voice->amp_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) +{ + signed short peak_max = 0; + signed short peak_min = 0; + signed short peak; + fluid_real_t normalized_amplitude_during_loop; + double result; + int i; + + /* ignore ROM and other(?) invalid samples */ + if (!s->valid) return (FLUID_OK); + + if (!s->amplitude_that_reaches_noise_floor_is_valid){ /* Only once */ + /* Scan the loop */ + for (i = (int)s->loopstart; i < (int) s->loopend; i ++){ + signed short val = s->data[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)/32768.; + 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; +} + +fluid_real_t +fluid_voice_get_overflow_prio(fluid_voice_t* voice, + fluid_overflow_prio_t* score, + unsigned int cur_time) +{ + fluid_real_t this_voice_prio = 0; + + /* 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 (_SUSTAINED(voice) || _HELD_BY_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.1) + a = 0.1; // Avoid div by zero + this_voice_prio += score->volume / a; + } + + return this_voice_prio; +} diff --git a/libs/fluidsynth/src/fluid_voice.h b/libs/fluidsynth/src/fluid_voice.h new file mode 100644 index 0000000000..c43fe59738 --- /dev/null +++ b/libs/fluidsynth/src/fluid_voice.h @@ -0,0 +1,228 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_sys.h" + +#define NO_CHANNEL 0xff + +typedef struct _fluid_overflow_prio_t fluid_overflow_prio_t; + +struct _fluid_overflow_prio_t +{ + fluid_real_t percussion; /**< Is this voice on the drum channel? Then add this score */ + fluid_real_t released; /**< Is this voice in release stage? Then add this score (usually negative) */ + fluid_real_t sustained; /**< Is this voice sustained? Then add this score (usually negative) */ + fluid_real_t volume; /**< Multiply current (or future) volume (a value between 0 and 1) */ + fluid_real_t age; /**< This score will be divided by the number of seconds the voice has lasted */ +}; + +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, quick access for noteoff */ + unsigned char vel; /* the velocity */ + fluid_channel_t* channel; + fluid_gen_t gen[GEN_LAST]; + fluid_mod_t mod[FLUID_NUM_MOD]; + int mod_count; + fluid_sample_t* sample; /* Pointer to sample (dupe in rvoice) */ + + int has_noteoff; /* Flag set when noteoff has been sent */ + + /* basic parameters */ + fluid_real_t output_rate; /* the sample rate of the synthesizer (dupe in rvoice) */ + + unsigned int start_time; + fluid_adsr_env_t volenv; /* Volume envelope (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; + fluid_real_t amp_left; + fluid_real_t amp_right; + + /* reverb */ + fluid_real_t reverb_send; + fluid_real_t amp_reverb; + + /* chorus */ + fluid_real_t chorus_send; + fluid_real_t amp_chorus; + + /* rvoice control */ + fluid_rvoice_t* rvoice; + fluid_rvoice_t* overflow_rvoice; /* Used temporarily and only in overflow situations */ + int can_access_rvoice; /* False if rvoice is being rendered in separate thread */ + int can_access_overflow_rvoice; /* False if overflow_rvoice is being rendered in separate thread */ + + /* for debugging */ + int debug; + double ref; +}; + + +fluid_voice_t* new_fluid_voice(fluid_real_t output_rate); +int 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_write (fluid_voice_t* voice, fluid_real_t *dsp_buf); + +int fluid_voice_init(fluid_voice_t* voice, fluid_sample_t* sample, + 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, int abs); + + +/** Set the gain. */ +int fluid_voice_set_gain(fluid_voice_t* voice, fluid_real_t gain); + +int 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); + +/** fluid_voice_release + Force the voice into release stage. Usefuf anywhere a voice + needs to be damped even if pedals (sustain sostenuto) are depressed. + See fluid_synth_damp_voices_LOCAL(), fluid_synth_damp_voices_by_sostenuto_LOCAL, + fluid_voice_noteoff(), fluid_synth_stop_LOCAL(). +*/ +void fluid_voice_release(fluid_voice_t* voice); +int fluid_voice_noteoff(fluid_voice_t* voice); +int fluid_voice_off(fluid_voice_t* voice); +void fluid_voice_overflow_rvoice_finished(fluid_voice_t* voice); +void fluid_voice_mix (fluid_voice_t *voice, int count, fluid_real_t* dsp_buf, + fluid_real_t* left_buf, fluid_real_t* right_buf, + fluid_real_t* reverb_buf, fluid_real_t* chorus_buf); + +int fluid_voice_kill_excl(fluid_voice_t* voice); +fluid_real_t 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 fluid_rvoice_t* +fluid_voice_lock_rvoice(fluid_voice_t* voice) +{ + voice->can_access_rvoice = 0; + return voice->rvoice; +} + +/** + * 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 fluid_voice_get_channel(voice) ((voice)->channel) + + +#define fluid_voice_set_id(_voice, _id) { (_voice)->id = (_id); } +#define fluid_voice_get_chan(_voice) (_voice)->chan + + +#define _PLAYING(voice) (((voice)->status == FLUID_VOICE_ON) || \ + _SUSTAINED(voice) || \ + _HELD_BY_SOSTENUTO(voice) ) + +/* A voice is 'ON', if it has not yet received a noteoff + * event. Sending a noteoff event will advance the envelopes to + * section 5 (release). */ +#define _ON(voice) ((voice)->status == FLUID_VOICE_ON && !voice->has_noteoff) +#define _SUSTAINED(voice) ((voice)->status == FLUID_VOICE_SUSTAINED) +#define _HELD_BY_SOSTENUTO(voice) ((voice)->status == FLUID_VOICE_HELD_BY_SOSTENUTO) +#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) + + +/* FIXME - This doesn't seem to be used anywhere - JG */ +fluid_real_t fluid_voice_gen_value(fluid_voice_t* voice, int num); + +#define fluid_voice_get_loudness(voice) (fluid_adsr_env_get_max_val(&voice->volenv)) + +#define _GEN(_voice, _n) \ + ((fluid_real_t)(_voice)->gen[_n].val \ + + (fluid_real_t)(_voice)->gen[_n].mod \ + + (fluid_real_t)(_voice)->gen[_n].nrpn) + +/* defined in fluid_dsp_float.c */ + +void fluid_dsp_float_config (void); +int fluid_dsp_float_interpolate_none (fluid_voice_t *voice); +int fluid_dsp_float_interpolate_linear (fluid_voice_t *voice); +int fluid_dsp_float_interpolate_4th_order (fluid_voice_t *voice); +int fluid_dsp_float_interpolate_7th_order (fluid_voice_t *voice); + +#endif /* _FLUID_VOICE_H */ diff --git a/libs/fluidsynth/src/fluidsynth_priv.h b/libs/fluidsynth/src/fluidsynth_priv.h new file mode 100644 index 0000000000..74b9f4b9c6 --- /dev/null +++ b/libs/fluidsynth/src/fluidsynth_priv.h @@ -0,0 +1,267 @@ +/* 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 Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library 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_PRIV_H +#define _FLUIDSYNTH_PRIV_H + +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#if HAVE_STRING_H +#include +#endif + +#if HAVE_STDLIB_H +#include +#endif + +#if HAVE_STDIO_H +#include +#endif + +#if HAVE_MATH_H +#include +#endif + +#if HAVE_ERRNO_H +#include +#endif + +#if HAVE_STDARG_H +#include +#endif + +#if HAVE_UNISTD_H +#include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#if HAVE_SYS_MMAN_H +#include +#endif + +#if HAVE_SYS_TYPES_H +#include +#endif + +#if HAVE_SYS_STAT_H +#include +#endif + +#if HAVE_SYS_TIME_H +#include +#endif + +#if HAVE_SYS_SOCKET_H +#include +#endif + +#if HAVE_NETINET_IN_H +#include +#endif + +#if HAVE_NETINET_TCP_H +#include +#endif + +#if HAVE_ARPA_INET_H +#include +#endif + +#if HAVE_LIMITS_H +#include +#endif + +#if HAVE_PTHREAD_H +#include +#endif + +#if HAVE_IO_H +#include +#endif + +#if HAVE_WINDOWS_H +#include +#include +#include +#endif + +/* MinGW32 special defines */ +#ifdef MINGW32 + +#include +#define snprintf _snprintf +#define vsnprintf _vsnprintf + +#define DSOUND_SUPPORT 1 +#define WINMIDI_SUPPORT 1 +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#endif + +/* Darwin special defines (taken from config_macosx.h) */ +#ifdef DARWIN +#define MACINTOSH +#define __Types__ +#define WITHOUT_SERVER 1 +#endif + + +#include "fluidsynth.h" + + +/*************************************************************** + * + * BASIC TYPES + */ + +#if defined(WITH_FLOAT) +typedef float fluid_real_t; +#else +typedef double fluid_real_t; +#endif + + +#if defined(WIN32) +typedef SOCKET fluid_socket_t; +#else +typedef int fluid_socket_t; +#define INVALID_SOCKET -1 +#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 + + +/** Integer types */ +//typedef gint8 sint8; +typedef guint8 uint8; +//typedef gint16 sint16; +//typedef guint16 uint16; +typedef gint32 sint32; +typedef guint32 uint32; +//typedef gint64 sint64; +//typedef guint64 uint64; + + +/*************************************************************** + * + * 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; + +/*************************************************************** + * + * CONSTANTS + */ + +#define FLUID_BUFSIZE 64 /**< FluidSynth internal buffer size (in samples) */ +#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 */ + +#ifndef PI +#define PI 3.141592654 +#endif + +/*************************************************************** + * + * SYSTEM INTERFACE + */ +typedef FILE* fluid_file; + +#define FLUID_MALLOC(_n) malloc(_n) +#define FLUID_REALLOC(_p,_n) realloc(_p,_n) +#define FLUID_NEW(_t) (_t*)malloc(sizeof(_t)) +#define FLUID_ARRAY(_t,_n) (_t*)malloc((_n)*sizeof(_t)) +#define FLUID_FREE(_p) free(_p) +#define FLUID_FOPEN(_f,_m) fopen(_f,_m) +#define FLUID_FCLOSE(_f) fclose(_f) +#define FLUID_FREAD(_p,_s,_n,_f) fread(_p,_s,_n,_f) +#define FLUID_FSEEK(_f,_n,_set) fseek(_f,_n,_set) +#define FLUID_MEMCPY(_dst,_src,_n) memcpy(_dst,_src,_n) +#define FLUID_MEMSET(_s,_c,_n) memset(_s,_c,_n) +#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_STRNCPY(_dst,_src,_n) strncpy(_dst,_src,_n) +#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 + +#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 + +#define FLUID_LOG fluid_log + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433832795 +#endif + + +#define FLUID_ASSERT(a,b) +#define FLUID_ASSERT_P(a,b) + +char* fluid_error(void); + + +/* Internationalization */ +#define _(s) s + + +#endif /* _FLUIDSYNTH_PRIV_H */ diff --git a/libs/fluidsynth/wscript b/libs/fluidsynth/wscript new file mode 100644 index 0000000000..6451ec829c --- /dev/null +++ b/libs/fluidsynth/wscript @@ -0,0 +1,79 @@ +#!/usr/bin/env python +from waflib.extras import autowaf as autowaf +from waflib import TaskGen +import os +import sys + +# Version of this package (even if built as a child) +MAJOR = '1' +MINOR = '6' +MICRO = '0' +LIBFLUIDSYNTH_VERSION = "%s.%s.%s" % (MAJOR, MINOR, MICRO) + +# Library version (UNIX style major, minor, micro) +# major increment <=> incompatible changes +# minor increment <=> compatible changes (additions) +# micro increment <=> no interface changes +LIBLTC_LIB_VERSION = '1.1.1' + +# Variables for 'waf dist' +APPNAME = 'libltc' +VERSION = LIBFLUIDSYNTH_VERSION +I18N_PACKAGE = 'libfluidsynth' + +# Mandatory variables +top = '.' +out = 'build' + +def options(opt): + autowaf.set_options(opt) + +def configure(conf): + if conf.is_defined('USE_EXTERNAL_LIBS'): + autowaf.check_pkg(conf, 'fluidsynth', uselib_store='LIBFLUIDSYNTH', atleast_version=LIBFLUIDSYNTH_LIB_VERSION, mandatory=True) + else: + conf.load('compiler_c') + autowaf.configure(conf) + +def build(bld): + if bld.is_defined('USE_EXTERNAL_LIBS'): + return + bld (export_includes = ['fluidsynth'], + name = 'libfluidsynth_includes' + ) + bld.stlib (source = [ + 'src/fluid_midi.c', + 'src/fluid_adsr_env.c', + 'src/fluid_chorus.c', + 'src/fluid_iir_filter.c', + 'src/fluid_lfo.c', + 'src/fluid_rev.c', + 'src/fluid_rvoice.c', + 'src/fluid_rvoice_dsp.c', + 'src/fluid_rvoice_event.c', + 'src/fluid_rvoice_mixer.c', + 'src/fluid_defsfont.c', + 'src/fluid_chan.c', + 'src/fluid_event.c', + 'src/fluid_gen.c', + 'src/fluid_mod.c', + 'src/fluid_synth.c', + 'src/fluid_tuning.c', + 'src/fluid_voice.c', + 'src/fluid_conv.c', + 'src/fluid_hash.c', + 'src/fluid_list.c', + 'src/fluid_ringbuffer.c', + 'src/fluid_settings.c', + 'src/fluid_sys.c' + ], + cflags = [ '-fPIC', '-fvisibility=hidden' ], + includes = ['.', 'src/' ], + target = 'libfluidsynth', + use = 'libfluidsynth_includes', + uselib = 'GLIB', + defines = [ 'HAVE_CONFIG_H', 'DEFAULT_SOUNDFONT=""' ] + ) + +def shutdown(): + autowaf.shutdown() diff --git a/tools/ardour_fluidsynth.diff b/tools/ardour_fluidsynth.diff new file mode 100644 index 0000000000..8b9821cf33 --- /dev/null +++ b/tools/ardour_fluidsynth.diff @@ -0,0 +1,252 @@ +diff --git a/libs/fluidsynth/src/fluid_defsfont.c b/libs/fluidsynth/src/fluid_defsfont.c +index 3eea95c..c395218 100644 +--- a/libs/fluidsynth/src/fluid_defsfont.c ++++ b/libs/fluidsynth/src/fluid_defsfont.c +@@ -109,11 +109,13 @@ char* fluid_defsfont_sfont_get_name(fluid_sfont_t* sfont) + return fluid_defsfont_get_name((fluid_defsfont_t*) sfont->data); + } + ++#if 0 + fluid_sample_t* fluid_defsfont_get_sample(fluid_defsfont_t* sfont, char *s) + { + /* This function is here just to avoid an ABI/SONAME bump, see ticket #98. Should never be used. */ + return NULL; + } ++#endif + + fluid_preset_t* + fluid_defsfont_sfont_get_preset(fluid_sfont_t* sfont, unsigned int bank, unsigned int prenum) +diff --git a/libs/fluidsynth/src/fluid_hash.c b/libs/fluidsynth/src/fluid_hash.c +index a063e29..9d5a920 100644 +--- a/libs/fluidsynth/src/fluid_hash.c ++++ b/libs/fluidsynth/src/fluid_hash.c +@@ -93,7 +93,7 @@ static const guint primes[] = + + static const unsigned int nprimes = sizeof (primes) / sizeof (primes[0]); + +-unsigned int ++static unsigned int + spaced_primes_closest (unsigned int num) + { + unsigned int i; +@@ -984,6 +984,7 @@ fluid_hashtable_foreach_remove_or_steal (fluid_hashtable_t *hashtable, + return deleted; + } + ++#if 0 + /** + * fluid_hashtable_foreach_remove: + * @hashtable: a #fluid_hashtable_t. +@@ -1001,7 +1002,7 @@ fluid_hashtable_foreach_remove_or_steal (fluid_hashtable_t *hashtable, + * + * Return value: the number of key/value pairs removed. + **/ +-unsigned int ++static unsigned int + fluid_hashtable_foreach_remove (fluid_hashtable_t *hashtable, + fluid_hr_func_t func, void *user_data) + { +@@ -1010,6 +1011,7 @@ fluid_hashtable_foreach_remove (fluid_hashtable_t *hashtable, + + return fluid_hashtable_foreach_remove_or_steal (hashtable, func, user_data, TRUE); + } ++#endif + + /** + * fluid_hashtable_foreach_steal: +diff --git a/libs/fluidsynth/src/fluid_midi.c b/libs/fluidsynth/src/fluid_midi.c +index 5ceab01..1ee3dd2 100644 +--- a/libs/fluidsynth/src/fluid_midi.c ++++ b/libs/fluidsynth/src/fluid_midi.c +@@ -1115,10 +1115,11 @@ fluid_track_get_duration(fluid_track_t *track) + return time; + } + ++#if 0 + /* + * fluid_track_count_events + */ +-int ++static int + fluid_track_count_events(fluid_track_t *track, int *on, int *off) + { + fluid_midi_event_t *evt = track->first; +@@ -1132,6 +1133,7 @@ fluid_track_count_events(fluid_track_t *track, int *on, int *off) + } + return FLUID_OK; + } ++#endif + + /* + * fluid_track_add_event +@@ -1533,7 +1535,7 @@ fluid_player_load(fluid_player_t *player, fluid_playlist_item *item) + return FLUID_OK; + } + +-void ++static void + fluid_player_advancefile(fluid_player_t *player) + { + if (player->playlist == NULL) { +@@ -1553,7 +1555,7 @@ fluid_player_advancefile(fluid_player_t *player) + } + } + +-void ++static void + fluid_player_playlist_load(fluid_player_t *player, unsigned int msec) + { + fluid_playlist_item* current_playitem; +diff --git a/libs/fluidsynth/src/fluid_rev.c b/libs/fluidsynth/src/fluid_rev.c +index 51b0e79..166007d 100644 +--- a/libs/fluidsynth/src/fluid_rev.c ++++ b/libs/fluidsynth/src/fluid_rev.c +@@ -75,7 +75,7 @@ void fluid_allpass_init(fluid_allpass* allpass); + void fluid_allpass_setfeedback(fluid_allpass* allpass, fluid_real_t val); + fluid_real_t fluid_allpass_getfeedback(fluid_allpass* allpass); + +-void ++static void + fluid_allpass_setbuffer(fluid_allpass* allpass, int size) + { + allpass->bufidx = 0; +@@ -83,7 +83,7 @@ fluid_allpass_setbuffer(fluid_allpass* allpass, int size) + allpass->bufsize = size; + } + +-void ++static void + fluid_allpass_release(fluid_allpass* allpass) + { + FLUID_FREE(allpass->buffer); +diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.c b/libs/fluidsynth/src/fluid_rvoice_mixer.c +index 4672cb8..cc633f5 100644 +--- a/libs/fluidsynth/src/fluid_rvoice_mixer.c ++++ b/libs/fluidsynth/src/fluid_rvoice_mixer.c +@@ -24,7 +24,7 @@ + #include "fluid_rev.h" + #include "fluid_chorus.h" + #include "fluidsynth_priv.h" +-#include "fluid_ladspa.h" ++//#include "fluid_ladspa.h" + + #define SYNTH_REVERB_CHANNEL 0 + #define SYNTH_CHORUS_CHANNEL 1 +diff --git a/libs/fluidsynth/src/fluid_rvoice_mixer.h b/libs/fluidsynth/src/fluid_rvoice_mixer.h +index eeb49ec..d4e41ca 100644 +--- a/libs/fluidsynth/src/fluid_rvoice_mixer.h ++++ b/libs/fluidsynth/src/fluid_rvoice_mixer.h +@@ -24,7 +24,7 @@ + + #include "fluidsynth_priv.h" + #include "fluid_rvoice.h" +-#include "fluid_ladspa.h" ++//#include "fluid_ladspa.h" + + typedef struct _fluid_rvoice_mixer_t fluid_rvoice_mixer_t; + +diff --git a/libs/fluidsynth/src/fluid_settings.c b/libs/fluidsynth/src/fluid_settings.c +index 78725fb..2061c90 100644 +--- a/libs/fluidsynth/src/fluid_settings.c ++++ b/libs/fluidsynth/src/fluid_settings.c +@@ -22,9 +22,9 @@ + #include "fluid_sys.h" + #include "fluid_hash.h" + #include "fluid_synth.h" +-#include "fluid_cmd.h" +-#include "fluid_adriver.h" +-#include "fluid_mdriver.h" ++//#include "fluid_cmd.h" ++//#include "fluid_adriver.h" ++//#include "fluid_mdriver.h" + #include "fluid_settings.h" + #include "fluid_midi.h" + +@@ -294,11 +294,13 @@ fluid_settings_init(fluid_settings_t* settings) + fluid_return_if_fail (settings != NULL); + + fluid_synth_settings(settings); +- fluid_shell_settings(settings); ++ //fluid_shell_settings(settings); + fluid_player_settings(settings); ++#if 0 + fluid_file_renderer_settings(settings); + fluid_audio_driver_settings(settings); + fluid_midi_driver_settings(settings); ++#endif + } + + static int +diff --git a/libs/fluidsynth/src/fluid_synth.c b/libs/fluidsynth/src/fluid_synth.c +index 84ee289..a12260c 100644 +--- a/libs/fluidsynth/src/fluid_synth.c ++++ b/libs/fluidsynth/src/fluid_synth.c +@@ -471,7 +471,7 @@ struct _fluid_sample_timer_t + /* + * fluid_sample_timer_process - called when synth->ticks is updated + */ +-void fluid_sample_timer_process(fluid_synth_t* synth) ++static void fluid_sample_timer_process(fluid_synth_t* synth) + { + fluid_sample_timer_t* st; + long msec; +diff --git a/libs/fluidsynth/src/fluid_synth.h b/libs/fluidsynth/src/fluid_synth.h +index 3af336d..019a8e0 100644 +--- a/libs/fluidsynth/src/fluid_synth.h ++++ b/libs/fluidsynth/src/fluid_synth.h +@@ -37,8 +37,8 @@ + #include "fluid_rev.h" + #include "fluid_voice.h" + #include "fluid_chorus.h" +-#include "fluid_ladspa.h" +-#include "fluid_midi_router.h" ++//#include "fluid_ladspa.h" ++//#include "fluid_midi_router.h" + #include "fluid_sys.h" + #include "fluid_rvoice_event.h" + +diff --git a/libs/fluidsynth/src/fluid_sys.c b/libs/fluidsynth/src/fluid_sys.c +index ee7d8d9..600b04e 100644 +--- a/libs/fluidsynth/src/fluid_sys.c ++++ b/libs/fluidsynth/src/fluid_sys.c +@@ -686,7 +686,7 @@ fluid_thread_join(fluid_thread_t* thread) + } + + +-void ++static void + fluid_timer_run (void *data) + { + fluid_timer_t *timer; +diff --git a/libs/fluidsynth/src/fluid_tuning.c b/libs/fluidsynth/src/fluid_tuning.c +index cc440aa..8977ed6 100644 +--- a/libs/fluidsynth/src/fluid_tuning.c ++++ b/libs/fluidsynth/src/fluid_tuning.c +@@ -143,7 +143,7 @@ char* fluid_tuning_get_name(fluid_tuning_t* tuning) + return tuning->name; + } + +-void fluid_tuning_set_key(fluid_tuning_t* tuning, int key, double pitch) ++static void fluid_tuning_set_key(fluid_tuning_t* tuning, int key, double pitch) + { + tuning->pitch[key] = pitch; + } +diff --git a/libs/fluidsynth/src/fluidsynth_priv.h b/libs/fluidsynth/src/fluidsynth_priv.h +index faf2772..74b9f4b 100644 +--- a/libs/fluidsynth/src/fluidsynth_priv.h ++++ b/libs/fluidsynth/src/fluidsynth_priv.h +@@ -28,14 +28,6 @@ + #include "config.h" + #endif + +-#if defined(__POWERPC__) && !(defined(__APPLE__) && defined(__MACH__)) +-#include "config_maxmsp43.h" +-#endif +- +-#if defined(WIN32) && !defined(MINGW32) +-#include "config_win32.h" +-#endif +- + #if HAVE_STRING_H + #include + #endif diff --git a/tools/update_fluidsynth.sh b/tools/update_fluidsynth.sh new file mode 100755 index 0000000000..66ad7835fa --- /dev/null +++ b/tools/update_fluidsynth.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +if ! test -f wscript || ! test -d gtk2_ardour || ! test -d libs/fluidsynth/;then + echo "This script needs to run from ardour's top-level src tree" + exit 1 +fi + +if test -z "`which rsync`" -o -z "`which git`"; then + echo "this script needs rsync and git" + exit 1 +fi + +ASRC=`pwd` +set -e + +TMP=`mktemp -d` +test -d "$TMP" +echo $TMP + +trap "rm -rf $TMP" EXIT + +cd $TMP +git clone git://git.code.sf.net/p/fluidsynth/code-git fs-git + +FSR=fs-git/fluidsynth/ + +rsync -auc --info=progress2 \ + ${FSR}src/midi/fluid_midi.c \ + ${FSR}src/midi/fluid_midi.h \ + ${FSR}src/rvoice/fluid_adsr_env.c \ + ${FSR}src/rvoice/fluid_adsr_env.h \ + ${FSR}src/rvoice/fluid_chorus.c \ + ${FSR}src/rvoice/fluid_chorus.h \ + ${FSR}src/rvoice/fluid_iir_filter.c \ + ${FSR}src/rvoice/fluid_iir_filter.h \ + ${FSR}src/rvoice/fluid_lfo.c \ + ${FSR}src/rvoice/fluid_lfo.h \ + ${FSR}src/rvoice/fluid_phase.h \ + ${FSR}src/rvoice/fluid_rev.c \ + ${FSR}src/rvoice/fluid_rev.h \ + ${FSR}src/rvoice/fluid_rvoice.c \ + ${FSR}src/rvoice/fluid_rvoice_dsp.c \ + ${FSR}src/rvoice/fluid_rvoice_event.c \ + ${FSR}src/rvoice/fluid_rvoice_event.h \ + ${FSR}src/rvoice/fluid_rvoice.h \ + ${FSR}src/rvoice/fluid_rvoice_mixer.c \ + ${FSR}src/rvoice/fluid_rvoice_mixer.h \ + ${FSR}src/sfloader/fluid_defsfont.c \ + ${FSR}src/sfloader/fluid_defsfont.h \ + ${FSR}src/sfloader/fluid_sfont.h \ + ${FSR}src/synth/fluid_chan.c \ + ${FSR}src/synth/fluid_chan.h \ + ${FSR}src/synth/fluid_event.c \ + ${FSR}src/synth/fluid_event_priv.h \ + ${FSR}src/synth/fluid_event_queue.h \ + ${FSR}src/synth/fluid_gen.c \ + ${FSR}src/synth/fluid_gen.h \ + ${FSR}src/synth/fluid_mod.c \ + ${FSR}src/synth/fluid_mod.h \ + ${FSR}src/synth/fluid_synth.c \ + ${FSR}src/synth/fluid_synth.h \ + ${FSR}src/synth/fluid_tuning.c \ + ${FSR}src/synth/fluid_tuning.h \ + ${FSR}src/synth/fluid_voice.c \ + ${FSR}src/synth/fluid_voice.h \ + ${FSR}src/utils/fluid_conv.c \ + ${FSR}src/utils/fluid_conv.h \ + ${FSR}src/utils/fluid_hash.c \ + ${FSR}src/utils/fluid_hash.h \ + ${FSR}src/utils/fluid_list.c \ + ${FSR}src/utils/fluid_list.h \ + ${FSR}src/utils/fluid_ringbuffer.c \ + ${FSR}src/utils/fluid_ringbuffer.h \ + ${FSR}src/utils/fluid_settings.c \ + ${FSR}src/utils/fluid_settings.h \ + ${FSR}src/utils/fluidsynth_priv.h \ + ${FSR}src/utils/fluid_sys.c \ + ${FSR}src/utils/fluid_sys.h \ + \ + "$ASRC/libs/fluidsynth/src/" + +rsync -auc --info=progress2 \ + --exclude fluidsynth.h \ + ${FSR}include/fluidsynth/event.h \ + ${FSR}include/fluidsynth/gen.h \ + ${FSR}include/fluidsynth/log.h \ + ${FSR}include/fluidsynth/midi.h \ + ${FSR}include/fluidsynth/misc.h \ + ${FSR}include/fluidsynth/mod.h \ + ${FSR}include/fluidsynth/settings.h \ + ${FSR}include/fluidsynth/sfont.h \ + ${FSR}include/fluidsynth/synth.h \ + ${FSR}include/fluidsynth/types.h \ + ${FSR}include/fluidsynth/voice.h \ + \ + "$ASRC/libs/fluidsynth/fluidsynth/" + +cd "$ASRC" +patch -p1 < tools/ardour_fluidsynth.diff diff --git a/wscript b/wscript index fce1359b04..a4ee4ab590 100644 --- a/wscript +++ b/wscript @@ -204,6 +204,7 @@ children = [ 'libs/qm-dsp', 'libs/vamp-plugins', 'libs/libltc', + 'libs/fluidsynth', 'libs/lua', 'libs/ptformat', # core ardour libraries