diff --git a/CMakeLists.txt b/CMakeLists.txt index d1fa324f..de839d62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,7 +68,9 @@ option ( enable-midishare "compile MidiShare support (if it is available)" on ) option ( enable-network "enable network support (requires BSD sockets)" on ) option ( enable-oss "compile OSS support (if it is available)" on ) option ( enable-dsound "compile DirectSound support (if it is available)" on ) +option ( enable-waveout "compile Windows WaveOut support (if it is available)" on ) option ( enable-winmidi "compile Windows MIDI support (if it is available)" on ) +option ( enable-sdl2 "compile SDL2 audio support (if it is available)" on ) option ( enable-pkgconfig "use pkg-config to locate fluidsynth's (mostly optional) dependencies" on ) option ( enable-pulseaudio "compile PulseAudio support (if it is available)" on ) option ( enable-readline "compile readline lib line editing (if it is available)" on ) @@ -194,6 +196,7 @@ endif ( CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE unset ( WINDOWS_SUPPORT CACHE ) unset ( WINDOWS_LIBS CACHE ) unset ( DSOUND_SUPPORT CACHE ) +unset ( WAVEOUT_SUPPORT CACHE ) unset ( WINMIDI_SUPPORT CACHE ) unset ( MINGW32 CACHE ) if ( WIN32 ) @@ -220,6 +223,11 @@ if ( WIN32 ) set ( WINMIDI_SUPPORT 1 ) endif () + if ( enable-waveout AND HAVE_MMSYSTEM_H ) + set ( WINDOWS_LIBS "${WINDOWS_LIBS};winmm" ) + set ( WAVEOUT_SUPPORT 1 ) + endif () + set ( LIBFLUID_CPPFLAGS "-DFLUIDSYNTH_DLL_EXPORTS" ) set ( FLUID_CPPFLAGS "-DFLUIDSYNTH_NOT_A_DLL" ) if ( MSVC ) @@ -547,6 +555,20 @@ if ( enable-oss ) set ( OSS_SUPPORT ${OSS_FOUND} ) endif ( enable-oss ) +unset ( SDL2_SUPPORT CACHE ) +unset ( SDL2_INCLUDE_DIR CACHE ) +unset ( SDL2_LIBRARY CACHE ) +if ( enable-sdl2 ) + find_package ( SDL2 ) + + if ( SDL2_FOUND ) + set ( SDL2_SUPPORT ${SDL2_FOUND} ) + else ( SDL2_FOUND) + unset ( SDL2_INCLUDE_DIR CACHE ) + unset ( SDL2_LIBRARY CACHE ) + endif ( SDL2_FOUND ) +endif ( enable-sdl2 ) + unset ( MIDISHARE_SUPPORT CACHE ) if ( enable-midishare ) find_package ( MidiShare QUIET ) diff --git a/cmake_admin/FindSDL2.cmake b/cmake_admin/FindSDL2.cmake new file mode 100644 index 00000000..860d2381 --- /dev/null +++ b/cmake_admin/FindSDL2.cmake @@ -0,0 +1,164 @@ +# Locate SDL2 library +# This module defines +# SDL2_LIBRARY, the name of the library to link against +# SDL2_FOUND, if false, do not try to link to SDL2 +# SDL2_INCLUDE_DIR, where to find SDL.h +# +# This module responds to the the flag: +# SDL2_BUILDING_LIBRARY +# If this is defined, then no SDL2main will be linked in because +# only applications need main(). +# Otherwise, it is assumed you are building an application and this +# module will attempt to locate and set the the proper link flags +# as part of the returned SDL2_LIBRARY variable. +# +# Don't forget to include SDLmain.h and SDLmain.m your project for the +# OS X framework based version. (Other versions link to -lSDL2main which +# this module will try to find on your behalf.) Also for OS X, this +# module will automatically add the -framework Cocoa on your behalf. +# +# +# Additional Note: If you see an empty SDL2_LIBRARY_TEMP in your configuration +# and no SDL2_LIBRARY, it means CMake did not find your SDL2 library +# (SDL2.dll, libsdl2.so, SDL2.framework, etc). +# Set SDL2_LIBRARY_TEMP to point to your SDL2 library, and configure again. +# Similarly, if you see an empty SDL2MAIN_LIBRARY, you should set this value +# as appropriate. These values are used to generate the final SDL2_LIBRARY +# variable, but when these values are unset, SDL2_LIBRARY does not get created. +# +# +# $SDL2DIR is an environment variable that would +# correspond to the ./configure --prefix=$SDL2DIR +# used in building SDL2. +# l.e.galup 9-20-02 +# +# Modified by Eric Wing. +# Added code to assist with automated building by using environmental variables +# and providing a more controlled/consistent search behavior. +# Added new modifications to recognize OS X frameworks and +# additional Unix paths (FreeBSD, etc). +# Also corrected the header search path to follow "proper" SDL guidelines. +# Added a search for SDL2main which is needed by some platforms. +# Added a search for threads which is needed by some platforms. +# Added needed compile switches for MinGW. +# +# On OSX, this will prefer the Framework version (if found) over others. +# People will have to manually change the cache values of +# SDL2_LIBRARY to override this selection or set the CMake environment +# CMAKE_INCLUDE_PATH to modify the search paths. +# +# Note that the header path has changed from SDL2/SDL.h to just SDL.h +# This needed to change because "proper" SDL convention +# is #include "SDL.h", not . This is done for portability +# reasons because not all systems place things in SDL2/ (see FreeBSD). + +#============================================================================= +# Copyright 2003-2009 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +SET(SDL2_SEARCH_PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt +) + +FIND_PATH(SDL2_INCLUDE_DIR SDL.h + HINTS + $ENV{SDL2DIR} ${SDL2_ROOT} + PATH_SUFFIXES include/SDL2 include + PATHS ${SDL2_SEARCH_PATHS} +) + +FIND_LIBRARY(SDL2_LIBRARY_TEMP + NAMES SDL2 + HINTS + $ENV{SDL2DIR} ${SDL2_ROOT} + PATH_SUFFIXES lib64 lib + PATHS ${SDL2_SEARCH_PATHS} +) + +IF(NOT SDL2_BUILDING_LIBRARY) + IF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") + # Non-OS X framework versions expect you to also dynamically link to + # SDL2main. This is mainly for Windows and OS X. Other (Unix) platforms + # seem to provide SDL2main for compatibility even though they don't + # necessarily need it. + FIND_LIBRARY(SDL2MAIN_LIBRARY + NAMES SDL2main + HINTS + $ENV{SDL2DIR} ${SDL2_ROOT} + PATH_SUFFIXES lib64 lib + PATHS ${SDL2_SEARCH_PATHS} + ) + ENDIF(NOT ${SDL2_INCLUDE_DIR} MATCHES ".framework") +ENDIF(NOT SDL2_BUILDING_LIBRARY) + +# SDL2 may require threads on your system. +# The Apple build may not need an explicit flag because one of the +# frameworks may already provide it. +# But for non-OSX systems, I will use the CMake Threads package. +IF(NOT APPLE) + FIND_PACKAGE(Threads) +ENDIF(NOT APPLE) + +# MinGW needs an additional library, mwindows +# It's total link flags should look like -lmingw32 -lSDL2main -lSDL2 -lmwindows +# (Actually on second look, I think it only needs one of the m* libraries.) +IF(MINGW) + SET(MINGW32_LIBRARY mingw32 CACHE STRING "mwindows for MinGW") +ENDIF(MINGW) + +IF(SDL2_LIBRARY_TEMP) + # For SDL2main + IF(NOT SDL2_BUILDING_LIBRARY) + IF(SDL2MAIN_LIBRARY) + SET(SDL2_LIBRARY_TEMP ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(SDL2MAIN_LIBRARY) + ENDIF(NOT SDL2_BUILDING_LIBRARY) + + # For OS X, SDL2 uses Cocoa as a backend so it must link to Cocoa. + # CMake doesn't display the -framework Cocoa string in the UI even + # though it actually is there if I modify a pre-used variable. + # I think it has something to do with the CACHE STRING. + # So I use a temporary variable until the end so I can set the + # "real" variable in one-shot. + IF(APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} "-framework Cocoa") + ENDIF(APPLE) + + # For threads, as mentioned Apple doesn't need this. + # In fact, there seems to be a problem if I used the Threads package + # and try using this line, so I'm just skipping it entirely for OS X. + IF(NOT APPLE) + SET(SDL2_LIBRARY_TEMP ${SDL2_LIBRARY_TEMP} ${CMAKE_THREAD_LIBS_INIT}) + ENDIF(NOT APPLE) + + # For MinGW library + IF(MINGW) + SET(SDL2_LIBRARY_TEMP ${MINGW32_LIBRARY} ${SDL2_LIBRARY_TEMP}) + ENDIF(MINGW) + + # Set the final string here so the GUI reflects the final state. + SET(SDL2_LIBRARY ${SDL2_LIBRARY_TEMP} CACHE STRING "Where the SDL2 Library can be found") + # Set the temp variable to INTERNAL so it is not seen in the CMake GUI + SET(SDL2_LIBRARY_TEMP "${SDL2_LIBRARY_TEMP}" CACHE INTERNAL "") +ENDIF(SDL2_LIBRARY_TEMP) + +INCLUDE(FindPackageHandleStandardArgs) + +FIND_PACKAGE_HANDLE_STANDARD_ARGS(SDL2 REQUIRED_VARS SDL2_LIBRARY SDL2_INCLUDE_DIR) + diff --git a/cmake_admin/report.cmake b/cmake_admin/report.cmake index e25a989d..88cd7ff3 100644 --- a/cmake_admin/report.cmake +++ b/cmake_admin/report.cmake @@ -80,12 +80,24 @@ else ( DSOUND_SUPPORT ) message ( "DSound: no" ) endif ( DSOUND_SUPPORT ) +if ( WAVEOUT_SUPPORT ) + message ( "WaveOut support: yes" ) +else ( WAVEOUT_SUPPORT ) + message ( "WaveOut support: no" ) +endif ( WAVEOUT_SUPPORT ) + if ( WINMIDI_SUPPORT ) message ( "WinMidi support: yes" ) else ( WINMIDI_SUPPORT ) message ( "WinMidi support: no" ) endif ( WINMIDI_SUPPORT ) +if ( SDL2_SUPPORT ) + message ( "SDL2 support: yes" ) +else ( SDL2_SUPPORT ) + message ( "SDL2 support: no" ) +endif ( SDL2_SUPPORT ) + if ( LADSPA_SUPPORT ) message ( "LADSPA support: yes" ) else ( LADSPA_SUPPORT ) diff --git a/doc/fluidsettings.xml b/doc/fluidsettings.xml index 7bc3adcc..2d2e0d4a 100644 --- a/doc/fluidsettings.xml +++ b/doc/fluidsettings.xml @@ -367,9 +367,9 @@ Developers: Settings can be deprecated by adding: SOME TEXT dart (OS/2) - jack, alsa, oss, pulseaudio, coreaudio, dsound, portaudio, sndman, dart, file + alsa, coreaudio, dart, dsound, file, jack, oss, portaudio, pulseaudio, sdl2, sndman, waveout - The audio system to be used. + The audio system to be used. In order to use sdl2 as audio driver, the application is responsible for initializing SDL's audio subsystem. diff --git a/doc/fluidsynth-v20-devdoc.txt b/doc/fluidsynth-v20-devdoc.txt index fae89f12..75428b85 100644 --- a/doc/fluidsynth-v20-devdoc.txt +++ b/doc/fluidsynth-v20-devdoc.txt @@ -206,7 +206,7 @@ For a full list of available synthesizer settings, please refer The synthesizer itself does not write any audio to the audio output. This allows application developers to manage the audio output themselves if they wish. The next section describes the use of the synthesizer without an audio driver in more detail. -Creating the audio driver is straightforward: set the appropriate settings and create the driver object. Because the FluidSynth has support for several audio systems, you may want to change which one you want to use. The list below shows the audio systems that are currently supported. It displays the name, as used by the fluidsynth library, and a description. +Creating the audio driver is straightforward: set the audio.driver settings and create the driver object. Because the FluidSynth has support for several audio systems, you may want to change which one you want to use. The list below shows the audio systems that are currently supported. It displays the name, as used by the fluidsynth library, and a description. - jack: JACK Audio Connection Kit (Linux, Mac OS X, Windows) - alsa: Advanced Linux Sound Architecture (Linux) @@ -218,6 +218,7 @@ Creating the audio driver is straightforward: set the appropriate settings and c - sndman: Apple SoundManager (Mac OS Classic) - dart: DART sound driver (OS/2) - file: Driver to output audio to a file +- sdl2*: Simple DirectMedia Layer (Linux, Windows, Mac OS X, iOS, Android, FreeBSD, Haiku, etc.) The default audio driver depends on the settings with which FluidSynth was compiled. You can get the default driver with fluid_settings_getstr_default(). To get the list of available drivers use the fluid_settings_foreach_option() function. Finally, you can set the driver with fluid_settings_setstr(). In most cases, the default driver should work out of the box. @@ -245,7 +246,7 @@ As soon as the audio driver is created, it will start playing. The audio driver There are a number of general audio driver settings. The audio.driver settings define the audio subsystem that will be used. The audio.periods and audio.period-size settings define the latency and robustness against scheduling delays. There are additional settings for the audio subsystems used. For a full list of available audio driver settings, please refer to FluidSettings Documentation. - +*Note: In order to use sdl2 as audio driver, the application is responsible for initializing SDL (e.g. with SDL_Init()). This must be done before the first call to new_fluid_settings()! Also make sure to call SDL_Quit() after all fluidsynth instances have been destroyed. \section UsingSynth Using the synthesizer without an audio driver diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a0ba53d1..67c5a6f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,6 +32,7 @@ include_directories ( ${CMAKE_SOURCE_DIR}/include ${CMAKE_BINARY_DIR}/include ${PTHREADS_INCLUDE_DIR} + ${SDL2_INCLUDE_DIR} ) include_directories ( @@ -82,10 +83,18 @@ if ( DSOUND_SUPPORT ) set ( fluid_dsound_SOURCES drivers/fluid_dsound.c ) endif ( DSOUND_SUPPORT ) +if ( WAVEOUT_SUPPORT ) + set ( fluid_waveout_SOURCES drivers/fluid_waveout.c ) +endif ( WAVEOUT_SUPPORT ) + if ( WINMIDI_SUPPORT ) set ( fluid_winmidi_SOURCES drivers/fluid_winmidi.c ) endif ( WINMIDI_SUPPORT ) +if ( SDL2_SUPPORT ) + set ( fluid_sdl2_SOURCES drivers/fluid_sdl2.c ) +endif ( SDL2_SUPPORT ) + if ( OSS_SUPPORT ) set ( fluid_oss_SOURCES drivers/fluid_oss.c ) endif ( OSS_SUPPORT ) @@ -248,7 +257,9 @@ add_library ( libfluidsynth-OBJ OBJECT ${fluid_portaudio_SOURCES} ${fluid_pulse_SOURCES} ${fluid_dsound_SOURCES} + ${fluid_waveout_SOURCES} ${fluid_winmidi_SOURCES} + ${fluid_sdl2_SOURCES} ${libfluidsynth_SOURCES} ${public_HEADERS} ${public_main_HEADER} @@ -316,6 +327,7 @@ target_link_libraries ( libfluidsynth ${PULSE_LIBRARIES} ${PORTAUDIO_LIBRARIES} ${LIBSNDFILE_LIBRARIES} + ${SDL2_LIBRARY} ${DBUS_LIBRARIES} ${READLINE_LIBS} ${DART_LIBS} diff --git a/src/config.cmake b/src/config.cmake index ae7c7dbe..746f035a 100644 --- a/src/config.cmake +++ b/src/config.cmake @@ -193,9 +193,15 @@ /* Define to enable DirectSound driver */ #cmakedefine DSOUND_SUPPORT @DSOUND_SUPPORT@ +/* Define to enable Windows WaveOut driver */ +#cmakedefine WAVEOUT_SUPPORT @WAVEOUT_SUPPORT@ + /* Define to enable Windows MIDI driver */ #cmakedefine WINMIDI_SUPPORT @WINMIDI_SUPPORT@ +/* Define to enable SDL2 audio driver */ +#cmakedefine SDL2_SUPPORT @SDL2_SUPPORT@ + /* Define to 1 if you have the ANSI C header files. */ #cmakedefine STDC_HEADERS @STDC_HEADERS@ diff --git a/src/drivers/fluid_adriver.c b/src/drivers/fluid_adriver.c index f1ab772a..a37ea2f8 100644 --- a/src/drivers/fluid_adriver.c +++ b/src/drivers/fluid_adriver.c @@ -99,6 +99,16 @@ static const fluid_audriver_definition_t fluid_audio_drivers[] = }, #endif +#if WAVEOUT_SUPPORT + { + "waveout", + new_fluid_waveout_audio_driver, + NULL, + delete_fluid_waveout_audio_driver, + fluid_waveout_audio_driver_settings + }, +#endif + #if SNDMAN_SUPPORT { "sndman", @@ -129,6 +139,16 @@ static const fluid_audriver_definition_t fluid_audio_drivers[] = }, #endif +#if SDL2_SUPPORT + { + "sdl2", + new_fluid_sdl2_audio_driver, + NULL, + delete_fluid_sdl2_audio_driver, + fluid_sdl2_audio_driver_settings + }, +#endif + #if AUFILE_SUPPORT { "file", diff --git a/src/drivers/fluid_adriver.h b/src/drivers/fluid_adriver.h index 1564168f..271761c5 100644 --- a/src/drivers/fluid_adriver.h +++ b/src/drivers/fluid_adriver.h @@ -83,6 +83,13 @@ void delete_fluid_dsound_audio_driver(fluid_audio_driver_t *p); void fluid_dsound_audio_driver_settings(fluid_settings_t *settings); #endif +#if WAVEOUT_SUPPORT +fluid_audio_driver_t *new_fluid_waveout_audio_driver(fluid_settings_t *settings, + fluid_synth_t *synth); +void delete_fluid_waveout_audio_driver(fluid_audio_driver_t *p); +void fluid_waveout_audio_driver_settings(fluid_settings_t *settings); +#endif + #if PORTAUDIO_SUPPORT void fluid_portaudio_driver_settings(fluid_settings_t *settings); fluid_audio_driver_t *new_fluid_portaudio_driver(fluid_settings_t *settings, @@ -114,6 +121,13 @@ void delete_fluid_dart_audio_driver(fluid_audio_driver_t *p); void fluid_dart_audio_driver_settings(fluid_settings_t *settings); #endif +#if SDL2_SUPPORT +fluid_audio_driver_t *new_fluid_sdl2_audio_driver(fluid_settings_t *settings, + fluid_synth_t *synth); +void delete_fluid_sdl2_audio_driver(fluid_audio_driver_t *p); +void fluid_sdl2_audio_driver_settings(fluid_settings_t *settings); +#endif + #if AUFILE_SUPPORT fluid_audio_driver_t *new_fluid_file_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth); diff --git a/src/drivers/fluid_aufile.c b/src/drivers/fluid_aufile.c index 04973e0e..ab6a2586 100644 --- a/src/drivers/fluid_aufile.c +++ b/src/drivers/fluid_aufile.c @@ -29,6 +29,8 @@ #include "fluid_settings.h" +#if AUFILE_SUPPORT + /** fluid_file_audio_driver_t * * This structure should not be accessed directly. Use audio port @@ -129,3 +131,5 @@ static int fluid_file_audio_run_s16(void *d, unsigned int clock_time) return fluid_file_renderer_process_block(dev->renderer) == FLUID_OK ? 1 : 0; } + +#endif /* AUFILE_SUPPORT */ diff --git a/src/drivers/fluid_sdl2.c b/src/drivers/fluid_sdl2.c new file mode 100644 index 00000000..fa39f397 --- /dev/null +++ b/src/drivers/fluid_sdl2.c @@ -0,0 +1,251 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * Copyright (C) 2018 Carlo Bramini + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_synth.h" +#include "fluid_adriver.h" +#include "fluid_settings.h" + +#if SDL2_SUPPORT + +#include "SDL.h" + +typedef struct +{ + fluid_audio_driver_t driver; + + fluid_synth_t *synth; + fluid_audio_callback_t write_ptr; + + SDL_AudioDeviceID devid; + + int frame_size; + +} fluid_sdl2_audio_driver_t; + + +static void +SDLAudioCallback(void *data, void *stream, int len) +{ + fluid_sdl2_audio_driver_t *dev = (fluid_sdl2_audio_driver_t *)data; + + len /= dev->frame_size; + + dev->write_ptr(dev->synth, len, stream, 0, 2, stream, 1, 2); +} + +void fluid_sdl2_audio_driver_settings(fluid_settings_t *settings) +{ + int n, nDevs; + + fluid_settings_register_str(settings, "audio.sdl2.device", "default", 0); + fluid_settings_add_option(settings, "audio.sdl2.device", "default"); + + if(!SDL_WasInit(SDL_INIT_AUDIO)) + { + FLUID_LOG(FLUID_ERR, "SDL2 not initialized"); + return; + } + + nDevs = SDL_GetNumAudioDevices(0); + + for(n = 0; n < nDevs; n++) + { + const char *dev_name = SDL_GetAudioDeviceName(n, 0); + + if(dev_name != NULL) + { + FLUID_LOG(FLUID_DBG, "Testing audio device: %s", dev_name); + fluid_settings_add_option(settings, "audio.sdl2.device", dev_name); + } + } +} + + +/* + * new_fluid_sdl2_audio_driver + */ +fluid_audio_driver_t * +new_fluid_sdl2_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) +{ + fluid_sdl2_audio_driver_t *dev = NULL; + fluid_audio_callback_t write_ptr; + double sample_rate; + int period_size, sample_size; + SDL_AudioSpec aspec, rspec; + char *device; + const char *dev_name; + + /* Retrieve the settings */ + fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); + fluid_settings_getint(settings, "audio.period-size", &period_size); + + /* Lower values do not seem to give good results */ + if(period_size < 1024) + { + period_size = 1024; + } + else + + /* According to documentation, it MUST be a power of two */ + if((period_size & (period_size - 1)) != 0) + { + FLUID_LOG(FLUID_DBG, "\"audio.period-size\" must be a power of 2"); + return NULL; + } + + /* Clear the format buffer */ + FLUID_MEMSET(&aspec, 0, sizeof(aspec)); + + /* Setup mixing frequency */ + aspec.freq = (int)sample_rate; + + /* Check the format */ + if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) + { + FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format"); + + sample_size = sizeof(float); + write_ptr = fluid_synth_write_float; + + aspec.format = AUDIO_F32SYS; + } + else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) + { + FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format"); + + sample_size = sizeof(short); + write_ptr = fluid_synth_write_s16; + + aspec.format = AUDIO_S16SYS; + } + else + { + FLUID_LOG(FLUID_ERR, "Unhandled sample format"); + return NULL; + } + + /* Compile the format buffer */ + aspec.channels = 2; + aspec.samples = aspec.channels * ((period_size + 7) & ~7); + aspec.callback = (SDL_AudioCallback)SDLAudioCallback; + + /* Check if SDL library has been started */ + if(!SDL_WasInit(SDL_INIT_AUDIO)) + { + FLUID_LOG(FLUID_ERR, "SDL2 not initialized"); + return NULL; + } + + /* Set default device to use */ + device = NULL; + dev_name = NULL; + + /* get the selected device name. if none is specified, use default device. */ + if(fluid_settings_dupstr(settings, "audio.sdl2.device", &device) == FLUID_OK + && device != NULL && device[0] != '\0') + { + int n, nDevs = SDL_GetNumAudioDevices(0); + + for(n = 0; n < nDevs; n++) + { + dev_name = SDL_GetAudioDeviceName(n, 0); + + if(FLUID_STRCASECMP(dev_name, device) == 0) + { + FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %s", dev_name); + break; + } + } + + if(n >= nDevs) + { + FLUID_LOG(FLUID_DBG, "Audio device %s, using \"default\"", device); + dev_name = NULL; + } + } + + if(device != NULL) + { + FLUID_FREE(device); + } + + do + { + /* create and clear the driver data */ + dev = FLUID_NEW(fluid_sdl2_audio_driver_t); + + if(dev == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + break; + } + + FLUID_MEMSET(dev, 0, sizeof(fluid_sdl2_audio_driver_t)); + + /* set device pointer to userdata */ + aspec.userdata = dev; + + /* Save copy of synth */ + dev->synth = synth; + + /* Save copy of other variables */ + dev->write_ptr = write_ptr; + dev->frame_size = sample_size * aspec.channels; + + /* Open audio device */ + dev->devid = SDL_OpenAudioDevice(dev_name, 0, &aspec, &rspec, 0); + + if(!dev->devid) + { + FLUID_LOG(FLUID_ERR, "Failed to open audio device"); + break; + } + + /* Start to play */ + SDL_PauseAudioDevice(dev->devid, 0); + + return (fluid_audio_driver_t *) dev; + } + while(0); + + delete_fluid_sdl2_audio_driver(&dev->driver); + return NULL; +} + + +void delete_fluid_sdl2_audio_driver(fluid_audio_driver_t *d) +{ + fluid_sdl2_audio_driver_t *dev = (fluid_sdl2_audio_driver_t *) d; + + if(dev != NULL) + { + if(dev->devid) + { + /* Stop audio and close */ + SDL_PauseAudioDevice(dev->devid, 1); + SDL_CloseAudioDevice(dev->devid); + } + + FLUID_FREE(dev); + } +} + +#endif /* SDL2_SUPPORT */ diff --git a/src/drivers/fluid_waveout.c b/src/drivers/fluid_waveout.c new file mode 100644 index 00000000..7fbaddcc --- /dev/null +++ b/src/drivers/fluid_waveout.c @@ -0,0 +1,386 @@ +/* FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * Copyright (C) 2018 Carlo Bramini + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include "fluid_synth.h" +#include "fluid_adriver.h" +#include "fluid_settings.h" + +#if WAVEOUT_SUPPORT + +#include + +#define NOBITMAP +#include + +/* Number of buffers in the chain */ +#define NB_SOUND_BUFFERS 4 + +/* Milliseconds of a single sound buffer */ +#define MS_BUFFER_LENGTH 20 + +typedef struct +{ + fluid_audio_driver_t driver; + + fluid_synth_t *synth; + fluid_audio_callback_t write_ptr; + + HWAVEOUT hWaveOut; + WAVEHDR waveHeader[NB_SOUND_BUFFERS]; + + int sample_size; + int num_frames; + + HANDLE hThread; + DWORD dwThread; + + int nQuit; + HANDLE hQuit; + +} fluid_waveout_audio_driver_t; + + +/* Thread for playing sample buffers */ +static DWORD WINAPI fluid_waveout_synth_thread(void *data) +{ + fluid_waveout_audio_driver_t *dev; + WAVEHDR *pWave; + + MSG msg; + int code; + + /* Forces creation of message queue */ + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + for(;;) + { + code = GetMessage(&msg, NULL, 0, 0); + + if(code < 0) + { + FLUID_LOG(FLUID_ERR, "fluid_waveout_synth_thread: GetMessage() failed."); + break; + } + + if(msg.message == WM_CLOSE) + { + break; + } + + switch(msg.message) + { + case MM_WOM_DONE: + pWave = (WAVEHDR *)msg.lParam; + dev = (fluid_waveout_audio_driver_t *)pWave->dwUser; + + if(dev->nQuit > 0) + { + /* Release the sample buffer */ + waveOutUnprepareHeader((HWAVEOUT)msg.wParam, pWave, sizeof(WAVEHDR)); + + if(--dev->nQuit == 0) + { + SetEvent(dev->hQuit); + } + } + else + { + dev->write_ptr(dev->synth, dev->num_frames, pWave->lpData, 0, 2, pWave->lpData, 1, 2); + + waveOutWrite((HWAVEOUT)msg.wParam, pWave, sizeof(WAVEHDR)); + } + + break; + } + } + + return 0; +} + +void fluid_waveout_audio_driver_settings(fluid_settings_t *settings) +{ + UINT n, nDevs = waveOutGetNumDevs(); +#ifdef _UNICODE + char dev_name[MAXPNAMELEN]; +#endif + + fluid_settings_register_str(settings, "audio.waveout.device", "default", 0); + fluid_settings_add_option(settings, "audio.waveout.device", "default"); + + for(n = 0; n < nDevs; n++) + { + WAVEOUTCAPS caps; + MMRESULT res; + + res = waveOutGetDevCaps(n, &caps, sizeof(caps)); + + if(res == MMSYSERR_NOERROR) + { +#ifdef _UNICODE + WideCharToMultiByte(CP_UTF8, 0, caps.szPname, -1, dev_name, MAXPNAMELEN, 0, 0); + FLUID_LOG(FLUID_DBG, "Testing audio device: %s", dev_name); + fluid_settings_add_option(settings, "audio.waveout.device", dev_name); +#else + FLUID_LOG(FLUID_DBG, "Testing audio device: %s", caps.szPname); + fluid_settings_add_option(settings, "audio.waveout.device", caps.szPname); +#endif + } + } +} + + +/* + * new_fluid_waveout_audio_driver + */ +fluid_audio_driver_t * +new_fluid_waveout_audio_driver(fluid_settings_t *settings, fluid_synth_t *synth) +{ + fluid_waveout_audio_driver_t *dev = NULL; + fluid_audio_callback_t write_ptr; + double sample_rate; + int periods, period_size, frequency, sample_size; + LPSTR ptrBuffer; + int lenBuffer; + int device; + int i; + WAVEFORMATEX wfx; + char dev_name[MAXPNAMELEN]; + MMRESULT errCode; + + /* Retrieve the settings */ + fluid_settings_getnum(settings, "synth.sample-rate", &sample_rate); + fluid_settings_getint(settings, "audio.periods", &periods); + fluid_settings_getint(settings, "audio.period-size", &period_size); + + /* Clear the format buffer */ + ZeroMemory(&wfx, sizeof(WAVEFORMATEX)); + + /* check the format */ + if(fluid_settings_str_equal(settings, "audio.sample-format", "float")) + { + FLUID_LOG(FLUID_DBG, "Selected 32 bit sample format"); + + sample_size = sizeof(float); + write_ptr = fluid_synth_write_float; + + wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + } + else if(fluid_settings_str_equal(settings, "audio.sample-format", "16bits")) + { + FLUID_LOG(FLUID_DBG, "Selected 16 bit sample format"); + + sample_size = sizeof(short); + write_ptr = fluid_synth_write_s16; + + wfx.wFormatTag = WAVE_FORMAT_PCM; + } + else + { + FLUID_LOG(FLUID_ERR, "Unhandled sample format"); + return NULL; + } + + /* Set frequency to integer */ + frequency = (int)sample_rate; + + /* Compile the format buffer */ + wfx.nChannels = 2; + wfx.wBitsPerSample = sample_size * 8; + wfx.nSamplesPerSec = frequency; + wfx.nBlockAlign = sample_size * wfx.nChannels; + wfx.nAvgBytesPerSec = frequency * wfx.nBlockAlign; + + /* Calculate the length of a single buffer */ + lenBuffer = (MS_BUFFER_LENGTH * wfx.nAvgBytesPerSec + 999) / 1000; + + /* Round to 8-bytes size */ + lenBuffer = (lenBuffer + 7) & ~7; + + /* create and clear the driver data */ + dev = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + sizeof(fluid_waveout_audio_driver_t) + lenBuffer * NB_SOUND_BUFFERS); + + if(dev == NULL) + { + FLUID_LOG(FLUID_ERR, "Out of memory"); + return NULL; + } + + /* Save copy of synth */ + dev->synth = synth; + + /* Save copy of other variables */ + dev->write_ptr = write_ptr; + dev->sample_size = sample_size; + + /* Calculate the number of frames in a block */ + dev->num_frames = lenBuffer / wfx.nBlockAlign; + + /* Set default device to use */ + device = WAVE_MAPPER; + + /* get the selected device name. if none is specified, use default device. */ + if(fluid_settings_copystr(settings, "audio.waveout.device", dev_name, MAXPNAMELEN) == FLUID_OK + && dev_name[0] != '\0') + { + UINT nDevs = waveOutGetNumDevs(); + UINT n; +#ifdef _UNICODE + WCHAR lpwDevName[MAXPNAMELEN]; + + MultiByteToWideChar(CP_UTF8, 0, dev_name, -1, lpwDevName, MAXPNAMELEN); +#endif + + for(n = 0; n < nDevs; n++) + { + WAVEOUTCAPS caps; + MMRESULT res; + + res = waveOutGetDevCaps(n, &caps, sizeof(caps)); + + if(res == MMSYSERR_NOERROR) + { +#ifdef _UNICODE + + if(wcsicmp(lpwDevName, caps.szPname) == 0) +#else + if(FLUID_STRCASECMP(dev_name, caps.szPname) == 0) +#endif + { + FLUID_LOG(FLUID_DBG, "Selected audio device GUID: %s", dev_name); + device = n; + break; + } + } + } + } + + do + { + + dev->hQuit = CreateEvent(NULL, FALSE, FALSE, NULL); + + if(dev->hQuit == NULL) + { + FLUID_LOG(FLUID_ERR, "Failed to create quit event"); + break; + } + + /* Create thread which processes re-adding SYSEX buffers */ + dev->hThread = CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE) + fluid_waveout_synth_thread, + dev, + 0, + &dev->dwThread); + + if(dev->hThread == NULL) + { + FLUID_LOG(FLUID_ERR, "Failed to create waveOut thread"); + break; + } + + errCode = waveOutOpen(&dev->hWaveOut, + device, + &wfx, + (DWORD_PTR)dev->dwThread, + 0, + CALLBACK_THREAD); + + if(errCode != MMSYSERR_NOERROR) + { + FLUID_LOG(FLUID_ERR, "Failed to open waveOut device"); + break; + } + + /* Get pointer to sound buffer memory */ + ptrBuffer = (LPSTR)(dev + 1); + + /* Setup the sample buffers */ + for(i = 0; i < NB_SOUND_BUFFERS; i++) + { + /* Clear the sample buffer */ + memset(ptrBuffer, 0, lenBuffer); + + /* Clear descriptor buffer */ + memset(dev->waveHeader + i, 0, sizeof(WAVEHDR)); + + /* Compile descriptor buffer */ + dev->waveHeader[i].lpData = ptrBuffer; + dev->waveHeader[i].dwBufferLength = lenBuffer; + dev->waveHeader[i].dwUser = (DWORD_PTR)dev; + + waveOutPrepareHeader(dev->hWaveOut, &dev->waveHeader[i], sizeof(WAVEHDR)); + + ptrBuffer += lenBuffer; + } + + /* Play the sample buffers */ + for(i = 0; i < NB_SOUND_BUFFERS; i++) + { + waveOutWrite(dev->hWaveOut, &dev->waveHeader[i], sizeof(WAVEHDR)); + } + + return (fluid_audio_driver_t *) dev; + + } + while(0); + + delete_fluid_waveout_audio_driver(&dev->driver); + return NULL; +} + + +void delete_fluid_waveout_audio_driver(fluid_audio_driver_t *d) +{ + int i; + + fluid_waveout_audio_driver_t *dev = (fluid_waveout_audio_driver_t *) d; + fluid_return_if_fail(dev != NULL); + + /* release all the allocated resources */ + if(dev->hWaveOut != NULL) + { + dev->nQuit = NB_SOUND_BUFFERS; + WaitForSingleObject(dev->hQuit, INFINITE); + + waveOutClose(dev->hWaveOut); + } + + if(dev->hThread != NULL) + { + PostThreadMessage(dev->dwThread, WM_CLOSE, 0, 0); + WaitForSingleObject(dev->hThread, INFINITE); + + CloseHandle(dev->hThread); + } + + if(dev->hQuit != NULL) + { + CloseHandle(dev->hQuit); + } + + HeapFree(GetProcessHeap(), 0, dev); +} + +#endif /* WAVEOUT_SUPPORT */ diff --git a/src/rvoice/fluid_rev.c b/src/rvoice/fluid_rev.c index 198a06e5..5f72cb7b 100644 --- a/src/rvoice/fluid_rev.c +++ b/src/rvoice/fluid_rev.c @@ -1,263 +1,998 @@ -/* +/****************************************************************************** + * FluidSynth - A Software Synthesizer + * + * Copyright (C) 2003 Peter Hanappe and others. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * + * + * FDN REVERB + * + * Freeverb used by fluidsynth (v.1.1.10 and previous) is based on + * Schroeder-Moorer reverberator: + * https://ccrma.stanford.edu/~jos/pasp/Freeverb.html + * + * This FDN reverberation is based on jot FDN reverberator. + * https://ccrma.stanford.edu/~jos/Reverb/FDN_Late_Reverberation.html + * Like Freeverb it is a late reverb which is convenient for Fluidsynth. + * + * + * .-------------------. + * .-----------------| | + * | - | Feedback | + * | .--------------| Matrix | + * | | |___________________| + * | | /|\ /|\ + * \|/ | .---------. .-------. | - | .------. + * .->+ ---->| Delay 0 |-|L.P.F 0|--*-------->| |-> out + * .---------. | | |_________| |_______| | | | left + * |Tone | | | - - | |Stereo| + * In ->|corrector|--* | - - | | unit | + * mono |_________| | \|/ .---------. .-------. | | |-> out + * ---->+ ->| Delay 7 |-|L.P.F 7|--------*-->| | right + * |_________| |_______| |______| + * /|\ /|\ /|\ /|\ + * | | | | + * roomsize --/ | width --/ | + * damp ------/ level ------/ + * + * It takes a monophonic input and produces a stereo output. + * + * The parameters are the same than for Freeverb. + * Also the default response of these parameters are the same than for Freeverb: + * - roomsize (0 to 1): control the reverb time from 0.7 to 12.5 s. + * This reverberation time is ofen called T60DC. + * + * - damp (0 to 1): controls the reverb time frequency dependency. + * This controls the reverb time for the frequency sample rate/2 + * + * When 0, the reverb time for high frequencies is the same as + * for DC frequency. + * When > 0, high frequencies have less reverb time than lower frequencies. + * + * - width (0 to 100): controls the left/right output separation. + * When 0, there are no separation and the signal on left and right. + * output is the same. This sounds like a monophonic signal. + * When 100, the separation between left and right is maximum. + * + * - level (0 to 1), controls the ouput level reverberation. + * + * This FDN reverb produces a better quality reverberation tail than Freeverb with + * far less ringing by using modulated delay lines that help to cancel + * the building of a lot of resonances in the reverberation tail even when + * using only 8 delays lines (NBR_DELAYS = 8) (default). + * + * Although 8 lines give good result, using 12 delays lines brings the overall + * frequency density quality a bit higher. This quality augmentation is noticeable + * particularly when using long reverb time (roomsize = 1) on solo instrument with + * long release time. Of course the cpu load augmentation is +50% relatively + * to 8 lines. + * + * As a general rule the reverberation tail quality is easier to perceive by ear + * when using: + * - percussive instruments (i.e piano and others). + * - long reverb time (roomsize = 1). + * - no damping (damp = 0). + * + * + * The cpu load for 8 lines is a bit lower than for freeverb (- 3%), + * but higher for 12 lines (+ 41%). + * + * + * The memory consumption is less than for freeverb. This saves 147480 bytes + * for 8 lines (- 72%) and 110152 bytes for 12 lines (- 54 %). + * (see the results table below). + * + * Two macros are usable at compiler time: + * - NBR_DELAYS: number of delay lines. 8 (default) or 12. + * - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response of + * roomsize parameter. + * When this macro is not defined (the default), roomsize has the same + * response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). + * + * When this macro is defined, roomsize behaves linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). + * This linear response is convenient when using GUI controls. + * + * -------------------------------------------------------------------------- + * Compare table: + * Note: the cpu load in % are relative each to other. These values are + * given by the fluidsynth profile commands. + * -------------------------------------------------------------------------- + * reverb | NBR_DELAYS | Performances | memory size | quality + * | | (cpu_load: %) | (bytes) | + * ========================================================================== + * freeverb | 2 x 8 comb | 0.670 % | 204616 | ringing + * | 2 x 4 all-pass | | | + * ----------|--------------------------------------------------------------- + * FDN | 8 | 0.650 % | 57136 | far less + * modulated | |(feeverb - 3%) |(freeverb - 72%) | ringing + * |--------------------------------------------------------------- + * | 12 | 0.942 % | 94464 | best than + * | |(freeverb + 41%) |(freeverb - 54%) | 8 lines + *--------------------------------------------------------------------------- + * + * + *---------------------------------------------------------------------------- + * 'Denormalise' method to avoid loss of performance. + * -------------------------------------------------- + * According to music-dsp thread 'Denormalise', Pentium processors + * have a hardware 'feature', that is of interest here, related to + * numeric underflow. We have a recursive filter. The output decays + * exponentially, if the input stops. So the numbers get smaller and + * smaller... At some point, they reach 'denormal' level. This will + * lead to drastic spikes in the CPU load. The effect was reproduced + * with the reverb - sometimes the average load over 10 s doubles!!. + * + * The 'undenormalise' macro fixes the problem: As soon as the number + * is close enough to denormal level, the macro forces the number to + * 0.0f. The original macro is: + * + * #define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f + * + * This will zero out a number when it reaches the denormal level. + * Advantage: Maximum dynamic range Disadvantage: We'll have to check + * every sample, expensive. The alternative macro comes from a later + * mail from Jon Watte. It will zap a number before it reaches + * denormal level. Jon suggests to run it once per block instead of + * every sample. + */ - 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_sys.h" +/* Denormalising part II: + * + * Another method fixes the problem cheaper: Use a small DC-offset in + * the filter calculations. Now the signals converge not against 0, + * but against the offset. The constant offset is invisible from the + * outside world (i.e. it does not appear at the output. There is a + * very small turn-on transient response, which should not cause + * problems. + */ #include "fluid_rev.h" +#include "fluid_sys.h" -/*************************************************************** - * - * REVERB - */ +/*---------------------------------------------------------------------------- + Configuration macros at compiler time. -/* Denormalising: - * - * 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. On some platforms this will lead to drastic spikes in the - * CPU load. This is especially noticable on some older Pentium (especially - * Pentium 3) processors, but even more modern Intel Core processors still show - * reduced performance with denormals. While there are compile-time switches to - * treat denormals as zero for a lot of processors, those are not available or - * effective on all platforms. - * - * The fix used here: 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 ((fluid_real_t)1e-8) + 3 macros are usable at compiler time: + - NBR_DELAYs: number of delay lines. 8 (default) or 12. + - ROOMSIZE_RESPONSE_LINEAR: allows to choose an alternate response for + roomsize parameter. + - DENORMALISING enable denormalising handling. +-----------------------------------------------------------------------------*/ +/* Number of delay lines (must be only 8 or 12) + 8 is the default. + 12 produces a better quality but is +50% cpu expensive +*/ +#define NBR_DELAYS 8 /* default*/ -typedef struct _fluid_allpass fluid_allpass; -typedef struct _fluid_comb fluid_comb; +/* response curve of parameter roomsize */ +/* + The default response is the same as Freeverb: + - roomsize (0 to 1) controls concave reverb time (0.7 to 12.5 s). -struct _fluid_allpass -{ - fluid_real_t feedback; - fluid_real_t *buffer; - int bufsize; - int bufidx; -}; + when ROOMSIZE_RESPONSE_LINEAR is defined, the response is: + - roomsize (0 to 1) controls reverb time linearly (0.7 to 12.5 s). +*/ +//#define ROOMSIZE_RESPONSE_LINEAR -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); +/* DENORMALISING enable denormalising handling */ +#define DENORMALISING -void -fluid_allpass_setbuffer(fluid_allpass *allpass, int size) -{ - allpass->bufidx = 0; - allpass->buffer = FLUID_ARRAY(fluid_real_t, size); - allpass->bufsize = size; -} +#ifdef DENORMALISING +#define DC_OFFSET 1e-8 +#else +#define DC_OFFSET 0.0 +#endif -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; \ -} - -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; \ -} - -#define numcombs 8 -#define numallpasses 4 -#define fixedgain 0.015f -/* scale_wet_width is a compensation weight factor to get an output +/*---------------------------------------------------------------------------- + Initial internal reverb settings (at reverb creation time) +-----------------------------------------------------------------------------*/ +/* SCALE_WET_WIDTH is a compensation weight factor to get an output amplitude (wet) rather independent of the width setting. 0: the output amplitude is fully dependant on the width setting. >0: the output amplitude is less dependant on the width setting. - With a scale_wet_width of 0.2 the output amplitude is rather + With a SCALE_WET_WIDTH of 0.2 the output amplitude is rather independent of width setting (see fluid_revmodel_update()). */ -#define scale_wet_width 0.2f -#define scalewet 3.0f -#define scaledamp 1.0f -#define scaleroom 0.28f -#define offsetroom 0.7f -#define stereospread 23 +#define SCALE_WET_WIDTH 0.2f -/* - 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. +/* It is best to inject the input signal less ofen. This contributes to obtain +a flatter response on comb filter. So the input gain is set to 0.1 rather 1.0. */ +#define FIXED_GAIN 0.1f /* input gain */ + +/* SCALE_WET is adjusted to 5.0 to get internal output level equivalent to freeverb */ +#define SCALE_WET 5.0f /* scale output gain */ + +/*---------------------------------------------------------------------------- + Internal FDN late reverb settings +-----------------------------------------------------------------------------*/ + +/*-- Reverberation time settings ---------------------------------- + MIN_DC_REV_TIME est defined egal to the minimum value of freeverb: + MAX_DC_REV_TIME est defined egal to the maximum value of freeverb: + T60DC is computed from gi and the longuest delay line in freeverb: L8 = 1617 + T60 = -3 * Li * T / log10(gi) + T60 = -3 * Li * / (log10(gi) * sr) + + - Li: length of comb filter delay line. + - sr: sample rate. + - gi: the feedback gain. + + The minimum value for freeverb correspond to gi = 0.7. + with Mi = 1617, sr at 44100 Hz, and gi = 0.7 => MIN_DC_REV_TIME = 0.7 s + + The maximum value for freeverb correspond to gi = 0.98. + with Mi = 1617, sr at 44100 Hz, and gi = 0.98 => MAX_DC_REV_TIME = 12.5 s */ -#define 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 +#define MIN_DC_REV_TIME 0.7f /* minimum T60DC reverb time: seconds */ +#define MAX_DC_REV_TIME 12.5f /* maximumm T60DC time in seconds */ +#define RANGE_REV_TIME (MAX_DC_REV_TIME - MIN_DC_REV_TIME) + +/* macro to compute internal reverberation time versus roomsize parameter */ +#define GET_DC_REV_TIME(roomsize) (MIN_DC_REV_TIME + RANGE_REV_TIME * roomsize) + +/*-- Modulation related settings ----------------------------------*/ +/* For many instruments, the range for MOD_FREQ and MOD_DEPTH should be: + + MOD_DEPTH: [3..6] (in samples). + MOD_FREQ: [0.5 ..2.0] (in Hz). + + Values below the lower limits are often not sufficient to cancel unwanted + "ringing"(resonant frequency). + Values above upper limits augment the unwanted "chorus". + + With NBR_DELAYS to 8: + MOD_DEPTH must be >= 4 to cancel the unwanted "ringing".[4..6]. + With NBR_DELAYS to 12: + MOD_DEPTH to 3 is sufficient to cancel the unwanted "ringing".[3..6] +*/ +#define MOD_DEPTH 4 /* modulation depth (samples)*/ +#define MOD_RATE 50 /* modulation rate (samples)*/ +#define MOD_FREQ 1.0f /* modulation frequency (Hz) */ +/* + Number of samples to add to the desired length of a delay line. This + allow to take account of modulation interpolation. + 1 is sufficient with MOD_DEPTH equal to 6. +*/ +#define INTERP_SAMPLES_NBR 1 + +/* phase offset between modulators waveform */ +#define MOD_PHASE (360.0/(float) NBR_DELAYS) + +#if (NBR_DELAYS == 8) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1129 +#elif (NBR_DELAYS == 12) + #define DELAY_L0 601 + #define DELAY_L1 691 + #define DELAY_L2 773 + #define DELAY_L3 839 + #define DELAY_L4 919 + #define DELAY_L5 997 + #define DELAY_L6 1061 + #define DELAY_L7 1093 + #define DELAY_L8 1129 + #define DELAY_L9 1151 + #define DELAY_L10 1171 + #define DELAY_L11 1187 +#endif + +/* Delay lines length table (in samples) */ +static const int delay_length[NBR_DELAYS] = { - fluid_real_t roomsize; - fluid_real_t damp; - fluid_real_t level, 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]; + DELAY_L0, DELAY_L1, DELAY_L2, DELAY_L3, + DELAY_L4, DELAY_L5, DELAY_L6, DELAY_L7, +#if (NBR_DELAYS == 12) + DELAY_L8, DELAY_L9, DELAY_L10, DELAY_L11 +#endif }; -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); +/*---------------------------------------------------------------------------*/ +/* The FDN late feed back matrix: A + T + A = P - 2 / N * u * u + N N N N + + N: the matrix dimension (i.e NBR_DELAYS). + P: permutation matrix. + u: is a colomn vector of 1. + +*/ +#define FDN_MATRIX_FACTOR (fluid_real_t)(-2.0 / NBR_DELAYS) + +/*---------------------------------------------------------------------------- + Internal FDN late structures and static functions +-----------------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------------- + Delay absorbent low pass filter +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t buffer; + fluid_real_t b0, a1; /* filter coefficients */ +} fdn_delay_lpf; + +/*----------------------------------------------------------------------------- + Sets coefficients for delay absorbent low pass filter. + @param lpf pointer on low pass filter structure. + @param b0,a1 coefficients. +-----------------------------------------------------------------------------*/ +static void set_fdn_delay_lpf(fdn_delay_lpf *lpf, + fluid_real_t b0, fluid_real_t a1) +{ + lpf->b0 = b0; + lpf->a1 = a1; +} + +/*----------------------------------------------------------------------------- + Process delay absorbent low pass filter. + @param mod_delay modulated delay line. + @param in, input sample. + @param out output sample. +-----------------------------------------------------------------------------*/ +/* process low pass damping filter (input, output, delay) */ +#define process_damping_filter(in,out,mod_delay) \ +{\ + out = in * mod_delay->dl.damping.b0 - mod_delay->dl.damping.buffer * \ + mod_delay->dl.damping.a1;\ + mod_delay->dl.damping.buffer = out;\ +}\ + + +/*----------------------------------------------------------------------------- + Delay line : + The delay line is composed of the line plus an absorbent low pass filter + to get frequency dependant reverb time. +-----------------------------------------------------------------------------*/ +typedef struct +{ + fluid_real_t *line; /* buffer line */ + int size; /* effective internal size (in samples) */ + /*-------------*/ + int line_in; /* line in position */ + int line_out; /* line out position */ + /*-------------*/ + fdn_delay_lpf damping; /* damping low pass filter */ +} delay_line; + + +/*----------------------------------------------------------------------------- + Clears a delay line to DC_OFFSET float value. + @param dl pointer on delay line structure +-----------------------------------------------------------------------------*/ +static void clear_delay_line(delay_line *dl) +{ + int i; + + for(i = 0; i < dl->size; i++) + { + dl->line[i] = DC_OFFSET; + } +} + +/*----------------------------------------------------------------------------- + Push a sample val into the delay line +-----------------------------------------------------------------------------*/ +#define push_in_delay_line(dl, val) \ +{\ + dl->line[dl->line_in] = val;\ + /* Incrementation and circular motion if necessary */\ + if(++dl->line_in >= dl->size) dl->line_in -= dl->size;\ +}\ + +/*----------------------------------------------------------------------------- + Modulator for modulated delay line +-----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------- + Sinusoidal modulator +-----------------------------------------------------------------------------*/ +/* modulator are integrated in modulated delay line */ +typedef struct +{ + fluid_real_t a1; /* Coefficient: a1 = 2 * cos(w) */ + fluid_real_t buffer1; /* buffer1 */ + fluid_real_t buffer2; /* buffer2 */ + fluid_real_t reset_buffer2;/* reset value of buffer2 */ +} sinus_modulator; + +/*----------------------------------------------------------------------------- + Sets the frequency of sinus oscillator. + + @param mod pointer on modulator structure. + @param freq frequency of the oscillator in Hz. + @param sample_rate sample rate on audio output in Hz. + @param phase initial phase of the oscillator in degree (0 to 360). +-----------------------------------------------------------------------------*/ +static void set_mod_frequency(sinus_modulator *mod, + float freq, float sample_rate, float phase) +{ + fluid_real_t w = 2 * M_PI * freq / sample_rate; /* intial angle */ + + mod->a1 = 2 * cos(w); + mod->buffer2 = sin(2 * M_PI * phase / 360 - w); /* y(n-1) = sin(-intial angle) */ + mod->buffer1 = sin(2 * M_PI * phase / 360); /* y(n) = sin(initial phase) */ + mod->reset_buffer2 = sin(M_PI / 2.0 - w); /* reset value for PI/2 */ +} + +/*----------------------------------------------------------------------------- + Gets current value of sinus modulator: + y(n) = a1 . y(n-1) - y(n-2) + out = a1 . buffer1 - buffer2 + + @param pointer on modulator structure. + @return current value of the modulator sine wave. +-----------------------------------------------------------------------------*/ +static inline fluid_real_t get_mod_sinus(sinus_modulator *mod) +{ + fluid_real_t out; + out = mod->a1 * mod->buffer1 - mod->buffer2; + mod->buffer2 = mod->buffer1; + + if(out >= 1.0) /* reset in case of instability near PI/2 */ + { + out = 1.0; /* forces output to the right value */ + mod->buffer2 = mod->reset_buffer2; + } + + if(out <= -1.0) /* reset in case of instability near -PI/2 */ + { + out = -1.0; /* forces output to the right value */ + mod->buffer2 = - mod->reset_buffer2; + } + + mod->buffer1 = out; + return out; +} + +/*----------------------------------------------------------------------------- + Modulated delay line. The line is composed of: + - the delay line with its damping low pass filter. + - the sinusoidal modulator. + - center output position modulated by the modulator. + - variable rate control of center output position. + - first order All-Pass interpolator. +-----------------------------------------------------------------------------*/ +typedef struct +{ + /* delay line with damping low pass filter member */ + delay_line dl; /* delayed line */ + /*---------------------------*/ + /* Sinusoidal modulator member */ + sinus_modulator mod; /* sinus modulator */ + /*-------------------------*/ + /* center output position members */ + fluid_real_t center_pos_mod; /* center output position modulated by modulator */ + int mod_depth; /* modulation depth (in samples) */ + /*-------------------------*/ + /* variable rate control of center output position */ + int index_rate; /* index rate to know when to update center_pos_mod */ + int mod_rate; /* rate at which center_pos_mod is updated */ + /*-------------------------*/ + /* first order All-Pass interpolator members */ + fluid_real_t frac_pos_mod; /* fractional position part between samples) */ + /* previous value used when interpolating using fractional */ + fluid_real_t buffer; +} mod_delay_line; + + +/*----------------------------------------------------------------------------- + Modulated delay line initialization. + + Sets the length line ( alloc delay samples). + Remark: the function sets the internal size accordling to the length delay_length. + As the delay line is a modulated line, its internal size is augmented by mod_depth. + The size is also augmented by INTERP_SAMPLES_NBR to take account of interpolation. + + @param mdl, pointer on modulated delay line. + @param delay_length the length of the delay line in samples. + @param mod_depth depth of the modulation in samples (amplitude of the sine wave). + @param mod_rate the rate of the modulation in samples. + @return FLUID_OK if success , FLUID_FAILED if memory error. + + Return FLUID_OK if success, FLUID_FAILED if memory error. +-----------------------------------------------------------------------------*/ +static int set_mod_delay_line(mod_delay_line *mdl, + int delay_length, + int mod_depth, + int mod_rate + ) +{ + /*-----------------------------------------------------------------------*/ + /* checks parameter */ + if(delay_length < 1) + { + return FLUID_FAILED; + } + + /* limits mod_depth to the requested delay length */ + if(mod_depth >= delay_length) + { + FLUID_LOG(FLUID_INFO, + "fdn reverb: modulation depth has been limited"); + mod_depth = delay_length - 1; + } + + mdl->mod_depth = mod_depth; + /*----------------------------------------------------------------------- + allocates delay_line and initialize members: + - line, size, line_in, line_out... + */ + { + /* total size of the line: + size = INTERP_SAMPLES_NBR + mod_depth + delay_length */ + mdl->dl.size = delay_length + mod_depth + INTERP_SAMPLES_NBR; + mdl->dl.line = FLUID_ARRAY(fluid_real_t, mdl->dl.size); + + if(! mdl->dl.line) + { + return FLUID_FAILED; + } + + clear_delay_line(&mdl->dl); /* clears the buffer */ + + /* Initializes line_in to the start of the buffer */ + mdl->dl.line_in = 0; + /* Initializes line_out index INTERP_SAMPLES_NBR samples after line_in */ + /* so that the delay between line_out and line_in is: + mod_depth + delay_length */ + mdl->dl.line_out = mdl->dl.line_in + INTERP_SAMPLES_NBR; + } + + /* Damping low pass filter -------------------*/ + mdl->dl.damping.buffer = 0; + /*------------------------------------------------------------------------ + Initializes modulation members: + - modulated center position: center_pos_mod + - index rate to know when to update center_pos_mod:index_rate + - modulation rate (the speed at which center_pos_mod is modulated: mod_rate + - interpolator member: buffer, frac_pos_mod + -------------------------------------------------------------------------*/ + /* Sets the modulation rate. This rate defines how often + the center position (center_pos_mod ) is modulated . + The value is expressed in samples. The default value is 1 that means that + center_pos_mod is updated at every sample. + For example with a value of 2, the center position position will be + updated only one time every 2 samples only. + */ + mdl->mod_rate = 1; /* default modulation rate: every one sample */ + + if(mod_rate > mdl->dl.size) + { + FLUID_LOG(FLUID_INFO, + "fdn reverb: modulation rate is out of range"); + } + else + { + mdl->mod_rate = mod_rate; + } + + /* Initializes the modulated center position (center_pos_mod) so that: + - the delay between line_out and center_pos_mod is mod_depth. + - the delay between center_pos_mod and line_in is delay_length. + */ + mdl->center_pos_mod = (fluid_real_t) INTERP_SAMPLES_NBR + mod_depth; + + /* index rate to control when to update center_pos_mod */ + /* Important: must be set to get center_pos_mod immediatly used for the + reading of first sample (see get_mod_delay()) */ + mdl->index_rate = mdl->mod_rate; + + /* initializes 1st order All-Pass interpolator members */ + mdl->buffer = 0; /* previous delay sample value */ + mdl->frac_pos_mod = 0; /* fractional position (between consecutives sample) */ + return FLUID_OK; +} + +/*----------------------------------------------------------------------------- + Reads the sample value out of the modulated delay line. + @param mdl, pointer on modulated delay line. + @return the sample value. +-----------------------------------------------------------------------------*/ +static inline fluid_real_t get_mod_delay(mod_delay_line *mdl) +{ + fluid_real_t out_index; /* new modulated index position */ + int int_out_index; /* integer part of out_index */ + fluid_real_t out; /* value to return */ + + /* Checks if the modulator must be updated (every mod_rate samples). */ + /* Important: center_pos_mod must be used immediatly for the + first sample. So, mdl->index_rate must be initialized + to mdl->mod_rate (set_mod_delay_line()) */ + + if(++mdl->index_rate >= mdl->mod_rate) + { + mdl->index_rate = 0; + + /* out_index = center position (center_pos_mod) + sinus waweform */ + out_index = mdl->center_pos_mod + + get_mod_sinus(&mdl->mod) * mdl->mod_depth; + + /* extracts integer part in int_out_index */ + if(out_index >= 0.0f) + { + int_out_index = (int)out_index; /* current integer part */ + + /* forces read index (line_out) with integer modulation value */ + /* Boundary check and circular motion as needed */ + if((mdl->dl.line_out = int_out_index) >= mdl->dl.size) + { + mdl->dl.line_out -= mdl->dl.size; + } + } + else /* negative */ + { + int_out_index = (int)(out_index - 1); /* previous integer part */ + /* forces read index (line_out) with integer modulation value */ + /* circular motion as needed */ + mdl->dl.line_out = int_out_index + mdl->dl.size; + } + + /* extracts fractionnal part. (it will be used when interpolating + between line_out and line_out +1) and memorize it. + Memorizing is necessary for modulation rate above 1 */ + mdl->frac_pos_mod = out_index - int_out_index; + + /* updates center position (center_pos_mod) to the next position + specified by modulation rate */ + if((mdl->center_pos_mod += mdl->mod_rate) >= mdl->dl.size) + { + mdl->center_pos_mod -= mdl->dl.size; + } + } + + /* First order all-pass interpolation ----------------------------------*/ + /* https://ccrma.stanford.edu/~jos/pasp/First_Order_Allpass_Interpolation.html */ + /* begins interpolation: read current sample */ + out = mdl->dl.line[mdl->dl.line_out]; + + /* updates line_out to the next sample. + Boundary check and circular motion as needed */ + if(++mdl->dl.line_out >= mdl->dl.size) + { + mdl->dl.line_out -= mdl->dl.size; + } + + /* Fractional interpolation beetween next sample (at next position) and + previous output added to current sample. + */ + out += mdl->frac_pos_mod * (mdl->dl.line[mdl->dl.line_out] - mdl->buffer); + mdl->buffer = out; /* memorizes current output */ + return out; +} + +/*----------------------------------------------------------------------------- + Late structure +-----------------------------------------------------------------------------*/ +struct _fluid_late +{ + fluid_real_t samplerate; /* sample rate */ + /*----- High pass tone corrector -------------------------------------*/ + fluid_real_t tone_buffer; + fluid_real_t b1, b2; + /*----- Modulated delay lines lines ----------------------------------*/ + mod_delay_line mod_delay_lines[NBR_DELAYS]; + /*-----------------------------------------------------------------------*/ + /* Output coefficients for separate Left and right stereo outputs */ + fluid_real_t out_left_gain[NBR_DELAYS]; /* Left delay lines' output gains */ + fluid_real_t out_right_gain[NBR_DELAYS];/* Right delay lines' output gains*/ +}; + +typedef struct _fluid_late fluid_late; +/*----------------------------------------------------------------------------- + fluidsynth reverb structure +-----------------------------------------------------------------------------*/ +struct _fluid_revmodel_t +{ + /* reverb parameters */ + fluid_real_t roomsize; /* acting on reverb time */ + fluid_real_t damp; /* acting on frequency dependent reverb time */ + fluid_real_t level, wet1, wet2; /* output level */ + fluid_real_t width; /* width stereo separation */ + + /* fdn reverberation structure */ + fluid_late late; +}; + +/*----------------------------------------------------------------------------- + Updates Reverb time and absorbent filters coefficients from parameters: + + @param late pointer on late structure. + @param roomsize (0 to 1): acting on reverb time. + @param damping (0 to 1): acting on absorbent damping filter. + + Design formulas: + https://ccrma.stanford.edu/~jos/Reverb/First_Order_Delay_Filter_Design.html + https://ccrma.stanford.edu/~jos/Reverb/Tonal_Correction_Filter.html +-----------------------------------------------------------------------------*/ +static void update_rev_time_damping(fluid_late *late, + fluid_real_t roomsize, fluid_real_t damp) +{ + int i; + fluid_real_t sample_period = 1 / late->samplerate; /* Sampling period */ + fluid_real_t dc_rev_time; /* Reverb time at 0 Hz (in seconds) */ + + fluid_real_t alpha; + + /*-------------------------------------------- + Computes dc_rev_time and alpha + ----------------------------------------------*/ + { + fluid_real_t gi_tmp, ai_tmp; +#ifdef ROOMSIZE_RESPONSE_LINEAR + /* roomsize parameter behave linearly: + * - roomsize (0 to 1) controls reverb time linearly (0.7 to 10 s). + * This linear response is convenient when using GUI controls. + */ + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + dc_rev_time = GET_DC_REV_TIME(roomsize); + /* computes gi_tmp from dc_rev_time using relation E2 */ + gi_tmp = (fluid_real_t) pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / dc_rev_time); /* E2 */ +#else + /* roomsize parameters have the same response that Freeverb, that is: + * - roomsize (0 to 1) controls concave reverb time (0.7 to 10 s). + */ + { + /*----------------------------------------- + Computes dc_rev_time + ------------------------------------------*/ + fluid_real_t gi_min, gi_max; + /* values gi_min et gi_max are computed using E2 for the line with + maximum delay */ + gi_max = (fluid_real_t)pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / MAX_DC_REV_TIME); /* E2 */ + gi_min = (fluid_real_t)pow(10, -3 * delay_length[NBR_DELAYS - 1] * + sample_period / MIN_DC_REV_TIME); /* E2 */ + /* gi = f(roomsize, gi_max, gi_min) */ + gi_tmp = gi_min + roomsize * (gi_max - gi_min); + /* Computes T60DC from gi using inverse of relation E2.*/ + dc_rev_time = -3 * delay_length[NBR_DELAYS - 1] * sample_period / log10(gi_tmp); + } +#endif /* ROOMSIZE_RESPONSE_LINEAR */ + /*-------------------------------------------- + Computes alpha + ----------------------------------------------*/ + /* Computes alpha from damp,ai_tmp,gi_tmp using relation R */ + /* - damp (0 to 1) controls concave reverb time for fs/2 frequency (T60DC to 0) */ + ai_tmp = 1.0 * damp; + alpha = sqrt(1 / (1 - ai_tmp / (20 * log10(gi_tmp) * log(10) / 80))); /* R */ + } + + /* updates tone corrector coefficients b1,b2 from alpha */ + { + /* + Beta = (1 - alpha) / (1 + alpha) + b1 = 1/(1-beta) + b2 = beta * b1 + */ + fluid_real_t beta = (1 - alpha) / (1 + alpha); + late->b1 = 1 / (1 - beta); + late->b2 = beta * late->b1; + late->tone_buffer = 0.0f; + } + + /* updates damping coefficients of all lines (gi , ai) from dc_rev_time, alpha */ + for(i = 0; i < NBR_DELAYS; i++) + { + /* iir low pass filter gain */ + fluid_real_t gi = (fluid_real_t)pow(10, -3 * delay_length[i] * + sample_period / dc_rev_time); + + /* iir low pass filter feedback gain */ + fluid_real_t ai = (fluid_real_t)(20 * log10(gi) * log(10) / 80 * + (1 - 1 / pow(alpha, 2))); + /* b0 = gi * (1 - ai), a1 = - ai */ + set_fdn_delay_lpf(&late->mod_delay_lines[i].dl.damping, + gi * (1 - ai), -ai); + } +} + +/*----------------------------------------------------------------------------- + Updates stereo coefficients + @param late pointer on late structure + @param wet level integrated in stereo coefficients. +-----------------------------------------------------------------------------*/ +static void update_stereo_coefficient(fluid_late *late, fluid_real_t wet1) +{ + int i; + + for(i = 0; i < NBR_DELAYS; i++) + { + /* delay lines output gains vectors Left and Right + + L R + 0 | 1 1| + 1 |-1 1| + 2 | 1 -1| + 3 |-1 -1| + + 4 | 1 1| + 5 |-1 1| + stereo gain = 6 | 1 -1| + 7 |-1 -1| + + 8 | 1 1| + 9 |-1 1| + 10| 1 -1| + 11|-1 -1| + */ + + late->out_left_gain[i] = wet1; + + /* Sets Left coefficients first */ + if(i % 2) /* Left is 1,-1, 1,-1, 1,-1,.... */ + { + late->out_left_gain[i] *= -1; + } + + /* Now sets right gain as function of Left */ + /* for right line 1,2,5,6,9,10,13,14, right = - left */ + if((i == 1) || (i == 2) || (i == 5) || (i == 6) || (i == 9) || (i == 10) || (i == 13) || (i == 14)) + { + /* Right is reverse of Left */ + late->out_right_gain[i] = -1 * late->out_left_gain[i]; + } + else /* for Right : line 0,3,4,7,8,11,12,15 */ + { + /* Right is same as Left */ + late->out_right_gain[i] = late->out_left_gain[i]; + } + } +} + +/*----------------------------------------------------------------------------- + fluid_late destructor. + @param late pointer on late structure. +-----------------------------------------------------------------------------*/ +static void delete_fluid_rev_late(fluid_late *late) +{ + int i; + fluid_return_if_fail(late != NULL); + + /* free the delay lines */ + for(i = 0; i < NBR_DELAYS; i++) + { + FLUID_FREE(late->mod_delay_lines[i].dl.line); + } +} + +/*----------------------------------------------------------------------------- + Creates the fdn reverb. + @param late, pointer on the fnd late reverb to initialize. + @param sample_rate the sample rate. + @return FLUID_OK if success, FLUID_FAILED otherwise. +-----------------------------------------------------------------------------*/ +static int create_fluid_rev_late(fluid_late *late, fluid_real_t sample_rate) +{ + int result; /* return value */ + int i; + + FLUID_MEMSET(late, 0, sizeof(fluid_late)); + + late->samplerate = sample_rate; + + /*-------------------------------------------------------------------------- + First initialize the modulated delay lines + */ + + for(i = 0; i < NBR_DELAYS; i++) + { + /* sets local delay lines's parameters */ + result = set_mod_delay_line(&late->mod_delay_lines[i], + delay_length[i], MOD_DEPTH, MOD_RATE); + + if(result == FLUID_FAILED) + { + return FLUID_FAILED; + } + + /* Sets local Modulators parameters: frequency and phase + Each modulateur are shifted of MOD_PHASE degree + */ + set_mod_frequency(&late->mod_delay_lines[i].mod, + MOD_FREQ * MOD_RATE, + sample_rate, + (float)(MOD_PHASE * i)); + } + + /*-----------------------------------------------------------------------*/ + update_stereo_coefficient(late, 1.0f); + return FLUID_OK; +} + +/* + Clears the delay lines. + + @param rev pointer on the reverb. +*/ +static void +fluid_revmodel_init(fluid_revmodel_t *rev) +{ + int i; + + /* clears all the delay lines */ + for(i = 0; i < NBR_DELAYS; i ++) + { + clear_delay_line(&rev->late.mod_delay_lines[i].dl); + } +} + + +/* + updates internal parameters. + + @param rev pointer on the reverb. +*/ +static void +fluid_revmodel_update(fluid_revmodel_t *rev) +{ + /* Recalculate internal values after parameters change */ + + /* The stereo amplitude equation (wet1 and wet2 below) have a + tendency to produce high amplitude with high width values ( 1 < width < 100). + This results in an unwanted noisy output clipped by the audio card. + To avoid this dependency, we divide by (1 + rev->width * SCALE_WET_WIDTH) + Actually, with a SCALE_WET_WIDTH of 0.2, (regardless of level setting), + the output amplitude (wet) seems rather independent of width setting */ + fluid_real_t wet = (rev->level * SCALE_WET) / + (1.0f + rev->width * SCALE_WET_WIDTH); + + /* wet1 and wet2 are used by the stereo effect controled by the width setting + for producing a stereo ouptput from a monophonic reverb signal. + Please see the note above about a side effect tendency */ + + rev->wet1 = wet * (rev->width / 2.0f + 0.5f); + rev->wet2 = wet * ((1.0f - rev->width) / 2.0f); + + /* integrates wet1 in stereo coefficient (this will save one multiply) */ + update_stereo_coefficient(&rev->late, rev->wet1); + + if(rev->wet1 > 0.0) + { + rev->wet2 /= rev->wet1; + } + + /* Reverberation time and damping */ + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); +} + +/*---------------------------------------------------------------------------- + Reverb API +-----------------------------------------------------------------------------*/ + +/* +* Creates a reverb. +* @param sample_rate sample rate in Hz. +* @return pointer on the new reverb or NULL if memory error. +* Reverb API. +*/ fluid_revmodel_t * new_fluid_revmodel(fluid_real_t sample_rate) { @@ -269,280 +1004,355 @@ new_fluid_revmodel(fluid_real_t sample_rate) 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; + /* create fdn reverb */ + if(create_fluid_rev_late(&rev->late, sample_rate) != FLUID_OK) + { + delete_fluid_revmodel(rev); + return NULL; + } return rev; } +/* +* free the reverb. +* @param pointer on rev to free. +* Reverb API. +*/ void delete_fluid_revmodel(fluid_revmodel_t *rev) { - int i; fluid_return_if_fail(rev != NULL); - - 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]); - } - + delete_fluid_rev_late(&rev->late); 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, const 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, const 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; - - /* The stereo amplitude equation (wet1 and wet2 below) have a - tendency to produce high amplitude with high width values ( 1 < width < 100). - This results in an unwanted noisy output clipped by the audio card. - To avoid this dependency, we divide by (1 + rev->width * scale_wet_width) - Actually, with a scale_wet_width of 0.2, (regardless of level setting), - the output amplitude (wet) seems rather independent of width setting */ - fluid_real_t wet = (rev->level * scalewet) / - (1.0f + rev->width * scale_wet_width); - - /* wet1 and wet2 are used by the stereo effect controled by the width setting - for producing a stereo ouptput from a monophonic reverb signal. - Please see the note above about a side effect tendency */ - rev->wet1 = wet * (rev->width / 2.0f + 0.5f); - rev->wet2 = wet * ((1.0f - rev->width) / 2.0f); - - 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 - */ +/* +* Sets 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. +* +* Reverb API. +*/ void fluid_revmodel_set(fluid_revmodel_t *rev, int set, fluid_real_t roomsize, fluid_real_t damping, fluid_real_t width, fluid_real_t level) { + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_ROOMSIZE) { - /* With upper limit above 1.07, the output amplitude will grow - exponentially. So, keeping this upper limit to 1.0 seems sufficient - as it produces yet a long reverb time */ fluid_clip(roomsize, 0.0f, 1.0f); - rev->roomsize = (roomsize * scaleroom) + offsetroom; + rev->roomsize = roomsize; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_DAMPING) { - rev->damp = damping * scaledamp; + fluid_clip(damping, 0.0f, 1.0f); + rev->damp = damping; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_WIDTH) { rev->width = width; } + /*-----------------------------------*/ if(set & FLUID_REVMODEL_SET_LEVEL) { fluid_clip(level, 0.0f, 1.0f); rev->level = level; } + /* updates internal parameters */ fluid_revmodel_update(rev); } +/* +* Applies a sample rate change on the reverb. +* @param rev the reverb. +* @param sample_rate new sample rate value. +* +* Reverb API. +*/ void fluid_revmodel_samplerate_change(fluid_revmodel_t *rev, fluid_real_t sample_rate) { int i; - for(i = 0; i < numcombs; i++) + /* updates modulator frequency according to sample rate change */ + for(i = 0; i < NBR_DELAYS; i++) { - fluid_comb_release(&rev->combL[i]); - fluid_comb_release(&rev->combR[i]); + set_mod_frequency(&rev->late.mod_delay_lines[i].mod, + MOD_FREQ * MOD_RATE, + sample_rate, + (float)(MOD_PHASE * i)); } - for(i = 0; i < numallpasses; i++) - { - fluid_allpass_release(&rev->allpassL[i]); - fluid_allpass_release(&rev->allpassR[i]); - } + /* updates damping filter coefficients according to sample rate change */ + rev->late.samplerate = sample_rate; + update_rev_time_damping(&rev->late, rev->roomsize, rev->damp); - fluid_set_revmodel_buffers(rev, sample_rate); + /* clears all delay lines */ + fluid_revmodel_init(rev); +} + +/* +* Damps the reverb by clearing the delay lines. +* @param rev the reverb. +* +* Reverb API. +*/ +void +fluid_revmodel_reset(fluid_revmodel_t *rev) +{ + fluid_revmodel_init(rev); +} + +/*----------------------------------------------------------------------------- +* fdn reverb process replace. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE sample). +* @param left_out stereo left processed output (FLUID_BUFSIZE sample). +* @param right_out stereo right processed output (FLUID_BUFSIZE sample). +* +* The processed reverb is replacing anything there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void +fluid_revmodel_processreplace(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix computation */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) + { + /* stereo output */ + out_left = out_right = 0; + +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction. + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in delay_out[]. + - also matrix_factor is computed (to simplify further matrix product) + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These wil be use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line.Each input is a combination of + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } + + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + + /* Calculates stereo output REPLACING anything already there: */ + /* + left_out[k] = out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] = out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right we simplify previous + relation by suppression of one multiply as this: + + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; + */ + left_out[k] = out_left + out_right * rev->wet2; + right_out[k] = out_right + out_left * rev->wet2; + } +} + + +/*----------------------------------------------------------------------------- +* fdn reverb process mix. +* @param rev pointer on reverb. +* @param in monophonic buffer input (FLUID_BUFSIZE samples). +* @param left_out stereo left processed output (FLUID_BUFSIZE samples). +* @param right_out stereo right processed output (FLUID_BUFSIZE samples). +* +* The processed reverb is mixed in out with samples already there in out. +* Reverb API. +-----------------------------------------------------------------------------*/ +void fluid_revmodel_processmix(fluid_revmodel_t *rev, const fluid_real_t *in, + fluid_real_t *left_out, fluid_real_t *right_out) +{ + int i, k; + + fluid_real_t xn; /* mono input x(n) */ + fluid_real_t out_tone_filter; /* tone corrector output */ + fluid_real_t out_left, out_right; /* output stereo Left and Right */ + fluid_real_t matrix_factor; /* partial matrix term */ + fluid_real_t delay_out_s; /* sample */ + fluid_real_t delay_out[NBR_DELAYS]; /* Line output + damper output */ + + for(k = 0; k < FLUID_BUFSIZE; k++) + { + /* stereo output */ + out_left = out_right = 0; +#ifdef DENORMALISING + /* Input is adjusted by DC_OFFSET. */ + xn = (in[k]) * FIXED_GAIN + DC_OFFSET; +#else + xn = (in[k]) * FIXED_GAIN; +#endif + + /*-------------------------------------------------------------------- + tone correction + */ + out_tone_filter = xn * rev->late.b1 - rev->late.b2 * rev->late.tone_buffer; + rev->late.tone_buffer = xn; + xn = out_tone_filter; + /*-------------------------------------------------------------------- + process feedback delayed network: + - xn is the input signal. + - before inserting in the line input we first we get the delay lines + output, filter them and compute output in local delay_out[]. + - also matrix_factor is computed (to simplify further matrix product). + ---------------------------------------------------------------------*/ + /* We begin with the modulated output delay line + damping filter */ + matrix_factor = 0; + + for(i = 0; i < NBR_DELAYS; i++) + { + mod_delay_line *mdl = &rev->late.mod_delay_lines[i]; + /* get current modulated output */ + delay_out_s = get_mod_delay(mdl); + + /* process low pass damping filter + (input:delay_out_s, output:delay_out_s) */ + process_damping_filter(delay_out_s, delay_out_s, mdl); + + /* Result in delay_out[], and matrix_factor. + These wil be use later during input line process */ + delay_out[i] = delay_out_s; /* result in delay_out[] */ + matrix_factor += delay_out_s; /* result in matrix_factor */ + + /* Process stereo output */ + /* stereo left = left + out_left_gain * delay_out */ + out_left += rev->late.out_left_gain[i] * delay_out_s; + /* stereo right= right+ out_right_gain * delay_out */ + out_right += rev->late.out_right_gain[i] * delay_out_s; + } + + /* now we process the input delay line. Each input is a combination of: + - xn: input signal + - delay_out[] the output of a delay line given by a permutation matrix P + - and matrix_factor. + This computes: in_delay_line = xn + (delay_out[] * matrix A) with + an algorithm equivalent but faster than using a product with matrix A. + */ + /* matrix_factor = output sum * (-2.0)/N */ + matrix_factor *= FDN_MATRIX_FACTOR; + matrix_factor += xn; /* adds reverb input signal */ + + for(i = 1; i < NBR_DELAYS; i++) + { + /* delay_in[i-1] = delay_out[i] + matrix_factor */ + delay_line *dl = &rev->late.mod_delay_lines[i - 1].dl; + push_in_delay_line(dl, delay_out[i] + matrix_factor); + } + + /* last line input (NB_DELAY-1) */ + /* delay_in[0] = delay_out[NB_DELAY -1] + matrix_factor */ + { + delay_line *dl = &rev->late.mod_delay_lines[NBR_DELAYS - 1].dl; + push_in_delay_line(dl, delay_out[0] + matrix_factor); + } + + /*-------------------------------------------------------------------*/ +#ifdef DENORMALISING + /* Removes the DC offset */ + out_left -= DC_OFFSET; + out_right -= DC_OFFSET; +#endif + /* Calculates stereo output MIXING anything already there: */ + /* + left_out[k] += out_left * rev->wet1 + out_right * rev->wet2; + right_out[k] += out_right * rev->wet1 + out_left * rev->wet2; + + As wet1 is integrated in stereo coefficient wet 1 is now + integrated in out_left and out_right we simplify previous + relation by suppression of one multiply as this: + + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + */ + left_out[k] += out_left + out_right * rev->wet2; + right_out[k] += out_right + out_left * rev->wet2; + } } diff --git a/src/rvoice/fluid_rvoice.c b/src/rvoice/fluid_rvoice.c index f6565bf0..6b64c9a9 100644 --- a/src/rvoice/fluid_rvoice.c +++ b/src/rvoice/fluid_rvoice.c @@ -305,6 +305,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) { int ticks = voice->envlfo.ticks; int count, is_looping; + fluid_real_t modenv_val; /******************* sample sanity check **********/ @@ -361,6 +362,12 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) /******************* phase **********************/ + /* SF2.04 section 8.1.2 #26: + * attack of modEnv is convex ?!? + */ + modenv_val = (fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + ? fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)) + : fluid_adsr_env_get_val(&voice->envlfo.modenv); /* Calculate the number of samples, that the DSP loop advances * through the original waveform with each step in the output * buffer. It is the ratio between the frequencies of original @@ -369,7 +376,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) voice->dsp.pitchoffset + fluid_lfo_get_val(&voice->envlfo.modlfo) * voice->envlfo.modlfo_to_pitch + fluid_lfo_get_val(&voice->envlfo.viblfo) * voice->envlfo.viblfo_to_pitch - + fluid_adsr_env_get_val(&voice->envlfo.modenv) * voice->envlfo.modenv_to_pitch) + + modenv_val * voice->envlfo.modenv_to_pitch) / voice->dsp.root_pitch_hz; /******************* portamento ****************/ @@ -455,7 +462,7 @@ fluid_rvoice_write(fluid_rvoice_t *voice, fluid_real_t *dsp_buf) 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); + modenv_val * voice->envlfo.modenv_to_fc); fluid_iir_filter_apply(&voice->resonant_filter, dsp_buf, count); @@ -585,7 +592,7 @@ fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) /* 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 + * volenv_val to achieve equivalent amplitude during the release phase * for seamless volume transition. */ if(fluid_adsr_env_get_val(&voice->envlfo.volenv) > 0) @@ -598,6 +605,24 @@ fluid_rvoice_noteoff_LOCAL(fluid_rvoice_t *voice, unsigned int min_ticks) } } + if(fluid_adsr_env_get_section(&voice->envlfo.modenv) == FLUID_VOICE_ENVATTACK) + { + /* A voice is turned off during the attack section of the modulation + * envelope. The attack section use convex scaling with pitch and filter + * frequency cutoff (see fluid_rvoice_write(): modenv_val = fluid_convex(127 * modenv.val) + * The other sections use linear scaling: modenv_val = modenv.val + * + * Calculate new modenv.val to achieve equivalent modenv_val during the release phase + * for seamless pitch and filter frequency cutoff transition. + */ + if(fluid_adsr_env_get_val(&voice->envlfo.modenv) > 0) + { + fluid_real_t env_value = fluid_convex(127 * fluid_adsr_env_get_val(&voice->envlfo.modenv)); + fluid_clip(env_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, env_value); + } + } + fluid_adsr_env_set_section(&voice->envlfo.volenv, FLUID_VOICE_ENVRELEASE); fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVRELEASE); } @@ -650,11 +675,12 @@ static FLUID_INLINE void fluid_rvoice_local_retrigger_attack(fluid_rvoice_t *voi DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) { fluid_rvoice_t *voice = obj; - int section = fluid_adsr_env_get_section(&voice->envlfo.volenv); + int section; /* volume or modulation section */ /*------------------------------------------------------------------------- Section skip for volume envelope --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.volenv); if(section >= FLUID_VOICE_ENVHOLD) { /* DECAY, SUSTAIN,RELEASE section use logarithmic scaling. Calculates new @@ -672,16 +698,30 @@ DECLARE_FLUID_RVOICE_FUNCTION(fluid_rvoice_multi_retrigger_attack) /* skips to Attack section from any section */ /* Update vol and attack data */ fluid_rvoice_local_retrigger_attack(voice); + /*------------------------------------------------------------------------- Section skip for modulation envelope --------------------------------------------------------------------------*/ + section = fluid_adsr_env_get_section(&voice->envlfo.modenv); + if(section >= FLUID_VOICE_ENVHOLD) + { + /* DECAY, SUSTAIN,RELEASE section use linear scaling. + Since v 2.1 , as recommended by soundfont 2.01/2.4 spec, ATTACK section + uses convex shape (see fluid_rvoice_write() - fluid_convex()). + Calculate new modenv value (new_value) for seamless attack transition. + Here we need the inverse of fluid_convex() function defined as: + new_value = pow(10, (1 - current_val) . FLUID_PEAK_ATTENUATION / -200 . 2.0) + For performance reason we use fluid_cb2amp(Val) = pow(10, val/-200) with + val = (1 – current_val) . FLUID_PEAK_ATTENUATION / 2.0 + */ + fluid_real_t new_value; /* new modenv value */ + new_value = fluid_cb2amp((1.0f - fluid_adsr_env_get_val(&voice->envlfo.modenv)) + * FLUID_PEAK_ATTENUATION / 2.0); + fluid_clip(new_value, 0.0, 1.0); + fluid_adsr_env_set_val(&voice->envlfo.modenv, new_value); + } /* Skips from any section to ATTACK section */ fluid_adsr_env_set_section(&voice->envlfo.modenv, FLUID_VOICE_ENVATTACK); - /* Actually (v 1.1.6) all sections are linear, so there is no need to - correct val value. However soundfont 2.01/2.4 spec. says that Attack should - be convex (see issue #153 from Christian Collins). In the case Attack - section would be changed to a non linear shape it will be necessary to do - a correction for seamless val transition. Here is the place to do this */ } /** diff --git a/src/sfloader/fluid_defsfont.c b/src/sfloader/fluid_defsfont.c index b81aaca2..d7960cbb 100644 --- a/src/sfloader/fluid_defsfont.c +++ b/src/sfloader/fluid_defsfont.c @@ -682,6 +682,140 @@ fluid_defpreset_next(fluid_defpreset_t *defpreset) return defpreset->next; } +/* + * Adds global and local modulators list to the voice. This is done in 2 steps: + * - Step 1: Local modulators replace identic global modulators. + * - Step 2: global + local modulators are added to the voice using mode. + * + * Instrument zone list (local/global) must be added using FLUID_VOICE_OVERWRITE. + * Preset zone list (local/global) must be added using FLUID_VOICE_ADD. + * + * @param voice voice instance. + * @param global_mod global list of modulators. + * @param local_mod local list of modulators. + * @param mode Determines how to handle an existing identical modulator. + * #FLUID_VOICE_ADD to add (offset) the modulator amounts, + * #FLUID_VOICE_OVERWRITE to replace the modulator, +*/ +static void +fluid_defpreset_noteon_add_mod_to_voice(fluid_voice_t *voice, + fluid_mod_t *global_mod, fluid_mod_t *local_mod, + int mode) +{ + fluid_mod_t *mod; + /* list for 'sorting' global/local modulators */ + fluid_mod_t *mod_list[FLUID_NUM_MOD]; + int mod_list_count, i; + + /* identity_limit_count is the modulator upper limit number to handle with + * existing identical modulators. + * When identity_limit_count is below the actual number of modulators, this + * will restrict identity check to this upper limit, + * This is useful when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at + * noteon. + */ + int identity_limit_count; + + /* Step 1: Local modulators replace identic global modulators. */ + + /* local (instrument zone/preset zone), modulators: Put them all into a list. */ + mod_list_count = 0; + + while(local_mod) + { + /* As modulators number in local_mod list was limited to FLUID_NUM_MOD at + soundfont loading time (fluid_limit_mod_list()), here we don't need + to check if mod_list is full. + */ + mod_list[mod_list_count++] = local_mod; + local_mod = local_mod->next; + } + + /* global (instrument zone/preset zone), modulators. + * Replace modulators with the same definition in the global list: + * (Instrument zone: SF 2.01 page 69, 'bullet' 8) + * (Preset zone: SF 2.01 page 69, second-last bullet). + * + * mod_list contains local modulators. Now we know that there + * is no global modulator identic to another global modulator (this has + * been checked at soundfont loading time). So global modulators + * are only checked against local modulators number. + */ + + /* Restrict identity check to the number of local modulators */ + identity_limit_count = mod_list_count; + + while(global_mod) + { + /* 'Identical' global modulators are ignored. + * SF2.01 section 9.5.1 + * page 69, 'bullet' 3 defines 'identical'. */ + + for(i = 0; i < identity_limit_count; i++) + { + if(fluid_mod_test_identity(global_mod, mod_list[i])) + { + break; + } + } + + /* Finally add the new modulator to the list. */ + if(i >= identity_limit_count) + { + /* Although local_mod and global_mod lists was limited to + FLUID_NUM_MOD at soundfont loading time, it is possible that + local + global modulators exceeds FLUID_NUM_MOD. + So, checks if mod_list_count reachs the limit. + */ + if(mod_list_count >= FLUID_NUM_MOD) + { + /* mod_list is full, we silently forget this modulator and + next global modulators. When mod_list will be added to the + voice, a warning will be displayed if the voice list is full. + (see fluid_voice_add_mod_local()). + */ + break; + } + + mod_list[mod_list_count++] = global_mod; + } + + global_mod = global_mod->next; + } + + /* Step 2: global + local modulators are added to the voice using mode. */ + + /* + * mod_list contains local and global modulators, we know that: + * - there is no global modulator identic to another global modulator, + * - there is no local modulator identic to another local modulator, + * So these local/global modulators are only checked against + * actual number of voice modulators. + */ + + /* Restrict identity check to the actual number of voice modulators */ + /* Acual number of voice modulators : defaults + [instruments] */ + identity_limit_count = voice->mod_count; + + for(i = 0; i < mod_list_count; i++) + { + + mod = mod_list[i]; + /* in mode FLUID_VOICE_OVERWRITE disabled instruments modulators CANNOT be skipped. */ + /* in mode FLUID_VOICE_ADD disabled preset modulators can be skipped. */ + + if((mode == FLUID_VOICE_OVERWRITE) || (mod->amount != 0)) + { + /* Instrument modulators -supersede- existing (default) modulators. + SF 2.01 page 69, 'bullet' 6 */ + + /* Preset modulators -add- to existing instrument modulators. + SF2.01 page 70 first bullet on page */ + fluid_voice_add_mod_local(voice, mod, mode, identity_limit_count); + } + } +} /* * fluid_defpreset_noteon @@ -695,9 +829,6 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c fluid_voice_zone_t *voice_zone; fluid_list_t *list; 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(defpreset); @@ -769,63 +900,12 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c } /* 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); - } - } + /* Adds instrument zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global instrument modulators */ + global_inst_zone ? global_inst_zone->mod : NULL, + inst_zone->mod, /* local instrument modulators */ + FLUID_VOICE_OVERWRITE); /* mode */ /* Preset level, generators */ @@ -868,57 +948,12 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c } } /* 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); - } - } + /* Adds preset zone modulators (global and local) to the voice.*/ + fluid_defpreset_noteon_add_mod_to_voice(voice, + /* global preset modulators */ + global_preset_zone ? global_preset_zone->mod : NULL, + preset_zone->mod, /* local preset modulators */ + FLUID_VOICE_ADD); /* mode */ /* add the synthesis process to the synthesis loop. */ fluid_synth_start_voice(synth, voice); @@ -930,7 +965,6 @@ fluid_defpreset_noteon(fluid_defpreset_t *defpreset, fluid_synth_t *synth, int c * class - for example when using stereo samples) */ } - } } @@ -1110,6 +1144,7 @@ new_fluid_preset_zone(char *name) static void delete_fluid_list_mod(fluid_mod_t *mod) { fluid_mod_t *tmp; + while(mod) /* delete the modulators */ { tmp = mod; @@ -1192,6 +1227,129 @@ static int fluid_preset_zone_create_voice_zones(fluid_preset_zone_t *preset_zone return FLUID_OK; } +/** + * Checks if modulator mod is identic to another modulator in the list + * (specs SF 2.0X 7.4, 7.8). + * @param mod, modulator list. + * @param name, if not NULL, pointer on a string displayed as warning. + * @return TRUE if mod is identic to another modulator, FALSE otherwise. + */ +static int +fluid_zone_is_mod_identic(fluid_mod_t *mod, char *name) +{ + fluid_mod_t *next = mod->next; + + while(next) + { + /* is mod identic to next ? */ + if(fluid_mod_test_identity(mod, next)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, "Ignoring identic modulator %s", name); + } + + return TRUE; + } + + next = next->next; + } + + return FALSE; +} + +/** + * Limits the number of modulators in a modulator list. + * This is appropriate to internal synthesizer modulators tables + * which have a fixed size (FLUID_NUM_MOD). + * + * @param zone_name, zone name + * @param list_mod, address of pointer on modulator list. + */ +static void fluid_limit_mod_list(char *zone_name, fluid_mod_t **list_mod) +{ + int mod_idx = 0; /* modulator index */ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + + while(mod) + { + if((mod_idx + 1) > FLUID_NUM_MOD) + { + /* truncation of list_mod */ + if(mod_idx) + { + prev_mod->next = NULL; + } + else + { + *list_mod = NULL; + } + + delete_fluid_list_mod(mod); + FLUID_LOG(FLUID_WARN, "%s, modulators count limited to %d", zone_name, + FLUID_NUM_MOD); + break; + } + + mod_idx++; + prev_mod = mod; + mod = mod->next; + } +} + +/** + * Checks and remove invalid modulators from a zone modulators list. + * - checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1). + * - checks identic modulators in the list (specs SF 2.01 7.4, 7.8). + * @param zone_name, zone name. + * @param list_mod, address of pointer on modulators list. + */ +static void +fluid_zone_check_mod(char *zone_name, fluid_mod_t **list_mod) +{ + fluid_mod_t *prev_mod = NULL; /* previous modulator in list_mod */ + fluid_mod_t *mod = *list_mod; /* first modulator in list_mod */ + int mod_idx = 0; /* modulator index */ + + while(mod) + { + char zone_mod_name[256]; + fluid_mod_t *next = mod->next; + + /* prepare modulator name: zonename/#modulator */ + FLUID_SNPRINTF(zone_mod_name, sizeof(zone_mod_name), "%s/mod%d", zone_name, mod_idx); + + /* has mod invalid sources ? */ + if(!fluid_mod_check_sources(mod, zone_mod_name) + /* or is mod identic to any following modulator ? */ + || fluid_zone_is_mod_identic(mod, zone_mod_name)) + { + /* the modulator is useless so we remove it */ + if(prev_mod) + { + prev_mod->next = next; + } + else + { + *list_mod = next; + } + + delete_fluid_mod(mod); /* freeing */ + } + else + { + prev_mod = mod; + } + + mod = next; + mod_idx++; + } + + /* limits the size of modulators list */ + fluid_limit_mod_list(zone_name, list_mod); +} + /* * fluid_zone_gen_import_sfont * Imports generators from sfzone to gen and range. @@ -1205,7 +1363,7 @@ fluid_zone_gen_import_sfont(fluid_gen_t *gen, fluid_zone_range_t *range, SFZone fluid_list_t *r; SFGen *sfgen; - for( r = sfzone->gen; r != NULL; ) + for(r = sfzone->gen; r != NULL;) { sfgen = (SFGen *)fluid_list_get(r); @@ -1258,6 +1416,7 @@ fluid_zone_mod_source_import_sfont(unsigned char *src, unsigned char *flags, uns /* Bit 7: CC flag SF 2.01 section 8.2.1 page 50*/ flags_dest = 0; + if(sf_source & (1 << 7)) { flags_dest |= FLUID_MOD_CC; @@ -1313,19 +1472,21 @@ fluid_zone_mod_source_import_sfont(unsigned char *src, unsigned char *flags, uns /* This shouldn't happen - unknown type! */ return FALSE; } + *flags = flags_dest; return TRUE; } /* * fluid_zone_mod_import_sfont - * Imports modulators from sfzone to mod list. - * @param mod, pointer on modulator list to return. + * Imports modulators from sfzone to modulators list mod. + * @param zone_name, zone name. + * @param mod, address of pointer on modulators list to return. * @param sfzone, pointer on soundfont zone. * @return FLUID_OK if success, FLUID_FAILED otherwise. */ static int -fluid_zone_mod_import_sfont(fluid_mod_t **mod, SFZone *sfzone) +fluid_zone_mod_import_sfont(char *zone_name, fluid_mod_t **mod, SFZone *sfzone) { fluid_list_t *r; int count; @@ -1367,7 +1528,7 @@ fluid_zone_mod_import_sfont(fluid_mod_t **mod, SFZone *sfzone) mod_dest->amount = 0; } - /* *** Dest *** */ + /* *** Dest *** */ mod_dest->dest = mod_src->dest; /* index of controlled generator */ /* *** Amount source *** */ @@ -1422,6 +1583,8 @@ fluid_zone_mod_import_sfont(fluid_mod_t **mod, SFZone *sfzone) r = fluid_list_next(r); } /* foreach modulator */ + /* checks and removes invalid modulators in modulators list*/ + fluid_zone_check_mod(zone_name, mod); return FLUID_OK; } @@ -1457,7 +1620,7 @@ fluid_preset_zone_import_sfont(fluid_preset_zone_t *zone, SFZone *sfzone, fluid_ } /* Import the modulators (only SF2.1 and higher) */ - return fluid_zone_mod_import_sfont(&zone->mod, sfzone); + return fluid_zone_mod_import_sfont(zone->name, &zone->mod, sfzone); } /* @@ -1570,7 +1733,9 @@ fluid_inst_import_sfont(fluid_preset_zone_t *preset_zone, SFInst *sfinst, fluid_ { sfzone = (SFZone *)fluid_list_get(p); - FLUID_SNPRINTF(zone_name, sizeof(zone_name), "%s/%d", inst->name, count); + /* integrates preset zone name in instrument zone name */ + FLUID_SNPRINTF(zone_name, sizeof(zone_name), "%s/%s/%d", preset_zone->name, + inst->name, count); inst_zone = new_fluid_inst_zone(zone_name); @@ -1728,7 +1893,7 @@ fluid_inst_zone_import_sfont(fluid_inst_zone_t *inst_zone, SFZone *sfzone, fluid } /* Import the modulators (only SF2.1 and higher) */ - return fluid_zone_mod_import_sfont(&inst_zone->mod, sfzone); + return fluid_zone_mod_import_sfont(inst_zone->name, &inst_zone->mod, sfzone); } /* diff --git a/src/synth/fluid_mod.c b/src/synth/fluid_mod.c index 127de3f0..84e97731 100644 --- a/src/synth/fluid_mod.c +++ b/src/synth/fluid_mod.c @@ -504,6 +504,177 @@ size_t fluid_mod_sizeof() return sizeof(fluid_mod_t); } +/** + * Checks if modulator with source 1 other than CC is FLUID_MOD_NONE. + * + * @param mod, modulator. + * @return TRUE if modulator source 1 other than cc is FLUID_MOD_NONE, FALSE otherwise. + */ +static int +fluid_mod_is_src1_none(const fluid_mod_t *mod) +{ + return(((mod->flags1 & FLUID_MOD_CC) == 0) && (mod->src1 == FLUID_MOD_NONE)); +} + +/** + * Checks if modulators source other than CC source is invalid. + * (specs SF 2.01 7.4, 7.8, 8.2.1) + * + * @param mod, modulator. + * @param src1_select, source input selection to check. + * 1 to check src1 source. + * 0 to check src2 source. + * @return FALSE if selected modulator source other than cc is invalid, TRUE otherwise. + */ +static int +fluid_mod_check_non_cc_source(const fluid_mod_t *mod, unsigned char src1_select) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) != 0) /* src is a CC */ + /* SF2.01 section 8.2.1: Constant value */ + || ((src == FLUID_MOD_NONE) + || (src == FLUID_MOD_VELOCITY) /* Note-on velocity */ + || (src == FLUID_MOD_KEY) /* Note-on key number */ + || (src == FLUID_MOD_KEYPRESSURE) /* Poly pressure */ + || (src == FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ + || (src == FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ + || (src == FLUID_MOD_PITCHWHEELSENS) /* Pitch wheel sensitivity */ + )); +} + +/** + * Checks if modulator CC source is invalid (specs SF 2.01 7.4, 7.8, 8.2.1). + * @param mod, modulator. + * @src1_select, source input selection: + * 1 to check src1 source or + * 0 to check src2 source. + * @return FALSE if selected modulator's source CC is invalid, TRUE otherwise. + */ +static int +fluid_mod_check_cc_source(const fluid_mod_t *mod, unsigned char src1_select) +{ + unsigned char flags, src; + + if(src1_select) + { + flags = mod->flags1; + src = mod->src1; + } + else + { + flags = mod->flags2; + src = mod->src2; + } + + return(((flags & FLUID_MOD_CC) == 0) /* src is non CC */ + || ((src != BANK_SELECT_MSB) + && (src != BANK_SELECT_LSB) + && (src != DATA_ENTRY_MSB) + && (src != DATA_ENTRY_LSB) + /* is src not NRPN_LSB, NRPN_MSB, RPN_LSB, RPN_MSB */ + && ((src < NRPN_LSB) || (RPN_MSB < src)) + /* is src not ALL_SOUND_OFF, ALL_CTRL_OFF, LOCAL_CONTROL, ALL_NOTES_OFF ? */ + /* is src not OMNI_OFF, OMNI_ON, POLY_OFF, POLY_ON ? */ + && (src < ALL_SOUND_OFF) + /* CC lsb shouldn't allowed to modulate (spec SF 2.01 - 8.2.1) + However, as long fluidsynth will use only CC 7 bits resolution, + it is safe to ignore these SF recommendations on CC receive. + See explanations in fluid_synth_cc_LOCAL() */ + /* uncomment next line to forbid CC lsb */ + /* && ((src < 32) || (63 < src)) */ + )); +} + +/** + * Checks valid modulator sources (specs SF 2.01 7.4, 7.8, 8.2.1) + * @param mod, modulator. + * @param name,if not NULL, pointer on a string displayed as a warning. + * @return TRUE if modulator sources src1, src2 are valid, FALSE otherwise. + */ +int fluid_mod_check_sources(const fluid_mod_t *mod, char *name) +{ + static const char *invalid_non_cc_src = + "Invalid modulator, using non-CC source %s.src%d=%d"; + static const char *invalid_cc_src = + "Invalid modulator, using CC source %s.src%d=%d"; + static const char *src1_is_none = + "Modulator with source 1 none %s.src1=%d"; + + /* checks valid non cc sources */ + if(!fluid_mod_check_non_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + /* + When src1 is non CC source FLUID_MOD_NONE, the modulator is valid but + the output of this modulator will be forced to 0 at synthesis time. + Also this modulator cannot be used to overwrite a default modulator (as + there is no default modulator with src1 source equal to FLUID_MOD_NONE). + Consequently it is useful to return FALSE to indicate this modulator + being useless. It will be removed later with others invalid modulators. + */ + if(fluid_mod_is_src1_none(mod)) + { + if(name) + { + FLUID_LOG(FLUID_WARN, src1_is_none, name, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_non_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_non_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + /* checks valid cc sources */ + if(!fluid_mod_check_cc_source(mod, 1)) /* check src1 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 1, mod->src1); + } + + return FALSE; + } + + if(!fluid_mod_check_cc_source(mod, 0)) /* check src2 */ + { + if(name) + { + FLUID_LOG(FLUID_WARN, invalid_cc_src, name, 2, mod->src2); + } + + return FALSE; + } + + return TRUE; +} + /** * Checks if two modulators are identical in sources, flags and destination. * @param mod1 First modulator diff --git a/src/synth/fluid_mod.h b/src/synth/fluid_mod.h index e834baa5..3e766174 100644 --- a/src/synth/fluid_mod.h +++ b/src/synth/fluid_mod.h @@ -44,6 +44,7 @@ struct _fluid_mod_t }; fluid_real_t fluid_mod_get_value(fluid_mod_t *mod, fluid_voice_t *voice); +int fluid_mod_check_sources(const fluid_mod_t *mod, char *name); #ifdef DEBUG void fluid_dump_modulator(fluid_mod_t *mod); diff --git a/src/synth/fluid_synth.c b/src/synth/fluid_synth.c index 15e167a1..07bfc042 100644 --- a/src/synth/fluid_synth.c +++ b/src/synth/fluid_synth.c @@ -227,7 +227,7 @@ void fluid_synth_settings(fluid_settings_t *settings) #else fluid_settings_register_int(settings, "synth.cpu-cores", 1, 1, 1, 0); #endif - + fluid_settings_register_int(settings, "synth.min-note-length", 10, 0, 65535, 0); fluid_settings_register_int(settings, "synth.threadsafe-api", 1, 0, 1, FLUID_HINT_TOGGLED); @@ -1331,6 +1331,13 @@ fluid_synth_add_default_mod(fluid_synth_t *synth, const fluid_mod_t *mod, int mo fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(mod != NULL, FLUID_FAILED); + + /* Checks if modulators sources are valid */ + if(!fluid_mod_check_sources(mod, "api fluid_synth_add_default_mod mod")) + { + return FLUID_FAILED; + } + fluid_synth_api_enter(synth); default_mod = synth->default_mod; @@ -3369,7 +3376,7 @@ fluid_synth_nwrite_float(fluid_synth_t *synth, int len, fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(left != NULL, FLUID_FAILED); fluid_return_val_if_fail(right != NULL, FLUID_FAILED); - + /* First, take what's still available in the buffer */ count = 0; num = synth->cur; @@ -3522,7 +3529,7 @@ fluid_synth_nwrite_float(fluid_synth_t *synth, int len, /** * mixes the samples of \p in to \p out - * + * * @param out the output sample buffer to mix to * @param ooff sample offset in \p out * @param in the rvoice_mixer input sample buffer to mix from @@ -3540,6 +3547,7 @@ static FLUID_INLINE void fluid_synth_mix_single_buffer(float *FLUID_RESTRICT out if(out != NULL) { int j; + for(j = 0; j < num; j++) { out[j + ooff] += (float) in[buf_idx * FLUID_BUFSIZE * FLUID_MIXER_MAX_BUFFERS_DEFAULT + j + ioff]; @@ -3688,7 +3696,7 @@ fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; - + float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, 0, fx_left_in, synth->cur, buf_idx, num); @@ -3731,10 +3739,10 @@ fluid_synth_process(fluid_synth_t *synth, int len, int nfx, float *fx[], for(i = 0; i < nfxchan; i++) { int buf_idx = f * nfxchan + i; - + float *out_buf = fx[(buf_idx * 2) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_left_in, 0, buf_idx, num); - + out_buf = fx[(buf_idx * 2 + 1) % nfx]; fluid_synth_mix_single_buffer(out_buf, count, fx_right_in, 0, buf_idx, num); } @@ -3785,7 +3793,7 @@ fluid_synth_write_float(fluid_synth_t *synth, int len, float cpu_load; fluid_profile_ref_var(prof_ref); - + fluid_return_val_if_fail(synth != NULL, FLUID_FAILED); fluid_return_val_if_fail(lout != NULL, FLUID_FAILED); fluid_return_val_if_fail(rout != NULL, FLUID_FAILED); @@ -3854,10 +3862,12 @@ static FLUID_INLINE int16_t round_clip_to_i16(float x) { long i; + if(x >= 0.0f) { i = (long)(x + 0.5f); - if (FLUID_UNLIKELY(i > 32767)) + + if(FLUID_UNLIKELY(i > 32767)) { i = 32767; } @@ -3865,12 +3875,13 @@ round_clip_to_i16(float x) else { i = (long)(x - 0.5f); - if (FLUID_UNLIKELY(i < -32768)) + + if(FLUID_UNLIKELY(i < -32768)) { i = -32768; } } - + return (int16_t)i; } @@ -4348,11 +4359,11 @@ fluid_synth_alloc_voice_LOCAL(fluid_synth_t *synth, fluid_sample_t *sample, int ) { // Replacement of default_vel2att modulator by custom_breath2att_modulator - fluid_voice_add_mod(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT); + fluid_voice_add_mod_local(voice, &custom_breath2att_mod, FLUID_VOICE_DEFAULT, 0); } else { - fluid_voice_add_mod(voice, default_mod, FLUID_VOICE_DEFAULT); + fluid_voice_add_mod_local(voice, default_mod, FLUID_VOICE_DEFAULT, 0); } // Next default modulator to add to the voice diff --git a/src/synth/fluid_voice.c b/src/synth/fluid_voice.c index 2bbe5de2..7c626574 100644 --- a/src/synth/fluid_voice.c +++ b/src/synth/fluid_voice.c @@ -1185,7 +1185,7 @@ fluid_voice_update_param(fluid_voice_t *voice, int gen) */ #define NBR_BIT_BY_VAR_LN2 5 /* for 32 bits variables */ -#define NBR_BIT_BY_VAR (1 << NBR_BIT_BY_VAR_LN2) +#define NBR_BIT_BY_VAR (1 << NBR_BIT_BY_VAR_LN2) #define NBR_BIT_BY_VAR_ANDMASK (NBR_BIT_BY_VAR - 1) #define SIZE_UPDATED_GEN_BIT ((GEN_LAST + NBR_BIT_BY_VAR_ANDMASK) / NBR_BIT_BY_VAR) @@ -1209,7 +1209,7 @@ int fluid_voice_modulate(fluid_voice_t *voice, int cc, int ctrl) mod = &voice->mod[i]; /* step 1: find all the modulators that have the changed controller - as input source. When ctrl is -1 all modulators destination + as input source. When ctrl is -1 all modulators destination are updated */ if(ctrl < 0 || fluid_mod_has_source(mod, cc, ctrl)) { @@ -1465,10 +1465,10 @@ fluid_voice_stop(fluid_voice_t *voice) } /** - * 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 + * Adds a modulator to the voice if the modulator has valid sources. + * @param voice Voice instance. + * @param mod Modulator info (copied). + * @param mode Determines how to handle an existing identical modulator. * #FLUID_VOICE_ADD to add (offset) the modulator amounts, * #FLUID_VOICE_OVERWRITE to replace the modulator, * #FLUID_VOICE_DEFAULT when adding a default modulator - no duplicate should @@ -1476,33 +1476,43 @@ fluid_voice_stop(fluid_voice_t *voice) */ void fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) +{ + /* Ignore the modulator if its sources inputs are invalid */ + if(fluid_mod_check_sources(mod, "api fluid_voice_add_mod mod")) + { + fluid_voice_add_mod_local(voice, mod, mode, FLUID_NUM_MOD); + } +} + +/** + * Adds a modulator to the voice. + * local version of fluid_voice_add_mod function. Called at noteon time. + * @param voice, mod, mode, same as for fluid_voice_add_mod() (see above). + * @param check_limit_count is the modulator number limit to handle with existing + * identical modulator(i.e mode FLUID_VOICE_OVERWRITE, FLUID_VOICE_ADD). + * - When FLUID_NUM_MOD, all the voices modulators (since the previous call) + * are checked for identity. + * - When check_count_limit is below the actual number of voices modulators + * (voice->mod_count), this will restrict identity check to this number, + * This is usefull when we know by advance that there is no duplicate with + * modulators at index above this limit. This avoid wasting cpu cycles at noteon. + */ +void +fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count) { int i; - /* - * 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 != FLUID_MOD_NONE) /* SF2.01 section 8.2.1: Constant value */ - && (mod->src1 != FLUID_MOD_VELOCITY) /* Note-on velocity */ - && (mod->src1 != FLUID_MOD_KEY) /* Note-on key number */ - && (mod->src1 != FLUID_MOD_KEYPRESSURE) /* Poly pressure */ - && (mod->src1 != FLUID_MOD_CHANNELPRESSURE) /* Channel pressure */ - && (mod->src1 != FLUID_MOD_PITCHWHEEL) /* Pitch wheel */ - && (mod->src1 != FLUID_MOD_PITCHWHEELSENS)))/* Pitch wheel sensitivity */ + /* check_limit_count cannot be above voice->mod_count */ + if(check_limit_count > voice->mod_count) { - FLUID_LOG(FLUID_WARN, "Ignoring invalid controller, using non-CC source %i.", mod->src1); - return; + check_limit_count = voice->mod_count; } if(mode == FLUID_VOICE_ADD) { /* if identical modulator exists, add them */ - for(i = 0; i < voice->mod_count; i++) + for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { @@ -1517,7 +1527,7 @@ fluid_voice_add_mod(fluid_voice_t *voice, fluid_mod_t *mod, int mode) { /* if identical modulator exists, replace it (only the amount has to be changed) */ - for(i = 0; i < voice->mod_count; i++) + for(i = 0; i < check_limit_count; i++) { if(fluid_mod_test_identity(&voice->mod[i], mod)) { diff --git a/src/synth/fluid_voice.h b/src/synth/fluid_voice.h index 6038a1a9..fb0b2823 100644 --- a/src/synth/fluid_voice.h +++ b/src/synth/fluid_voice.h @@ -157,6 +157,7 @@ void fluid_voice_release(fluid_voice_t *voice); void fluid_voice_noteoff(fluid_voice_t *voice); void fluid_voice_off(fluid_voice_t *voice); void fluid_voice_stop(fluid_voice_t *voice); +void fluid_voice_add_mod_local(fluid_voice_t *voice, fluid_mod_t *mod, int mode, int check_limit_count); void fluid_voice_overflow_rvoice_finished(fluid_voice_t *voice); int fluid_voice_kill_excl(fluid_voice_t *voice);