From ad1124cde9eec2dcbfc07c248d39e8e4b7174ee8 Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Mon, 10 Aug 2009 13:42:09 +0200 Subject: [PATCH] Added CALF LADSPA plugins As per popular demand I added CALF LADSPA plugins to be shipped with LMMS. After some minor modifications the plugins compile and work on win32 platform too. (cherry picked from commit 35ca0aab694f91720af12e210ac0b7b0bcdd3cbd) --- CMakeLists.txt | 9 + plugins/ladspa_effect/CMakeLists.txt | 4 + plugins/ladspa_effect/calf/AUTHORS | 9 + plugins/ladspa_effect/calf/CMakeLists.txt | 15 + plugins/ladspa_effect/calf/COPYING | 504 +++++ plugins/ladspa_effect/calf/COPYING.GPL | 340 +++ plugins/ladspa_effect/calf/ChangeLog | 170 ++ plugins/ladspa_effect/calf/INSTALL | 254 +++ plugins/ladspa_effect/calf/NEWS | 0 plugins/ladspa_effect/calf/README | 49 + plugins/ladspa_effect/calf/TODO | 40 + plugins/ladspa_effect/calf/calf/audio_fx.h | 790 +++++++ plugins/ladspa_effect/calf/calf/biquad.h | 580 +++++ plugins/ladspa_effect/calf/calf/buffer.h | 229 ++ plugins/ladspa_effect/calf/calf/delay.h | 185 ++ plugins/ladspa_effect/calf/calf/envelope.h | 206 ++ plugins/ladspa_effect/calf/calf/fft.h | 113 + plugins/ladspa_effect/calf/calf/fixed_point.h | 269 +++ plugins/ladspa_effect/calf/calf/giface.h | 561 +++++ plugins/ladspa_effect/calf/calf/inertia.h | 256 +++ plugins/ladspa_effect/calf/calf/ladspa_wrap.h | 516 +++++ plugins/ladspa_effect/calf/calf/loudness.h | 90 + plugins/ladspa_effect/calf/calf/lv2helpers.h | 281 +++ plugins/ladspa_effect/calf/calf/lv2wrap.h | 349 +++ plugins/ladspa_effect/calf/calf/metadata.h | 312 +++ plugins/ladspa_effect/calf/calf/modmatrix.h | 112 + plugins/ladspa_effect/calf/calf/modulelist.h | 95 + plugins/ladspa_effect/calf/calf/modules.h | 1016 +++++++++ plugins/ladspa_effect/calf/calf/modules_dev.h | 119 + .../ladspa_effect/calf/calf/modules_small.h | 202 ++ .../ladspa_effect/calf/calf/modules_synths.h | 264 +++ plugins/ladspa_effect/calf/calf/multichorus.h | 213 ++ plugins/ladspa_effect/calf/calf/onepole.h | 192 ++ plugins/ladspa_effect/calf/calf/organ.h | 374 ++++ plugins/ladspa_effect/calf/calf/osc.h | 306 +++ plugins/ladspa_effect/calf/calf/osctl.h | 502 +++++ plugins/ladspa_effect/calf/calf/plugininfo.h | 105 + plugins/ladspa_effect/calf/calf/preset.h | 142 ++ plugins/ladspa_effect/calf/calf/primitives.h | 516 +++++ plugins/ladspa_effect/calf/calf/synth.h | 228 ++ plugins/ladspa_effect/calf/calf/utils.h | 161 ++ plugins/ladspa_effect/calf/giface.cpp | 325 +++ plugins/ladspa_effect/calf/modmatrix.cpp | 136 ++ plugins/ladspa_effect/calf/modules.cpp | 614 ++++++ plugins/ladspa_effect/calf/modules_dsp.cpp | 638 ++++++ plugins/ladspa_effect/calf/modules_small.cpp | 1916 +++++++++++++++++ plugins/ladspa_effect/calf/monosynth.cpp | 663 ++++++ plugins/ladspa_effect/calf/organ.cpp | 857 ++++++++ plugins/ladspa_effect/calf/plugin.cpp | 101 + plugins/ladspa_effect/calf/synth.cpp | 228 ++ plugins/ladspa_effect/calf/utils.cpp | 132 ++ 51 files changed, 16288 insertions(+) create mode 100644 plugins/ladspa_effect/calf/AUTHORS create mode 100644 plugins/ladspa_effect/calf/CMakeLists.txt create mode 100644 plugins/ladspa_effect/calf/COPYING create mode 100644 plugins/ladspa_effect/calf/COPYING.GPL create mode 100644 plugins/ladspa_effect/calf/ChangeLog create mode 100644 plugins/ladspa_effect/calf/INSTALL create mode 100644 plugins/ladspa_effect/calf/NEWS create mode 100644 plugins/ladspa_effect/calf/README create mode 100644 plugins/ladspa_effect/calf/TODO create mode 100644 plugins/ladspa_effect/calf/calf/audio_fx.h create mode 100644 plugins/ladspa_effect/calf/calf/biquad.h create mode 100644 plugins/ladspa_effect/calf/calf/buffer.h create mode 100644 plugins/ladspa_effect/calf/calf/delay.h create mode 100644 plugins/ladspa_effect/calf/calf/envelope.h create mode 100644 plugins/ladspa_effect/calf/calf/fft.h create mode 100644 plugins/ladspa_effect/calf/calf/fixed_point.h create mode 100644 plugins/ladspa_effect/calf/calf/giface.h create mode 100644 plugins/ladspa_effect/calf/calf/inertia.h create mode 100644 plugins/ladspa_effect/calf/calf/ladspa_wrap.h create mode 100644 plugins/ladspa_effect/calf/calf/loudness.h create mode 100644 plugins/ladspa_effect/calf/calf/lv2helpers.h create mode 100644 plugins/ladspa_effect/calf/calf/lv2wrap.h create mode 100644 plugins/ladspa_effect/calf/calf/metadata.h create mode 100644 plugins/ladspa_effect/calf/calf/modmatrix.h create mode 100644 plugins/ladspa_effect/calf/calf/modulelist.h create mode 100644 plugins/ladspa_effect/calf/calf/modules.h create mode 100644 plugins/ladspa_effect/calf/calf/modules_dev.h create mode 100644 plugins/ladspa_effect/calf/calf/modules_small.h create mode 100644 plugins/ladspa_effect/calf/calf/modules_synths.h create mode 100644 plugins/ladspa_effect/calf/calf/multichorus.h create mode 100644 plugins/ladspa_effect/calf/calf/onepole.h create mode 100644 plugins/ladspa_effect/calf/calf/organ.h create mode 100644 plugins/ladspa_effect/calf/calf/osc.h create mode 100644 plugins/ladspa_effect/calf/calf/osctl.h create mode 100644 plugins/ladspa_effect/calf/calf/plugininfo.h create mode 100644 plugins/ladspa_effect/calf/calf/preset.h create mode 100644 plugins/ladspa_effect/calf/calf/primitives.h create mode 100644 plugins/ladspa_effect/calf/calf/synth.h create mode 100644 plugins/ladspa_effect/calf/calf/utils.h create mode 100644 plugins/ladspa_effect/calf/giface.cpp create mode 100644 plugins/ladspa_effect/calf/modmatrix.cpp create mode 100644 plugins/ladspa_effect/calf/modules.cpp create mode 100644 plugins/ladspa_effect/calf/modules_dsp.cpp create mode 100644 plugins/ladspa_effect/calf/modules_small.cpp create mode 100644 plugins/ladspa_effect/calf/monosynth.cpp create mode 100644 plugins/ladspa_effect/calf/organ.cpp create mode 100644 plugins/ladspa_effect/calf/plugin.cpp create mode 100644 plugins/ladspa_effect/calf/synth.cpp create mode 100644 plugins/ladspa_effect/calf/utils.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 16eed8650..dec60660a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ ELSE(LMMS_HOST_X86_64) ENDIF(LMMS_HOST_X86_64) OPTION(WANT_ALSA "Include ALSA (Advanced Linux Sound Architecture) support" ON) +OPTION(WANT_CALF "Include CALF LADSPA plugins" ON) OPTION(WANT_CAPS "Include C* Audio Plugin Suite (LADSPA plugins)" ON) OPTION(WANT_CMT "Include Computer Music Toolkit LADSPA plugins" ON) OPTION(WANT_FFTW3F "Include SpectrumAnalyzer and ZynAddSubFX plugin" ON) @@ -118,6 +119,13 @@ IF(NOT SNDFILE_FOUND) MESSAGE(FATAL_ERROR "LMMS requires libsndfile1 and libsndfile1-dev >= 1.0.11 - please install, remove CMakeCache.txt and try again!") ENDIF(NOT SNDFILE_FOUND) +IF(WANT_CALF) +SET(LMMS_HAVE_CALF TRUE) +SET(STATUS_CALF "OK") +ELSE(WANT_CALF) +SET(STATUS_CALF "not built as requested") +ENDIF(WANT_CALF) + IF(WANT_CAPS) SET(LMMS_HAVE_CAPS TRUE) SET(STATUS_CAPS "OK") @@ -604,6 +612,7 @@ MESSAGE( "* VST-instrument hoster : ${STATUS_VST}\n" "* VST-effect hoster : ${STATUS_VST}\n" "* SpectrumAnalyzer : ${STATUS_FFTW3F}\n" +"* CALF LADSPA plugins : ${STATUS_CALF}\n" "* CAPS LADSPA plugins : ${STATUS_CAPS}\n" "* CMT LADSPA plugins : ${STATUS_CMT}\n" "* TAP LADSPA plugins : ${STATUS_TAP}\n" diff --git a/plugins/ladspa_effect/CMakeLists.txt b/plugins/ladspa_effect/CMakeLists.txt index 18b56a209..26bce76fe 100644 --- a/plugins/ladspa_effect/CMakeLists.txt +++ b/plugins/ladspa_effect/CMakeLists.txt @@ -14,6 +14,10 @@ IF(WANT_CMT) ADD_SUBDIRECTORY(cmt) ENDIF(WANT_CMT) +IF(WANT_CALF) +ADD_SUBDIRECTORY(calf) +ENDIF(WANT_CALF) + INCLUDE(BuildPlugin) diff --git a/plugins/ladspa_effect/calf/AUTHORS b/plugins/ladspa_effect/calf/AUTHORS new file mode 100644 index 000000000..c354559b1 --- /dev/null +++ b/plugins/ladspa_effect/calf/AUTHORS @@ -0,0 +1,9 @@ +Krzysztof Foltman +Hermann Meyer +Thor Harald Johansen +Thorsten Wilms +Hans Baier +Torben Hohn + +Additional bugfixes/enhancement patches: +David Täht diff --git a/plugins/ladspa_effect/calf/CMakeLists.txt b/plugins/ladspa_effect/calf/CMakeLists.txt new file mode 100644 index 000000000..609d4085a --- /dev/null +++ b/plugins/ladspa_effect/calf/CMakeLists.txt @@ -0,0 +1,15 @@ +FILE(GLOB SOURCES *.cpp) +ADD_LIBRARY(calf MODULE ${SOURCES}) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/calf) +INSTALL(TARGETS calf LIBRARY DESTINATION ${PLUGIN_DIR}/ladspa) +ADD_DEFINITIONS(-DUSE_LADSPA=1) +SET_TARGET_PROPERTIES(calf PROPERTIES PREFIX "") +SET_TARGET_PROPERTIES(calf PROPERTIES COMPILE_FLAGS "-O2 -finline-limit=80 -funroll-loops") + +IF(LMMS_BUILD_WIN32) + ADD_CUSTOM_COMMAND(TARGET calf POST_BUILD COMMAND ${STRIP} ${CMAKE_CURRENT_BINARY_DIR}/calf.dll) +ENDIF(LMMS_BUILD_WIN32) +IF(NOT LMMS_BUILD_APPLE) + SET_TARGET_PROPERTIES(calf PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") +ENDIF(NOT LMMS_BUILD_APPLE) + diff --git a/plugins/ladspa_effect/calf/COPYING b/plugins/ladspa_effect/calf/COPYING new file mode 100644 index 000000000..223ede7de --- /dev/null +++ b/plugins/ladspa_effect/calf/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/plugins/ladspa_effect/calf/COPYING.GPL b/plugins/ladspa_effect/calf/COPYING.GPL new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/plugins/ladspa_effect/calf/COPYING.GPL @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/plugins/ladspa_effect/calf/ChangeLog b/plugins/ladspa_effect/calf/ChangeLog new file mode 100644 index 000000000..b8f999563 --- /dev/null +++ b/plugins/ladspa_effect/calf/ChangeLog @@ -0,0 +1,170 @@ +Version 0.0.18.2 + ++ Organ: fix voice stealing of released notes, sort out GUI, add quadratic + mode for amplitude envelope (enabled by default) - sounds more natural ++ Monosynth: fix the bug that caused JACK to kick the client out due + to precalculating waves in a completely wrong place, fix portamento + for off-stack notes ++ Presets: 3 new presets for Organ, 4 for Monosynth, 2 for Reverb + +Version 0.0.18.1 + ++ Filter: fixed subtle redraw bugs ++ Icons: fixed packaging-incompatible paths + +Version 0.0.18 + ++ Filterclavier: new plugin (a MIDI controlled filter) by Hans Baier ++ DSSI: added a basic implementation of live graphs. The graphs have a + limited resolution (128 data points), and are rather inefficient + (as the graph data need to be transmitted via OSC to a different + process), but it's better than nothing ++ GUI: Torben Hohn's drawing optimizations (critical for Intel graphics + cards, but should also reduce CPU usage on other hardware) ++ Phaser: added frequency response graph ++ JACK host: discontinue the broken option -p; allow giving preset names + after a colon sign (reverb:DiscoVerb instead of -p DiscoVerb reverb) ++ Reverb: less modulation; tone controls; 2 more room types ++ MultiChorus: add double bandpass filter on input ++ GUI: added frequency grid ++ Organ: added progress reporting on load (works with JACK host and LV2) ++ JACK host: use sensible port names (possibly breaking new LASH sessions) ++ Organ: added polyphony limit ++ Small plugins: added support for polymorphic port extension to allow + the same plugins to be used for control and audio signals ++ DSSI: renamed all the plugins from "plugin LADSPA" to "plugin DSSI" ++ LADSPA: more reasonable default value hints, fixed locale issue in LRDF ++ JACK host: added icons by Thorsten Wilms (thanks!) ++ Organ, Monosynth: better memory usage ++ LV2: attempt at supporting configure-like parameters (key mapping curve + in Organ) by the new String Port extension ++ AutoHell: header files are not installed anymore (they are of little + use anyway) ++ AutoHell: configure script prints if --enable-experimental was specified + +Version 0.0.17 + ++ Compressor: new plugin by Thor Harald Johansen ++ GUI: control improvements (new LED control, improved VU meter, XML + improvements, line graph with dots and grid lines - no legend yet), move + autolayout code from the plugin libraries to makerdf executable, ++ Most plugins: use custom GUI layouts instead of autogenerated ones ++ Most plugins: add dry amount (for aux bus type uses) ++ Flanger, Filter, MultiChorus: added live graphs displaying frequency + response and (in case of MultiChorus) LFO positions ++ LV2 GUI: added a way to display live graphs in Ardour and Zynjacku/LV2Rack + (only works when the plugin and the GUI are in the same process) ++ Framework: general improvements/cleanups to reduce the chance of the + kind of errors that were introduced in 0.0.16 and reduce dependencies ++ Monosynth: removed soft clipper on output + +Version 0.0.16.3 + ++ Fixed compilation without LV2 core installed + +Version 0.0.16.2 + ++ Fixed DSSI GUI for MultiChorus ++ Fixed LV2 GUI for MultiChorus ++ Make knob control mouse wheel handling work better in Ingen + +Version 0.0.16 + ++ New MultiChorus plugin (stereo multitap chorus with maximum of 8 voices) ++ Experimental set of plugins for modular synthesizers like Ingen by + Dave Robillard (enabled using --enable-experimental option in configure + script) ++ Minor improvements to other plugins (like Rotary Speaker) ++ More work on API documentation + +Version 0.0.15 + ++ Organ: new percussive section, using 2-operator FM synthesis for + monophonic or polyphonic percussive attack; added global transpose and + detune; rearrangement of controls between sections ++ Rotary Speaker: another attempt at making it useful (thanks FishB8) ++ JACK host: eliminate deadlock on exit ++ GUI: bipolar knobs now have a "dead zone" (magnet) in the middle point ++ GUI: dragging a knob with SHIFT held allows for fine adjustments ++ GUI: new controls - curve editor and keyboard ++ LV2: improved extension support (supports my "extended port properties" + extension now) ++ Added some API documentation + +Version 0.0.14 ++ OSC: totally new OSC wrapper, to allow for realtime-safe parsing (doesn't + matter as far as functionality goes, will probably be rewritten again + anyway) ++ Everything: memory management fixes (should improve stability and + compatibility) ++ Organ: improved memory usage ++ GUI: improved bipolar knobs, added endless knobs ++ Presets: separate 'built-in' and 'user' presets (so that built-in presets + can be upgraded without affecting user's own presets) ++ Monosynth: new presets + +Version 0.0.13 ++ Fixed several problems related to 64-bit environments and OpenSUSE (thanks +oc2pus!) ++ Added NOCONFIGURE environment variable support to autogen.sh + +Version 0.0.12 ++ RotarySpeaker: work in progress; enabled by default just in case it's + useful for anyone ++ Organ: reworked to add a complete subtractive synth section, a selection + of waveform (settable on a per-drawbar basis), individual settings of + phase, detune, panning, routing for each drawbar, as well as improved(?) + percussive section and vibrato/phaser section. It is usable (and sounds + good!), but some parameters, waveform set etc. may change in future. May + take up to 100 MB of RAM due to pre-calculated bandlimited waveforms. ++ Added half-complete implementation of LV2 (including GUI and events). ++ Lots of small "polishing" kind of fixes in many places (like proper + rounding of values in the GUIs, another set of hold/sostenuto fixes etc) + +Version 0.0.11 + ++ Fixed x86-64 bugs ++ JackHost: implemented LASH support ++ RotarySpeaker: fixed panning bug, implemented acceleration/decceleration + for "off" state + +Version 0.0.10 + ++ First attempt at DSSI GUI, does not support some features from JACK host, + but that's inevitable because of API limitations ++ Reverb: improvements (more parameters, fixed denormals) ++ Knob: added custom support for scroll wheel (instead of one inherited from + GtkRange) + +Version 0.0.9 + ++ started creating an XML-based GUI ++ LineGraph: new GTK+ control for displaying waveforms and filter response + graphs in Monosynth (and maybe others in future) ++ Monosynth: notch filter changes (made notch bandwidth proportional to Q, + just for fun, might be a bad idea) ++ Monosynth: more waveforms (these might be final?) ++ Monosynth: capped Sustain level to 0.999 so that decay time actually means + something with Sustain = 100% (not a great way to do it, but acceptable in + this case) ++ Monosynth: GUI refreshes less often (which means less CPU use) ++ Monosynth: less clicks on sounds using LP filter with very low cutoff + (using ramp of 256 samples instead of 64 samples as before) ++ Knob: new GTK+ control based on GtkRange, with my primitive bitmap set + (generated with Python and Cairo) ++ Organ: added a GUI too, very provisional ++ Organ: fixed Hold pedal (doesn't release the notes which are still depressed) ++ RotarySpeaker: new effect (split off Organ) ++ all: denormal fixes (still some denormals present in reverb) ++ Reverb: better time setting (decay time somewhat corresponds to -60dB + attenuation time) ++ JackHost: -M switch allows for automatic connection to JACK MIDI event source + (use -M system:midi_capture_2 or -M 2 for autoconnection to + system:midi_capture_2; of course, the short numeric form only work for + system:midi_capture_ ports) ++ JackHost: -p switch selects a preset automatically ++ JackHost: better size setting algorithm ++ JackHost: duplicate client name (causing JACK to rename the client) doesn't + break autoconnecting functionality ++ autotools configuration update (detect Cairo and require newer GTK+) ++ more presets diff --git a/plugins/ladspa_effect/calf/INSTALL b/plugins/ladspa_effect/calf/INSTALL new file mode 100644 index 000000000..9ac4dbad9 --- /dev/null +++ b/plugins/ladspa_effect/calf/INSTALL @@ -0,0 +1,254 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free +Software Foundation, Inc. +Copyright (C) 2007-2008 Krzysztof Foltman + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Prerequisites +============= + +To compile and install Calf, you need: + +- POSIX-compliant operating system +- G++ version 4.0 or higher (tested with 4.1.3) +- GTK+2 headers and libraries (glib 2.10, gtk+ 2.12) +- Cairo headers and libraries +- Glade 2 headers and libraries + +Optional but recommended: +- JACK header and libraries (tested with 0.109.0) +- LADSPA header +- DSSI header +- LV2 core + +Basic Installation +================== + +These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). Here is a another example: + + /bin/bash ./configure CONFIG_SHELL=/bin/bash + +Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent +configuration-related scripts to be executed by `/bin/bash'. + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/plugins/ladspa_effect/calf/NEWS b/plugins/ladspa_effect/calf/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/ladspa_effect/calf/README b/plugins/ladspa_effect/calf/README new file mode 100644 index 000000000..4cba953e6 --- /dev/null +++ b/plugins/ladspa_effect/calf/README @@ -0,0 +1,49 @@ +Calf is a pack of audio plugins - effects and instruments, currently in +development. The goal is to create a set of plugins using decent algorithms +and parameter settings, available in a form which is compatible with as many +open source applications as possible. + +How to use Calf plugins: + +* LADSPA plugins + +Calf is installed as calf.so library in your LADSPA directory (typically +/usr/lib/ladspa). It means that typical LADSPA host should be able to find +Calf's plugins. + +* DSSI plugins + +Calf .so module is also installed in your DSSI plugin directory, which means +your DSSI host (like jack-dssi-host or rosegarden) should find it and +include its plugins in the plugin list. + +* JACK client application + +You can also use Calf plugins as separate applications, connecting to other +applications using JACK Audio Connection Kit (version 0.103 or newer is +required). To run the client, type: + + calfjackhost monosynth ! + +(! means "connect", last "!" means "connect to output") + +Other examples: + + calfjackhost monosynth ! vintagedelay ! flanger ! + +(runs monosynth into vintagedelay and vintagedelay into flanger, then to +output) + + calfjackhost ! reverb ! + +(takes signal from system:capture_1 and _2, puts it through reverb, and then +sends to system:playback_1 and _2) + +You can also change client name or input/output port names with command-line +options (type calfjackhost --help). Use qjackctl, patchage or jack_connect +to connect the Calf JACK client to your sound card or other applications, if +"!" is inadequate for any reason (if I didn't explain it properly, or if it +doesn't provide the connectivity options needed). + +Keep in mind this project is in the early development phase. It is usable +for certain purposes, but drop me a note if you need something. diff --git a/plugins/ladspa_effect/calf/TODO b/plugins/ladspa_effect/calf/TODO new file mode 100644 index 000000000..55bcfb1ca --- /dev/null +++ b/plugins/ladspa_effect/calf/TODO @@ -0,0 +1,40 @@ +1. More effects + +- auto-wah (might be integrated into filter) +- envelope follower +- better reverb (more features, use nested allpasses, use 1-pole + 1-zero allpass instead of fractional delays) +- dynamics processing (Thor already did the compressor) +- distortion? +- windy rotary speakery stuff +- filter: more types + +2. Some instruments + +- some virtual analogue thing (something larger than Monosynth) +- FM (by reusing my MMX code, or something) + +3. DSP library + +- profiling framework +- optimized code (the one I have now only pretends to be optimized :) ) +- underflow handling + +4. Wrappers + +- LADSPA: proper rdf (get clearance from drobilla ;) ) +- better jack host (controls etc) +- BSE +- buzztard +- Linux VST +- LV2 + Message Context (for Organ) + EPP (the rest of them) + Mixing Controls + +5. Organization stuff (autotools etc) + +- correct compilation and installation of LADSPA plugins (current version is a hack!) +- switch to -O3 +- get to work on 64-bit architectures +- i18n (gettext or whatever) diff --git a/plugins/ladspa_effect/calf/calf/audio_fx.h b/plugins/ladspa_effect/calf/calf/audio_fx.h new file mode 100644 index 000000000..56d08f6d6 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/audio_fx.h @@ -0,0 +1,790 @@ +/* Calf DSP Library + * Reusable audio effect classes. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#ifndef __CALF_AUDIOFX_H +#define __CALF_AUDIOFX_H + +#include +#include +#include +#include +#include "primitives.h" +#include "delay.h" +#include "fixed_point.h" +#include "inertia.h" + +namespace dsp { +#if 0 +}; to keep editor happy +#endif + +/** + * Audio effect base class. Not really useful until it gets more developed. + */ +class audio_effect +{ +public: + virtual void setup(int sample_rate)=0; + virtual ~audio_effect() {} +}; + +class modulation_effect: public audio_effect +{ +protected: + int sample_rate; + float rate, wet, dry, odsr; + gain_smoothing gs_wet, gs_dry; +public: + fixed_point phase, dphase; + float get_rate() { + return rate; + } + void set_rate(float rate) { + this->rate = rate; + dphase = rate/sample_rate*4096; + } + float get_wet() { + return wet; + } + void set_wet(float wet) { + this->wet = wet; + gs_wet.set_inertia(wet); + } + float get_dry() { + return dry; + } + void set_dry(float dry) { + this->dry = dry; + gs_dry.set_inertia(dry); + } + void reset_phase(float req_phase) + { + phase = req_phase * 4096.0; + } + void inc_phase(float req_phase) + { + phase += fixed_point(req_phase * 4096.0); + } + void setup(int sample_rate) + { + this->sample_rate = sample_rate; + this->odsr = 1.0 / sample_rate; + phase = 0; + set_rate(get_rate()); + } +}; + +/** + * A monophonic phaser. If you want stereo, combine two :) + * Also, gave up on using template args for signal type. + */ +template +class simple_phaser: public modulation_effect +{ +protected: + float base_frq, mod_depth, fb; + float state; + int cnt, stages; + dsp::onepole stage1; + float x1[MaxStages], y1[MaxStages]; +public: + simple_phaser() + { + set_base_frq(1000); + set_mod_depth(1000); + set_fb(0); + state = 0; + cnt = 0; + stages = 0; + set_stages(6); + } + float get_base_frq() { + return base_frq; + } + void set_base_frq(float _base_frq) { + base_frq = _base_frq; + } + int get_stages() { + return stages; + } + void set_stages(int _stages) { + if (_stages > stages) + { + for (int i = stages; i < _stages; i++) + { + x1[i] = x1[stages-1]; + y1[i] = y1[stages-1]; + } + } + stages = _stages; + } + float get_mod_depth() { + return mod_depth; + } + void set_mod_depth(float _mod_depth) { + mod_depth = _mod_depth; + } + float get_fb() { + return fb; + } + void set_fb(float fb) { + this->fb = fb; + } + virtual void setup(int sample_rate) { + modulation_effect::setup(sample_rate); + reset(); + } + void reset() + { + cnt = 0; + state = 0; + phase.set(0); + for (int i = 0; i < MaxStages; i++) + x1[i] = y1[i] = 0; + control_step(); + } + inline void control_step() + { + cnt = 0; + int v = phase.get() + 0x40000000; + int sign = v >> 31; + v ^= sign; + // triangle wave, range from 0 to INT_MAX + double vf = (double)((v >> 16) * (1.0 / 16384.0) - 1); + + float freq = base_frq * pow(2.0, vf * mod_depth / 1200.0); + freq = dsp::clip(freq, 10.0, 0.49 * sample_rate); + stage1.set_ap_w(freq * (M_PI / 2.0) * odsr); + phase += dphase * 32; + for (int i = 0; i < stages; i++) + { + dsp::sanitize(x1[i]); + dsp::sanitize(y1[i]); + } + dsp::sanitize(state); + } + void process(float *buf_out, float *buf_in, int nsamples) { + for (int i=0; i cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); // z^-1 + + cfloat p = cfloat(1.0); + cfloat stg = stage1.h_z(z); + + for (int i = 0; i < stages; i++) + p = p * stg; + + p = p / (cfloat(1.0) - cfloat(fb) * p); + return std::abs(cfloat(gs_dry.get_last()) + cfloat(gs_wet.get_last()) * p); + } +}; + +/** + * Base class for chorus and flanger. Wouldn't be needed if it wasn't + * for odd behaviour of GCC when deriving templates from template + * base classes (not seeing fields from base classes!). + */ +class chorus_base: public modulation_effect +{ +protected: + int min_delay_samples, mod_depth_samples; + float min_delay, mod_depth; + sine_table sine; +public: + float get_min_delay() { + return min_delay; + } + void set_min_delay(float min_delay) { + this->min_delay = min_delay; + this->min_delay_samples = (int)(min_delay * 65536.0 * sample_rate); + } + float get_mod_depth() { + return mod_depth; + } + void set_mod_depth(float mod_depth) { + this->mod_depth = mod_depth; + // 128 because it's then multiplied by (hopefully) a value of 32768..-32767 + this->mod_depth_samples = (int)(mod_depth * 32.0 * sample_rate); + } +}; + +/** + * Single-tap chorus without feedback. + * Perhaps MaxDelay should be a bit longer! + */ +template +class simple_chorus: public chorus_base +{ +protected: + simple_delay delay; +public: + simple_chorus() { + rate = 0.63f; + dry = 0.5f; + wet = 0.5f; + min_delay = 0.005f; + mod_depth = 0.0025f; + setup(44100); + } + void reset() { + delay.reset(); + } + virtual void setup(int sample_rate) { + modulation_effect::setup(sample_rate); + delay.reset(); + set_min_delay(get_min_delay()); + set_mod_depth(get_mod_depth()); + } + template + void process(OutIter buf_out, InIter buf_in, int nsamples) { + int mds = min_delay_samples + mod_depth_samples * 1024 + 2*65536; + int mdepth = mod_depth_samples; + for (int i=0; i(sine.data[ipart], sine.data[ipart+1]); + int v = mds + (mdepth * lfo >> 6); + // if (!(i & 7)) printf("%d\n", v); + int ifv = v >> 16; + delay.put(in); + T fd; // signal from delay's output + delay.get_interp(fd, ifv, (v & 0xFFFF)*(1.0/65536.0)); + T sdry = in * gs_dry.get(); + T swet = fd * gs_wet.get(); + *buf_out++ = sdry + swet; + } + } +}; + +/** + * Single-tap flanger (chorus plus feedback). + */ +template +class simple_flanger: public chorus_base +{ +protected: + simple_delay delay; + float fb; + int last_delay_pos, last_actual_delay_pos; + int ramp_pos, ramp_delay_pos; +public: + simple_flanger() + : fb(0) {} + void reset() { + delay.reset(); + last_delay_pos = last_actual_delay_pos = ramp_delay_pos = 0; + ramp_pos = 1024; + } + virtual void setup(int sample_rate) { + this->sample_rate = sample_rate; + this->odsr = 1.0 / sample_rate; + delay.reset(); + phase = 0; + set_rate(get_rate()); + set_min_delay(get_min_delay()); + } + float get_fb() { + return fb; + } + void set_fb(float fb) { + this->fb = fb; + } + template + void process(OutIter buf_out, InIter buf_in, int nsamples) { + if (!nsamples) + return; + int mds = this->min_delay_samples + this->mod_depth_samples * 1024 + 2 * 65536; + int mdepth = this->mod_depth_samples; + int delay_pos; + unsigned int ipart = this->phase.ipart(); + int lfo = phase.lerp_by_fract_int(this->sine.data[ipart], this->sine.data[ipart+1]); + delay_pos = mds + (mdepth * lfo >> 6); + + if (delay_pos != last_delay_pos || ramp_pos < 1024) + { + if (delay_pos != last_delay_pos) { + // we need to ramp from what the delay tap length actually was, + // not from old (ramp_delay_pos) or desired (delay_pos) tap length + ramp_delay_pos = last_actual_delay_pos; + ramp_pos = 0; + } + + int64_t dp = 0; + for (int i=0; i> 10; + ramp_pos++; + if (ramp_pos > 1024) ramp_pos = 1024; + this->delay.get_interp(fd, dp >> 16, (dp & 0xFFFF)*(1.0/65536.0)); + sanitize(fd); + T sdry = in * this->dry; + T swet = fd * this->wet; + *buf_out++ = sdry + swet; + this->delay.put(in+fb*fd); + + this->phase += this->dphase; + ipart = this->phase.ipart(); + lfo = phase.lerp_by_fract_int(this->sine.data[ipart], this->sine.data[ipart+1]); + delay_pos = mds + (mdepth * lfo >> 6); + } + last_actual_delay_pos = dp; + } + else { + for (int i=0; idelay.get_interp(fd, delay_pos >> 16, (delay_pos & 0xFFFF)*(1.0/65536.0)); + sanitize(fd); + T sdry = in * this->gs_dry.get(); + T swet = fd * this->gs_wet.get(); + *buf_out++ = sdry + swet; + this->delay.put(in+fb*fd); + + this->phase += this->dphase; + ipart = this->phase.ipart(); + lfo = phase.lerp_by_fract_int(this->sine.data[ipart], this->sine.data[ipart+1]); + delay_pos = mds + (mdepth * lfo >> 6); + } + last_actual_delay_pos = delay_pos; + } + last_delay_pos = delay_pos; + } + float freq_gain(float freq, float sr) + { + typedef std::complex cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); // z^-1 + + float ldp = last_delay_pos / 65536.0; + float fldp = floor(ldp); + cfloat zn = std::pow(z, fldp); // z^-N + cfloat zn1 = zn * z; // z^-(N+1) + // simulate a lerped comb filter - H(z) = 1 / (1 + fb * (lerp(z^-N, z^-(N+1), fracpos))), N = int(pos), fracpos = pos - int(pos) + cfloat delayed = zn + (zn1 - zn) * cfloat(ldp - fldp); + cfloat h = cfloat(delayed) / (cfloat(1.0) - cfloat(fb) * delayed); + // mix with dry signal + float v = std::abs(cfloat(gs_dry.get_last()) + cfloat(gs_wet.get_last()) * h); + return v; + } +}; + +/** + * A classic allpass loop reverb with modulated allpass filter. + * Just started implementing it, so there is no control over many + * parameters. + */ +template +class reverb: public audio_effect +{ + simple_delay<2048, T> apL1, apL2, apL3, apL4, apL5, apL6; + simple_delay<2048, T> apR1, apR2, apR3, apR4, apR5, apR6; + fixed_point phase, dphase; + sine_table sine; + onepole lp_left, lp_right; + T old_left, old_right; + int type; + float time, fb, cutoff, diffusion; + int tl[6], tr[6]; + float ldec[6], rdec[6]; + + int sr; +public: + reverb() + { + phase = 0.0; + time = 1.0; + cutoff = 9000; + type = 2; + diffusion = 1.f; + setup(44100); + } + virtual void setup(int sample_rate) { + sr = sample_rate; + set_time(time); + set_cutoff(cutoff); + phase = 0.0; + dphase = 0.5*128/sr; + update_times(); + } + void update_times() + { + switch(type) + { + case 0: + tl[0] = 397 << 16, tr[0] = 383 << 16; + tl[1] = 457 << 16, tr[1] = 429 << 16; + tl[2] = 549 << 16, tr[2] = 631 << 16; + tl[3] = 649 << 16, tr[3] = 756 << 16; + tl[4] = 773 << 16, tr[4] = 803 << 16; + tl[5] = 877 << 16, tr[5] = 901 << 16; + break; + case 1: + tl[0] = 697 << 16, tr[0] = 783 << 16; + tl[1] = 957 << 16, tr[1] = 929 << 16; + tl[2] = 649 << 16, tr[2] = 531 << 16; + tl[3] = 1049 << 16, tr[3] = 1177 << 16; + tl[4] = 473 << 16, tr[4] = 501 << 16; + tl[5] = 587 << 16, tr[5] = 681 << 16; + break; + case 2: + default: + tl[0] = 697 << 16, tr[0] = 783 << 16; + tl[1] = 957 << 16, tr[1] = 929 << 16; + tl[2] = 649 << 16, tr[2] = 531 << 16; + tl[3] = 1249 << 16, tr[3] = 1377 << 16; + tl[4] = 1573 << 16, tr[4] = 1671 << 16; + tl[5] = 1877 << 16, tr[5] = 1781 << 16; + break; + case 3: + tl[0] = 1097 << 16, tr[0] = 1087 << 16; + tl[1] = 1057 << 16, tr[1] = 1031 << 16; + tl[2] = 1049 << 16, tr[2] = 1039 << 16; + tl[3] = 1083 << 16, tr[3] = 1055 << 16; + tl[4] = 1075 << 16, tr[4] = 1099 << 16; + tl[5] = 1003 << 16, tr[5] = 1073 << 16; + break; + case 4: + tl[0] = 197 << 16, tr[0] = 133 << 16; + tl[1] = 357 << 16, tr[1] = 229 << 16; + tl[2] = 549 << 16, tr[2] = 431 << 16; + tl[3] = 949 << 16, tr[3] = 1277 << 16; + tl[4] = 1173 << 16, tr[4] = 1671 << 16; + tl[5] = 1477 << 16, tr[5] = 1881 << 16; + break; + case 5: + tl[0] = 197 << 16, tr[0] = 133 << 16; + tl[1] = 257 << 16, tr[1] = 179 << 16; + tl[2] = 549 << 16, tr[2] = 431 << 16; + tl[3] = 619 << 16, tr[3] = 497 << 16; + tl[4] = 1173 << 16, tr[4] = 1371 << 16; + tl[5] = 1577 << 16, tr[5] = 1881 << 16; + break; + } + + float fDec=1000 + 2400.f * diffusion; + for (int i = 0 ; i < 6; i++) { + ldec[i]=exp(-float(tl[i] >> 16) / fDec), + rdec[i]=exp(-float(tr[i] >> 16) / fDec); + } + } + float get_time() { + return time; + } + void set_time(float time) { + this->time = time; + // fb = pow(1.0f/4096.0f, (float)(1700/(time*sr))); + fb = 1.0 - 0.3 / (time * sr / 44100.0); + } + float get_type() { + return type; + } + void set_type(int type) { + this->type = type; + update_times(); + } + float get_diffusion() { + return diffusion; + } + void set_diffusion(float diffusion) { + this->diffusion = diffusion; + update_times(); + } + void set_type_and_diffusion(int type, float diffusion) { + this->type = type; + this->diffusion = diffusion; + update_times(); + } + float get_fb() + { + return this->fb; + } + void set_fb(float fb) + { + this->fb = fb; + } + float get_cutoff() { + return cutoff; + } + void set_cutoff(float cutoff) { + this->cutoff = cutoff; + lp_left.set_lp(cutoff,sr); + lp_right.set_lp(cutoff,sr); + } + void reset() + { + apL1.reset();apR1.reset(); + apL2.reset();apR2.reset(); + apL3.reset();apR3.reset(); + apL4.reset();apR4.reset(); + apL5.reset();apR5.reset(); + apL6.reset();apR6.reset(); + lp_left.reset();lp_right.reset(); + old_left = 0; old_right = 0; + } + void process(T &left, T &right) + { + unsigned int ipart = phase.ipart(); + + // the interpolated LFO might be an overkill here + int lfo = phase.lerp_by_fract_int(sine.data[ipart], sine.data[ipart+1]) >> 2; + phase += dphase; + + left += old_right; + left = apL1.process_allpass_comb_lerp16(left, tl[0] - 45*lfo, ldec[0]); + left = apL2.process_allpass_comb_lerp16(left, tl[1] + 47*lfo, ldec[1]); + float out_left = left; + left = apL3.process_allpass_comb_lerp16(left, tl[2] + 54*lfo, ldec[2]); + left = apL4.process_allpass_comb_lerp16(left, tl[3] - 69*lfo, ldec[3]); + left = apL5.process_allpass_comb_lerp16(left, tl[4] + 69*lfo, ldec[4]); + left = apL6.process_allpass_comb_lerp16(left, tl[5] - 46*lfo, ldec[5]); + old_left = lp_left.process(left * fb); + sanitize(old_left); + + right += old_left; + right = apR1.process_allpass_comb_lerp16(right, tr[0] - 45*lfo, rdec[0]); + right = apR2.process_allpass_comb_lerp16(right, tr[1] + 47*lfo, rdec[1]); + float out_right = right; + right = apR3.process_allpass_comb_lerp16(right, tr[2] + 54*lfo, rdec[2]); + right = apR4.process_allpass_comb_lerp16(right, tr[3] - 69*lfo, rdec[3]); + right = apR5.process_allpass_comb_lerp16(right, tr[4] + 69*lfo, rdec[4]); + right = apR6.process_allpass_comb_lerp16(right, tr[5] - 46*lfo, rdec[5]); + old_right = lp_right.process(right * fb); + sanitize(old_right); + + left = out_left, right = out_right; + } + void extra_sanitize() + { + lp_left.sanitize(); + lp_right.sanitize(); + } +}; + +class filter_module_iface +{ +public: + virtual void calculate_filter(float freq, float q, int mode, float gain = 1.0) = 0; + virtual void filter_activate() = 0; + virtual void sanitize() = 0; + virtual int process_channel(uint16_t channel_no, float *in, float *out, uint32_t numsamples, int inmask) = 0; + virtual float freq_gain(int subindex, float freq, float srate) = 0; + + virtual ~filter_module_iface() {} +}; + + +class biquad_filter_module: public filter_module_iface +{ +private: + dsp::biquad_d1 left[3], right[3]; + int order; + +public: + uint32_t srate; + + enum { mode_12db_lp = 0, mode_24db_lp = 1, mode_36db_lp = 2, + mode_12db_hp = 3, mode_24db_hp = 4, mode_36db_hp = 5, + mode_6db_bp = 6, mode_12db_bp = 7, mode_18db_bp = 8, + mode_6db_br = 9, mode_12db_br = 10, mode_18db_br = 11, + mode_count + }; + +public: + biquad_filter_module() : order(0) {} + + void calculate_filter(float freq, float q, int mode, float gain = 1.0) + { + if (mode <= mode_36db_lp) { + order = mode + 1; + left[0].set_lp_rbj(freq, pow(q, 1.0 / order), srate, gain); + } else if ( mode_12db_hp <= mode && mode <= mode_36db_hp ) { + order = mode - mode_12db_hp + 1; + left[0].set_hp_rbj(freq, pow(q, 1.0 / order), srate, gain); + } else if ( mode_6db_bp <= mode && mode <= mode_18db_bp ) { + order = mode - mode_6db_bp + 1; + left[0].set_bp_rbj(freq, pow(q, 1.0 / order), srate, gain); + } else { // mode_6db_br <= mode <= mode_18db_br + order = mode - mode_6db_br + 1; + left[0].set_br_rbj(freq, order * 0.1 * q, srate, gain); + } + + right[0].copy_coeffs(left[0]); + for (int i = 1; i < order; i++) { + left[i].copy_coeffs(left[0]); + right[i].copy_coeffs(left[0]); + } + } + + void filter_activate() + { + for (int i=0; i < order; i++) { + left[i].reset(); + right[i].reset(); + } + } + + void sanitize() + { + for (int i=0; i < order; i++) { + left[i].sanitize(); + right[i].sanitize(); + } + } + + inline int process_channel(uint16_t channel_no, float *in, float *out, uint32_t numsamples, int inmask) { + dsp::biquad_d1 *filter; + switch (channel_no) { + case 0: + filter = left; + break; + + case 1: + filter = right; + break; + + default: + assert(false); + return 0; + } + + if (inmask) { + switch(order) { + case 1: + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[0].process(in[i]); + break; + case 2: + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[1].process(filter[0].process(in[i])); + break; + case 3: + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[2].process(filter[1].process(filter[0].process(in[i]))); + break; + } + } else { + if (filter[order - 1].empty()) + return 0; + switch(order) { + case 1: + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[0].process_zeroin(); + break; + case 2: + if (filter[0].empty()) + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[1].process_zeroin(); + else + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[1].process(filter[0].process_zeroin()); + break; + case 3: + if (filter[1].empty()) + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[2].process_zeroin(); + else + for (uint32_t i = 0; i < numsamples; i++) + out[i] = filter[2].process(filter[1].process(filter[0].process_zeroin())); + break; + } + } + for (int i = 0; i < order; i++) + filter[i].sanitize(); + return filter[order - 1].empty() ? 0 : inmask; + } + + float freq_gain(int subindex, float freq, float srate) + { + float level = 1.0; + for (int j = 0; j < order; j++) + level *= left[j].freq_gain(freq, srate); + return level; + } +}; + +class two_band_eq +{ +private: + dsp::onepole lowcut, highcut; + float low_gain, high_gain; + +public: + void reset() + { + lowcut.reset(); + highcut.reset(); + } + + inline float process(float v) + { + v = dsp::lerp(lowcut.process_hp(v), v, low_gain); + v = dsp::lerp(highcut.process_lp(v), v, high_gain); + return v; + } + + inline void copy_coeffs(const two_band_eq &src) + { + lowcut.copy_coeffs(src.lowcut); + highcut.copy_coeffs(src.highcut); + low_gain = src.low_gain; + high_gain = src.high_gain; + } + + void sanitize() + { + lowcut.sanitize(); + highcut.sanitize(); + } + + void set(float _low_freq, float _low_gain, float _high_freq, float _high_gain, float sr) + { + lowcut.set_hp(_low_freq, sr); + highcut.set_lp(_high_freq, sr); + low_gain = _low_gain; + high_gain = _high_gain; + } +}; + +#if 0 +{ to keep editor happy +#endif +} + +#endif diff --git a/plugins/ladspa_effect/calf/calf/biquad.h b/plugins/ladspa_effect/calf/calf/biquad.h new file mode 100644 index 000000000..e1f419508 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/biquad.h @@ -0,0 +1,580 @@ +/* Calf DSP Library + * Biquad filters + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * Most of code in this file is based on freely + * available other work of other people (filter equations). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#ifndef __CALF_BIQUAD_H +#define __CALF_BIQUAD_H + +#include +#include "primitives.h" + +namespace dsp { + +/** + * Coefficients for two-pole two-zero filter, for floating point values, + * plus a bunch of functions to set them to typical values. + * + * Coefficient calculation is based on famous Robert Bristow-Johnson's equations, + * except where it's not. + * The coefficient calculation is NOT mine, the only exception is the lossy + * optimization in Zoelzer and rbj HP filter code. + * + * See http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt for reference. + * + * don't use this for integers because it won't work + */ +template +class biquad_coeffs +{ +public: + // filter coefficients + Coeff a0, a1, a2, b1, b2; + typedef std::complex cfloat; + + biquad_coeffs() + { + set_null(); + } + + inline void set_null() + { + a0 = 1.0; + b1 = b2 = a1 = a2 = 0.f; + } + + /** Lowpass filter based on Robert Bristow-Johnson's equations + * Perhaps every synth code that doesn't use SVF uses these + * equations :) + * @param fc resonant frequency + * @param q resonance (gain at fc) + * @param sr sample rate + * @param gain amplification (gain at 0Hz) + */ + inline void set_lp_rbj(float fc, float q, float sr, float gain = 1.0) + { + float omega=(float)(2*M_PI*fc/sr); + float sn=sin(omega); + float cs=cos(omega); + float alpha=(float)(sn/(2*q)); + float inv=(float)(1.0/(1.0+alpha)); + + a2 = a0 = (float)(gain*inv*(1 - cs)*0.5f); + a1 = a0 + a0; + b1 = (float)(-2*cs*inv); + b2 = (float)((1 - alpha)*inv); + } + + // different lowpass filter, based on Zoelzer's equations, modified by + // me (kfoltman) to use polynomials to approximate tangent function + // not very accurate, but perhaps good enough for synth work :) + // odsr is "one divided by samplerate" + // from how it looks, it perhaps uses bilinear transform - but who knows :) + inline void set_lp_zoelzer(float fc, float q, float odsr, float gain=1.0) + { + Coeff omega=(Coeff)(M_PI*fc*odsr); + Coeff omega2=omega*omega; + Coeff K=omega*(1+omega2*omega2*Coeff(1.0/1.45)); + Coeff KK=K*K; + Coeff QK=q*(KK+1.f); + Coeff iQK=1.0f/(QK+K); + Coeff inv=q*iQK; + b2 = (Coeff)(iQK*(QK-K)); + b1 = (Coeff)(2.f*(KK-1.f)*inv); + a2 = a0 = (Coeff)(inv*gain*KK); + a1 = a0 + a0; + } + + /** Highpass filter based on Robert Bristow-Johnson's equations + * @param fc resonant frequency + * @param q resonance (gain at fc) + * @param sr sample rate + * @param gain amplification (gain at sr/2) + */ + inline void set_hp_rbj(float fc, float q, float esr, float gain=1.0) + { + Coeff omega=(float)(2*M_PI*fc/esr); + Coeff sn=sin(omega); + Coeff cs=cos(omega); + Coeff alpha=(float)(sn/(2*q)); + + float inv=(float)(1.0/(1.0+alpha)); + + a0 = (Coeff)(gain*inv*(1 + cs)/2); + a1 = -2.f * a0; + a2 = a0; + b1 = (Coeff)(-2*cs*inv); + b2 = (Coeff)((1 - alpha)*inv); + } + + // this replaces sin/cos with polynomial approximation + inline void set_hp_rbj_optimized(float fc, float q, float esr, float gain=1.0) + { + Coeff omega=(float)(2*M_PI*fc/esr); + Coeff sn=omega+omega*omega*omega*(1.0/6.0)+omega*omega*omega*omega*omega*(1.0/120); + Coeff cs=1-omega*omega*(1.0/2.0)+omega*omega*omega*omega*(1.0/24); + Coeff alpha=(float)(sn/(2*q)); + + float inv=(float)(1.0/(1.0+alpha)); + + a0 = (Coeff)(gain*inv*(1 + cs)*(1.0/2.0)); + a1 = -2.f * a0; + a2 = a0; + b1 = (Coeff)(-2*cs*inv); + b2 = (Coeff)((1 - alpha)*inv); + } + + /** Bandpass filter based on Robert Bristow-Johnson's equations (normalized to 1.0 at center frequency) + * @param fc center frequency (gain at fc = 1.0) + * @param q =~ fc/bandwidth (not quite, but close) - 1/Q = 2*sinh(ln(2)/2*BW*w0/sin(w0)) + * @param sr sample rate + * @param gain amplification (gain at sr/2) + */ + inline void set_bp_rbj(double fc, double q, double esr, double gain=1.0) + { + float omega=(float)(2*M_PI*fc/esr); + float sn=sin(omega); + float cs=cos(omega); + float alpha=(float)(sn/(2*q)); + + float inv=(float)(1.0/(1.0+alpha)); + + a0 = (float)(gain*inv*alpha); + a1 = 0.f; + a2 = (float)(-gain*inv*alpha); + b1 = (float)(-2*cs*inv); + b2 = (float)((1 - alpha)*inv); + } + + // rbj's bandreject + inline void set_br_rbj(double fc, double q, double esr, double gain=1.0) + { + float omega=(float)(2*M_PI*fc/esr); + float sn=sin(omega); + float cs=cos(omega); + float alpha=(float)(sn/(2*q)); + + float inv=(float)(1.0/(1.0+alpha)); + + a0 = (Coeff)(gain*inv); + a1 = (Coeff)(-gain*inv*2*cs); + a2 = (Coeff)(gain*inv); + b1 = (Coeff)(-2*cs*inv); + b2 = (Coeff)((1 - alpha)*inv); + } + // this is mine (and, I guess, it sucks/doesn't work) + void set_allpass(float freq, float pole_r, float sr) + { + float a=prewarp(freq, sr); + float q=pole_r; + set_bilinear(a*a+q*q, -2.0f*a, 1, a*a+q*q, 2.0f*a, 1); + } + /// prewarping for bilinear transform, maps given digital frequency to analog counterpart for analog filter design + static inline float prewarp(float freq, float sr) + { + if (freq>sr*0.49) freq=(float)(sr*0.49); + return (float)(tan(M_PI*freq/sr)); + } + /// convert analog angular frequency value to digital + static inline float unwarp(float omega, float sr) + { + float T = 1.0 / sr; + return (2 / T) * atan(omega * T / 2); + } + /// convert analog filter time constant to digital counterpart + static inline float unwarpf(float t, float sr) + { + // this is most likely broken and works by pure accident! + float omega = 1.0 / t; + omega = unwarp(omega, sr); + // I really don't know why does it have to be M_PI and not 2 * M_PI! + float f = M_PI / omega; + return f / sr; + } + /// set digital filter parameters based on given analog filter parameters + void set_bilinear(float aa0, float aa1, float aa2, float ab0, float ab1, float ab2) + { + float q=(float)(1.0/(ab0+ab1+ab2)); + a0 = (aa0+aa1+aa2)*q; + a1 = 2*(aa0-aa2)*q; + a2 = (aa0-aa1+aa2)*q; + b1 = 2*(ab0-ab2)*q; + b2 = (ab0-ab1+ab2)*q; + } + + /// RBJ peaking EQ + /// @param freq peak frequency + /// @param q q (correlated to freq/bandwidth, @see set_bp_rbj) + /// @param peak peak gain (1.0 means no peak, >1.0 means a peak, less than 1.0 is a dip) + inline void set_peakeq_rbj(float freq, float q, float peak, float sr) + { + float A = sqrt(peak); + float w0 = freq * 2 * M_PI * (1.0 / sr); + float alpha = sin(w0) / (2 * q); + float ib0 = 1.0 / (1 + alpha/A); + a1 = b1 = -2*cos(w0) * ib0; + a0 = ib0 * (1 + alpha*A); + a2 = ib0 * (1 - alpha*A); + b2 = ib0 * (1 - alpha/A); + } + + /// RBJ low shelf EQ - amplitication of 'peak' at 0 Hz and of 1.0 (0dB) at sr/2 Hz + /// @param freq corner frequency (gain at freq is sqrt(peak)) + /// @param q q (relates bandwidth and peak frequency), the higher q, the louder the resonant peak (situated below fc) is + /// @param peak shelf gain (1.0 means no peak, >1.0 means a peak, less than 1.0 is a dip) + inline void set_lowshelf_rbj(float freq, float q, float peak, float sr) + { + float A = sqrt(peak); + float w0 = freq * 2 * M_PI * (1.0 / sr); + float alpha = sin(w0) / (2 * q); + float cw0 = cos(w0); + float tmp = 2 * sqrt(A) * alpha; + float b0 = 0.f, ib0 = 0.f; + + a0 = A*( (A+1) - (A-1)*cw0 + tmp); + a1 = 2*A*( (A-1) - (A+1)*cw0); + a2 = A*( (A+1) - (A-1)*cw0 - tmp); + b0 = (A+1) + (A-1)*cw0 + tmp; + b1 = -2*( (A-1) + (A+1)*cw0); + b2 = (A+1) + (A-1)*cw0 - tmp; + + ib0 = 1.0 / b0; + b1 *= ib0; + b2 *= ib0; + a0 *= ib0; + a1 *= ib0; + a2 *= ib0; + } + + /// RBJ high shelf EQ - amplitication of 0dB at 0 Hz and of peak at sr/2 Hz + /// @param freq corner frequency (gain at freq is sqrt(peak)) + /// @param q q (relates bandwidth and peak frequency), the higher q, the louder the resonant peak (situated above fc) is + /// @param peak shelf gain (1.0 means no peak, >1.0 means a peak, less than 1.0 is a dip) + inline void set_highshelf_rbj(float freq, float q, float peak, float sr) + { + float A = sqrt(peak); + float w0 = freq * 2 * M_PI * (1.0 / sr); + float alpha = sin(w0) / (2 * q); + float cw0 = cos(w0); + float tmp = 2 * sqrt(A) * alpha; + float b0 = 0.f, ib0 = 0.f; + + a0 = A*( (A+1) + (A-1)*cw0 + tmp); + a1 = -2*A*( (A-1) + (A+1)*cw0); + a2 = A*( (A+1) + (A-1)*cw0 - tmp); + b0 = (A+1) - (A-1)*cw0 + tmp; + b1 = 2*( (A-1) - (A+1)*cw0); + b2 = (A+1) - (A-1)*cw0 - tmp; + + ib0 = 1.0 / b0; + b1 *= ib0; + b2 *= ib0; + a0 *= ib0; + a1 *= ib0; + a2 *= ib0; + } + + /// copy coefficients from another biquad + template + inline void copy_coeffs(const biquad_coeffs &src) + { + a0 = src.a0; + a1 = src.a1; + a2 = src.a2; + b1 = src.b1; + b2 = src.b2; + } + + /// Return the filter's gain at frequency freq + /// @param freq Frequency to look up + /// @param sr Filter sample rate (used to convert frequency to angular frequency) + float freq_gain(float freq, float sr) + { + typedef std::complex cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); + + return std::abs(h_z(z)); + } + + /// Return H(z) the filter's gain at frequency freq + /// @param z Z variable (e^jw) + cfloat h_z(const cfloat &z) + { + + return (cfloat(a0) + double(a1) * z + double(a2) * z*z) / (cfloat(1.0) + double(b1) * z + double(b2) * z*z); + } + +}; + +/** + * Two-pole two-zero filter, for floating point values. + * Uses "traditional" Direct I form (separate FIR and IIR halves). + * don't use this for integers because it won't work + */ +template +struct biquad_d1: public biquad_coeffs +{ + using biquad_coeffs::a0; + using biquad_coeffs::a1; + using biquad_coeffs::a2; + using biquad_coeffs::b1; + using biquad_coeffs::b2; + /// input[n-1] + T x1; + /// input[n-2] + T x2; + /// output[n-1] + T y1; + /// output[n-2] + T y2; + /// Constructor (initializes state to all zeros) + biquad_d1() + { + reset(); + } + /// direct I form with four state variables + inline T process(T in) + { + T out = in * a0 + x1 * a1 + x2 * a2 - y1 * b1 - y2 * b2; + x2 = x1; + y2 = y1; + x1 = in; + y1 = out; + return out; + } + + /// direct I form with zero input + inline T process_zeroin() + { + T out = - y1 * b1 - y2 * b2; + y2 = y1; + y1 = out; + return out; + } + + /// simplified version for lowpass case with two zeros at -1 + inline T process_lp(T in) + { + T out = a0*(in + x1 + x1 + x2) - y1 * b1 - y2 * b2; + x2 = x1; + y2 = y1; + x1 = in; + y1 = out; + return out; + } + /// Sanitize (set to 0 if potentially denormal) filter state + inline void sanitize() + { + dsp::sanitize(x1); + dsp::sanitize(y1); + dsp::sanitize(x2); + dsp::sanitize(y2); + } + /// Reset state variables + inline void reset() + { + dsp::zero(x1); + dsp::zero(y1); + dsp::zero(x2); + dsp::zero(y2); + } + inline bool empty() { + return (y1 == 0.f && y2 == 0.f); + } + +}; + +/** + * Two-pole two-zero filter, for floating point values. + * Uses slightly faster Direct II form (combined FIR and IIR halves). + * However, when used with wildly varying coefficients, it may + * make more zipper noise than Direct I form, so it's better to + * use it when filter coefficients are not changed mid-stream. + */ +template +struct biquad_d2: public biquad_coeffs +{ + using biquad_coeffs::a0; + using biquad_coeffs::a1; + using biquad_coeffs::a2; + using biquad_coeffs::b1; + using biquad_coeffs::b2; + /// state[n-1] + float w1; + /// state[n-2] + float w2; + /// Constructor (initializes state to all zeros) + biquad_d2() + { + reset(); + } + /// direct II form with two state variables + inline T process(T in) + { + T tmp = in - w1 * b1 - w2 * b2; + T out = tmp * a0 + w1 * a1 + w2 * a2; + w2 = w1; + w1 = tmp; + return out; + } + + // direct II form with two state variables, lowpass version + // interesting fact: this is actually slower than the general version! + inline T process_lp(T in) + { + T tmp = in - w1 * b1 - w2 * b2; + T out = (tmp + w2 + w1* 2) * a0; + w2 = w1; + w1 = tmp; + return out; + } + + /// Is the filter state completely silent? (i.e. set to 0 by sanitize function) + inline bool empty() { + return (w1 == 0.f && w2 == 0.f); + } + + + /// Sanitize (set to 0 if potentially denormal) filter state + inline void sanitize() + { + dsp::sanitize(w1); + dsp::sanitize(w2); + } + + /// Reset state variables + inline void reset() + { + dsp::zero(w1); + dsp::zero(w2); + } +}; + +/** + * Two-pole two-zero filter, for floating point values. + * Uses "traditional" Direct I form (separate FIR and IIR halves). + * don't use this for integers because it won't work + */ +template +struct biquad_d1_lerp: public biquad_coeffs +{ + using biquad_coeffs::a0; + using biquad_coeffs::a1; + using biquad_coeffs::a2; + using biquad_coeffs::b1; + using biquad_coeffs::b2; + Coeff a0cur, a1cur, a2cur, b1cur, b2cur; + Coeff a0delta, a1delta, a2delta, b1delta, b2delta; + /// input[n-1] + T x1; + /// input[n-2] + T x2; + /// output[n-1] + T y1; + /// output[n-2] + T y2; + /// Constructor (initializes state to all zeros) + biquad_d1_lerp() + { + reset(); + } + #define _DO_COEFF(coeff) coeff##delta = (coeff - coeff##cur) * (frac) + void big_step(Coeff frac) + { + _DO_COEFF(a0); + _DO_COEFF(a1); + _DO_COEFF(a2); + _DO_COEFF(b1); + _DO_COEFF(b2); + } + #undef _DO_COEFF + /// direct I form with four state variables + inline T process(T in) + { + T out = in * a0cur + x1 * a1cur + x2 * a2cur - y1 * b1cur - y2 * b2cur; + x2 = x1; + y2 = y1; + x1 = in; + y1 = out; + a0cur += a0delta; + a1cur += a1delta; + a2cur += a2delta; + b1cur += b1delta; + b2cur += b2delta; + return out; + } + + /// direct I form with zero input + inline T process_zeroin() + { + T out = - y1 * b1 - y2 * b2; + y2 = y1; + y1 = out; + b1cur += b1delta; + b2cur += b2delta; + return out; + } + + /// simplified version for lowpass case with two zeros at -1 + inline T process_lp(T in) + { + T out = a0*(in + x1 + x1 + x2) - y1 * b1 - y2 * b2; + x2 = x1; + y2 = y1; + x1 = in; + y1 = out; + return out; + } + /// Sanitize (set to 0 if potentially denormal) filter state + inline void sanitize() + { + dsp::sanitize(x1); + dsp::sanitize(y1); + dsp::sanitize(x2); + dsp::sanitize(y2); + dsp::sanitize(a0cur); + dsp::sanitize(a1cur); + dsp::sanitize(a2cur); + dsp::sanitize(b1cur); + dsp::sanitize(b2cur); + } + /// Reset state variables + inline void reset() + { + dsp::zero(x1); + dsp::zero(y1); + dsp::zero(x2); + dsp::zero(y2); + dsp::zero(a0cur); + dsp::zero(a1cur); + dsp::zero(a2cur); + dsp::zero(b1cur); + dsp::zero(b2cur); + } + inline bool empty() { + return (y1 == 0.f && y2 == 0.f); + } + +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/buffer.h b/plugins/ladspa_effect/calf/calf/buffer.h new file mode 100644 index 000000000..373fbd1cd --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/buffer.h @@ -0,0 +1,229 @@ +/* Calf DSP Library + * Buffer abstractions. + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#ifndef __BUFFER_H +#define __BUFFER_H + +namespace dsp { + +/// decrease by N if >= N (useful for circular buffers) +template inline int wrap_around(int a) { + return (a >= N) ? a - N : a; +} + +// provide fast specializations for powers of 2 +template<> inline int wrap_around<2>(int a) { return a & 1; } +template<> inline int wrap_around<4>(int a) { return a & 3; } +template<> inline int wrap_around<8>(int a) { return a & 7; } +template<> inline int wrap_around<16>(int a) { return a & 15; } +template<> inline int wrap_around<32>(int a) { return a & 31; } +template<> inline int wrap_around<64>(int a) { return a & 63; } +template<> inline int wrap_around<128>(int a) { return a & 127; } +template<> inline int wrap_around<256>(int a) { return a & 255; } +template<> inline int wrap_around<512>(int a) { return a & 511; } +template<> inline int wrap_around<1024>(int a) { return a & 1023; } +template<> inline int wrap_around<2048>(int a) { return a & 2047; } +template<> inline int wrap_around<4096>(int a) { return a & 4095; } +template<> inline int wrap_around<8192>(int a) { return a & 8191; } +template<> inline int wrap_around<16384>(int a) { return a & 16383; } +template<> inline int wrap_around<32768>(int a) { return a & 32767; } +template<> inline int wrap_around<65536>(int a) { return a & 65535; } + +template +void fill(Buf &buf, T value) { + T* data = buf.data(); + int size = buf.size(); + for (int i=0; i +void fill(T *data, int size, T value) { + for (int i=0; i +void copy(T *dest, U *src, int size, T scale = 1, T add = 0) { + for (int i=0; i +struct sample_traits { + enum { + channels = 1, + bps = sizeof(T)*8 + }; +}; + +template +struct sample_traits > { + enum { + channels = 2, + bps = sizeof(T)*8 + }; +}; + +template +class fixed_size_buffer { +public: + typedef T data_type; + enum { buffer_size = N }; + inline int size() { return N; } +}; + +template +class mem_fixed_size_buffer: public fixed_size_buffer { + T *buf; +public: + mem_fixed_size_buffer(T ubuf[N]) { buf = ubuf; } + void set_data(T buf[N]) { this->buf = buf; } + inline T* data() { return buf; } + inline const T* data() const { return buf; } + inline T& operator[](int pos) { return buf[pos]; } + inline const T& operator[](int pos) const { return buf[pos]; } +}; + +template +class auto_buffer: public fixed_size_buffer { + T buf[N]; +public: + T* data() const { return buf; } + inline T& operator[](int pos) { return buf[pos]; } + inline const T& operator[](int pos) const { return buf[pos]; } +}; + +template +class dynamic_buffer { + T *buf; + int buf_size; + bool owns; +public: + dynamic_buffer() { owns = false; } + dynamic_buffer(T *_buf, int _buf_size, bool _own) + : buf(_buf), buf_size(_buf_size), owns(_own) { + } + dynamic_buffer(int _size) { + buf = new T[_size]; + buf_size = _size; + owns = true; + } + inline T* data() { return buf; } + inline const T* data() const { return buf; } + inline int size() { return buf_size; } + void resize(int new_size, bool fill_with_zeros = false) { + T *new_buf = new T[new_size]; + memcpy(new_buf, buf, std::min(buf_size, new_size)); + if (fill_with_zeros && buf_size < new_size) + dsp::zero(new_buf + buf_size, new_size - buf_size); + if (owns) + delete []buf; + buf = new_buf; + buf_size = new_size; + owns = true; + } + inline T& operator[](int pos) { return buf[pos]; } + inline const T& operator[](int pos) const { return buf[pos]; } + ~dynamic_buffer() { + if (owns) + delete []buf; + } +}; + +template +void copy_buf(T &dest_buf, const U &src_buf, T scale = 1, T add = 0) { + typedef typename T::data_type data_type; + data_type *dest = dest_buf.data(); + const data_type *src = src_buf.data(); + int size = src.size(); + for (int i=0; i +struct buffer_traits { +}; + +/// this class template defines some basic position operations for fixed_size_buffers +template +struct buffer_traits > { + int inc_wrap(int pos) const { + return wrap_around(pos+1); + } + + int pos_diff(int pos1, int pos2) const { + int pos = pos1 - pos2; + if (pos < 0) pos += T::size; + return pos; + } +}; + +/// this is useless for now (and untested too) +template +class circular_buffer: public B { + typedef typename B::data_type data_type; + typedef class buffer_traits traits; + B buffer; + int rpos, wpos; + circular_buffer() { + clear(); + } + void clear() { + rpos = 0; + wpos = 0; + } + inline void put(data_type data) { + buffer[wpos] = data; + wpos = traits::inc_wrap(wpos); + } + inline bool empty() { + return rpos == wpos; + } + inline bool full() { + return rpos == traits::inc_wrap(wpos); + } + inline const data_type& get() { + int oldrpos = rpos; + rpos = traits::inc_wrap(rpos); + return buffer[oldrpos]; + } + inline int get_rbytes() { + return traits::pos_diff(wpos, rpos); + } + inline int get_wbytes() { + if (full()) return 0; + return traits::pos_diff(rpos, wpos); + } +}; + +/// this is useless for now +template +class mono_auto_buffer: public auto_buffer { +}; + +/// this is useless for now +template +class stereo_auto_buffer: public auto_buffer > { +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/delay.h b/plugins/ladspa_effect/calf/calf/delay.h new file mode 100644 index 000000000..4fd37a56f --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/delay.h @@ -0,0 +1,185 @@ +/* Calf DSP Library + * Reusable audio effect classes. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#ifndef __CALF_DELAY_H +#define __CALF_DELAY_H + +#include "primitives.h" +#include "buffer.h" +#include "onepole.h" + +namespace dsp { + +/** + * Delay primitive. Can be used for most delay stuff, including + * variable (modulated) delays like chorus/flanger. Note that + * for modulated delay effects use of GetInterp is preferred, + * because it handles fractional positions and uses linear + * interpolation, which sounds better most of the time. + * + * @param N maximum length + * @param C number of channels read/written for each sample (1 mono, 2 stereo etc) + */ +template +struct simple_delay { + auto_buffer data; + int pos; + + simple_delay() { + reset(); + } + void reset() { + pos = 0; + for (int i=0; i(pos+1); + } + + /** + * Read one C-channel sample into odata[0], odata[1] etc into buffer. + * Don't use for modulated delays (chorus/flanger etc) unless you + * want them to crackle and generally sound ugly + * @param odata pointer to write into + * @param delay delay relative to current writing pos + */ + template + inline void get(U &odata, int delay) { + assert(delay >= 0 && delay < N); + int ppos = wrap_around(pos + N - delay); + odata = data[ppos]; + } + + /** + * Read and write during the same function call + */ + inline T process(T idata, int delay) + { + assert(delay >= 0 && delay < N); + int ppos = wrap_around(pos + N - delay); + T odata = data[ppos]; + data[pos] = idata; + pos = wrap_around(pos+1); + return odata; + } + + /** Read one C-channel sample at fractional position. + * This version can be used for modulated delays, because + * it uses linear interpolation. + * @param odata value to write into + * @param delay delay relative to current writing pos + * @param udelay fractional delay (0..1) + */ + template + inline void get_interp(U &odata, int delay, float udelay) { +// assert(delay >= 0 && delay < N-1); + int ppos = wrap_around(pos + N - delay); + int pppos = wrap_around(ppos + N - 1); + odata = lerp(data[ppos], data[pppos], udelay); + } + + /** Read one C-channel sample at fractional position. + * This version can be used for modulated delays, because + * it uses linear interpolation. + * @param odata value to write into + * @param delay delay relative to current writing pos + * @param udelay fractional delay (0..1) + */ + inline T get_interp_1616(unsigned int delay) { + float udelay = (float)((delay & 0xFFFF) * (1.0 / 65536.0)); + delay = delay >> 16; +// assert(delay >= 0 && delay < N-1); + int ppos = wrap_around(pos + N - delay); + int pppos = wrap_around(ppos + N - 1); + return lerp(data[ppos], data[pppos], udelay); + } + + /** + * Comb filter. Feedback delay line with given delay and feedback values + * @param in input signal + * @param delay delay length (must be >16, dsp::fract16(delay)); + cur = in + fb*old; + sanitize(cur); + put(cur); + return old; + } + + /** + * Comb allpass filter. The comb filter with additional direct path, which is supposed to cancel the coloration. + * @param in input signal + * @param delay delay length (must be >16, dsp::fract16(delay)); + cur = in + fb*old; + sanitize(cur); + put(cur); + return old - fb * cur; + } +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/envelope.h b/plugins/ladspa_effect/calf/calf/envelope.h new file mode 100644 index 000000000..8a8fce13e --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/envelope.h @@ -0,0 +1,206 @@ +/* Calf DSP Library + * ADSR envelope class (and other envelopes in future) + * + * Copyright (C) 2007-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#ifndef __CALF_ENVELOPE_H +#define __CALF_ENVELOPE_H + +#include "primitives.h" + +namespace dsp { + +/// Rate-based ADSFR envelope class. Note that if release rate is slower than decay +/// rate, this envelope won't use release rate until output level falls below sustain level +/// it's different to what certain hardware synth companies did, but it prevents the very +/// un-musical (IMHO) behaviour known from (for example) SoundFont 2. +class adsr +{ +public: + enum env_state { + STOP, ///< envelope is stopped + ATTACK, ///< attack - rise from 0 to 1 + DECAY, ///< decay - fall from 1 to sustain level + SUSTAIN, ///< sustain - remain at sustain level (unless sustain is 0 - then it gets stopped); with fade != 0 it goes towards 0% (positive fade) or 100% (negative fade) + RELEASE, ///< release - fall from sustain (or pre-sustain) level to 0 + LOCKDECAY, ///< locked decay + }; + + /// Current envelope stage + env_state state; + /// @note these are *rates*, not times + double attack, decay, sustain, release, fade; + /// Requested release time (not the rate!) in frames, used for recalculating the rate if sustain is changed + double release_time; + /// Current envelope (output) level + double value; + /// Release rate used for the current note (calculated from this note's sustain level, and not the current sustain level, + /// which may have changed after note has been released) + double thisrelease; + /// Sustain level used for the current note (used to calculate release rate if sustain changed during release stage + /// of the current note) + double thiss; + /// Value from the time before advance() was called last time + double old_value; + + adsr() + { + attack = decay = sustain = release = thisrelease = thiss = 0.f; + reset(); + } + /// Stop (reset) the envelope + inline void reset() + { + old_value = value = 0.0; + thiss = 0.0; + state = STOP; + } + /// Set the envelope parameters (updates rate member variables based on values passed) + /// @param a attack time + /// @param d decay time + /// @param s sustain level + /// @param r release time + /// @param er Envelope (update) rate + /// @param f fade time (if applicable) + inline void set(float a, float d, float s, float r, float er, float f = 0.f) + { + attack = 1.0 / (a * er); + decay = (1 - s) / (d * er); + sustain = s; + release_time = r * er; + release = s / release_time; + if (fabs(f) > small_value()) + fade = 1.0 / (f * er); + else + fade = 0.0; + // in release: + // lock thiss setting (start of release for current note) and unlock thisrelease setting (current note's release rate) + if (state != RELEASE) + thiss = s; + else + thisrelease = thiss / release_time; + } + /// @retval true if envelope is in released state (forced decay, release or stopped) + inline bool released() const + { + return state == LOCKDECAY || state == RELEASE || state == STOP; + } + /// @retval true if envelope is stopped (has not been started or has run till its end) + inline bool stopped() const + { + return state == STOP; + } + /// Start the envelope + inline void note_on() + { + state = ATTACK; + thiss = sustain; + } + /// Release the envelope + inline void note_off() + { + // Do nothing if envelope is already stopped + if (state == STOP) + return; + // XXXKF what if envelope is already released? (doesn't happen in any current synth, but who knows?) + // Raise sustain value if it has been changed... I'm not sure if it's needed + thiss = std::max(sustain, value); + // Calculate release rate from sustain level + thisrelease = thiss / release_time; + // we're in attack or decay, and if decay is faster than release + if (value > sustain && decay > thisrelease) { + // use standard release time later (because we'll be switching at sustain point) + thisrelease = release; + state = LOCKDECAY; + } else { + // in attack/decay, but use fixed release time + // in case value fell below sustain, assume it didn't (for the purpose of calculating release rate only) + state = RELEASE; + } + } + /// Calculate next envelope value + inline void advance() + { + old_value = value; + // XXXKF This may use a state array instead of a switch some day (at least for phases other than attack and possibly sustain) + switch(state) + { + case ATTACK: + value += attack; + if (value >= 1.0) { + value = 1.0; + state = DECAY; + } + break; + case DECAY: + value -= decay; + if (value < sustain) + { + value = sustain; + state = SUSTAIN; + } + break; + case LOCKDECAY: + value -= decay; + if (value < sustain) + { + if (value < 0.f) + value = 0.f; + state = RELEASE; + thisrelease = release; + } + break; + case SUSTAIN: + if (fade != 0.f) + { + value -= fade; + if (value > 1.f) + value = 1.f; + } + else + value = sustain; + if (value < 0.00001f) { + value = 0; + state = STOP; + } + break; + case RELEASE: + value -= thisrelease; + if (value <= 0.f) { + value = 0.f; + state = STOP; + } + break; + case STOP: + value = 0.f; + break; + } + } + /// Return a value between old_value (previous step) and value (current step) + /// @param pos between 0 and 1 + inline double interpolate(double pos) + { + return old_value + (value - old_value) * pos; + } +}; + +}; + +#endif + + diff --git a/plugins/ladspa_effect/calf/calf/fft.h b/plugins/ladspa_effect/calf/calf/fft.h new file mode 100644 index 000000000..5eef9fe8e --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/fft.h @@ -0,0 +1,113 @@ +/* Calf DSP Library + * FFT class + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_FFT_H +#define __CALF_FFT_H + +#include + +namespace dsp { + +/// FFT routine copied from my old OneSignal library, modified to +/// match Calf's style. It's not fast at all, just a straightforward +/// implementation. +template +class fft +{ + typedef typename std::complex complex; + int scramble[1<= 4); + for (int i=0; i>(j+1)); + scramble[i]=v; + } + int N90 = N >> 2; + T divN = 2 * M_PI / N; + // use symmetry + for (int i=0; i>bits; }; +inline int32_t shr(int32_t v, int bits = 1) { return v>>bits; }; +inline uint64_t shr(uint64_t v, int bits = 1) { return v>>bits; }; +inline int64_t shr(int64_t v, int bits = 1) { return v>>bits; }; +inline float shr(float v, int bits = 1) { return v*(1.0/(1< +inline T shr(T v, int bits = 1) { + v.set(v >> bits); + return v; +} + +template class fixed_point { + T value; + enum { IntBits = (sizeof(T)/8) - FracBits }; + +public: + /// default constructor, does not initialize the value, just like - say - float doesn't + inline fixed_point() { + } + + /// copy constructor from any other fixed_point value + template inline fixed_point(const fixed_point &v) { + if (FracBits == FracBits2) value = v.get(); + else if (FracBits > FracBits2) value = v.get() << abs(FracBits - FracBits2); + else value = v.get() >> abs(FracBits - FracBits2); + } + + /* this would be way too confusing, it wouldn't be obvious if it expects a whole fixed point or an integer part + explicit inline fixed_point(T v) { + this->value = v; + } + */ + explicit inline fixed_point(double v) { + value = (T)(v*one()); + } + + /// Makes an instance from a representation value (ie. same type of value as is used for internal storage and get/set) + static inline fixed_point from_base(const T &v) + { + fixed_point result; + result.value = v; + return result; + } + + inline static T one() { + return (T)(1) << FracBits; + } + + inline void set(T value) { + this->value = value; + } + + inline T get() const { + return value; + } + + inline operator double() const { + return value * (1.0/one()); + } + + inline fixed_point &operator=(double v) { + value = (T)(v*one()); + return *this; + } + + template static inline T rebase(const fixed_point &v) { + if (FracBits == FracBits2) + return v.get(); + if (FracBits > FracBits2) + return v.get() << abs(FracBits - FracBits2); + return v.get() >> abs(FracBits2 - FracBits); + } + + template inline fixed_point &operator=(const fixed_point &v) { + value = rebase(v); + return *this; + } + + template inline fixed_point &operator+=(const fixed_point &v) { + value += rebase(v); + return *this; + } + + template inline fixed_point &operator-=(const fixed_point &v) { + value -= rebase(v); + return *this; + } + + template inline fixed_point operator+(const fixed_point &v) const { + fixed_point fpv; + fpv.set(value + rebase(v)); + return fpv; + } + + template inline fixed_point operator-(const fixed_point &v) const { + fixed_point fpv; + fpv.set(value - rebase(v)); + return fpv; + } + + /// multiply two fixed point values, using long long int to store the temporary multiplication result + template inline fixed_point operator*(const fixed_point &v) const { + fixed_point tmp; + tmp.set(((int64_t)value) * v.get() >> FracBits2); + return tmp; + } + + /// multiply two fixed point values, using BigType (usually 64-bit int) to store the temporary multiplication result + template inline fixed_point& operator*=(const fixed_point &v) { + value = (T)(((BigType)value) * v.get() >> FracBits2); + return *this; + } + + inline fixed_point operator+(int v) const { + fixed_point tmp; + tmp.set(value + (v << FracBits)); + return tmp; + } + + inline fixed_point operator-(int v) const { + fixed_point tmp; + tmp.set(value - (v << FracBits)); + return tmp; + } + + inline fixed_point operator*(int v) const { + fixed_point tmp; + tmp.value = value*v; + return tmp; + } + + inline fixed_point& operator+=(int v) { + value += (v << FracBits); + return *this; + } + + inline fixed_point& operator-=(int v) { + value -= (v << FracBits); + return *this; + } + + inline fixed_point& operator*=(int v) { + value *= v; + return *this; + } + + /// return integer part + inline T ipart() const { + return value >> FracBits; + } + + /// return integer part as unsigned int + inline unsigned int uipart() const { + return ((unsigned)value) >> FracBits; + } + + /// return integer part as unsigned int + inline unsigned int ui64part() const { + return ((uint64_t)value) >> FracBits; + } + + /// return fractional part as 0..(2^FracBits-1) + inline T fpart() const { + return value & ((1 << FracBits)-1); + } + + /// return fractional part as 0..(2^Bits-1) + template + inline T fpart() const { + int fbits = value & ((1 << FracBits)-1); + if (Bits == FracBits) return fbits; + int shift = abs(Bits-FracBits); + return (Bits < FracBits) ? (fbits >> shift) : (fbits << shift); + } + + /// return fractional part as 0..1 + inline double fpart_as_double() const { + return (value & ((1 << FracBits)-1)) * (1.0 / (1 << FracBits)); + } + + /// use fractional part (either whole or given number of most significant bits) for interpolating between two values + /// note that it uses integer arithmetic only, and isn't suitable for floating point or fixed point U! + /// @param UseBits can be used when there's a risk of exceeding range of U because max(fpart)*max(v1 or v2) > range of U + template + inline U lerp_by_fract_int(U v1, U v2) const { + int fp = fpart(); + assert ( fp >=0 && fp <= (1< + inline U lerp_table_lookup_int(U data[(1<(data[pos], data[pos+1]); + } + + /// Untested... I've started it to get a sin/cos readout for rotaryorgan, but decided to use table-less solution instead + /// Do not assume it works, because it most probably doesn't + template + inline U lerp_table_lookup_int_shift(U data[(1<(data[pos], data[pos+1]); + } + + template + inline U lerp_table_lookup_float(U data[(1< + inline U lerp_table_lookup_float_mask(U data[(1< + inline U lerp_ptr_lookup_int(U *data) const { + unsigned int pos = ui64part(); + return lerp_by_fract_int(data[pos], data[pos+1]); + } + + template + inline U lerp_ptr_lookup_float(U *data) const { + unsigned int pos = ui64part(); + return data[pos] + (data[pos+1]-data[pos]) * fpart_as_double(); + } +}; + +template +inline fixed_point operator*(int v, fixed_point v2) { + v2 *= v; + return v2; +} + +/// wave position (unsigned 64-bit int including 24-bit fractional part) +typedef fixed_point wpos; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/giface.h b/plugins/ladspa_effect/calf/calf/giface.h new file mode 100644 index 000000000..320b04bfb --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/giface.h @@ -0,0 +1,561 @@ +/* Calf DSP Library + * Common plugin interface definitions (shared between LADSPA/LV2/DSSI/standalone). + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_GIFACE_H +#define __CALF_GIFACE_H + +#include +#include +#include +#include +#include +#include "primitives.h" +#include "preset.h" + +namespace osctl { + struct osc_client; +} + +namespace calf_plugins { + +enum { + MAX_SAMPLE_RUN = 256 +}; + +/// Values ORed together for flags field in parameter_properties +enum parameter_flags +{ + PF_TYPEMASK = 0x000F, ///< bit mask for type + PF_FLOAT = 0x0000, ///< any float value + PF_INT = 0x0001, ///< integer value (still represented as float) + PF_BOOL = 0x0002, ///< bool value (usually >=0.5f is treated as TRUE, which is inconsistent with LV2 etc. which treats anything >0 as TRUE) + PF_ENUM = 0x0003, ///< enum value (min, min+1, ..., max, only guaranteed to work when min = 0) + PF_ENUM_MULTI = 0x0004, ///< SET / multiple-choice + PF_STRING = 0x0005, ///< see: http://lv2plug.in/docs/index.php?title=String_port + + PF_SCALEMASK = 0xF0, ///< bit mask for scale + PF_SCALE_DEFAULT = 0x00, ///< no scale given + PF_SCALE_LINEAR = 0x10, ///< linear scale + PF_SCALE_LOG = 0x20, ///< log scale + PF_SCALE_GAIN = 0x30, ///< gain = -96dB..0 or -inf dB + PF_SCALE_PERC = 0x40, ///< percent + PF_SCALE_QUAD = 0x50, ///< quadratic scale (decent for some gain/amplitude values) + PF_SCALE_LOG_INF = 0x60, ///< log scale + +inf (FAKE_INFINITY) + + PF_CTLMASK = 0x0F00, ///< bit mask for control type + PF_CTL_DEFAULT = 0x0000, ///< try to figure out automatically + PF_CTL_KNOB = 0x0100, ///< knob + PF_CTL_FADER = 0x0200, ///< fader (slider) + PF_CTL_TOGGLE = 0x0300, ///< toggle button + PF_CTL_COMBO = 0x0400, ///< combo box + PF_CTL_RADIO = 0x0500, ///< radio button + PF_CTL_BUTTON = 0x0600, ///< push button + PF_CTL_METER = 0x0700, ///< volume meter + PF_CTL_LED = 0x0800, ///< light emitting diode + + PF_CTLOPTIONS = 0x00F000, ///< bit mask for control (widget) options + PF_CTLO_HORIZ = 0x001000, ///< horizontal version of the control (unused) + PF_CTLO_VERT = 0x002000, ///< vertical version of the control (unused) + PF_CTLO_LABEL = 0x004000, ///< add a text display to the control (meters only) + PF_CTLO_REVERSE = 0x008000, ///< use VU_MONOCHROME_REVERSE mode (meters only) + + PF_PROP_NOBOUNDS = 0x010000, ///< no epp:hasStrictBounds + PF_PROP_EXPENSIVE = 0x020000, ///< epp:expensive, may trigger expensive calculation + PF_PROP_OUTPUT_GAIN=0x050000, ///< epp:outputGain + skip epp:hasStrictBounds + PF_PROP_OUTPUT = 0x080000, ///< output port + PF_PROP_OPTIONAL = 0x100000, ///< connection optional + PF_PROP_GRAPH = 0x200000, ///< add graph + PF_PROP_MSGCONTEXT= 0x400000, ///< message context + + PF_UNITMASK = 0xFF000000, ///< bit mask for units \todo reduce to use only 5 bits + PF_UNIT_DB = 0x01000000, ///< decibels + PF_UNIT_COEF = 0x02000000, ///< multiply-by factor + PF_UNIT_HZ = 0x03000000, ///< Hertz + PF_UNIT_SEC = 0x04000000, ///< second + PF_UNIT_MSEC = 0x05000000, ///< millisecond + PF_UNIT_CENTS = 0x06000000, ///< cents (1/100 of a semitone, 1/1200 of an octave) + PF_UNIT_SEMITONES = 0x07000000,///< semitones + PF_UNIT_BPM = 0x08000000, ///< beats per minute + PF_UNIT_DEG = 0x09000000, ///< degrees + PF_UNIT_NOTE = 0x0A000000, ///< MIDI note number + PF_UNIT_RPM = 0x0B000000, ///< revolutions per minute +}; + +/// A fake infinity value (because real infinity may break some hosts) +#define FAKE_INFINITY (65536.0 * 65536.0) +/// Check for infinity (with appropriate-ish tolerance) +#define IS_FAKE_INFINITY(value) (fabs(value-FAKE_INFINITY) < 1.0) + +/// Information record about plugin's menu command +struct plugin_command_info +{ + const char *label; ///< short command name / label + const char *name; ///< human-readable command name + const char *description; ///< description (for status line etc.) +}; + +/// Range, default value, flags and names for a parameter +struct parameter_properties +{ + /// default value + float def_value; + /// minimum value + float min; + /// maximum value + float max; + /// number of steps (for an integer value from 0 to 100 this will be 101; for 0/90/180/270/360 this will be 5), or 0 for continuous + float step; + /// logical OR of parameter_flags + uint32_t flags; + /// for PF_ENUM: array of text values (from min to max step 1), otherwise NULL + const char **choices; + /// parameter label (for use in LV2 label field etc.) + const char *short_name; + /// parameter human-readable name + const char *name; + /// convert from [0, 1] range to [min, max] (applying scaling) + float from_01(double value01) const; + /// convert from [min, max] to [0, 1] range (applying reverse scaling) + double to_01(float value) const; + /// stringify (in sensible way) + std::string to_string(float value) const; + /// get required width (for reserving GUI space) + int get_char_count() const; + /// get increment step based on step value (if specified) and other factors + float get_increment() const; +}; + +struct cairo_iface +{ + virtual void set_source_rgba(float r, float g, float b, float a = 1.f) = 0; + virtual void set_line_width(float width) = 0; + virtual ~cairo_iface() {} +}; + +struct progress_report_iface +{ + virtual void report_progress(float percentage, const std::string &message) = 0; + virtual ~progress_report_iface() {} +}; + +/// 'provides live line graph values' interface +struct line_graph_iface +{ + /// Obtain subindex'th graph of parameter 'index' + /// @param index parameter/graph number (usually tied to particular plugin control port) + /// @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters) + /// @param data buffer for normalized output values + /// @param points number of points to fill + /// @param context cairo context to adjust (for multicolour graphs etc.) + /// @retval true graph data was returned; subindex+1 graph may or may not be available + /// @retval false graph data was not returned; subindex+1 graph does not exist either + virtual bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context) { return false; } + + /// Obtain subindex'th dot of parameter 'index' + /// @param index parameter/dot number (usually tied to particular plugin control port) + /// @param subindex dot number (there may be multiple dots graphs for one parameter) + virtual bool get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context) { return false; } + + /// Obtain subindex'th dot of parameter 'index' + /// @param index parameter/dot number (usually tied to particular plugin control port) + /// @param subindex dot number (there may be multiple dots graphs for one parameter) + virtual bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context) { return false; } + + /// Obtain subindex'th static graph of parameter index (static graphs are only dependent on parameter value, not plugin state) + /// @param index parameter/graph number (usually tied to particular plugin control port) + /// @param subindex graph number (there may be multiple overlaid graphs for one parameter, eg. for monosynth 2x12dB filters) + /// @param value parameter value to pick the graph for + /// @param data buffer for normalized output values + /// @param points number of points to fill + /// @param context cairo context to adjust (for multicolour graphs etc.) + /// @retval true graph data was returned; subindex+1 graph may or may not be available + /// @retval false graph data was not returned; subindex+1 graph does not exist either + virtual bool get_static_graph(int index, int subindex, float value, float *data, int points, cairo_iface *context) { return false; } + + /// Return which graphs need to be redrawn and which can be cached for later reuse + /// @param generation 0 (at start) or the last value returned by the function (corresponds to a set of input values) + /// @param subindex_graph First graph that has to be redrawn (because it depends on values that might have changed) + /// @param subindex_dot First dot that has to be redrawn + /// @param subindex_gridline First gridline/legend that has to be redrawn + /// @retval Current generation (to pass when calling the function next time); if different than passed generation value, call the function again to retrieve which graph offsets should be put into cache + virtual int get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline) { subindex_graph = subindex_dot = subindex_gridline = 0; return 0; } + + /// Standard destructor to make compiler happy + virtual ~line_graph_iface() {} +}; + +enum table_column_type +{ + TCT_UNKNOWN, ///< guard invalid type + TCT_FLOAT, ///< float value (encoded as C locale string) + TCT_ENUM, ///< enum value (see: 'values' array in table_column_info) - encoded as string base 10 representation of integer + TCT_STRING, ///< string value (encoded as C-escaped string) + TCT_OBJECT, ///< external object, encoded as string + TCT_LABEL, ///< string value (encoded as C-escaped string) +}; + +/// parameters of +struct table_column_info +{ + const char *name; ///< column label + table_column_type type; ///< column data type + float min; ///< minimum value (for float) + float max; ///< maximum value (for float and enum) + float def_value; ///< default value (for float and enum) + const char **values; ///< NULL unless a TCT_ENUM, where it represents a NULL-terminated list of choices +}; + +/// 'has string parameters containing tabular data' interface +struct table_edit_iface +{ + /// retrieve the table layout for specific parameter + virtual const table_column_info *get_table_columns(int param) = 0; + + /// return the current number of rows + virtual uint32_t get_table_rows(int param) = 0; + + /// retrieve data item from the plugin + virtual std::string get_cell(int param, int row, int column) { return calf_utils::i2s(row)+":"+calf_utils::i2s(column); } + + /// set data item to the plugin + virtual void set_cell(int param, int row, int column, const std::string &src, std::string &error) { error.clear(); } + + /// return a line graph interface for a specific parameter/column (unused for now) + virtual line_graph_iface *get_graph_iface(int param, int column) { return NULL; } + + /// return an editor name for a specific grid cell (unused for now - I don't even know how editors be implemented) + virtual const char *get_cell_editor(int param, int column) { return NULL; } + + virtual ~table_edit_iface() {} +}; + +/// 'may receive configure variables' interface +struct send_configure_iface +{ + /// Called to set configure variable + /// @param key variable name + /// @param value variable content + virtual void send_configure(const char *key, const char *value) = 0; + + virtual ~send_configure_iface() {} +}; + +/// 'may receive new status values' interface +struct send_updates_iface +{ + /// Called to set configure variable + /// @param key variable name + /// @param value variable content + virtual void send_status(const char *key, const char *value) = 0; + + virtual ~send_updates_iface() {} +}; + +struct plugin_command_info; + +/// General information about the plugin - @todo XXXKF lacks the "new" id-label-name triple +struct ladspa_plugin_info +{ + /// LADSPA ID + uint32_t unique_id; + /// plugin short name (camel case) + const char *label; + /// plugin human-readable name + const char *name; + /// maker (author) + const char *maker; + /// copyright notice + const char *copyright; + /// plugin type for LRDF/LV2 + const char *plugin_type; +}; + +/// An interface returning metadata about a plugin +struct plugin_metadata_iface +{ + /// @return plugin long name + virtual const char *get_name() = 0; + /// @return plugin LV2 label + virtual const char *get_id() = 0; + /// @return plugin human-readable label + virtual const char *get_label() = 0; + /// @return total number of parameters + virtual int get_param_count() = 0; + /// Return custom XML + virtual const char *get_gui_xml() = 0; + /// @return number of audio inputs + virtual int get_input_count()=0; + /// @return number of audio outputs + virtual int get_output_count()=0; + /// @return true if plugin can work in hard-realtime conditions + virtual bool is_rt_capable()=0; + /// @return true if plugin has MIDI input + virtual bool get_midi()=0; + /// @return true if plugin has MIDI input + virtual bool requires_midi()=0; + /// @return port offset of first control (parameter) port (= number of audio inputs + number of audio outputs in all existing plugins as for 1 Aug 2008) + virtual int get_param_port_offset() = 0; + /// @return line_graph_iface if any + virtual line_graph_iface *get_line_graph_iface() = 0; + /// @return table_edit_iface if any + virtual table_edit_iface *get_table_edit_iface() = 0; + /// @return NULL-terminated list of menu commands + virtual plugin_command_info *get_commands() { return NULL; } + /// @return description structure for given parameter + virtual parameter_properties *get_param_props(int param_no) = 0; + /// @return retrieve names of audio ports (@note control ports are named in parameter_properties, not here) + virtual const char **get_port_names() = 0; + /// @return description structure for the plugin + virtual const ladspa_plugin_info &get_plugin_info() = 0; + /// is a given parameter a control voltage? + virtual bool is_cv(int param_no) = 0; + /// is the given parameter non-interpolated? + virtual bool is_noisy(int param_no) = 0; + /// does the plugin require message context? (or DSSI configure) may be slow + virtual bool requires_message_context() = 0; + /// does the plugin require string port extension? (or DSSI configure) may be slow + virtual bool requires_string_ports() = 0; + /// add all message context parameter numbers to the ports vector + virtual void get_message_context_parameters(std::vector &ports) = 0; + + /// Do-nothing destructor to silence compiler warning + virtual ~plugin_metadata_iface() {} +}; + +/// Interface for host-GUI-plugin interaction (should be really split in two, but ... meh) +struct plugin_ctl_iface: public virtual plugin_metadata_iface +{ + /// @return value of given parameter + virtual float get_param_value(int param_no) = 0; + /// Set value of given parameter + virtual void set_param_value(int param_no, float value) = 0; + /// Load preset with given number + virtual bool activate_preset(int bank, int program) = 0; + /// @return volume level for port'th port (if supported by the implementation, currently only jack_host implements that by measuring signal level on plugin ports) + virtual float get_level(unsigned int port)=0; + /// Execute menu command with given number + virtual void execute(int cmd_no)=0; + /// Set a configure variable on a plugin + virtual char *configure(const char *key, const char *value) { return NULL; } + /// Send all configure variables set within a plugin to given destination (which may be limited to only those that plugin understands) + virtual void send_configures(send_configure_iface *)=0; + /// Restore all state (parameters and configure vars) to default values - implemented in giface.cpp + virtual void clear_preset(); + /// Call a named function in a plugin - this will most likely be redesigned soon - and never used + /// @retval false call has failed, result contains an error message + virtual bool blobcall(const char *command, const std::string &request, std::string &result) { result = "Call not supported"; return false; } + /// Update status variables changed since last_serial + /// @return new last_serial + virtual int send_status_updates(send_updates_iface *sui, int last_serial) { return last_serial; } + /// Do-nothing destructor to silence compiler warning + virtual ~plugin_ctl_iface() {} +}; + +struct plugin_list_info_iface; + +/// Get a list of all "large" (effect/synthesizer) plugins +extern void get_all_plugins(std::vector &plugins); +/// Get a list of all "small" (module) plugins +extern void get_all_small_plugins(plugin_list_info_iface *plii); +/// Load and strdup a text file with GUI definition +extern const char *load_gui_xml(const std::string &plugin_id); + +/// Empty implementations for plugin functions. Note, that functions aren't virtual, because they're called via the particular +/// subclass (flanger_audio_module etc) via template wrappers (ladspa_wrapper<> etc), not via base class pointer/reference +template +class audio_module: public Metadata +{ +public: + typedef Metadata metadata_type; + + progress_report_iface *progress_report; + + audio_module() { + progress_report = NULL; + } + + /// Handle MIDI Note On + inline void note_on(int note, int velocity) {} + /// Handle MIDI Note Off + inline void note_off(int note, int velocity) {} + /// Handle MIDI Program Change + inline void program_change(int program) {} + /// Handle MIDI Control Change + inline void control_change(int controller, int value) {} + /// Handle MIDI Pitch Bend + /// @param value pitch bend value (-8192 to 8191, defined as in MIDI ie. 8191 = 200 ct by default) + inline void pitch_bend(int value) {} + /// Handle MIDI Channel Pressure + /// @param value channel pressure (0 to 127) + inline void channel_pressure(int value) {} + /// Called when params are changed (before processing) + inline void params_changed() {} + /// LADSPA-esque activate function, except it is called after ports are connected, not before + inline void activate() {} + /// LADSPA-esque deactivate function + inline void deactivate() {} + /// Set sample rate for the plugin + inline void set_sample_rate(uint32_t sr) { } + /// Execute menu command with given number + inline void execute(int cmd_no) {} + /// DSSI configure call + virtual char *configure(const char *key, const char *value) { return NULL; } + /// Send all understood configure vars (none by default) + inline void send_configures(send_configure_iface *sci) {} + /// Send all supported status vars (none by default) + inline int send_status_updates(send_updates_iface *sui, int last_serial) { return last_serial; } + /// Reset parameter values for epp:trigger type parameters (ones activated by oneshot push button instead of check box) + inline void params_reset() {} + /// Called after instantiating (after all the feature pointers are set - including interfaces like progress_report_iface) + inline void post_instantiate() {} + /// Handle 'message context' port message + /// @arg output_ports pointer to bit array of output port "changed" flags, note that 0 = first audio input, not first parameter (use input_count + output_count) + inline uint32_t message_run(const void *valid_ports, void *output_ports) { + fprintf(stderr, "ERROR: message run not implemented\n"); + return 0; + } +}; + +extern bool check_for_message_context_ports(parameter_properties *parameters, int count); +extern bool check_for_string_ports(parameter_properties *parameters, int count); + +#if USE_DSSI + +enum line_graph_item +{ + LGI_END = 0, + LGI_GRAPH, + LGI_SUBGRAPH, + LGI_LEGEND, + LGI_DOT, + LGI_END_ITEM, + LGI_SET_RGBA, + LGI_SET_WIDTH, +}; + +/// A class to send status updates via OSC +struct dssi_feedback_sender +{ + /// OSC client object used to send updates + osctl::osc_client *client; + /// Background thread handle + pthread_t bg_thread; + /// Quit flag (used to terminate the thread) + bool quit; + /// Indices of graphs to send + std::vector indices; + /// Source for the graph data (interface to marshal) + calf_plugins::line_graph_iface *graph; + + dssi_feedback_sender(const char *URI, line_graph_iface *_graph, calf_plugins::parameter_properties *props, int num_params); + void update(); + ~dssi_feedback_sender(); +}; +#endif + +/// Metadata base class template, to provide default versions of interface functions +template +class plugin_metadata: public virtual plugin_metadata_iface +{ +public: + static const char *port_names[]; + static parameter_properties param_props[]; + static ladspa_plugin_info plugin_info; + + // These below are stock implementations based on enums and static members in Metadata classes + // they may be overridden to provide more interesting functionality + + const char *get_name() { return Metadata::impl_get_name(); } + const char *get_id() { return Metadata::impl_get_id(); } + const char *get_label() { return Metadata::impl_get_label(); } + int get_input_count() { return Metadata::in_count; } + int get_output_count() { return Metadata::out_count; } + int get_param_count() { return Metadata::param_count; } + bool get_midi() { return Metadata::support_midi; } + bool requires_midi() { return Metadata::require_midi; } + bool is_rt_capable() { return Metadata::rt_capable; } + line_graph_iface *get_line_graph_iface() { return dynamic_cast(this); } + table_edit_iface *get_table_edit_iface() { return dynamic_cast(this); } + int get_param_port_offset() { return Metadata::in_count + Metadata::out_count; } + const char *get_gui_xml() { static const char *data_ptr = calf_plugins::load_gui_xml(get_id()); return data_ptr; } + plugin_command_info *get_commands() { return NULL; } + parameter_properties *get_param_props(int param_no) { return ¶m_props[param_no]; } + const char **get_port_names() { return port_names; } + bool is_cv(int param_no) { return true; } + bool is_noisy(int param_no) { return false; } + const ladspa_plugin_info &get_plugin_info() { return plugin_info; } + bool requires_message_context() { return check_for_message_context_ports(param_props, Metadata::param_count); } + bool requires_string_ports() { return check_for_string_ports(param_props, Metadata::param_count); } + void get_message_context_parameters(std::vector &ports) { + for (int i = 0; i < get_param_count(); ++i) { + if (get_param_props(i)->flags & PF_PROP_MSGCONTEXT) + ports.push_back(i); + } + } +}; + +/// A class for delegating metadata implementation to a "remote" metadata class. +/// Used for GUI wrappers that cannot have a dependency on actual classes, +/// and which instead take an "external" metadata object pointer, obtained +/// through get_all_plugins. +class plugin_metadata_proxy: public virtual plugin_metadata_iface +{ +public: + plugin_metadata_iface *impl; +public: + plugin_metadata_proxy(plugin_metadata_iface *_impl) { impl = _impl; } + const char *get_name() { return impl->get_name(); } + const char *get_id() { return impl->get_id(); } + const char *get_label() { return impl->get_label(); } + int get_input_count() { return impl->get_input_count(); } + int get_output_count() { return impl->get_output_count(); } + int get_param_count() { return impl->get_param_count(); } + bool get_midi() { return impl->get_midi(); } + bool requires_midi() { return impl->requires_midi(); } + bool is_rt_capable() { return impl->is_rt_capable(); } + line_graph_iface *get_line_graph_iface() { return impl->get_line_graph_iface(); } + table_edit_iface *get_table_edit_iface() { return impl->get_table_edit_iface(); } + int get_param_port_offset() { return impl->get_param_port_offset(); } + const char *get_gui_xml() { return impl->get_gui_xml(); } + plugin_command_info *get_commands() { return impl->get_commands(); } + parameter_properties *get_param_props(int param_no) { return impl->get_param_props(param_no); } + const char **get_port_names() { return impl->get_port_names(); } + bool is_cv(int param_no) { return impl->is_cv(param_no); } + bool is_noisy(int param_no) { return impl->is_noisy(param_no); } + const ladspa_plugin_info &get_plugin_info() { return impl->get_plugin_info(); } + bool requires_message_context() { return impl->requires_message_context(); } + bool requires_string_ports() { return impl->requires_string_ports(); } + void get_message_context_parameters(std::vector &ports) { impl->get_message_context_parameters(ports); } +}; + +#define CALF_PORT_NAMES(name) template<> const char *::plugin_metadata::port_names[] +#define CALF_PORT_PROPS(name) template<> parameter_properties plugin_metadata::param_props[] +#define CALF_PLUGIN_INFO(name) template<> calf_plugins::ladspa_plugin_info plugin_metadata::plugin_info +#define PLUGIN_NAME_ID_LABEL(name, id, label) \ + static const char *impl_get_name() { return name; } \ + static const char *impl_get_id() { return id; } \ + static const char *impl_get_label() { return label; } \ + + +extern const char *calf_copyright_info; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/inertia.h b/plugins/ladspa_effect/calf/calf/inertia.h new file mode 100644 index 000000000..aca1fbfec --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/inertia.h @@ -0,0 +1,256 @@ +/* Calf DSP Library + * Basic "inertia" (parameter smoothing) classes. + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_INERTIA_H +#define __CALF_INERTIA_H + +#include "primitives.h" + +namespace dsp { + +/// Algorithm for a constant time linear ramp +class linear_ramp +{ +public: + int ramp_len; + float mul, delta; +public: + /// Construct for given ramp length + linear_ramp(int _ramp_len) { + ramp_len = _ramp_len; + mul = (float)(1.0f / ramp_len); + } + /// Change ramp length + inline void set_length(int _ramp_len) { + ramp_len = _ramp_len; + mul = (float)(1.0f / ramp_len); + } + inline int length() + { + return ramp_len; + } + inline void start_ramp(float start, float end) + { + delta = mul * (end - start); + } + /// Return value after single step + inline float ramp(float value) + { + return value + delta; + } + /// Return value after many steps + inline float ramp_many(float value, int count) + { + return value + delta * count; + } +}; + +/// Algorithm for a constant time linear ramp +class exponential_ramp +{ +public: + int ramp_len; + float root, delta; +public: + exponential_ramp(int _ramp_len) { + ramp_len = _ramp_len; + root = (float)(1.0f / ramp_len); + } + inline void set_length(int _ramp_len) { + ramp_len = _ramp_len; + root = (float)(1.0f / ramp_len); + } + inline int length() + { + return ramp_len; + } + inline void start_ramp(float start, float end) + { + delta = pow(end / start, root); + } + /// Return value after single step + inline float ramp(float value) + { + return value * delta; + } + /// Return value after many steps + inline float ramp_many(float value, float count) + { + return value * pow(delta, count); + } +}; + +/// Generic inertia using ramping algorithm specified as template argument. The basic idea +/// is producing smooth(ish) output for discrete input, using specified algorithm to go from +/// last output value to input value. It is not the same as classic running average lowpass +/// filter, because ramping time is finite and pre-determined (it calls ramp algorithm's length() +/// function to obtain the expected ramp length) +template +class inertia +{ +public: + float old_value; + float value; + unsigned int count; + Ramp ramp; + +public: + inertia(const Ramp &_ramp, float init_value = 0.f) + : ramp(_ramp) + { + value = old_value = init_value; + count = 0; + } + /// Set value immediately (no inertia) + void set_now(float _value) + { + value = old_value = _value; + count = 0; + } + /// Set with inertia + void set_inertia(float source) + { + if (source != old_value) { + ramp.start_ramp(value, source); + count = ramp.length(); + old_value = source; + } + } + /// Get smoothed value of given source value + inline float get(float source) + { + if (source != old_value) { + ramp.start_ramp(value, source); + count = ramp.length(); + old_value = source; + } + if (!count) + return old_value; + value = ramp.ramp(value); + count--; + if (!count) // finished ramping, set to desired value to get rid of accumulated rounding errors + value = old_value; + return value; + } + /// Get smoothed value assuming no new input + inline float get() + { + if (!count) + return old_value; + value = ramp.ramp(value); + count--; + if (!count) // finished ramping, set to desired value to get rid of accumulated rounding errors + value = old_value; + return value; + } + /// Do one inertia step, without returning the new value and without changing destination value + inline void step() + { + if (count) { + value = ramp.ramp(value); + count--; + if (!count) // finished ramping, set to desired value to get rid of accumulated rounding errors + value = old_value; + } + } + /// Do many inertia steps, without returning the new value and without changing destination value + inline void step_many(unsigned int steps) + { + if (steps < count) { + // Skip only a part of the current ramping period + value = ramp.ramp_many(value, steps); + count -= steps; + if (!count) // finished ramping, set to desired value to get rid of accumulated rounding errors + value = old_value; + } + else + { + // The whole ramping period has been skipped, just go to destination + value = old_value; + count = 0; + } + } + /// Get last smoothed value, without affecting anything + inline float get_last() const + { + return value; + } + /// Is it still ramping? + inline bool active() const + { + return count > 0; + } +}; + +class once_per_n +{ +public: + unsigned int frequency; + unsigned int left; +public: + once_per_n(unsigned int _frequency) + : frequency(_frequency), left(_frequency) + {} + inline void start() + { + left = frequency; + } + /// Set timer to "elapsed" state (elapsed() will return true during next call) + inline void signal() + { + left = 0; + } + inline unsigned int get(unsigned int desired) + { + if (desired > left) { + desired = left; + left = 0; + return desired; + } + left -= desired; + return desired; + } + inline bool elapsed() + { + if (!left) { + left = frequency; + return true; + } + return false; + } +}; + +class gain_smoothing: public inertia +{ +public: + gain_smoothing() + : inertia(linear_ramp(64)) + { + } + void set_sample_rate(int sr) + { + ramp = linear_ramp(sr / 441); + } + // to change param, use set_inertia(value) + // to read param, use get() +}; + +} + +#endif diff --git a/plugins/ladspa_effect/calf/calf/ladspa_wrap.h b/plugins/ladspa_effect/calf/calf/ladspa_wrap.h new file mode 100644 index 000000000..73c396914 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/ladspa_wrap.h @@ -0,0 +1,516 @@ +/* Calf DSP Library + * API wrappers for LADSPA/DSSI + * + * Copyright (C) 2007-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_LADSPA_WRAP_H +#define __CALF_LADSPA_WRAP_H + +#if USE_LADSPA + +#include +#if USE_DSSI +#include +#endif +#include "giface.h" + +namespace calf_plugins { + +template +inline int calc_real_param_count() +{ + for (int i=0; i < Module::param_count; i++) + { + if ((Module::param_props[i].flags & PF_TYPEMASK) >= PF_STRING) + return i; + } + return Module::param_count; +} + +/// A template implementing plugin_ctl_iface for a given plugin +template +struct ladspa_instance: public Module, public plugin_ctl_iface +{ + bool activate_flag; +#if USE_DSSI + dssi_feedback_sender *feedback_sender; +#endif + + static int real_param_count() + { + static int _real_param_count = calc_real_param_count(); + return _real_param_count; + } + ladspa_instance() + { + for (int i=0; i < Module::in_count; i++) + Module::ins[i] = NULL; + for (int i=0; i < Module::out_count; i++) + Module::outs[i] = NULL; + int rpc = real_param_count(); + for (int i=0; i < rpc; i++) + Module::params[i] = NULL; + activate_flag = true; +#if USE_DSSI + feedback_sender = NULL; +#endif + } + virtual parameter_properties *get_param_props(int param_no) + { + return &Module::param_props[param_no]; + } + virtual float get_param_value(int param_no) + { + // XXXKF hack + if (param_no >= real_param_count()) + return 0; + return *Module::params[param_no]; + } + virtual void set_param_value(int param_no, float value) + { + // XXXKF hack + if (param_no >= real_param_count()) + return; + *Module::params[param_no] = value; + } + virtual int get_param_count() + { + return real_param_count(); + } + virtual int get_param_port_offset() + { + return Module::in_count + Module::out_count; + } + virtual const char *get_gui_xml() { + return Module::get_gui_xml(); + } + virtual line_graph_iface *get_line_graph_iface() + { + return dynamic_cast(this); + } + virtual bool activate_preset(int bank, int program) { + return false; + } + virtual const char *get_name() + { + return Module::get_name(); + } + virtual const char *get_id() + { + return Module::get_id(); + } + virtual const char *get_label() + { + return Module::get_label(); + } + virtual char *configure(const char *key, const char *value) + { +#if USE_DSSI + if (!strcmp(key, "OSC:FEEDBACK_URI")) + { + line_graph_iface *lgi = dynamic_cast(this); + if (!lgi) + return NULL; + if (*value) + { + if (feedback_sender) { + delete feedback_sender; + feedback_sender = NULL; + } + feedback_sender = new dssi_feedback_sender(value, lgi, get_param_props(0), get_param_count()); + } + else + { + if (feedback_sender) { + delete feedback_sender; + feedback_sender = NULL; + } + } + return NULL; + } + else + if (!strcmp(key, "OSC:UPDATE")) + { + if (feedback_sender) + feedback_sender->update(); + return NULL; + } + else +#endif + if (!strcmp(key, "ExecCommand")) + { + if (*value) + { + execute(atoi(value)); + } + return NULL; + } + return Module::configure(key, value); + } + virtual int get_input_count() { return Module::in_count; } + virtual int get_output_count() { return Module::out_count; } + virtual bool get_midi() { return Module::support_midi; } + virtual float get_level(unsigned int port) { return 0.f; } + virtual void execute(int cmd_no) { + Module::execute(cmd_no); + } + virtual void send_configures(send_configure_iface *sci) { + Module::send_configures(sci); + } +}; + +/// A wrapper class for plugin class object (there is only one ladspa_wrapper for many instances of the same plugin) +template +struct ladspa_wrapper +{ + typedef ladspa_instance instance; + + /// LADSPA descriptor + static LADSPA_Descriptor descriptor; + /// LADSPA descriptor for DSSI (uses a different name for the plugin, otherwise same as descriptor) + static LADSPA_Descriptor descriptor_for_dssi; +#if USE_DSSI + /// Extended DSSI descriptor (points to descriptor_for_dssi for things like name/label/port info etc.) + static DSSI_Descriptor dssi_descriptor; + static DSSI_Program_Descriptor dssi_default_program; + + static std::vector *presets; + static std::vector *preset_descs; +#endif + + ladspa_wrapper() + { + int ins = Module::in_count; + int outs = Module::out_count; + int params = ladspa_instance::real_param_count(); + ladspa_plugin_info &plugin_info = Module::plugin_info; + descriptor.UniqueID = plugin_info.unique_id; + descriptor.Label = plugin_info.label; + descriptor.Name = strdup((std::string(plugin_info.name) + " LADSPA").c_str()); + descriptor.Maker = plugin_info.maker; + descriptor.Copyright = plugin_info.copyright; + descriptor.Properties = Module::rt_capable ? LADSPA_PROPERTY_HARD_RT_CAPABLE : 0; + descriptor.PortCount = ins + outs + params; + descriptor.PortNames = new char *[descriptor.PortCount]; + descriptor.PortDescriptors = new LADSPA_PortDescriptor[descriptor.PortCount]; + descriptor.PortRangeHints = new LADSPA_PortRangeHint[descriptor.PortCount]; + int i; + for (i = 0; i < ins + outs; i++) + { + LADSPA_PortRangeHint &prh = ((LADSPA_PortRangeHint *)descriptor.PortRangeHints)[i]; + ((int *)descriptor.PortDescriptors)[i] = i < ins ? LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO + : i < ins + outs ? LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO + : LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL; + prh.HintDescriptor = 0; + ((const char **)descriptor.PortNames)[i] = Module::port_names[i]; + } + for (; i < ins + outs + params; i++) + { + LADSPA_PortRangeHint &prh = ((LADSPA_PortRangeHint *)descriptor.PortRangeHints)[i]; + parameter_properties &pp = Module::param_props[i - ins - outs]; + ((int *)descriptor.PortDescriptors)[i] = + LADSPA_PORT_CONTROL | (pp.flags & PF_PROP_OUTPUT ? LADSPA_PORT_OUTPUT : LADSPA_PORT_INPUT); + prh.HintDescriptor = LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW; + ((const char **)descriptor.PortNames)[i] = pp.name; + prh.LowerBound = pp.min; + prh.UpperBound = pp.max; + switch(pp.flags & PF_TYPEMASK) { + case PF_BOOL: + prh.HintDescriptor |= LADSPA_HINT_TOGGLED; + prh.HintDescriptor &= ~(LADSPA_HINT_BOUNDED_ABOVE | LADSPA_HINT_BOUNDED_BELOW); + break; + case PF_INT: + case PF_ENUM: + prh.HintDescriptor |= LADSPA_HINT_INTEGER; + break; + default: { + int defpt = (int)(100 * (pp.def_value - pp.min) / (pp.max - pp.min)); + if ((pp.flags & PF_SCALEMASK) == PF_SCALE_LOG) + defpt = (int)(100 * log(pp.def_value / pp.min) / log(pp.max / pp.min)); + if (defpt < 12) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM; + else if (defpt < 37) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_LOW; + else if (defpt < 63) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_MIDDLE; + else if (defpt < 88) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_HIGH; + else + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM; + } + } + if (pp.def_value == 0 || pp.def_value == 1 || pp.def_value == 100 || pp.def_value == 440 ) { + prh.HintDescriptor &= ~LADSPA_HINT_DEFAULT_MASK; + if (pp.def_value == 1) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_1; + else if (pp.def_value == 100) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_100; + else if (pp.def_value == 440) + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_440; + else + prh.HintDescriptor |= LADSPA_HINT_DEFAULT_0; + } + switch(pp.flags & PF_SCALEMASK) { + case PF_SCALE_LOG: + prh.HintDescriptor |= LADSPA_HINT_LOGARITHMIC; + break; + } + } + descriptor.ImplementationData = this; + descriptor.instantiate = cb_instantiate; + descriptor.connect_port = cb_connect; + descriptor.activate = cb_activate; + descriptor.run = cb_run; + descriptor.run_adding = NULL; + descriptor.set_run_adding_gain = NULL; + descriptor.deactivate = cb_deactivate; + descriptor.cleanup = cb_cleanup; +#if USE_DSSI + memcpy(&descriptor_for_dssi, &descriptor, sizeof(descriptor)); + descriptor_for_dssi.Name = strdup((std::string(plugin_info.name) + " DSSI").c_str()); + memset(&dssi_descriptor, 0, sizeof(dssi_descriptor)); + dssi_descriptor.DSSI_API_Version = 1; + dssi_descriptor.LADSPA_Plugin = &descriptor_for_dssi; + dssi_descriptor.configure = cb_configure; + dssi_descriptor.get_program = cb_get_program; + dssi_descriptor.select_program = cb_select_program; + if (Module::support_midi) + dssi_descriptor.run_synth = cb_run_synth; + + presets = new std::vector; + preset_descs = new std::vector; + + preset_list plist_tmp, plist; + plist.load_defaults(true); + plist_tmp.load_defaults(false); + plist.presets.insert(plist.presets.end(), plist_tmp.presets.begin(), plist_tmp.presets.end()); + + // XXXKF this assumes that plugin name in preset is case-insensitive equal to plugin label + // if I forget about this, I'll be in a deep trouble + dssi_default_program.Bank = 0; + dssi_default_program.Program = 0; + dssi_default_program.Name = "default"; + + int pos = 1; + for (unsigned int i = 0; i < plist.presets.size(); i++) + { + plugin_preset &pp = plist.presets[i]; + if (strcasecmp(pp.plugin.c_str(), descriptor.Label)) + continue; + DSSI_Program_Descriptor pd; + pd.Bank = pos >> 7; + pd.Program = pos++; + pd.Name = pp.name.c_str(); + preset_descs->push_back(pd); + presets->push_back(pp); + } + // printf("presets = %p:%d name = %s\n", presets, presets->size(), descriptor.Label); + +#endif + } + + ~ladspa_wrapper() + { + delete []descriptor.PortNames; + delete []descriptor.PortDescriptors; + delete []descriptor.PortRangeHints; +#if USE_DSSI + presets->clear(); + preset_descs->clear(); + delete presets; + delete preset_descs; +#endif + } + + /// LADSPA instantiation function (create a plugin instance) + static LADSPA_Handle cb_instantiate(const struct _LADSPA_Descriptor * Descriptor, unsigned long sample_rate) + { + instance *mod = new instance(); + mod->set_sample_rate(sample_rate); + mod->post_instantiate(); + return mod; + } + +#if USE_DSSI + /// DSSI get program descriptor function; for 0, it returns the default program (from parameter properties table), for others, it uses global or user preset + static const DSSI_Program_Descriptor *cb_get_program(LADSPA_Handle Instance, unsigned long index) { + if (index > presets->size()) + return NULL; + if (index) + return &(*preset_descs)[index - 1]; + return &dssi_default_program; + } + + /// DSSI select program function; for 0, it sets the defaults, for others, it sets global or user preset + static void cb_select_program(LADSPA_Handle Instance, unsigned long Bank, unsigned long Program) { + instance *mod = (instance *)Instance; + unsigned int no = (Bank << 7) + Program - 1; + // printf("no = %d presets = %p:%d\n", no, presets, presets->size()); + if (no == -1U) { + int rpc = ladspa_instance::real_param_count(); + for (int i =0 ; i < rpc; i++) + *mod->params[i] = Module::param_props[i].def_value; + return; + } + if (no >= presets->size()) + return; + plugin_preset &p = (*presets)[no]; + // printf("activating preset %s\n", p.name.c_str()); + p.activate(mod); + } + +#endif + + /// LADSPA port connection function + static void cb_connect(LADSPA_Handle Instance, unsigned long port, LADSPA_Data *DataLocation) { + unsigned long ins = Module::in_count; + unsigned long outs = Module::out_count; + unsigned long params = ladspa_instance::real_param_count(); + instance *const mod = (instance *)Instance; + if (port < ins) + mod->ins[port] = DataLocation; + else if (port < ins + outs) + mod->outs[port - ins] = DataLocation; + else if (port < ins + outs + params) { + int i = port - ins - outs; + mod->params[i] = DataLocation; + *mod->params[i] = Module::param_props[i].def_value; + } + } + + /// LADSPA activate function (note that at this moment the ports are not set) + static void cb_activate(LADSPA_Handle Instance) { + instance *const mod = (instance *)Instance; + mod->activate_flag = true; + } + + /// utility function: zero port values if mask is 0 + static inline void zero_by_mask(Module *module, uint32_t mask, uint32_t offset, uint32_t nsamples) + { + for (int i=0; iouts[i] + offset, nsamples); + } + } + } + + /// LADSPA run function - does set sample rate / activate logic when it's run first time after activation + static void cb_run(LADSPA_Handle Instance, unsigned long SampleCount) { + instance *const mod = (instance *)Instance; + if (mod->activate_flag) + { + mod->activate(); + mod->activate_flag = false; + } + mod->params_changed(); + process_slice(mod, 0, SampleCount); + } + + /// utility function: call process, and if it returned zeros in output masks, zero out the relevant output port buffers + static inline void process_slice(Module *mod, uint32_t offset, uint32_t end) + { + while(offset < end) + { + uint32_t newend = std::min(offset + MAX_SAMPLE_RUN, end); + uint32_t out_mask = mod->process(offset, newend - offset, -1, -1); + zero_by_mask(mod, out_mask, offset, newend - offset); + offset = newend; + } + } + +#if USE_DSSI + /// DSSI "run synth" function, same as run() except it allows for event delivery + static void cb_run_synth(LADSPA_Handle Instance, unsigned long SampleCount, + snd_seq_event_t *Events, unsigned long EventCount) { + instance *const mod = (instance *)Instance; + if (mod->activate_flag) + { + mod->activate(); + mod->activate_flag = false; + } + mod->params_changed(); + + uint32_t offset = 0; + for (uint32_t e = 0; e < EventCount; e++) + { + uint32_t timestamp = Events[e].time.tick; + if (timestamp != offset) + process_slice(mod, offset, timestamp); + process_dssi_event(mod, Events[e]); + offset = timestamp; + } + if (offset != SampleCount) + process_slice(mod, offset, SampleCount); + } + + /// DSSI configure function (named properties) + static char *cb_configure(LADSPA_Handle Instance, + const char *Key, + const char *Value) + { + instance *const mod = (instance *)Instance; + return mod->configure(Key, Value); + } + + /// Utility function: handle MIDI event (only handles a subset in this version) + static void process_dssi_event(Module *module, snd_seq_event_t &event) + { + switch(event.type) { + case SND_SEQ_EVENT_NOTEON: + module->note_on(event.data.note.note, event.data.note.velocity); + break; + case SND_SEQ_EVENT_NOTEOFF: + module->note_off(event.data.note.note, event.data.note.velocity); + break; + case SND_SEQ_EVENT_PGMCHANGE: + module->program_change(event.data.control.value); + break; + case SND_SEQ_EVENT_CONTROLLER: + module->control_change(event.data.control.param, event.data.control.value); + break; + case SND_SEQ_EVENT_PITCHBEND: + module->pitch_bend(event.data.control.value); + break; + } + } +#endif + + /// LADSPA deactivate function + static void cb_deactivate(LADSPA_Handle Instance) { + instance *const mod = (instance *)Instance; + mod->deactivate(); + } + + /// LADSPA cleanup (delete instance) function + static void cb_cleanup(LADSPA_Handle Instance) { + instance *const mod = (instance *)Instance; + delete mod; + } + + /// Get a wrapper singleton - used to prevent initialization order problems which were present in older versions + static ladspa_wrapper &get() { + static ladspa_wrapper instance; + return instance; + } +}; + +}; + +#endif + +#endif diff --git a/plugins/ladspa_effect/calf/calf/loudness.h b/plugins/ladspa_effect/calf/calf/loudness.h new file mode 100644 index 000000000..62a00b5be --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/loudness.h @@ -0,0 +1,90 @@ +/* Calf DSP Library + * A-weighting filter for + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * Most of code in this file is based on freely + * available other work of other people (filter equations). + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_LOUDNESS_H +#define __CALF_LOUDNESS_H + +#include "biquad.h" + +namespace dsp { + +class aweighter { +public: + biquad_d2 bq1, bq2, bq3; + + /// Produce one output sample from one input sample + float process(float sample) + { + return bq1.process(bq2.process(bq3.process(sample))); + } + + /// Set sample rate (updates filter coefficients) + void set(float sr) + { + // analog coeffs taken from: http://www.diracdelta.co.uk/science/source/a/w/aweighting/source.html + // first we need to adjust them by doing some obscene sort of reverse pre-warping (a broken one, too!) + float f1 = biquad_coeffs::unwarpf(20.6f, sr); + float f2 = biquad_coeffs::unwarpf(107.7f, sr); + float f3 = biquad_coeffs::unwarpf(738.f, sr); + float f4 = biquad_coeffs::unwarpf(12200.f, sr); + // then map s domain to z domain using bilinear transform + // note: f1 and f4 are double poles + bq1.set_bilinear(0, 0, 1, f1*f1, 2 * f1, 1); + bq2.set_bilinear(1, 0, 0, f2*f3, f2 + f3, 1); + bq3.set_bilinear(0, 0, 1, f4*f4, 2 * f4, 1); + // the coeffs above give non-normalized value, so it should be normalized to produce 0dB at 1 kHz + // find actual gain + float gain1kHz = freq_gain(1000.0, sr); + // divide one filter's x[n-m] coefficients by that value + float gc = 1.0 / gain1kHz; + bq1.a0 *= gc; + bq1.a1 *= gc; + bq1.a2 *= gc; + } + + /// Reset to zero if at risk of denormals + void sanitize() + { + bq1.sanitize(); + bq2.sanitize(); + bq3.sanitize(); + } + + /// Reset state to zero + void reset() + { + bq1.reset(); + bq2.reset(); + bq3.reset(); + } + + /// Gain and a given frequency + float freq_gain(float freq, float sr) + { + return bq1.freq_gain(freq, sr) * bq2.freq_gain(freq, sr) * bq3.freq_gain(freq, sr); + } + +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/lv2helpers.h b/plugins/ladspa_effect/calf/calf/lv2helpers.h new file mode 100644 index 000000000..c5a2452c3 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/lv2helpers.h @@ -0,0 +1,281 @@ +/* Calf DSP Library + * LV2-related helper classes and functions + * + * Copyright (C) 2001-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef CALF_LV2HELPERS_H +#define CALF_LV2HELPERS_H + +#if USE_LV2 + +#include +#include + +class uri_map_access +{ +public: + /// URI map feature pointer (previously in a mixin, but polymorphic ports made it necessary for most plugins) + LV2_URI_Map_Feature *uri_map; + + uri_map_access() + : uri_map(NULL) + {} + + /// Map an URI through an URI map + uint32_t map_uri(const char *ns, const char *URI) + { + if (uri_map) + return uri_map->uri_to_id(uri_map->callback_data, ns, URI); + return 0; + } + /// Called on instantiation for every LV2 feature sent by a host + void use_feature(const char *URI, void *data) { + if (!strcmp(URI, LV2_URI_MAP_URI)) + { + uri_map = (LV2_URI_Map_Feature *)data; + map_uris(); + } + } + virtual void map_uris() + { + } + virtual ~uri_map_access() {} +}; + +/// A mixin for adding the event feature and URI map to the small plugin +template +class event_mixin: public T +{ +public: + /// Event feature pointer + LV2_Event_Feature *event_feature; + virtual void use_feature(const char *URI, void *data) { + if (!strcmp(URI, LV2_EVENT_URI)) + { + event_feature = (LV2_Event_Feature *)data; + } + T::use_feature(URI, data); + } + /// Create a reference + inline void ref_event(LV2_Event *event) { event_feature->lv2_event_ref(event_feature->callback_data, event); } + /// Destroy a reference + inline void unref_event(LV2_Event *event) { event_feature->lv2_event_unref(event_feature->callback_data, event); } +}; + +/// A mixin for adding the URI map and MIDI event type retrieval to small plugins +template +class midi_mixin: public virtual event_mixin +{ +public: + /// MIDI event ID, as resolved using the URI map feature + uint32_t midi_event_type; + virtual void map_uris() { + midi_event_type = this->map_uri("http://lv2plug.in/ns/ext/event", "http://lv2plug.in/ns/ext/midi#MidiEvent"); + printf("MIDI event type = %d\n", midi_event_type); + event_mixin::map_uris(); + } +}; + +/// A mixin for adding the URI map and MIDI event type retrieval to small plugins +template +class message_mixin: public virtual event_mixin +{ +public: + /// MIDI event ID, as resolved using the URI map feature + uint32_t message_event_type; + virtual void map_uris() { + message_event_type = this->map_uri("http://lv2plug.in/ns/ext/event", "http://lv2plug.in/ns/dev/msg#MessageEvent"); + printf("Message event type = %d\n", message_event_type); + event_mixin::map_uris(); + } +}; + +/// LV2 event structure + payload as 0-length array for easy access +struct lv2_event: public LV2_Event +{ + uint8_t data[]; + inline lv2_event &operator=(const lv2_event &src) { + *(LV2_Event *)this = (const LV2_Event &)src; + memcpy(data, src.data, src.size); + return *this; + } + /// Returns a 64-bit timestamp for easy and inefficient comparison + inline uint64_t timestamp() const { + return ((uint64_t)frames << 32) | subframes; + } +private: + /// forbid default constructor - this object cannot be constructed, only obtained via cast from LV2_Event* (or &) to lv2_event* (or &) + lv2_event() {} + /// forbid copy constructor - see default constructor + lv2_event(const lv2_event &) {} +}; + +/// A read-only iterator-like object for reading from event buffers +class event_port_read_iterator +{ +protected: + const LV2_Event_Buffer *buffer; + uint32_t offset; +public: + /// Default constructor creating a useless iterator you can assign to + event_port_read_iterator() + : buffer(NULL) + , offset(0) + { + } + + /// Create an iterator based on specified buffer and index/offset values + event_port_read_iterator(const LV2_Event_Buffer *_buffer, uint32_t _offset = 0) + : buffer(_buffer) + , offset(0) + { + } + + /// Are any data left to be read? + inline operator bool() const { + return offset < buffer->size; + } + + /// Read pointer + inline const lv2_event &operator*() const { + return *(const lv2_event *)(buffer->data + offset); + } + /// Pointer to member + inline const lv2_event *operator->() const { + return &**this; + } + + /// Move to the next element + inline event_port_read_iterator operator++() { + offset += ((**this).size + 19) &~7; + return *this; + } + + /// Move to the next element + inline event_port_read_iterator operator++(int) { + event_port_read_iterator old = *this; + offset += ((**this).size + 19) &~7; + return old; + } +}; + +/// A write-only iterator-like object for writing to event buffers +class event_port_write_iterator +{ +protected: + LV2_Event_Buffer *buffer; +public: + /// Default constructor creating a useless iterator you can assign to + event_port_write_iterator() + : buffer(NULL) + { + } + + /// Create a write iterator based on specified buffer and index/offset values + event_port_write_iterator(LV2_Event_Buffer *_buffer) + : buffer(_buffer) + { + } + + /// @return the remaining buffer space + inline uint32_t space_left() const { + return buffer->capacity - buffer->size; + } + /// @return write pointer + inline lv2_event &operator*() { + return *(lv2_event *)(buffer->data + buffer->size); + } + /// Pointer to member + inline lv2_event *operator->() { + return &**this; + } + /// Move to the next element after the current one has been written (must be called after each write) + inline event_port_write_iterator operator++() { + buffer->size += ((**this).size + 19) &~7; + buffer->event_count ++; + return *this; + } + /// Move to the next element after the current one has been written + inline lv2_event *operator++(int) { + lv2_event *ptr = &**this; + buffer->size += ((**this).size + 19) &~7; + buffer->event_count ++; + return ptr; + } +}; + +template +class event_port_merge_iterator +{ +public: + Iter1 first; + Iter2 second; +public: + event_port_merge_iterator() {} + event_port_merge_iterator(const Iter1 &_first, const Iter2 &_second) + : first(_first) + , second(_second) + { + } + /// @retval true if any of the iterators have any data left + inline operator bool() const { + return ((bool)first) || ((bool)second); + } + inline bool select_first() const + { + if (!(bool)second) + return true; + if (!(bool)first) + return false; + return first->timestamp() < second->timestamp(); + } + /// Returns the earliest of (*first, *second) + inline const lv2_event &operator*() const { + if (select_first()) + { + assert((bool)first); + return *first; + } + assert((bool)second); + return *second; + } + /// Pointer to member + inline const lv2_event *operator->() const { + return &**this; + } + /// Prefix increment + inline event_port_merge_iterator operator++() { + if (select_first()) + first++; + else + second++; + return *this; + } + /// Postfix increment + inline event_port_merge_iterator operator++(int) { + event_port_merge_iterator ptr = *this; + if (select_first()) + first++; + else + second++; + return ptr; + } +}; + +#endif +#endif diff --git a/plugins/ladspa_effect/calf/calf/lv2wrap.h b/plugins/ladspa_effect/calf/calf/lv2wrap.h new file mode 100644 index 000000000..8d2f172c8 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/lv2wrap.h @@ -0,0 +1,349 @@ +/* Calf DSP Library + * LV2 wrapper templates + * + * Copyright (C) 2001-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef CALF_LV2WRAP_H +#define CALF_LV2WRAP_H + +#if USE_LV2 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace calf_plugins { + +template +struct lv2_instance: public plugin_ctl_iface, public progress_report_iface, public Module +{ + bool set_srate; + int srate_to_set; + LV2_MIDI *midi_data; + LV2_Event_Buffer *event_data; + LV2_URI_Map_Feature *uri_map; + LV2_Event_Feature *event_feature; + uint32_t midi_event_type; + std::vector message_params; + LV2_Progress *progress_report_feature; + lv2_instance() + { + for (int i=0; i < Module::in_count; i++) + Module::ins[i] = NULL; + for (int i=0; i < Module::out_count; i++) + Module::outs[i] = NULL; + for (int i=0; i < Module::param_count; i++) + Module::params[i] = NULL; + uri_map = NULL; + midi_data = NULL; + event_data = NULL; + midi_event_type = 0xFFFFFFFF; + set_srate = true; + srate_to_set = 44100; + get_message_context_parameters(message_params); + progress_report_feature = NULL; + // printf("message params %d\n", (int)message_params.size()); + } + /// This, and not Module::post_instantiate, is actually called by lv2_wrapper class + void post_instantiate() + { + if (progress_report_feature) + Module::progress_report = this; + Module::post_instantiate(); + } + virtual parameter_properties *get_param_props(int param_no) + { + return &Module::param_props[param_no]; + } + virtual float get_param_value(int param_no) + { + return *Module::params[param_no]; + } + virtual void set_param_value(int param_no, float value) + { + *Module::params[param_no] = value; + } + virtual int get_param_count() + { + return Module::param_count; + } + virtual int get_param_port_offset() + { + return Module::in_count + Module::out_count; + } + virtual const char *get_gui_xml() { + return Module::get_gui_xml(); + } + virtual line_graph_iface *get_line_graph_iface() + { + return dynamic_cast(this); + } + virtual bool activate_preset(int bank, int program) { + return false; + } + virtual const char *get_name() + { + return Module::get_name(); + } + virtual const char *get_id() + { + return Module::get_id(); + } + virtual const char *get_label() + { + return Module::get_label(); + } + virtual int get_input_count() { return Module::in_count; } + virtual int get_output_count() { return Module::out_count; } + virtual bool get_midi() { return Module::support_midi; } + virtual float get_level(unsigned int port) { return 0.f; } + virtual void execute(int cmd_no) { + Module::execute(cmd_no); + } + virtual void report_progress(float percentage, const std::string &message) { + if (progress_report_feature) + (*progress_report_feature->progress)(progress_report_feature->context, percentage, !message.empty() ? message.c_str() : NULL); + } + void send_configures(send_configure_iface *sci) { + Module::send_configures(sci); + } + uint32_t impl_message_run(const void *valid_inputs, void *output_ports) { + for (unsigned int i = 0; i < message_params.size(); i++) + { + int pn = message_params[i]; + parameter_properties &pp = *get_param_props(pn); + if ((pp.flags & PF_TYPEMASK) == PF_STRING + && (((LV2_String_Data *)Module::params[pn])->flags & LV2_STRING_DATA_CHANGED_FLAG)) { + printf("Calling configure on %s\n", pp.short_name); + configure(pp.short_name, ((LV2_String_Data *)Module::params[pn])->data); + } + } + return Module::message_run(valid_inputs, output_ports); + } + char *configure(const char *key, const char *value) { + // disambiguation - the plugin_ctl_iface version is just a stub, so don't use it + return Module::configure(key, value); + } +#if 0 + // the default implementation should be fine + virtual void clear_preset() { + // This is never called in practice, at least for now + // However, it will change when presets are implemented + for (int i=0; i < Module::param_count; i++) + *Module::params[i] = Module::param_props[i].def_value; + /* + const char **p = Module::get_default_configure_vars(); + if (p) + { + for(; p[0]; p += 2) + configure(p[0], p[1]); + } + */ + } +#endif +}; + +struct LV2_Calf_Descriptor { + plugin_ctl_iface *(*get_pci)(LV2_Handle Instance); +}; + +template +struct lv2_wrapper +{ + typedef lv2_instance instance; + static LV2_Descriptor descriptor; + static LV2_Calf_Descriptor calf_descriptor; + static LV2MessageContext message_context; + std::string uri; + + lv2_wrapper() + { + ladspa_plugin_info &info = Module::plugin_info; + uri = "http://calf.sourceforge.net/plugins/" + std::string(info.label); + descriptor.URI = uri.c_str(); + descriptor.instantiate = cb_instantiate; + descriptor.connect_port = cb_connect; + descriptor.activate = cb_activate; + descriptor.run = cb_run; + descriptor.deactivate = cb_deactivate; + descriptor.cleanup = cb_cleanup; + descriptor.extension_data = cb_ext_data; + calf_descriptor.get_pci = cb_get_pci; + message_context.message_connect_port = cb_connect; + message_context.message_run = cb_message_run; + } + + static void cb_connect(LV2_Handle Instance, uint32_t port, void *DataLocation) { + unsigned long ins = Module::in_count; + unsigned long outs = Module::out_count; + unsigned long params = Module::param_count; + instance *const mod = (instance *)Instance; + if (port < ins) + mod->ins[port] = (float *)DataLocation; + else if (port < ins + outs) + mod->outs[port - ins] = (float *)DataLocation; + else if (port < ins + outs + params) { + int i = port - ins - outs; + mod->params[i] = (float *)DataLocation; + } + else if (Module::support_midi && port == ins + outs + params) { + mod->event_data = (LV2_Event_Buffer *)DataLocation; + } + } + + static void cb_activate(LV2_Handle Instance) { + instance *const mod = (instance *)Instance; + mod->set_srate = true; + } + + static void cb_deactivate(LV2_Handle Instance) { + instance *const mod = (instance *)Instance; + mod->deactivate(); + } + + static LV2_Handle cb_instantiate(const LV2_Descriptor * Descriptor, double sample_rate, const char *bundle_path, const LV2_Feature *const *features) + { + instance *mod = new instance(); + // XXXKF some people use fractional sample rates; we respect them ;-) + mod->srate_to_set = (uint32_t)sample_rate; + mod->set_srate = true; + while(*features) + { + if (!strcmp((*features)->URI, LV2_URI_MAP_URI)) + { + mod->uri_map = (LV2_URI_Map_Feature *)((*features)->data); + mod->midi_event_type = mod->uri_map->uri_to_id( + mod->uri_map->callback_data, + "http://lv2plug.in/ns/ext/event", + "http://lv2plug.in/ns/ext/midi#MidiEvent"); + } + else if (!strcmp((*features)->URI, LV2_EVENT_URI)) + { + mod->event_feature = (LV2_Event_Feature *)((*features)->data); + } + else if (!strcmp((*features)->URI, LV2_PROGRESS_URI)) + { + mod->progress_report_feature = (LV2_Progress *)((*features)->data); + } + features++; + } + mod->post_instantiate(); + return mod; + } + static inline void zero_by_mask(Module *module, uint32_t mask, uint32_t offset, uint32_t nsamples) + { + for (int i=0; iouts[i] + offset, nsamples); + } + } + } + static plugin_ctl_iface *cb_get_pci(LV2_Handle Instance) + { + return static_cast(Instance); + } + + static inline void process_slice(Module *mod, uint32_t offset, uint32_t end) + { + while(offset < end) + { + uint32_t newend = std::min(offset + MAX_SAMPLE_RUN, end); + uint32_t out_mask = mod->process(offset, newend - offset, -1, -1); + zero_by_mask(mod, out_mask, offset, newend - offset); + offset = newend; + } + } + + static uint32_t cb_message_run(LV2_Handle Instance, const void *valid_inputs, void *outputs_written) { + instance *mod = (instance *)Instance; + return mod->impl_message_run(valid_inputs, outputs_written); + } + static void cb_run(LV2_Handle Instance, uint32_t SampleCount) { + instance *const mod = (instance *)Instance; + if (mod->set_srate) { + mod->set_sample_rate(mod->srate_to_set); + mod->activate(); + mod->set_srate = false; + } + mod->params_changed(); + uint32_t offset = 0; + if (mod->event_data) + { + // printf("Event data: count %d\n", mod->event_data->event_count); + struct LV2_Midi_Event: public LV2_Event { + unsigned char data[1]; + }; + unsigned char *data = (unsigned char *)(mod->event_data->data); + for (uint32_t i = 0; i < mod->event_data->event_count; i++) { + LV2_Midi_Event *item = (LV2_Midi_Event *)data; + uint32_t ts = item->frames; + // printf("Event: timestamp %d subframes %d type %d vs %d\n", item->frames, item->subframes, item->type, mod->midi_event_type); + if (ts > offset) + { + process_slice(mod, offset, ts); + offset = ts; + } + if (item->type == mod->midi_event_type) + { + // printf("Midi message %x %x %x %x %d\n", item->data[0], item->data[1], item->data[2], item->data[3], item->size); + switch(item->data[0] >> 4) + { + case 8: mod->note_off(item->data[1], item->data[2]); break; + case 9: mod->note_on(item->data[1], item->data[2]); break; + case 11: mod->control_change(item->data[1], item->data[2]); break; + case 12: mod->program_change(item->data[1]); break; + case 14: mod->pitch_bend(item->data[1] + 128 * item->data[2] - 8192); break; + } + } + else + if (item->type == 0 && mod->event_feature) + mod->event_feature->lv2_event_unref(mod->event_feature->callback_data, item); + // printf("timestamp %f item size %d first byte %x\n", item->timestamp, item->size, item->data[0]); + data += ((sizeof(LV2_Event) + item->size + 7))&~7; + } + } + process_slice(mod, offset, SampleCount); + } + static void cb_cleanup(LV2_Handle Instance) { + instance *const mod = (instance *)Instance; + delete mod; + } + static const void *cb_ext_data(const char *URI) { + if (!strcmp(URI, "http://foltman.com/ns/calf-plugin-instance")) + return &calf_descriptor; + if (!strcmp(URI, LV2_CONTEXT_MESSAGE)) + return &message_context; + return NULL; + } + static lv2_wrapper &get() { + static lv2_wrapper instance; + return instance; + } +}; + +}; + +#endif +#endif diff --git a/plugins/ladspa_effect/calf/calf/metadata.h b/plugins/ladspa_effect/calf/calf/metadata.h new file mode 100644 index 000000000..4d2bd0c1f --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/metadata.h @@ -0,0 +1,312 @@ +/* Calf DSP Library + * Audio module (plugin) metadata - header file + * + * Copyright (C) 2007-2008 Krzysztof Foltman + * Copyright (C) 2008 Thor Harald Johansen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_METADATA_H +#define __CALF_METADATA_H + +#include "giface.h" + +namespace calf_plugins { + +struct flanger_metadata: public plugin_metadata +{ +public: + enum { par_delay, par_depth, par_rate, par_fb, par_stereo, par_reset, par_amount, par_dryamount, param_count }; + enum { in_count = 2, out_count = 2, support_midi = false, require_midi = false, rt_capable = true }; + PLUGIN_NAME_ID_LABEL("flanger", "flanger", "Flanger") +}; + +struct phaser_metadata: public plugin_metadata +{ + enum { par_freq, par_depth, par_rate, par_fb, par_stages, par_stereo, par_reset, par_amount, par_dryamount, param_count }; + enum { in_count = 2, out_count = 2, support_midi = false, require_midi = false, rt_capable = true }; + PLUGIN_NAME_ID_LABEL("phaser", "phaser", "Phaser") +}; + +struct filter_metadata: public plugin_metadata +{ + enum { par_cutoff, par_resonance, par_mode, par_inertia, param_count }; + enum { in_count = 2, out_count = 2, rt_capable = true, require_midi = false, support_midi = false }; + PLUGIN_NAME_ID_LABEL("filter", "filter", "Filter") + /// do not export mode and inertia as CVs, as those are settings and not parameters + bool is_cv(int param_no) { return param_no != par_mode && param_no != par_inertia; } +}; + +/// Filterclavier - metadata +struct filterclavier_metadata: public plugin_metadata +{ + enum { par_transpose, par_detune, par_max_resonance, par_mode, par_inertia, param_count }; + enum { in_count = 2, out_count = 2, rt_capable = true, require_midi = true, support_midi = true }; + PLUGIN_NAME_ID_LABEL("filterclavier", "filterclavier", "Filterclavier") + /// do not export mode and inertia as CVs, as those are settings and not parameters + bool is_cv(int param_no) { return param_no != par_mode && param_no != par_inertia; } +}; + +struct reverb_metadata: public plugin_metadata +{ + enum { par_decay, par_hfdamp, par_roomsize, par_diffusion, par_amount, par_dry, par_predelay, par_basscut, par_treblecut, param_count }; + enum { in_count = 2, out_count = 2, support_midi = false, require_midi = false, rt_capable = true }; + PLUGIN_NAME_ID_LABEL("reverb", "reverb", "Reverb") +}; + +struct vintage_delay_metadata: public plugin_metadata +{ + enum { par_bpm, par_divide, par_time_l, par_time_r, par_feedback, par_amount, par_mixmode, par_medium, par_dryamount, param_count }; + enum { in_count = 2, out_count = 2, rt_capable = true, support_midi = false, require_midi = false }; + PLUGIN_NAME_ID_LABEL("vintage_delay", "vintagedelay", "Vintage Delay") +}; + +struct rotary_speaker_metadata: public plugin_metadata +{ +public: + enum { par_speed, par_spacing, par_shift, par_moddepth, par_treblespeed, par_bassspeed, par_micdistance, par_reflection, param_count }; + enum { in_count = 2, out_count = 2, support_midi = true, require_midi = false, rt_capable = true }; + PLUGIN_NAME_ID_LABEL("rotary_speaker", "rotaryspeaker", "Rotary Speaker") +}; + +/// A multitap stereo chorus thing - metadata +struct multichorus_metadata: public plugin_metadata +{ +public: + enum { par_delay, par_depth, par_rate, par_stereo, par_voices, par_vphase, par_amount, par_dryamount, par_freq, par_freq2, par_q, par_overlap, param_count }; + enum { in_count = 2, out_count = 2, rt_capable = true, support_midi = false, require_midi = false }; + PLUGIN_NAME_ID_LABEL("multichorus", "multichorus", "Multi Chorus") +}; + +/// Monosynth - metadata +struct monosynth_metadata: public plugin_metadata +{ + enum { wave_saw, wave_sqr, wave_pulse, wave_sine, wave_triangle, wave_varistep, wave_skewsaw, wave_skewsqr, wave_test1, wave_test2, wave_test3, wave_test4, wave_test5, wave_test6, wave_test7, wave_test8, wave_count }; + enum { flt_lp12, flt_lp24, flt_2lp12, flt_hp12, flt_lpbr, flt_hpbr, flt_bp6, flt_2bp6 }; + enum { par_wave1, par_wave2, par_pw1, par_pw2, par_detune, par_osc2xpose, par_oscmode, par_oscmix, par_filtertype, par_cutoff, par_resonance, par_cutoffsep, par_envmod, par_envtores, par_envtoamp, par_attack, par_decay, par_sustain, par_fade, par_release, + par_keyfollow, par_legato, par_portamento, par_vel2filter, par_vel2amp, par_master, par_pwhlrange, + par_lforate, par_lfodelay, par_lfofilter, par_lfopitch, par_lfopw, par_mwhl_lfo, par_scaledetune, + param_count }; + enum { in_count = 0, out_count = 2, support_midi = true, require_midi = true, rt_capable = true }; + enum { step_size = 64, step_shift = 6 }; + enum { + modsrc_none, + modsrc_velocity, + modsrc_pressure, + modsrc_modwheel, + modsrc_env1, + modsrc_lfo1, + modsrc_count, + }; + enum { + moddest_none, + moddest_attenuation, + moddest_oscmix, + moddest_cutoff, + moddest_resonance, + moddest_o1detune, + moddest_o2detune, + moddest_o1pw, + moddest_o2pw, + moddest_count, + }; + PLUGIN_NAME_ID_LABEL("monosynth", "monosynth", "Monosynth") +}; + +/// Thor's compressor - metadata +struct compressor_metadata: public plugin_metadata +{ + enum { in_count = 2, out_count = 2, support_midi = false, require_midi = false, rt_capable = true }; + enum { param_threshold, param_ratio, param_attack, param_release, param_makeup, param_knee, param_detection, param_stereo_link, param_aweighting, param_compression, param_peak, param_clip, param_bypass, // param_freq, param_bw, + param_count }; + PLUGIN_NAME_ID_LABEL("compressor", "compressor", "Compressor") +}; + +/// Organ - enums for parameter IDs etc. (this mess is caused by organ split between plugin and generic class - which was +/// a bad design decision and should be sorted out some day) XXXKF @todo +struct organ_enums +{ + enum { + par_drawbar1, par_drawbar2, par_drawbar3, par_drawbar4, par_drawbar5, par_drawbar6, par_drawbar7, par_drawbar8, par_drawbar9, + par_frequency1, par_frequency2, par_frequency3, par_frequency4, par_frequency5, par_frequency6, par_frequency7, par_frequency8, par_frequency9, + par_waveform1, par_waveform2, par_waveform3, par_waveform4, par_waveform5, par_waveform6, par_waveform7, par_waveform8, par_waveform9, + par_detune1, par_detune2, par_detune3, par_detune4, par_detune5, par_detune6, par_detune7, par_detune8, par_detune9, + par_phase1, par_phase2, par_phase3, par_phase4, par_phase5, par_phase6, par_phase7, par_phase8, par_phase9, + par_pan1, par_pan2, par_pan3, par_pan4, par_pan5, par_pan6, par_pan7, par_pan8, par_pan9, + par_routing1, par_routing2, par_routing3, par_routing4, par_routing5, par_routing6, par_routing7, par_routing8, par_routing9, + par_foldover, + par_percdecay, par_perclevel, par_percwave, par_percharm, par_percvel2amp, + par_percfmdecay, par_percfmdepth, par_percfmwave, par_percfmharm, par_percvel2fm, + par_perctrigger, par_percstereo, + par_filterchain, + par_filter1type, + par_master, + par_f1cutoff, par_f1res, par_f1env1, par_f1env2, par_f1env3, par_f1keyf, + par_f2cutoff, par_f2res, par_f2env1, par_f2env2, par_f2env3, par_f2keyf, + par_eg1attack, par_eg1decay, par_eg1sustain, par_eg1release, par_eg1velscl, par_eg1ampctl, + par_eg2attack, par_eg2decay, par_eg2sustain, par_eg2release, par_eg2velscl, par_eg2ampctl, + par_eg3attack, par_eg3decay, par_eg3sustain, par_eg3release, par_eg3velscl, par_eg3ampctl, + par_lforate, par_lfoamt, par_lfowet, par_lfophase, par_lfomode, + par_transpose, par_detune, + par_polyphony, + par_quadenv, + par_pwhlrange, + par_bassfreq, + par_bassgain, + par_treblefreq, + par_treblegain, + par_var_mapcurve, + param_count + }; + enum { + var_count = 1 + }; + enum organ_waveform { + wave_sine, + wave_sinepl1, wave_sinepl2, wave_sinepl3, + wave_ssaw, wave_ssqr, wave_spls, wave_saw, wave_sqr, wave_pulse, wave_sinepl05, wave_sqr05, wave_halfsin, wave_clvg, wave_bell, wave_bell2, + wave_w1, wave_w2, wave_w3, wave_w4, wave_w5, wave_w6, wave_w7, wave_w8, wave_w9, + wave_dsaw, wave_dsqr, wave_dpls, + wave_count_small, + wave_strings = wave_count_small, + wave_strings2, + wave_sinepad, + wave_bellpad, + wave_space, + wave_choir, + wave_choir2, + wave_choir3, + wave_count, + wave_count_big = wave_count - wave_count_small + }; + enum { + ampctl_none, + ampctl_direct, + ampctl_f1, + ampctl_f2, + ampctl_all, + ampctl_count + }; + enum { + lfomode_off = 0, + lfomode_direct, + lfomode_filter1, + lfomode_filter2, + lfomode_voice, + lfomode_global, + lfomode_count + }; + enum { + perctrig_first = 0, + perctrig_each, + perctrig_eachplus, + perctrig_polyphonic, + perctrig_count + }; +}; + +/// Organ - metadata +struct organ_metadata: public organ_enums, public plugin_metadata +{ + enum { in_count = 0, out_count = 2, support_midi = true, require_midi = true, rt_capable = true }; + PLUGIN_NAME_ID_LABEL("organ", "organ", "Organ") + plugin_command_info *get_commands(); + const char **get_default_configure_vars(); +}; + +/// FluidSynth - metadata +struct fluidsynth_metadata: public plugin_metadata +{ + enum { par_master, par_soundfont, par_interpolation, par_reverb, par_chorus, param_count }; + enum { in_count = 0, out_count = 2, support_midi = true, require_midi = true, rt_capable = false }; + PLUGIN_NAME_ID_LABEL("fluidsynth", "fluidsynth", "Fluidsynth") + const char **get_default_configure_vars(); +}; + +/// Wavetable - metadata +struct wavetable_metadata: public plugin_metadata +{ + enum { + wt_fmshiny, + wt_fmshiny2, + wt_rezo, + wt_metal, + wt_bell, + wt_blah, + wt_pluck, + wt_stretch, + wt_stretch2, + wt_hardsync, + wt_hardsync2, + wt_softsync, + wt_bell2, + wt_bell3, + wt_tine, + wt_tine2, + wt_clav, + wt_clav2, + wt_gtr, + wt_gtr2, + wt_gtr3, + wt_gtr4, + wt_gtr5, + wt_reed, + wt_reed2, + wt_silver, + wt_brass, + wt_multi, + wt_multi2, + wt_count + }; + enum { + modsrc_none, + modsrc_velocity, + modsrc_pressure, + modsrc_modwheel, + modsrc_env1, + modsrc_env2, + modsrc_env3, + modsrc_count, + }; + enum { + moddest_none, + moddest_attenuation, + moddest_oscmix, + moddest_cutoff, + moddest_resonance, + moddest_o1shift, + moddest_o2shift, + moddest_o1detune, + moddest_o2detune, + moddest_count, + }; + enum { + par_o1wave, par_o1offset, par_o1transpose, par_o1detune, par_o1level, + par_o2wave, par_o2offset, par_o2transpose, par_o2detune, par_o2level, + par_eg1attack, par_eg1decay, par_eg1sustain, par_eg1fade, par_eg1release, par_eg1velscl, + par_eg2attack, par_eg2decay, par_eg2sustain, par_eg2fade, par_eg2release, par_eg2velscl, + par_eg3attack, par_eg3decay, par_eg3sustain, par_eg3fade, par_eg3release, par_eg3velscl, + par_pwhlrange, + param_count }; + enum { in_count = 0, out_count = 2, support_midi = true, require_midi = true, rt_capable = true }; + enum { step_size = 64 }; + PLUGIN_NAME_ID_LABEL("wavetable", "wavetable", "Wavetable") +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/modmatrix.h b/plugins/ladspa_effect/calf/calf/modmatrix.h new file mode 100644 index 000000000..e43e09f7a --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modmatrix.h @@ -0,0 +1,112 @@ +/* Calf DSP Library + * Modulation matrix boilerplate code. + * + * Copyright (C) 2009 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MODMATRIX_H +#define __CALF_MODMATRIX_H + +#include "giface.h" + +namespace dsp { + +/// Mapping modes +enum mapping_mode { + map_positive, ///< 0..100% + map_bipolar, ///< -100%..100% + map_negative, ///< -100%..0% + map_squared, ///< x^2 + map_squared_bipolar, ///< x^2 scaled to -100%..100% + map_antisquared, ///< 1-(1-x)^2 scaled to 0..100% + map_antisquared_bipolar, ///< 1-(1-x)^2 scaled to -100..100% + map_parabola, ///< inverted parabola (peaks at 0.5, then decreases to 0) + map_type_count +}; + +/// Single entry in modulation matrix +struct modulation_entry +{ + /// Mapped source + int src1; + /// Source mapping mode + mapping_mode mapping; + /// Unmapped modulating source + int src2; + /// Modulation amount + float amount; + /// Modulation destination + int dest; + + modulation_entry() { + reset(); + } + + /// Reset the row to default + void reset() { + src1 = 0; + src2 = 0; + mapping = map_positive; + amount = 0.f; + dest = 0; + } +}; + +}; + +namespace calf_plugins { + +class mod_matrix: public table_edit_iface +{ +protected: + /// Polynomials for different scaling modes (1, x, x^2) + static const float scaling_coeffs[dsp::map_type_count][3]; + /// Column descriptions for table widget + table_column_info table_columns[6]; + + dsp::modulation_entry *matrix; + unsigned int matrix_rows; + const char **mod_src_names, **mod_dest_names; + + mod_matrix(dsp::modulation_entry *_matrix, unsigned int _rows, const char **_src_names, const char **_dest_names); +public: + virtual const table_column_info *get_table_columns(int param); + virtual uint32_t get_table_rows(int param); + virtual std::string get_cell(int param, int row, int column); + virtual void set_cell(int param, int row, int column, const std::string &src, std::string &error); + + /// Process modulation matrix, calculate outputs from inputs + inline void calculate_modmatrix(float *moddest, int moddest_count, float *modsrc) + { + for (int i = 0; i < moddest_count; i++) + moddest[i] = 0; + for (unsigned int i = 0; i < matrix_rows; i++) + { + dsp::modulation_entry &slot = matrix[i]; + if (slot.dest) { + float value = modsrc[slot.src1]; + const float *c = scaling_coeffs[slot.mapping]; + value = c[0] + c[1] * value + c[2] * value * value; + moddest[slot.dest] += value * modsrc[slot.src2] * slot.amount; + } + } + } +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/modulelist.h b/plugins/ladspa_effect/calf/calf/modulelist.h new file mode 100644 index 000000000..0c9f0b818 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modulelist.h @@ -0,0 +1,95 @@ +#ifdef PER_MODULE_ITEM + PER_MODULE_ITEM(filter, false, "filter") + PER_MODULE_ITEM(filterclavier, false, "filterclavier") + PER_MODULE_ITEM(flanger, false, "flanger") + PER_MODULE_ITEM(reverb, false, "reverb") + PER_MODULE_ITEM(monosynth, true, "monosynth") + PER_MODULE_ITEM(vintage_delay, false, "vintagedelay") + PER_MODULE_ITEM(organ, true, "organ") + PER_MODULE_ITEM(rotary_speaker, false, "rotaryspeaker") + PER_MODULE_ITEM(phaser, false, "phaser") + PER_MODULE_ITEM(multichorus, false, "multichorus") + PER_MODULE_ITEM(compressor, false, "compressor") +#ifdef ENABLE_EXPERIMENTAL + PER_MODULE_ITEM(fluidsynth, true, "fluidsynth") + PER_MODULE_ITEM(wavetable, true, "wavetable") +#endif +#undef PER_MODULE_ITEM +#endif +#ifdef PER_SMALL_MODULE_ITEM +#ifdef ENABLE_EXPERIMENTAL + PER_SMALL_MODULE_ITEM(lp_filter, "lowpass12") + PER_SMALL_MODULE_ITEM(hp_filter, "highpass12") + PER_SMALL_MODULE_ITEM(bp_filter, "bandpass6") + PER_SMALL_MODULE_ITEM(br_filter, "notch6") + PER_SMALL_MODULE_ITEM(onepole_lp_filter, "lowpass6") + PER_SMALL_MODULE_ITEM(onepole_hp_filter, "highpass6") + PER_SMALL_MODULE_ITEM(onepole_ap_filter, "allpass") + PER_SMALL_MODULE_ITEM(min, "min") + PER_SMALL_MODULE_ITEM(max, "max") + PER_SMALL_MODULE_ITEM(minus, "minus") + PER_SMALL_MODULE_ITEM(mul, "mul") + PER_SMALL_MODULE_ITEM(neg, "neg") + PER_SMALL_MODULE_ITEM(min_c, "min_c") + PER_SMALL_MODULE_ITEM(max_c, "max_c") + PER_SMALL_MODULE_ITEM(minus_c, "minus_c") + PER_SMALL_MODULE_ITEM(mul_c, "mul_c") + PER_SMALL_MODULE_ITEM(neg_c, "neg_c") + PER_SMALL_MODULE_ITEM(level2edge_c, "level2edge_c") + PER_SMALL_MODULE_ITEM(map_lin2exp, "lin2exp") + PER_SMALL_MODULE_ITEM(square_osc, "square_osc") + PER_SMALL_MODULE_ITEM(saw_osc, "saw_osc") + PER_SMALL_MODULE_ITEM(square_lfo, "square_lfo") + PER_SMALL_MODULE_ITEM(saw_lfo, "saw_lfo") + PER_SMALL_MODULE_ITEM(pulse_lfo, "pulse_lfo") + PER_SMALL_MODULE_ITEM(print_a, "print_a") + PER_SMALL_MODULE_ITEM(print_c, "print_c") + PER_SMALL_MODULE_ITEM(print_e, "print_e") + PER_SMALL_MODULE_ITEM(print_em, "print_em") + PER_SMALL_MODULE_ITEM(copy_em, "copy_em") + PER_SMALL_MODULE_ITEM(notefilter_m, "notefilter_m") + PER_SMALL_MODULE_ITEM(ccfilter_m, "ccfilter_m") + PER_SMALL_MODULE_ITEM(pcfilter_m, "pcfilter_m") + PER_SMALL_MODULE_ITEM(pressurefilter_m, "pressurefilter_m") + PER_SMALL_MODULE_ITEM(pitchbendfilter_m, "pitchbendfilter_m") + PER_SMALL_MODULE_ITEM(systemfilter_m, "systemfilter_m") + PER_SMALL_MODULE_ITEM(channelfilter_m, "channelfilter_m") + PER_SMALL_MODULE_ITEM(keyfilter_m, "keyfilter_m") + PER_SMALL_MODULE_ITEM(setchannel_m, "setchannel_m") + PER_SMALL_MODULE_ITEM(key_less_than_m, "key_less_than_m") + PER_SMALL_MODULE_ITEM(channel_less_than_m, "channel_less_than_m") + PER_SMALL_MODULE_ITEM(transpose_m, "transpose_m") + PER_SMALL_MODULE_ITEM(eventmerge_e, "eventmerge_e") + PER_SMALL_MODULE_ITEM(quadpower_a, "quadpower_a") + PER_SMALL_MODULE_ITEM(quadpower_c, "quadpower_c") + PER_SMALL_MODULE_ITEM(crossfader2_a, "crossfader2_a") + PER_SMALL_MODULE_ITEM(crossfader2_c, "crossfader2_c") + PER_SMALL_MODULE_ITEM(linear_inertia_c, "linear_inertia_c") + PER_SMALL_MODULE_ITEM(exp_inertia_c, "exp_inertia_c") + PER_SMALL_MODULE_ITEM(sample_hold_edge_c, "sample_hold_edge_c") + PER_SMALL_MODULE_ITEM(sample_hold_level_c, "sample_hold_level_c") + PER_SMALL_MODULE_ITEM(bit_and_c, "bit_and_c") + PER_SMALL_MODULE_ITEM(bit_or_c, "bit_or_c") + PER_SMALL_MODULE_ITEM(bit_xor_c, "bit_xor_c") + PER_SMALL_MODULE_ITEM(logical_and_c, "logical_and_c") + PER_SMALL_MODULE_ITEM(logical_or_c, "logical_or_c") + PER_SMALL_MODULE_ITEM(logical_xor_c, "logical_xor_c") + PER_SMALL_MODULE_ITEM(logical_not_c, "logical_not_c") + PER_SMALL_MODULE_ITEM(flipflop_c, "flipflop_c") + PER_SMALL_MODULE_ITEM(schmitt_c, "schmitt_c") + PER_SMALL_MODULE_ITEM(between_c, "between_c") + PER_SMALL_MODULE_ITEM(less_c, "less_c") + PER_SMALL_MODULE_ITEM(clip_c, "clip_c") + PER_SMALL_MODULE_ITEM(trigger_a2c, "trigger_a2c") + PER_SMALL_MODULE_ITEM(timer_c, "timer_c") + PER_SMALL_MODULE_ITEM(prio_mux_c, "prio_mux_c") + PER_SMALL_MODULE_ITEM(prio_enc8_c, "prio_enc8_c") + PER_SMALL_MODULE_ITEM(ifthenelse_c, "ifthenelse_c") + PER_SMALL_MODULE_ITEM(counter_c, "counter_c") + PER_SMALL_MODULE_ITEM(mux4_c, "mux4_c") + PER_SMALL_MODULE_ITEM(mux8_c, "mux8_c") + PER_SMALL_MODULE_ITEM(mux16_c, "mux16_c") + PER_SMALL_MODULE_ITEM(msgread_e, "msgread_e") +#endif +#undef PER_SMALL_MODULE_ITEM +#endif diff --git a/plugins/ladspa_effect/calf/calf/modules.h b/plugins/ladspa_effect/calf/calf/modules.h new file mode 100644 index 000000000..29aa47e89 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modules.h @@ -0,0 +1,1016 @@ +/* Calf DSP Library + * Example audio modules + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MODULES_H +#define __CALF_MODULES_H + +#include +#include +#include "biquad.h" +#include "inertia.h" +#include "audio_fx.h" +#include "multichorus.h" +#include "giface.h" +#include "metadata.h" +#include "loudness.h" +#include "primitives.h" + +namespace calf_plugins { + +using namespace dsp; + +struct ladspa_plugin_info; + +#if 0 +class amp_audio_module: public null_audio_module +{ +public: + enum { in_count = 2, out_count = 2, param_count = 1, support_midi = false, require_midi = false, rt_capable = true }; + float *ins[2]; + float *outs[2]; + float *params[1]; + uint32_t srate; + static parameter_properties param_props[]; + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + if (!inputs_mask) + return 0; + float gain = *params[0]; + numsamples += offset; + for (uint32_t i = offset; i < numsamples; i++) { + outs[0][i] = ins[0][i] * gain; + outs[1][i] = ins[1][i] * gain; + } + return inputs_mask; + } +}; +#endif + +class frequency_response_line_graph: public line_graph_iface +{ +public: + bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context); + virtual int get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline); +}; + +class flanger_audio_module: public audio_module, public frequency_response_line_graph +{ +public: + dsp::simple_flanger left, right; + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + uint32_t srate; + bool clear_reset; + float last_r_phase; + bool is_active; +public: + flanger_audio_module() { + is_active = false; + } + void set_sample_rate(uint32_t sr); + void params_changed() { + float dry = *params[par_dryamount]; + float wet = *params[par_amount]; + float rate = *params[par_rate]; // 0.01*pow(1000.0f,*params[par_rate]); + float min_delay = *params[par_delay] / 1000.0; + float mod_depth = *params[par_depth] / 1000.0; + float fb = *params[par_fb]; + left.set_dry(dry); right.set_dry(dry); + left.set_wet(wet); right.set_wet(wet); + left.set_rate(rate); right.set_rate(rate); + left.set_min_delay(min_delay); right.set_min_delay(min_delay); + left.set_mod_depth(mod_depth); right.set_mod_depth(mod_depth); + left.set_fb(fb); right.set_fb(fb); + float r_phase = *params[par_stereo] * (1.f / 360.f); + clear_reset = false; + if (*params[par_reset] >= 0.5) { + clear_reset = true; + left.reset_phase(0.f); + right.reset_phase(r_phase); + } else { + if (fabs(r_phase - last_r_phase) > 0.0001f) { + right.phase = left.phase; + right.inc_phase(r_phase); + last_r_phase = r_phase; + } + } + } + void params_reset() + { + if (clear_reset) { + *params[par_reset] = 0.f; + clear_reset = false; + } + } + void activate(); + void deactivate(); + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + left.process(outs[0] + offset, ins[0] + offset, nsamples); + right.process(outs[1] + offset, ins[1] + offset, nsamples); + return outputs_mask; // XXXKF allow some delay after input going blank + } + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + float freq_gain(int subindex, float freq, float srate); +}; + +class phaser_audio_module: public audio_module, public frequency_response_line_graph +{ +public: + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + uint32_t srate; + bool clear_reset; + float last_r_phase; + dsp::simple_phaser<12> left, right; + bool is_active; +public: + phaser_audio_module() { + is_active = false; + } + void params_changed() { + float dry = *params[par_dryamount]; + float wet = *params[par_amount]; + float rate = *params[par_rate]; // 0.01*pow(1000.0f,*params[par_rate]); + float base_frq = *params[par_freq]; + float mod_depth = *params[par_depth]; + float fb = *params[par_fb]; + int stages = (int)*params[par_stages]; + left.set_dry(dry); right.set_dry(dry); + left.set_wet(wet); right.set_wet(wet); + left.set_rate(rate); right.set_rate(rate); + left.set_base_frq(base_frq); right.set_base_frq(base_frq); + left.set_mod_depth(mod_depth); right.set_mod_depth(mod_depth); + left.set_fb(fb); right.set_fb(fb); + left.set_stages(stages); right.set_stages(stages); + float r_phase = *params[par_stereo] * (1.f / 360.f); + clear_reset = false; + if (*params[par_reset] >= 0.5) { + clear_reset = true; + left.reset_phase(0.f); + right.reset_phase(r_phase); + } else { + if (fabs(r_phase - last_r_phase) > 0.0001f) { + right.phase = left.phase; + right.inc_phase(r_phase); + last_r_phase = r_phase; + } + } + } + void params_reset() + { + if (clear_reset) { + *params[par_reset] = 0.f; + clear_reset = false; + } + } + void activate(); + void set_sample_rate(uint32_t sr); + void deactivate(); + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + left.process(outs[0] + offset, ins[0] + offset, nsamples); + right.process(outs[1] + offset, ins[1] + offset, nsamples); + return outputs_mask; // XXXKF allow some delay after input going blank + } + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context); + float freq_gain(int subindex, float freq, float srate); +}; + +class reverb_audio_module: public audio_module +{ +public: + dsp::reverb reverb; + dsp::simple_delay<16384, stereo_sample > pre_delay; + dsp::onepole left_lo, right_lo, left_hi, right_hi; + uint32_t srate; + gain_smoothing amount, dryamount; + int predelay_amt; + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + + void params_changed() { + //reverb.set_time(0.5*pow(8.0f, *params[par_decay])); + //reverb.set_cutoff(2000*pow(10.0f, *params[par_hfdamp])); + reverb.set_type_and_diffusion(fastf2i_drm(*params[par_roomsize]), *params[par_diffusion]); + reverb.set_time(*params[par_decay]); + reverb.set_cutoff(*params[par_hfdamp]); + amount.set_inertia(*params[par_amount]); + dryamount.set_inertia(*params[par_dry]); + left_lo.set_lp(dsp::clip(*params[par_treblecut], 20.f, (float)(srate * 0.49f)), srate); + left_hi.set_hp(dsp::clip(*params[par_basscut], 20.f, (float)(srate * 0.49f)), srate); + right_lo.copy_coeffs(left_lo); + right_hi.copy_coeffs(left_hi); + predelay_amt = (int) (srate * (*params[par_predelay]) * (1.0f / 1000.0f) + 1); + } + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + numsamples += offset; + + for (uint32_t i = offset; i < numsamples; i++) { + float dry = dryamount.get(); + float wet = amount.get(); + stereo_sample s(ins[0][i], ins[1][i]); + stereo_sample s2 = pre_delay.process(s, predelay_amt); + + float rl = s2.left, rr = s2.right; + rl = left_lo.process(left_hi.process(rl)); + rr = right_lo.process(right_hi.process(rr)); + reverb.process(rl, rr); + outs[0][i] = dry*s.left + wet*rl; + outs[1][i] = dry*s.right + wet*rr; + } + reverb.extra_sanitize(); + left_lo.sanitize(); + left_hi.sanitize(); + right_lo.sanitize(); + right_hi.sanitize(); + return outputs_mask; + } + void activate(); + void set_sample_rate(uint32_t sr); + void deactivate(); +}; + +class vintage_delay_audio_module: public audio_module +{ +public: + // 1MB of delay memory per channel... uh, RAM is cheap + enum { MAX_DELAY = 262144, ADDR_MASK = MAX_DELAY - 1 }; + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + float buffers[2][MAX_DELAY]; + int bufptr, deltime_l, deltime_r, mixmode, medium, old_medium; + /// number of table entries written (value is only important when it is less than MAX_DELAY, which means that the buffer hasn't been totally filled yet) + int age; + + gain_smoothing amt_left, amt_right, fb_left, fb_right; + float dry; + + dsp::biquad_d2 biquad_left[2], biquad_right[2]; + + uint32_t srate; + + vintage_delay_audio_module() + { + old_medium = -1; + for (int i = 0; i < MAX_DELAY; i++) { + buffers[0][i] = 0.f; + buffers[1][i] = 0.f; + } + } + + void params_changed() + { + float unit = 60.0 * srate / (*params[par_bpm] * *params[par_divide]); + deltime_l = dsp::fastf2i_drm(unit * *params[par_time_l]); + deltime_r = dsp::fastf2i_drm(unit * *params[par_time_r]); + amt_left.set_inertia(*params[par_amount]); amt_right.set_inertia(*params[par_amount]); + float fb = *params[par_feedback]; + dry = *params[par_dryamount]; + mixmode = dsp::fastf2i_drm(*params[par_mixmode]); + medium = dsp::fastf2i_drm(*params[par_medium]); + if (mixmode == 0) + { + fb_left.set_inertia(fb); + fb_right.set_inertia(pow(fb, *params[par_time_r] / *params[par_time_l])); + } else { + fb_left.set_inertia(fb); + fb_right.set_inertia(fb); + } + if (medium != old_medium) + calc_filters(); + } + void activate() { + bufptr = 0; + age = 0; + } + void deactivate() { + } + void set_sample_rate(uint32_t sr) { + srate = sr; + old_medium = -1; + amt_left.set_sample_rate(sr); amt_right.set_sample_rate(sr); + fb_left.set_sample_rate(sr); fb_right.set_sample_rate(sr); + } + void calc_filters() + { + // parameters are heavily influenced by gordonjcp and his tape delay unit + // although, don't blame him if it sounds bad - I've messed with them too :) + biquad_left[0].set_lp_rbj(6000, 0.707, srate); + biquad_left[1].set_bp_rbj(4500, 0.250, srate); + biquad_right[0].copy_coeffs(biquad_left[0]); + biquad_right[1].copy_coeffs(biquad_left[1]); + } + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + uint32_t ostate = 3; // XXXKF optimize! + uint32_t end = offset + numsamples; + int v = mixmode ? 1 : 0; + int orig_bufptr = bufptr; + for(uint32_t i = offset; i < end; i++) + { + float out_left, out_right, del_left, del_right; + // if the buffer hasn't been cleared yet (after activation), pretend we've read zeros + + if (deltime_l >= age) { + del_left = ins[0][i]; + out_left = dry * del_left; + amt_left.step(); + fb_left.step(); + } + else + { + float in_left = buffers[v][(bufptr - deltime_l) & ADDR_MASK]; + dsp::sanitize(in_left); + out_left = dry * ins[0][i] + in_left * amt_left.get(); + del_left = ins[0][i] + in_left * fb_left.get(); + } + if (deltime_r >= age) { + del_right = ins[1][i]; + out_right = dry * del_right; + amt_right.step(); + fb_right.step(); + } + else + { + float in_right = buffers[1 - v][(bufptr - deltime_r) & ADDR_MASK]; + dsp::sanitize(in_right); + out_right = dry * ins[1][i] + in_right * amt_right.get(); + del_right = ins[1][i] + in_right * fb_right.get(); + } + + age++; + outs[0][i] = out_left; outs[1][i] = out_right; buffers[0][bufptr] = del_left; buffers[1][bufptr] = del_right; + bufptr = (bufptr + 1) & (MAX_DELAY - 1); + } + if (age >= MAX_DELAY) + age = MAX_DELAY; + if (medium > 0) { + bufptr = orig_bufptr; + if (medium == 2) + { + for(uint32_t i = offset; i < end; i++) + { + buffers[0][bufptr] = biquad_left[0].process_lp(biquad_left[1].process(buffers[0][bufptr])); + buffers[1][bufptr] = biquad_right[0].process_lp(biquad_right[1].process(buffers[1][bufptr])); + bufptr = (bufptr + 1) & (MAX_DELAY - 1); + } + biquad_left[0].sanitize();biquad_right[0].sanitize(); + } else { + for(uint32_t i = offset; i < end; i++) + { + buffers[0][bufptr] = biquad_left[1].process(buffers[0][bufptr]); + buffers[1][bufptr] = biquad_right[1].process(buffers[1][bufptr]); + bufptr = (bufptr + 1) & (MAX_DELAY - 1); + } + } + biquad_left[1].sanitize();biquad_right[1].sanitize(); + + } + return ostate; + } +}; + +class rotary_speaker_audio_module: public audio_module +{ +public: + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + /// Current phases and phase deltas for bass and treble rotors + uint32_t phase_l, dphase_l, phase_h, dphase_h; + dsp::simple_delay<1024, float> delay; + dsp::biquad_d2 crossover1l, crossover1r, crossover2l, crossover2r; + dsp::simple_delay<8, float> phaseshift; + uint32_t srate; + int vibrato_mode; + /// Current CC1 (Modulation) value, normalized to [0, 1] + float mwhl_value; + /// Current CC64 (Hold) value, normalized to [0, 1] + float hold_value; + /// Current rotation speed for bass rotor - automatic mode + float aspeed_l; + /// Current rotation speed for treble rotor - automatic mode + float aspeed_h; + /// Desired speed (0=slow, 1=fast) - automatic mode + float dspeed; + /// Current rotation speed for bass rotor - manual mode + float maspeed_l; + /// Current rotation speed for treble rotor - manual mode + float maspeed_h; + + rotary_speaker_audio_module(); + void set_sample_rate(uint32_t sr); + void setup(); + void activate(); + void deactivate(); + + void params_changed() { + set_vibrato(); + } + void set_vibrato() + { + vibrato_mode = fastf2i_drm(*params[par_speed]); + // manual vibrato - do not recalculate speeds as they're not used anyway + if (vibrato_mode == 5) + return; + if (!vibrato_mode) + dspeed = -1; + else { + float speed = vibrato_mode - 1; + if (vibrato_mode == 3) + speed = hold_value; + if (vibrato_mode == 4) + speed = mwhl_value; + dspeed = (speed < 0.5f) ? 0 : 1; + } + update_speed(); + } + /// Convert RPM speed to delta-phase + inline uint32_t rpm2dphase(float rpm) + { + return (uint32_t)((rpm / (60.0 * srate)) * (1 << 30)) << 2; + } + /// Set delta-phase variables based on current calculated (and interpolated) RPM speed + void update_speed() + { + float speed_h = aspeed_h >= 0 ? (48 + (400-48) * aspeed_h) : (48 * (1 + aspeed_h)); + float speed_l = aspeed_l >= 0 ? 40 + (342-40) * aspeed_l : (40 * (1 + aspeed_l)); + dphase_h = rpm2dphase(speed_h); + dphase_l = rpm2dphase(speed_l); + } + void update_speed_manual(float delta) + { + float ts = *params[par_treblespeed]; + float bs = *params[par_bassspeed]; + incr_towards(maspeed_h, ts, delta * 200, delta * 200); + incr_towards(maspeed_l, bs, delta * 200, delta * 200); + dphase_h = rpm2dphase(maspeed_h); + dphase_l = rpm2dphase(maspeed_l); + } + /// map a ramp [int] to a sinusoid-like function [0, 65536] + static inline int pseudo_sine_scl(int counter) + { + // premature optimization is a root of all evil; it can be done with integers only - but later :) + double v = counter * (1.0 / (65536.0 * 32768.0)); + return (int) (32768 + 32768 * (v - v*v*v) * (1.0 / 0.3849)); + } + /// Increase or decrease aspeed towards raspeed, with required negative and positive rate + inline bool incr_towards(float &aspeed, float raspeed, float delta_decc, float delta_acc) + { + if (aspeed < raspeed) { + aspeed = std::min(raspeed, aspeed + delta_acc); + return true; + } + else if (aspeed > raspeed) + { + aspeed = std::max(raspeed, aspeed - delta_decc); + return true; + } + return false; + } + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) + { + int shift = (int)(300000 * (*params[par_shift])), pdelta = (int)(300000 * (*params[par_spacing])); + int md = (int)(100 * (*params[par_moddepth])); + float mix = 0.5 * (1.0 - *params[par_micdistance]); + float mix2 = *params[par_reflection]; + float mix3 = mix2 * mix2; + for (unsigned int i = 0; i < nsamples; i++) { + float in_l = ins[0][i + offset], in_r = ins[1][i + offset]; + float in_mono = 0.5f * (in_l + in_r); + + int xl = pseudo_sine_scl(phase_l), yl = pseudo_sine_scl(phase_l + 0x40000000); + int xh = pseudo_sine_scl(phase_h), yh = pseudo_sine_scl(phase_h + 0x40000000); + // printf("%d %d %d\n", shift, pdelta, shift + pdelta + 20 * xl); + + // float out_hi_l = in_mono - delay.get_interp_1616(shift + md * xh) + delay.get_interp_1616(shift + md * 65536 + pdelta - md * yh) - delay.get_interp_1616(shift + md * 65536 + pdelta + pdelta - md * xh); + // float out_hi_r = in_mono + delay.get_interp_1616(shift + md * 65536 - md * yh) - delay.get_interp_1616(shift + pdelta + md * xh) + delay.get_interp_1616(shift + pdelta + pdelta + md * yh); + float out_hi_l = in_mono + delay.get_interp_1616(shift + md * xh) - mix2 * delay.get_interp_1616(shift + md * 65536 + pdelta - md * yh) + mix3 * delay.get_interp_1616(shift + md * 65536 + pdelta + pdelta - md * xh); + float out_hi_r = in_mono + delay.get_interp_1616(shift + md * 65536 - md * yh) - mix2 * delay.get_interp_1616(shift + pdelta + md * xh) + mix3 * delay.get_interp_1616(shift + pdelta + pdelta + md * yh); + + float out_lo_l = in_mono + delay.get_interp_1616(shift + md * xl); // + delay.get_interp_1616(shift + md * 65536 + pdelta - md * yl); + float out_lo_r = in_mono + delay.get_interp_1616(shift + md * yl); // - delay.get_interp_1616(shift + pdelta + md * yl); + + out_hi_l = crossover2l.process(out_hi_l); // sanitize(out_hi_l); + out_hi_r = crossover2r.process(out_hi_r); // sanitize(out_hi_r); + out_lo_l = crossover1l.process(out_lo_l); // sanitize(out_lo_l); + out_lo_r = crossover1r.process(out_lo_r); // sanitize(out_lo_r); + + float out_l = out_hi_l + out_lo_l; + float out_r = out_hi_r + out_lo_r; + + float mic_l = out_l + mix * (out_r - out_l); + float mic_r = out_r + mix * (out_l - out_r); + + outs[0][i + offset] = mic_l * 0.5f; + outs[1][i + offset] = mic_r * 0.5f; + delay.put(in_mono); + phase_l += dphase_l; + phase_h += dphase_h; + } + crossover1l.sanitize(); + crossover1r.sanitize(); + crossover2l.sanitize(); + crossover2r.sanitize(); + float delta = nsamples * 1.0 / srate; + if (vibrato_mode == 5) + update_speed_manual(delta); + else + { + bool u1 = incr_towards(aspeed_l, dspeed, delta * 0.2, delta * 0.14); + bool u2 = incr_towards(aspeed_h, dspeed, delta, delta * 0.5); + if (u1 || u2) + set_vibrato(); + } + return outputs_mask; + } + virtual void control_change(int ctl, int val); +}; + +/// Compose two filters in series +template +class filter_compose { +public: + typedef std::complex cfloat; + F1 f1; + F2 f2; +public: + float process(float value) { + return f2.process(f1.process(value)); + } + + cfloat h_z(const cfloat &z) { + return f1.h_z(z) * f2.h_z(z); + } + + /// Return the filter's gain at frequency freq + /// @param freq Frequency to look up + /// @param sr Filter sample rate (used to convert frequency to angular frequency) + float freq_gain(float freq, float sr) + { + typedef std::complex cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); + + return std::abs(h_z(z)); + } + + void sanitize() { + f1.sanitize(); + f2.sanitize(); + } +}; + +/// Compose two filters in parallel +template +class filter_sum { +public: + typedef std::complex cfloat; + F1 f1; + F2 f2; +public: + float process(float value) { + return f2.process(value) + f1.process(value); + } + + inline cfloat h_z(const cfloat &z) { + return f1.h_z(z) + f2.h_z(z); + } + + /// Return the filter's gain at frequency freq + /// @param freq Frequency to look up + /// @param sr Filter sample rate (used to convert frequency to angular frequency) + float freq_gain(float freq, float sr) + { + typedef std::complex cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); + + return std::abs(h_z(z)); + } + + void sanitize() { + f1.sanitize(); + f2.sanitize(); + } +}; + +template +class filter_module_with_inertia: public FilterClass +{ +public: + typedef filter_module_with_inertia inertia_filter_module; + + float *ins[Metadata::in_count]; + float *outs[Metadata::out_count]; + float *params[Metadata::param_count]; + + inertia inertia_cutoff, inertia_resonance, inertia_gain; + once_per_n timer; + bool is_active; + volatile int last_generation, last_calculated_generation; + + filter_module_with_inertia() + : inertia_cutoff(exponential_ramp(128), 20) + , inertia_resonance(exponential_ramp(128), 20) + , inertia_gain(exponential_ramp(128), 1.0) + , timer(128) + { + is_active = false; + } + + void calculate_filter() + { + float freq = inertia_cutoff.get_last(); + // printf("freq=%g inr.cnt=%d timer.left=%d\n", freq, inertia_cutoff.count, timer.left); + // XXXKF this is resonance of a single stage, obviously for three stages, resonant gain will be different + float q = inertia_resonance.get_last(); + int mode = dsp::fastf2i_drm(*params[Metadata::par_mode]); + // printf("freq = %f q = %f mode = %d\n", freq, q, mode); + + int inertia = dsp::fastf2i_drm(*params[Metadata::par_inertia]); + if (inertia != inertia_cutoff.ramp.length()) { + inertia_cutoff.ramp.set_length(inertia); + inertia_resonance.ramp.set_length(inertia); + inertia_gain.ramp.set_length(inertia); + } + + FilterClass::calculate_filter(freq, q, mode, inertia_gain.get_last()); + } + + virtual void params_changed() + { + calculate_filter(); + } + + void on_timer() + { + int gen = last_generation; + inertia_cutoff.step(); + inertia_resonance.step(); + inertia_gain.step(); + calculate_filter(); + last_calculated_generation = gen; + } + + void activate() + { + params_changed(); + FilterClass::filter_activate(); + timer = once_per_n(FilterClass::srate / 1000); + timer.start(); + is_active = true; + } + + void set_sample_rate(uint32_t sr) + { + FilterClass::srate = sr; + } + + + void deactivate() + { + is_active = false; + } + + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) { +// printf("sr=%d cutoff=%f res=%f mode=%f\n", FilterClass::srate, *params[Metadata::par_cutoff], *params[Metadata::par_resonance], *params[Metadata::par_mode]); + uint32_t ostate = 0; + numsamples += offset; + while(offset < numsamples) { + uint32_t numnow = numsamples - offset; + // if inertia's inactive, we can calculate the whole buffer at once + if (inertia_cutoff.active() || inertia_resonance.active() || inertia_gain.active()) + numnow = timer.get(numnow); + + if (outputs_mask & 1) { + ostate |= FilterClass::process_channel(0, ins[0] + offset, outs[0] + offset, numnow, inputs_mask & 1); + } + if (outputs_mask & 2) { + ostate |= FilterClass::process_channel(1, ins[1] + offset, outs[1] + offset, numnow, inputs_mask & 2); + } + + if (timer.elapsed()) { + on_timer(); + } + offset += numnow; + } + return ostate; + } +}; + +/// biquad filter module +class filter_audio_module: + public audio_module, + public filter_module_with_inertia, + public frequency_response_line_graph +{ + float old_cutoff, old_resonance, old_mode; +public: + filter_audio_module() + { + last_generation = 0; + } + void params_changed() + { + inertia_cutoff.set_inertia(*params[par_cutoff]); + inertia_resonance.set_inertia(*params[par_resonance]); + inertia_filter_module::params_changed(); + } + + void activate() + { + inertia_filter_module::activate(); + } + + void set_sample_rate(uint32_t sr) + { + inertia_filter_module::set_sample_rate(sr); + } + + + void deactivate() + { + inertia_filter_module::deactivate(); + } + + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + int get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline); +}; + +/// A multitap stereo chorus thing - processing +class multichorus_audio_module: public audio_module, public frequency_response_line_graph +{ +public: + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + uint32_t srate; + dsp::multichorus, filter_sum, dsp::biquad_d2<> >, 4096> left, right; + float last_r_phase; + float cutoff; + bool is_active; + +public: + multichorus_audio_module() + { + is_active = false; + last_r_phase = -1; + } + + void params_changed() + { + // delicious copy-pasta from flanger module - it'd be better to keep it common or something + float dry = *params[par_dryamount]; + float wet = *params[par_amount]; + float rate = *params[par_rate]; + float min_delay = *params[par_delay] / 1000.0; + float mod_depth = *params[par_depth] / 1000.0; + float overlap = *params[par_overlap]; + left.set_dry(dry); right.set_dry(dry); + left.set_wet(wet); right.set_wet(wet); + left.set_rate(rate); right.set_rate(rate); + left.set_min_delay(min_delay); right.set_min_delay(min_delay); + left.set_mod_depth(mod_depth); right.set_mod_depth(mod_depth); + int voices = (int)*params[par_voices]; + left.lfo.set_voices(voices); right.lfo.set_voices(voices); + left.lfo.set_overlap(overlap);right.lfo.set_overlap(overlap); + float vphase = *params[par_vphase] * (1.f / 360.f); + left.lfo.vphase = right.lfo.vphase = vphase * (4096 / std::max(voices - 1, 1)); + float r_phase = *params[par_stereo] * (1.f / 360.f); + if (fabs(r_phase - last_r_phase) > 0.0001f) { + right.lfo.phase = left.lfo.phase; + right.lfo.phase += chorus_phase(r_phase * 4096); + last_r_phase = r_phase; + } + left.post.f1.set_bp_rbj(*params[par_freq], *params[par_q], srate); + left.post.f2.set_bp_rbj(*params[par_freq2], *params[par_q], srate); + right.post.f1.copy_coeffs(left.post.f1); + right.post.f2.copy_coeffs(left.post.f2); + } + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + left.process(outs[0] + offset, ins[0] + offset, numsamples); + right.process(outs[1] + offset, ins[1] + offset, numsamples); + return outputs_mask; // XXXKF allow some delay after input going blank + } + void activate(); + void deactivate(); + void set_sample_rate(uint32_t sr); + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + float freq_gain(int subindex, float freq, float srate); + bool get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context); + bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context); +}; + +class compressor_audio_module: public audio_module, public line_graph_iface { +private: + float linSlope, peak, detected, kneeSqrt, kneeStart, linKneeStart, kneeStop, threshold, ratio, knee, makeup, compressedKneeStop, adjKneeStart; + float old_threshold, old_ratio, old_knee, old_makeup, old_bypass; + int last_generation; + uint32_t clip; + aweighter awL, awR; + biquad_d2 bpL, bpR; +public: + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + uint32_t srate; + bool is_active; + compressor_audio_module(); + void activate(); + void deactivate(); + uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask); + + inline float output_level(float slope) { + return slope * output_gain(slope, false) * makeup; + } + + inline float output_gain(float linSlope, bool rms) { + if(linSlope > (rms ? adjKneeStart : linKneeStart)) { + float slope = log(linSlope); + if(rms) slope *= 0.5f; + + float gain = 0.f; + float delta = 0.f; + if(IS_FAKE_INFINITY(ratio)) { + gain = threshold; + delta = 0.f; + } else { + gain = (slope - threshold) / ratio + threshold; + delta = 1.f / ratio; + } + + if(knee > 1.f && slope < kneeStop) { + gain = hermite_interpolation(slope, kneeStart, kneeStop, kneeStart, compressedKneeStop, 1.f, delta); + } + + return exp(gain - slope); + } + + return 1.f; + } + + void set_sample_rate(uint32_t sr); + + virtual bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + virtual bool get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context); + virtual bool get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context); + + virtual int get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline) + { + subindex_graph = 0; + subindex_dot = 0; + subindex_gridline = generation ? INT_MAX : 0; + + if (fabs(threshold-old_threshold) + fabs(ratio - old_ratio) + fabs(knee - old_knee) + fabs( makeup - old_makeup) + fabs( *params[param_bypass] - old_bypass) > 0.01f) + { + old_threshold = threshold; + old_ratio = ratio; + old_knee = knee; + old_makeup = makeup; + old_bypass = *params[param_bypass]; + last_generation++; + } + + if (generation == last_generation) + subindex_graph = 2; + return last_generation; + } +}; + +/// Filterclavier --- MIDI controlled filter by Hans Baier +class filterclavier_audio_module: + public audio_module, + public filter_module_with_inertia, + public frequency_response_line_graph +{ + const float min_gain; + const float max_gain; + + int last_note; + int last_velocity; + +public: + filterclavier_audio_module() + : + min_gain(1.0), + max_gain(32.0), + last_note(-1), + last_velocity(-1) {} + + void params_changed() + { + inertia_filter_module::inertia_cutoff.set_inertia( + note_to_hz(last_note + *params[par_transpose], *params[par_detune])); + + float min_resonance = param_props[par_max_resonance].min; + inertia_filter_module::inertia_resonance.set_inertia( + (float(last_velocity) / 127.0) + // 0.001: see below + * (*params[par_max_resonance] - min_resonance + 0.001) + + min_resonance); + + adjust_gain_according_to_filter_mode(last_velocity); + + inertia_filter_module::calculate_filter(); + } + + void activate() + { + inertia_filter_module::activate(); + } + + void set_sample_rate(uint32_t sr) + { + inertia_filter_module::set_sample_rate(sr); + } + + + void deactivate() + { + inertia_filter_module::deactivate(); + } + + /// MIDI control + virtual void note_on(int note, int vel) + { + last_note = note; + last_velocity = vel; + inertia_filter_module::inertia_cutoff.set_inertia( + note_to_hz(note + *params[par_transpose], *params[par_detune])); + + float min_resonance = param_props[par_max_resonance].min; + inertia_filter_module::inertia_resonance.set_inertia( + (float(vel) / 127.0) + // 0.001: if the difference is equal to zero (which happens + // when the max_resonance knom is at minimum position + // then the filter gain doesnt seem to snap to zero on most note offs + * (*params[par_max_resonance] - min_resonance + 0.001) + + min_resonance); + + adjust_gain_according_to_filter_mode(vel); + + inertia_filter_module::calculate_filter(); + } + + virtual void note_off(int note, int vel) + { + if (note == last_note) { + inertia_filter_module::inertia_resonance.set_inertia(param_props[par_max_resonance].min); + inertia_filter_module::inertia_gain.set_inertia(min_gain); + inertia_filter_module::calculate_filter(); + last_velocity = 0; + } + } + + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + +private: + void adjust_gain_according_to_filter_mode(int velocity) { + int mode = dsp::fastf2i_drm(*params[par_mode]); + + // for bandpasses: boost gain for velocities > 0 + if ( (mode_6db_bp <= mode) && (mode <= mode_18db_bp) ) { + // gain for velocity 0: 1.0 + // gain for velocity 127: 32.0 + float mode_max_gain = max_gain; + // max_gain is right for mode_6db_bp + if (mode == mode_12db_bp) + mode_max_gain /= 6.0; + if (mode == mode_18db_bp) + mode_max_gain /= 10.5; + + inertia_filter_module::inertia_gain.set_now( + (float(velocity) / 127.0) * (mode_max_gain - min_gain) + min_gain); + } else { + inertia_filter_module::inertia_gain.set_now(min_gain); + } + } +}; + +extern std::string get_builtin_modules_rdf(); + +}; + +#include "modules_synths.h" + +#endif diff --git a/plugins/ladspa_effect/calf/calf/modules_dev.h b/plugins/ladspa_effect/calf/calf/modules_dev.h new file mode 100644 index 000000000..9861d1495 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modules_dev.h @@ -0,0 +1,119 @@ +/* Calf DSP Library + * Prototype audio modules + * + * Copyright (C) 2008 Thor Harald Johansen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MODULES_DEV_H +#define __CALF_MODULES_DEV_H + +#include +#include + +#if ENABLE_EXPERIMENTAL +#include +#endif + +namespace calf_plugins { + +#if ENABLE_EXPERIMENTAL + +/// Tiny wrapper for fluidsynth +class fluidsynth_audio_module: public audio_module +{ +public: + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + +protected: + /// Current sample rate + uint32_t srate; + /// FluidSynth Settings object + fluid_settings_t *settings; + /// FluidSynth Synth object + fluid_synth_t *synth; + /// Soundfont filename + std::string soundfont; + /// Soundfont filename (as received from Fluidsynth) + std::string soundfont_name; + /// TAB-separated preset list (preset+128*bank TAB preset name LF) + std::string soundfont_preset_list; + /// FluidSynth assigned SoundFont ID + int sfid; + /// Map of preset+128*bank to preset name + std::map sf_preset_names; + /// Last selected preset+128*bank + uint32_t last_selected_preset; + /// Serial number of status data + int status_serial; + /// Preset number to set on next process() call + volatile int set_preset; + + /// Update last_selected_preset based on synth object state + void update_preset_num(); + /// Create a fluidsynth object and load the current soundfont + fluid_synth_t *create_synth(int &new_sfid); +public: + /// Constructor to initialize handles to NULL + fluidsynth_audio_module(); + + void post_instantiate(); + void set_sample_rate(uint32_t sr) { srate = sr; } + /// Handle MIDI Note On message (by sending it to fluidsynth) + void note_on(int note, int vel); + /// Handle MIDI Note Off message (by sending it to fluidsynth) + void note_off(int note, int vel); + /// Handle pitch bend message. + inline void pitch_bend(int value) + { + fluid_synth_pitch_bend(synth, 0, value + 0x2000); + } + /// Handle control change messages. + void control_change(int controller, int value); + /// Handle program change messages. + void program_change(int program); + + /// Update variables from control ports. + void params_changed() { + } + void activate(); + void deactivate(); + /// No CV inputs for now + bool is_cv(int param_no) { return false; } + /// Practically all the stuff here is noisy... for now + bool is_noisy(int param_no) { return true; } + /// Main processing function + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask); + /// DSSI-style configure function for handling string port data + char *configure(const char *key, const char *value); + void send_configures(send_configure_iface *sci); + int send_status_updates(send_updates_iface *sui, int last_serial); + uint32_t message_run(const void *valid_inputs, void *output_ports) { + // silence a default printf (which is kind of a warning about unhandled message_run) + return 0; + } + ~fluidsynth_audio_module(); +}; + + + +#endif + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/modules_small.h b/plugins/ladspa_effect/calf/calf/modules_small.h new file mode 100644 index 000000000..c186e9972 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modules_small.h @@ -0,0 +1,202 @@ +/* Calf DSP Library + * "Small" audio modules for modular synthesis + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MODULES_SMALL_H +#define __CALF_MODULES_SMALL_H + +#if USE_LV2 + +#include +#include "plugininfo.h" +#include "lv2_polymorphic_port.h" +#include "lv2helpers.h" + +namespace calf_plugins { + +/// Empty implementations for plugin functions. Note, that some functions aren't virtual, because they're called via the particular +/// subclass via template wrappers (ladspa_small_wrapper<> etc), not via base class pointer/reference. On the other hand, +/// other functions are virtual when overhead is acceptable (instantiation time functions etc.) +class null_small_audio_module: public uri_map_access +{ +public: + uint32_t srate; + double odsr; + uint32_t poly_type_control, poly_type_audio; + /// for polymorphic ports: "is audio" flags for first 32 ports (should be sufficient for most plugins) + uint32_t poly_port_types; + + null_small_audio_module() + : srate((uint32_t)-1) + , odsr(0.) + , poly_type_control(0) + , poly_type_audio(0) + , poly_port_types(0) + { + } + + /// Called when host changes type of the polymorphic port + inline void set_port_type(uint32_t port, uint32_t type, void *type_data) { + if (port >= 32) + return; + uint32_t port_mask = 1 << port; + if (type == poly_type_control) + poly_port_types &= ~port_mask; + else if (type == poly_type_audio) + poly_port_types |= port_mask; + on_port_types_changed(); + } + + /// Returns 1 for audio ports and 0 for control ports + inline unsigned int port_is_audio(unsigned int port) { + return (poly_port_types >> port) & 1; + } + + /// Returns (unsigned)-1 for audio ports and 0 for control ports + inline unsigned int port_audio_mask(unsigned int port) { + return 0 - ((poly_port_types >> port) & 1); + } + + /// Returns (unsigned)-1 for audio ports and 0 for control ports + static inline unsigned int port_audio_mask(unsigned int port, uint32_t poly_port_types) { + return 0 - ((poly_port_types >> port) & 1); + } + + virtual void on_port_types_changed() {} + inline void set_bundle_path(const char *path) {} + /// Called to map all the necessary URIs + virtual void map_uris() + { + poly_type_control = map_uri(LV2_POLYMORPHIC_PORT_URI, "http://lv2plug.in/ns/lv2core#ControlPort"); + poly_type_audio = map_uri(LV2_POLYMORPHIC_PORT_URI, "http://lv2plug.in/ns/lv2core#AudioPort"); + } + /// Called on instantiation with the list of LV2 features called + virtual void use_features(const LV2_Feature *const *features) { + while(*features) + { + use_feature((*features)->URI, (*features)->data); + features++; + } + } + /// LADSPA-esque activate function, except it is called after ports are connected, not before + inline void activate() {} + /// LADSPA-esque deactivate function + inline void deactivate() {} + /// Set sample rate for the plugin + inline void set_sample_rate(uint32_t sr) { srate = sr; odsr = 1.0 / sr; } + static inline const void *ext_data(const char *URI) { return NULL; } +}; + +/// Templatized version useful when the number of inputs and outputs is small +template +class small_audio_module_base: public null_small_audio_module +{ +public: + enum { in_count = Inputs, out_count = Outputs }; + /// Input pointers + float *ins[in_count]; + /// Output pointers + float *outs[out_count]; +}; + +template +struct lv2_small_wrapper +{ + typedef Module instance; + static LV2_Descriptor descriptor; + std::string uri; + static uint32_t poly_port_types; + + lv2_small_wrapper(const char *id) + { + uri = "http://calf.sourceforge.net/small_plugins/" + std::string(id); + descriptor.URI = uri.c_str(); + descriptor.instantiate = cb_instantiate; + descriptor.connect_port = cb_connect; + descriptor.activate = cb_activate; + descriptor.run = cb_run; + descriptor.deactivate = cb_deactivate; + descriptor.cleanup = cb_cleanup; + descriptor.extension_data = cb_ext_data; + + plugin_port_type_grabber ptg(poly_port_types); + Module::plugin_info(&ptg); + } + + static void cb_connect(LV2_Handle Instance, uint32_t port, void *DataLocation) { + unsigned long ins = Module::in_count; + unsigned long outs = Module::out_count; + instance *const mod = (instance *)Instance; + if (port < ins) + mod->ins[port] = (float *)DataLocation; + else if (port < ins + outs) + mod->outs[port - ins] = (float *)DataLocation; + } + + static void cb_activate(LV2_Handle Instance) { + // Note the changed semantics (now more LV2-like) + instance *const mod = (instance *)Instance; + mod->activate(); + } + + static void cb_deactivate(LV2_Handle Instance) { + instance *const mod = (instance *)Instance; + mod->deactivate(); + } + + static uint32_t cb_set_type(LV2_Handle Instance, uint32_t port, uint32_t type, void *type_data) + { + instance *const mod = (instance *)Instance; + mod->set_port_type(port, type, type_data); + return 0; + } + + static LV2_Handle cb_instantiate(const LV2_Descriptor * Descriptor, double sample_rate, const char *bundle_path, const LV2_Feature *const *features) + { + instance *mod = new instance(); + mod->poly_port_types = poly_port_types; + // XXXKF some people use fractional sample rates; we respect them ;-) + mod->set_bundle_path(bundle_path); + mod->use_features(features); + mod->set_sample_rate((uint32_t)sample_rate); + return mod; + } + + static void cb_run(LV2_Handle Instance, uint32_t SampleCount) { + instance *const mod = (instance *)Instance; + mod->process(SampleCount); + } + + static void cb_cleanup(LV2_Handle Instance) { + instance *const mod = (instance *)Instance; + delete mod; + } + + static const void *cb_ext_data(const char *URI) { + return Module::ext_data(URI); + } +}; + +extern const LV2_Descriptor *lv2_small_descriptor(uint32_t index); + +}; + +#endif + +#endif diff --git a/plugins/ladspa_effect/calf/calf/modules_synths.h b/plugins/ladspa_effect/calf/calf/modules_synths.h new file mode 100644 index 000000000..615d9a19b --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/modules_synths.h @@ -0,0 +1,264 @@ +/* Calf DSP Library + * Audio modules - synthesizers + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MODULES_SYNTHS_H +#define __CALF_MODULES_SYNTHS_H + +#include +#include "biquad.h" +#include "onepole.h" +#include "audio_fx.h" +#include "inertia.h" +#include "osc.h" +#include "synth.h" +#include "envelope.h" +#include "organ.h" +#include "modmatrix.h" + +namespace calf_plugins { + +#define MONOSYNTH_WAVE_BITS 12 + +/// Monosynth-in-making. Parameters may change at any point, so don't make songs with it! +/// It lacks inertia for parameters, even for those that really need it. +class monosynth_audio_module: public audio_module, public line_graph_iface, public mod_matrix +{ +public: + enum { mod_matrix_slots = 10 }; + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + uint32_t srate, crate; + static dsp::waveform_family *waves; + dsp::waveform_oscillator osc1, osc2; + dsp::triangle_lfo lfo; + bool running, stopping, gate, force_fadeout; + int last_key; + + float buffer[step_size], buffer2[step_size]; + uint32_t output_pos; + dsp::onepole phaseshifter; + dsp::biquad_d1_lerp filter; + dsp::biquad_d1_lerp filter2; + /// Waveform number - OSC1 + int wave1; + /// Waveform number - OSC2 + int wave2; + /// Last used waveform number - OSC1 + int prev_wave1; + /// Last used waveform number - OSC2 + int prev_wave2; + int filter_type, last_filter_type; + float freq, start_freq, target_freq, cutoff, decay_factor, fgain, fgain_delta, separation; + float detune, xpose, xfade, ampctl, fltctl, queue_vel; + float odcr, porta_time, lfo_bend, lfo_clock, last_lfov, modwheel_value; + /// Last value of phase shift for pulse width emulation for OSC1 + int32_t last_pwshift1; + /// Last value of phase shift for pulse width emulation for OSC2 + int32_t last_pwshift2; + int queue_note_on, stop_count, modwheel_value_int; + int legato; + dsp::adsr envelope; + dsp::keystack stack; + dsp::gain_smoothing master; + /// Smoothed cutoff value + dsp::inertia inertia_cutoff; + /// Smoothed pitch bend value + dsp::inertia inertia_pitchbend; + /// Smoothed channel pressure value + dsp::inertia inertia_pressure; + /// Rows of the modulation matrix + dsp::modulation_entry mod_matrix_data[mod_matrix_slots]; + /// Currently used velocity + float velocity; + /// Last value of oscillator mix ratio + float last_xfade; + /// Current calculated mod matrix outputs + float moddest[moddest_count]; + + monosynth_audio_module(); + static void precalculate_waves(progress_report_iface *reporter); + void set_sample_rate(uint32_t sr); + void delayed_note_on(); + /// Handle MIDI Note On message (does not immediately trigger a note, as it must start on + /// boundary of step_size samples). + void note_on(int note, int vel); + /// Handle MIDI Note Off message + void note_off(int note, int vel); + /// Handle MIDI Channel Pressure + void channel_pressure(int value); + /// Handle pitch bend message. + inline void pitch_bend(int value) + { + inertia_pitchbend.set_inertia(pow(2.0, (value * *params[par_pwhlrange]) / (1200.0 * 8192.0))); + } + /// Update oscillator frequency based on base frequency, detune amount, pitch bend scaling factor and sample rate. + inline void set_frequency() + { + float detune_scaled = (detune - 1); // * log(freq / 440); + if (*params[par_scaledetune] > 0) + detune_scaled *= pow(20.0 / freq, *params[par_scaledetune]); + float p1 = 1, p2 = 1; + if (moddest[moddest_o1detune] != 0) + p1 = pow(2.0, moddest[moddest_o1detune] * (1.0 / 1200.0)); + if (moddest[moddest_o2detune] != 0) + p2 = pow(2.0, moddest[moddest_o2detune] * (1.0 / 1200.0)); + osc1.set_freq(freq * (1 - detune_scaled) * p1 * inertia_pitchbend.get_last() * lfo_bend, srate); + osc2.set_freq(freq * (1 + detune_scaled) * p2 * inertia_pitchbend.get_last() * lfo_bend * xpose, srate); + } + /// Handle control change messages. + void control_change(int controller, int value); + /// Update variables from control ports. + void params_changed() { + float sf = 0.001f; + envelope.set(*params[par_attack] * sf, *params[par_decay] * sf, std::min(0.999f, *params[par_sustain]), *params[par_release] * sf, srate / step_size, *params[par_fade] * sf); + filter_type = dsp::fastf2i_drm(*params[par_filtertype]); + decay_factor = odcr * 1000.0 / *params[par_decay]; + separation = pow(2.0, *params[par_cutoffsep] / 1200.0); + wave1 = dsp::clip(dsp::fastf2i_drm(*params[par_wave1]), 0, (int)wave_count - 1); + wave2 = dsp::clip(dsp::fastf2i_drm(*params[par_wave2]), 0, (int)wave_count - 1); + detune = pow(2.0, *params[par_detune] / 1200.0); + xpose = pow(2.0, *params[par_osc2xpose] / 12.0); + xfade = *params[par_oscmix]; + legato = dsp::fastf2i_drm(*params[par_legato]); + master.set_inertia(*params[par_master]); + set_frequency(); + if (wave1 != prev_wave1 || wave2 != prev_wave2) + lookup_waveforms(); + } + void activate(); + void deactivate(); + void post_instantiate() + { + precalculate_waves(progress_report); + } + /// Set waveform addresses for oscillators + void lookup_waveforms(); + /// Run oscillators + void calculate_buffer_oscs(float lfo); + /// Run two filters in series to produce mono output samples. + void calculate_buffer_ser(); + /// Run one filter to produce mono output samples. + void calculate_buffer_single(); + /// Run two filters (one per channel) to produce stereo output samples. + void calculate_buffer_stereo(); + /// Retrieve filter graph (which is 'live' so it cannot be generated by get_static_graph), or fall back to get_static_graph. + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + /// @retval true if the filter 1 is to be used for the left channel and filter 2 for the right channel + /// @retval false if filters are to be connected in series and sent (mono) to both channels + inline bool is_stereo_filter() const + { + return filter_type == flt_2lp12 || filter_type == flt_2bp6; + } + /// No CV inputs for now + bool is_cv(int param_no) { return false; } + /// Practically all the stuff here is noisy + bool is_noisy(int param_no) { return param_no != par_cutoff; } + /// Calculate control signals and produce step_size samples of output. + void calculate_step(); + /// Main processing function + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask); +}; + +struct organ_audio_module: public audio_module, public dsp::drawbar_organ, public line_graph_iface +{ +public: + using drawbar_organ::note_on; + using drawbar_organ::note_off; + using drawbar_organ::control_change; + enum { param_count = drawbar_organ::param_count}; + float *ins[in_count]; + float *outs[out_count]; + float *params[param_count]; + dsp::organ_parameters par_values; + uint32_t srate; + bool panic_flag; + /// Value for configure variable map_curve + std::string var_map_curve; + + organ_audio_module() + : drawbar_organ(&par_values) + { + var_map_curve = "2\n0 1\n1 1\n"; // XXXKF hacky bugfix + } + + void post_instantiate() + { + dsp::organ_voice_base::precalculate_waves(progress_report); + } + + void set_sample_rate(uint32_t sr) { + srate = sr; + } + void params_changed() { + for (int i = 0; i < param_count - var_count; i++) + ((float *)&par_values)[i] = *params[i]; + + unsigned int old_poly = polyphony_limit; + polyphony_limit = dsp::clip(dsp::fastf2i_drm(*params[par_polyphony]), 1, 32); + if (polyphony_limit < old_poly) + trim_voices(); + + update_params(); + } + inline void pitch_bend(int amt) + { + drawbar_organ::pitch_bend(amt); + } + void activate() { + setup(srate); + panic_flag = false; + } + void deactivate(); + uint32_t process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + float *o[2] = { outs[0] + offset, outs[1] + offset }; + if (panic_flag) + { + control_change(120, 0); // stop all sounds + control_change(121, 0); // reset all controllers + panic_flag = false; + } + render_separate(o, nsamples); + return 3; + } + /// No CV inputs for now + bool is_cv(int param_no) { return false; } + /// Practically all the stuff here is noisy + bool is_noisy(int param_no) { return true; } + void execute(int cmd_no); + bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context); + + char *configure(const char *key, const char *value); + void send_configures(send_configure_iface *); + uint32_t message_run(const void *valid_inputs, void *output_ports) { + // silence a default printf (which is kind of a warning about unhandled message_run) + return 0; + } +}; + +}; + +#if ENABLE_EXPERIMENTAL + +#include "wavetable.h" + +#endif + +#endif diff --git a/plugins/ladspa_effect/calf/calf/multichorus.h b/plugins/ladspa_effect/calf/calf/multichorus.h new file mode 100644 index 000000000..23a1f4fde --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/multichorus.h @@ -0,0 +1,213 @@ +/* Calf DSP Library + * Multitap chorus class. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_MULTICHORUS_H +#define __CALF_MULTICHORUS_H + +#include "audio_fx.h" + +namespace dsp { + +typedef fixed_point chorus_phase; + +template +class sine_multi_lfo +{ +protected: + sine_table sine; + +public: + /// Current LFO phase + chorus_phase phase; + /// LFO phase increment + chorus_phase dphase; + /// LFO phase per-voice increment + chorus_phase vphase; + /// Current number of voices + uint32_t voices; + /// Current scale (output multiplier) + T scale; + /// Per-voice offset unit (the value that says how much the voices are offset with respect to each other in non-100% 'overlap' mode), scaled so that full range = 131072 + int32_t voice_offset; + /// LFO Range scaling for non-100% overlap + uint32_t voice_depth; +public: + sine_multi_lfo() + { + phase = dphase = vphase = 0.0; + voice_offset = 0; + voice_depth = 1U << 31; + + set_voices(Voices); + } + inline uint32_t get_voices() const + { + return voices; + } + inline void set_voices(uint32_t value) + { + voices = value; + // use sqrt, because some phases will cancel each other - so 1 / N is usually too low + scale = sqrt(1.0 / voices); + } + inline void set_overlap(float overlap) + { + // If we scale the delay amount so that full range of a single LFO is 0..1, all the overlapped LFOs will cover 0..range + // How it's calculated: + // 1. First voice is assumed to always cover the range of 0..1 + // 2. Each remaining voice contributes an interval of a width = 1 - overlap, starting from the end of the interval of the previous voice + // Coverage = non-overlapped part of the LFO range in the 1st voice + float range = 1.f + (1.f - overlap) * (voices - 1); + float scaling = 1.f / range; + voice_offset = (int)(131072 * (1 - overlap) / range); + voice_depth = (unsigned int)((1U << 30) * 1.0 * scaling); + } + /// Get LFO value for given voice, returns a values in range of [-65536, 65535] (or close) + inline int get_value(uint32_t voice) { + // find this voice's phase (= phase + voice * 360 degrees / number of voices) + chorus_phase voice_phase = phase + vphase * (int)voice; + // find table offset + unsigned int ipart = voice_phase.ipart(); + // interpolate (use 14 bits of precision - because the table itself uses 17 bits and the result of multiplication must fit in int32_t) + // note, the result is still -65535 .. 65535, it's just interpolated + // it is never reaching -65536 - but that's acceptable + int intval = voice_phase.lerp_by_fract_int(sine.data[ipart], sine.data[ipart+1]); + // apply the voice offset/depth (rescale from -65535..65535 to appropriate voice's "band") + return -65535 + voice * voice_offset + ((voice_depth >> (30-13)) * (65536 + intval) >> 13); + } + inline void step() { + phase += dphase; + } + inline T get_scale() const { + return scale; + } + void reset() { + phase = 0.f; + } +}; + +/** + * Multi-tap chorus without feedback. + * Perhaps MaxDelay should be a bit longer! + */ +template +class multichorus: public chorus_base +{ +protected: + simple_delay delay; +public: + MultiLfo lfo; + Postprocessor post; +public: + multichorus() { + rate = 0.63f; + dry = 0.5f; + wet = 0.5f; + min_delay = 0.005f; + mod_depth = 0.0025f; + setup(44100); + } + void reset() { + delay.reset(); + lfo.reset(); + } + void set_rate(float rate) { + chorus_base::set_rate(rate); + lfo.dphase = dphase; + } + virtual void setup(int sample_rate) { + modulation_effect::setup(sample_rate); + delay.reset(); + lfo.reset(); + set_min_delay(get_min_delay()); + set_mod_depth(get_mod_depth()); + } + template + void process(OutIter buf_out, InIter buf_in, int nsamples) { + int mds = min_delay_samples + mod_depth_samples * 1024 + 2*65536; + int mdepth = mod_depth_samples; + // 1 sample peak-to-peak = mod_depth_samples of 32 (this scaling stuff is tricky and may - but shouldn't - be wrong) + // with 192 kHz sample rate, 1 ms = 192 samples, and the maximum 20 ms = 3840 samples (so, 4096 will be used) + // 3840 samples of mod depth = mdepth of 122880 (which multiplied by 65536 doesn't fit in int32_t) + // so, it will be right-shifted by 2, which gives it a safe range of 30720 + // NB: calculation of mod_depth_samples (and multiply-by-32) is in chorus_base::set_mod_depth + mdepth = mdepth >> 2; + T scale = lfo.get_scale(); + for (int i=0; i> 2) + 1 because the LFO value is in range of [-65535, 65535] (17 bits) + int dv = mds + (mdepth * lfo_output >> (3 + 1)); + int ifv = dv >> 16; + T fd; // signal from delay's output + delay.get_interp(fd, ifv, (dv & 0xFFFF)*(1.0/65536.0)); + out += fd; + } + // apply the post filter + out = post.process(out); + T sdry = in * gs_dry.get(); + T swet = out * gs_wet.get() * scale; + *buf_out++ = sdry + swet; + lfo.step(); + } + post.sanitize(); + } + float freq_gain(float freq, float sr) + { + typedef std::complex cfloat; + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); // z^-1 + cfloat h = 0.0; + int mds = min_delay_samples + mod_depth_samples * 1024 + 2*65536; + int mdepth = mod_depth_samples; + mdepth = mdepth >> 2; + T scale = lfo.get_scale(); + unsigned int nvoices = lfo.get_voices(); + for (unsigned int v = 0; v < nvoices; v++) + { + int lfo_output = lfo.get_value(v); + // 3 = log2(32 >> 2) + 1 because the LFO value is in range of [-65535, 65535] (17 bits) + int dv = mds + (mdepth * lfo_output >> (3 + 1)); + int fldp = dv >> 16; + cfloat zn = std::pow(z, fldp); // z^-N + h += zn + (zn * z - zn) * cfloat(dv / 65536.0 - fldp); + } + // apply the post filter + h *= post.h_z(z); + // mix with dry signal + float v = std::abs(cfloat(gs_dry.get_last()) + cfloat(scale * gs_wet.get_last()) * h); + return v; + } +}; + +}; + +#endif + + diff --git a/plugins/ladspa_effect/calf/calf/onepole.h b/plugins/ladspa_effect/calf/calf/onepole.h new file mode 100644 index 000000000..2bc23257b --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/onepole.h @@ -0,0 +1,192 @@ +/* Calf DSP Library + * Basic one-pole one-zero filters based on bilinear transform. + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_ONEPOLE_H +#define __CALF_ONEPOLE_H + +#include "primitives.h" + +namespace dsp { + +/** + * one-pole filter, for floating point values + * coefficient calculation is based on bilinear transform, and the code itself is based on my very old OneSignal lib + * lp and hp are *somewhat* tested, allpass is not tested at all + * don't use this for integers because it won't work + */ +template +class onepole +{ +public: + typedef std::complex cfloat; + + T x1, y1; + Coeff a0, a1, b1; + + onepole() + { + reset(); + } + + /// Set coefficients for a lowpass filter + void set_lp(float fc, float sr) + { + // x x + // x+1 x-1 + Coeff x = tan (M_PI * fc / (2 * sr)); + Coeff q = 1/(1+x); + a0 = a1 = x*q; + b1 = (x-1)*q; + } + + /// Set coefficients for an allpass filter + void set_ap(float fc, float sr) + { + // x-1 x+1 + // x+1 x-1 + Coeff x = tan (M_PI * fc / (2 * sr)); + Coeff q = 1/(1+x); + b1 = a0 = (x-1)*q; + a1 = 1; + } + + /// Set coefficients for an allpass filter, using omega instead of fc and sr + /// omega = (PI / 2) * fc / sr + void set_ap_w(float w) + { + // x-1 x+1 + // x+1 x-1 + Coeff x = tan (w); + Coeff q = 1/(1+x); + b1 = a0 = (x-1)*q; + a1 = 1; + } + + /// Set coefficients for a highpass filter + void set_hp(float fc, float sr) + { + // x -x + // x+1 x-1 + Coeff x = tan (M_PI * fc / (2 * sr)); + Coeff q = 1/(1+x); + a0 = q; + a1 = -a0; + b1 = (x-1)*q; + } + + /// Process one sample + inline T process(T in) + { + T out = in * a0 + x1 * a1 - y1 * b1; + x1 = in; + y1 = out; + return out; + } + + /// Process one sample, assuming it's a lowpass filter (optimized special case) + inline T process_lp(T in) + { + T out = (in + x1) * a0 - y1 * b1; + x1 = in; + y1 = out; + return out; + } + + /// Process one sample, assuming it's a highpass filter (optimized special case) + inline T process_hp(T in) + { + T out = (in - x1) * a0 - y1 * b1; + x1 = in; + y1 = out; + return out; + } + + /// Process one sample, assuming it's an allpass filter (optimized special case) + inline T process_ap(T in) + { + T out = (in - y1) * a0 + x1; + x1 = in; + y1 = out; + return out; + } + + /// Process one sample using external state variables + inline T process_ap(T in, float &x1, float &y1) + { + T out = (in - y1) * a0 + x1; + x1 = in; + y1 = out; + return out; + } + + /// Process one sample using external state variables, including filter coeff + inline T process_ap(T in, float &x1, float &y1, float a0) + { + T out = (in - y1) * a0 + x1; + x1 = in; + y1 = out; + return out; + } + + inline bool empty() { + return y1 == 0; + } + + inline void sanitize() + { + dsp::sanitize(x1); + dsp::sanitize(y1); + } + + inline void reset() + { + dsp::zero(x1); + dsp::zero(y1); + } + + template + inline void copy_coeffs(const onepole &src) + { + a0 = src.a0; + a1 = src.a1; + b1 = src.b1; + } + + /// Return the filter's gain at frequency freq + /// @param freq Frequency to look up + /// @param sr Filter sample rate (used to convert frequency to angular frequency) + float freq_gain(float freq, float sr) + { + freq *= 2.0 * M_PI / sr; + cfloat z = 1.0 / exp(cfloat(0.0, freq)); + + return std::abs(h_z(z)); + } + + /// Return H(z) the filter's gain at frequency freq + /// @param z Z variable (e^jw) + cfloat h_z(const cfloat &z) + { + return (cfloat(a0) + double(a1) * z) / (cfloat(1.0) + double(b1) * z); + } +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/organ.h b/plugins/ladspa_effect/calf/calf/organ.h new file mode 100644 index 000000000..8a9b31666 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/organ.h @@ -0,0 +1,374 @@ +/* Calf DSP Library + * Drawbar organ emulator. + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_ORGAN_H +#define __CALF_ORGAN_H + +#include "synth.h" +#include "envelope.h" +#include "metadata.h" + +#define ORGAN_KEYTRACK_POINTS 4 + +namespace dsp +{ + +struct organ_parameters { + enum { FilterCount = 2, EnvCount = 3 }; + struct organ_filter_parameters + { + float cutoff; + float resonance; + float envmod[organ_parameters::EnvCount]; + float keyf; + }; + + struct organ_env_parameters + { + float attack, decay, sustain, release, velscale, ampctl; + }; + + ////////////////////////////////////////////////////////////////////////// + // these parameters are binary-copied from control ports (order is important!) + + float drawbars[9]; + float harmonics[9]; + float waveforms[9]; + float detune[9]; + float phase[9]; + float pan[9]; + float routing[9]; + float foldover; + float percussion_time; + float percussion_level; + float percussion_wave; + float percussion_harmonic; + float percussion_vel2amp; + float percussion_fm_time; + float percussion_fm_depth; + float percussion_fm_wave; + float percussion_fm_harmonic; + float percussion_vel2fm; + float percussion_trigger; + float percussion_stereo; + float filter_chain; + float filter1_type; + float master; + + organ_filter_parameters filters[organ_parameters::FilterCount]; + organ_env_parameters envs[organ_parameters::EnvCount]; + float lfo_rate; + float lfo_amt; + float lfo_wet; + float lfo_phase; + float lfo_mode; + + float global_transpose; + float global_detune; + + float polyphony; + + float quad_env; + + float pitch_bend_range; + + float bass_freq; + float bass_gain; + float treble_freq; + float treble_gain; + + float dummy_mapcurve; + + ////////////////////////////////////////////////////////////////////////// + // these parameters are calculated + + double perc_decay_const, perc_fm_decay_const; + float multiplier[9]; + int phaseshift[9]; + float cutoff; + unsigned int foldvalue; + float pitch_bend; + + float percussion_keytrack[ORGAN_KEYTRACK_POINTS][2]; + + organ_parameters() : pitch_bend(1.0f) {} + + inline int get_percussion_wave() { return dsp::fastf2i_drm(percussion_wave); } + inline int get_percussion_fm_wave() { return dsp::fastf2i_drm(percussion_fm_wave); } +}; + +#define ORGAN_WAVE_BITS 12 +#define ORGAN_WAVE_SIZE 4096 +#define ORGAN_BIG_WAVE_BITS 17 +#define ORGAN_BIG_WAVE_SIZE 131072 +/// 2^ORGAN_BIG_WAVE_SHIFT = how many (quasi)periods per sample +#define ORGAN_BIG_WAVE_SHIFT 5 + +class organ_voice_base: public calf_plugins::organ_enums +{ +public: + typedef waveform_family small_wave_family; + typedef waveform_family big_wave_family; +public: + organ_parameters *parameters; +protected: + static small_wave_family (*waves)[wave_count_small]; + static big_wave_family (*big_waves)[wave_count_big]; + + // dsp::sine_table sine_wave; + int note; + dsp::decay amp; + /// percussion FM carrier amplitude envelope + dsp::decay pamp; + /// percussion FM modulator amplitude envelope + dsp::decay fm_amp; + dsp::fixed_point pphase, dpphase; + dsp::fixed_point modphase, moddphase; + float fm_keytrack; + int &sample_rate_ref; + bool &released_ref; + /// pamp per-sample (linear) step during release stage (calculated on release so that it will take 30ms for it to go from "current value at release point" to 0) + float rel_age_const; + + organ_voice_base(organ_parameters *_parameters, int &_sample_rate_ref, bool &_released_ref); + + inline float wave(float *data, dsp::fixed_point ph) { + return ph.lerp_table_lookup_float(data); + } + inline float big_wave(float *data, dsp::fixed_point &ph) { + // wrap to fit within the wave + return ph.lerp_table_lookup_float_mask(data, ORGAN_BIG_WAVE_SIZE - 1); + } +public: + static inline small_wave_family &get_wave(int wave) { + return (*waves)[wave]; + } + static inline big_wave_family &get_big_wave(int wave) { + return (*big_waves)[wave]; + } + static void precalculate_waves(calf_plugins::progress_report_iface *reporter); + void update_pitch() + { + float phase = dsp::midi_note_to_phase(note, 100 * parameters->global_transpose + parameters->global_detune, sample_rate_ref); + dpphase.set((long int) (phase * parameters->percussion_harmonic * parameters->pitch_bend)); + moddphase.set((long int) (phase * parameters->percussion_fm_harmonic * parameters->pitch_bend)); + } + // this doesn't really have a voice interface + void render_percussion_to(float (*buf)[2], int nsamples); + void perc_note_on(int note, int vel); + void perc_note_off(int note, int vel); + void perc_reset() + { + pphase = 0; + modphase = 0; + dpphase = 0; + moddphase = 0; + note = -1; + } +}; + +class organ_vibrato +{ +protected: + enum { VibratoSize = 6 }; + float vibrato_x1[VibratoSize][2], vibrato_y1[VibratoSize][2]; + float lfo_phase; + dsp::onepole vibrato[2]; +public: + void reset(); + void process(organ_parameters *parameters, float (*data)[2], unsigned int len, float sample_rate); +}; + +class organ_voice: public dsp::voice, public organ_voice_base { +protected: + enum { Channels = 2, BlockSize = 64, EnvCount = organ_parameters::EnvCount, FilterCount = organ_parameters::FilterCount }; + union { + float output_buffer[BlockSize][Channels]; + float aux_buffers[3][BlockSize][Channels]; + }; + dsp::fixed_point phase, dphase; + dsp::biquad_d1 filterL[2], filterR[2]; + adsr envs[EnvCount]; + dsp::inertia expression; + organ_vibrato vibrato; + float velocity; + bool perc_released; + /// The envelopes have ended and the voice is in final fadeout stage + bool finishing; + dsp::inertia inertia_pitchbend; + +public: + organ_voice() + : organ_voice_base(NULL, sample_rate, perc_released) + , expression(dsp::linear_ramp(16)) + , inertia_pitchbend(dsp::exponential_ramp(1)) + { + inertia_pitchbend.set_now(1); + } + + void reset() { + inertia_pitchbend.ramp.set_length(sample_rate / (BlockSize * 30)); // 1/30s + vibrato.reset(); + phase = 0; + for (int i = 0; i < FilterCount; i++) + { + filterL[i].reset(); + filterR[i].reset(); + } + } + + void note_on(int note, int vel) { + stolen = false; + finishing = false; + perc_released = false; + released = false; + reset(); + this->note = note; + const float sf = 0.001f; + for (int i = 0; i < EnvCount; i++) + { + organ_parameters::organ_env_parameters &p = parameters->envs[i]; + envs[i].set(sf * p.attack, sf * p.decay, p.sustain, sf * p.release, sample_rate / BlockSize); + envs[i].note_on(); + } + update_pitch(); + velocity = vel * 1.0 / 127.0; + amp.set(1.0f); + perc_note_on(note, vel); + } + + void note_off(int /* vel */) { + // reset age to 0 (because decay will turn from exponential to linear, necessary because of error cumulation prevention) + perc_released = true; + if (pamp.get_active()) + { + pamp.reinit(); + } + rel_age_const = pamp.get() * ((1.0/44100.0)/0.03); + for (int i = 0; i < EnvCount; i++) + envs[i].note_off(); + } + + virtual float get_priority() { return stolen ? 20000 : (perc_released ? 1 : (sostenuto ? 200 : 100)); } + + virtual void steal() { + perc_released = true; + finishing = true; + stolen = true; + } + + void render_block(); + + virtual int get_current_note() { + return note; + } + virtual bool get_active() { + // printf("note %d getactive %d use_percussion %d pamp active %d\n", note, amp.get_active(), use_percussion(), pamp.get_active()); + return (note != -1) && (amp.get_active() || (use_percussion() && pamp.get_active())); + } + void update_pitch(); + inline bool use_percussion() + { + return dsp::fastf2i_drm(parameters->percussion_trigger) == perctrig_polyphonic && parameters->percussion_level > 0; + } +}; + +/// Not a true voice, just something with similar-ish interface. +class percussion_voice: public organ_voice_base { +public: + int sample_rate; + bool released; + + percussion_voice(organ_parameters *_parameters) + : organ_voice_base(_parameters, sample_rate, released) + , released(false) + { + } + + bool get_active() { + return (note != -1) && pamp.get_active(); + } + bool get_noticable() { + return (note != -1) && (pamp.get() > 0.2 * parameters->percussion_level); + } + void setup(int sr) { + sample_rate = sr; + } +}; + +struct drawbar_organ: public dsp::basic_synth, public calf_plugins::organ_enums { + organ_parameters *parameters; + percussion_voice percussion; + organ_vibrato global_vibrato; + two_band_eq eq_l, eq_r; + + drawbar_organ(organ_parameters *_parameters) + : parameters(_parameters) + , percussion(_parameters) { + } + void render_separate(float *output[], int nsamples); + dsp::voice *alloc_voice() { + block_voice *v = new block_voice(); + v->parameters = parameters; + return v; + } + virtual void percussion_note_on(int note, int vel) { + percussion.perc_note_on(note, vel); + } + virtual void params_changed() = 0; + virtual void setup(int sr) { + basic_synth::setup(sr); + percussion.setup(sr); + parameters->cutoff = 0; + params_changed(); + global_vibrato.reset(); + } + void update_params(); + void control_change(int controller, int value) + { +#if 0 + if (controller == 11) + { + parameters->cutoff = value / 64.0 - 1; + } +#endif + dsp::basic_synth::control_change(controller, value); + } + void pitch_bend(int amt); + virtual bool check_percussion() { + switch(dsp::fastf2i_drm(parameters->percussion_trigger)) + { + case organ_voice_base::perctrig_first: + return active_voices.empty(); + case organ_voice_base::perctrig_each: + default: + return true; + case organ_voice_base::perctrig_eachplus: + return !percussion.get_noticable(); + case organ_voice_base::perctrig_polyphonic: + return false; + } + } +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/osc.h b/plugins/ladspa_effect/calf/calf/osc.h new file mode 100644 index 000000000..166f579a5 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/osc.h @@ -0,0 +1,306 @@ +/* Calf DSP Library + * Oscillators + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_OSC_H +#define __CALF_OSC_H + +#include "fft.h" +#include + +namespace dsp +{ + +/** Very simple, non-bandlimited saw oscillator. Should not be used for anything + * else than testing/prototyping. Unless get() function is replaced with something + * with "proper" oscillator code, as the frequency setting function is fine. + */ +struct simple_oscillator +{ + /// Phase (from 0 to 0xFFFFFFFF) + uint32_t phase; + /// Per-sample phase delta (phase increment), equal to 2^32*freq/sr. + uint32_t phasedelta; + /// Reset oscillator phase to zero. + void reset() + { + phase = 0; + } + /// Set phase delta based on oscillator frequency and sample rate. + void set_freq(float freq, float sr) + { + phasedelta = (int)(freq * 65536.0 * 256.0 * 16.0 / sr) << 4; + } + /// Set phase delta based on oscillator frequency and inverse of sample rate. + void set_freq_odsr(float freq, double odsr) + { + phasedelta = (int)(freq * 65536.0 * 256.0 * 16.0 * odsr) << 4; + } + inline float get() + { + float value = (phase >> 16 ) / 65535.0 - 0.5; + phase += phasedelta; + return value; + } +}; + +/** + * FFT-based bandlimiting helper class. Allows conversion between time and frequency domains and generating brickwall filtered + * versions of a waveform given a pre-computed spectrum. + * Waveform size must be a power of two, and template argument SIZE_BITS is log2 of waveform size. + */ +template +struct bandlimiter +{ + enum { SIZE = 1 << SIZE_BITS }; + static dsp::fft &get_fft() + { + static dsp::fft fft; + return fft; + } + + std::complex spectrum[SIZE]; + + /// Import time domain waveform and calculate spectrum from it + void compute_spectrum(float input[SIZE]) + { + dsp::fft &fft = get_fft(); + std::complex *data = new std::complex[SIZE]; + for (int i = 0; i < SIZE; i++) + data[i] = input[i]; + fft.calculate(data, spectrum, false); + delete []data; + } + + /// Generate the waveform from the contained spectrum. + void compute_waveform(float output[SIZE]) + { + dsp::fft &fft = get_fft(); + std::complex *data = new std::complex[SIZE]; + fft.calculate(spectrum, data, true); + for (int i = 0; i < SIZE; i++) + output[i] = data[i].real(); + delete []data; + } + + /// remove DC offset of the spectrum (it usually does more harm than good!) + void remove_dc() + { + spectrum[0] = 0.f; + } + + /// Very basic bandlimiting (brickwall filter) + /// might need to be improved much in future! + void make_waveform(float output[SIZE], int cutoff, bool foldover = false) + { + dsp::fft &fft = get_fft(); + std::vector > new_spec, iffted; + new_spec.resize(SIZE); + iffted.resize(SIZE); + // Copy original harmonics up to cutoff point + new_spec[0] = spectrum[0]; + for (int i = 1; i < cutoff; i++) + new_spec[i] = spectrum[i], + new_spec[SIZE - i] = spectrum[SIZE - i]; + // Fill the rest with zeros, optionally folding over harmonics over the + // cutoff point into the lower octaves while halving the amplitude. + // (I think it is almost nice for bell type waveforms when the original + // waveform has few widely spread harmonics) + if (foldover) + { + std::complex fatt(0.5); + cutoff /= 2; + if (cutoff < 2) + cutoff = 2; + for (int i = SIZE / 2; i >= cutoff; i--) + { + new_spec[i / 2] += new_spec[i] * fatt; + new_spec[SIZE - i / 2] += new_spec[SIZE - i] * fatt; + new_spec[i] = 0.f, + new_spec[SIZE - i] = 0.f; + } + } + else + { + if (cutoff < 1) + cutoff = 1; + for (int i = cutoff; i < SIZE / 2; i++) + new_spec[i] = 0.f, + new_spec[SIZE - i] = 0.f; + } + // convert back to time domain (IFFT) and extract only real part + fft.calculate(new_spec.data(), iffted.data(), true); + for (int i = 0; i < SIZE; i++) + output[i] = iffted[i].real(); + } +}; + +/// Set of bandlimited wavetables +template +struct waveform_family: public std::map +{ + enum { SIZE = 1 << SIZE_BITS }; + using std::map::iterator; + using std::map::end; + using std::map::lower_bound; + float original[SIZE]; + + /// Fill the family using specified bandlimiter and original waveform. Optionally apply foldover. + /// Does not produce harmonics over specified limit (limit = (SIZE / 2) / min_number_of_harmonics) + void make(bandlimiter &bl, float input[SIZE], bool foldover = false, uint32_t limit = SIZE / 2) + { + memcpy(original, input, sizeof(original)); + bl.compute_spectrum(input); + make_from_spectrum(bl, foldover); + } + + /// Fill the family using specified bandlimiter and spectrum contained within. Optionally apply foldover. + /// Does not produce harmonics over specified limit (limit = (SIZE / 2) / min_number_of_harmonics) + void make_from_spectrum(bandlimiter &bl, bool foldover = false, uint32_t limit = SIZE / 2) + { + bl.remove_dc(); + + uint32_t base = 1 << (32 - SIZE_BITS); + uint32_t cutoff = SIZE / 2, top = SIZE / 2; + float vmax = 0; + for (unsigned int i = 0; i < cutoff; i++) + vmax = std::max(vmax, abs(bl.spectrum[i])); + float vthres = vmax / 1024.0; // -60dB + float cumul = 0.f; + while(cutoff > (SIZE / limit)) { + if (!foldover) + { + // skip harmonics too quiet to be heard, but measure their loudness cumulatively, + // because even if a single harmonic is too quiet, a whole bunch of them may add up + // to considerable amount of space + cumul = 0.f; + while(cutoff > 1 && cumul + abs(bl.spectrum[cutoff - 1]) < vthres) + { + cumul += abs(bl.spectrum[cutoff - 1]); + cutoff--; + } + } + float *wf = new float[SIZE+1]; + bl.make_waveform(wf, cutoff, foldover); + wf[SIZE] = wf[0]; + (*this)[base * (top / cutoff)] = wf; + cutoff = (int)(0.75 * cutoff); + } + } + + /// Retrieve waveform pointer suitable for specified phase_delta + inline float *get_level(uint32_t phase_delta) + { + iterator i = upper_bound(phase_delta); + if (i == end()) + return NULL; + // printf("Level = %08x\n", i->first); + return i->second; + } + /// Destructor, deletes the waveforms and removes them from the map. + ~waveform_family() + { + for (iterator i = begin(); i != end(); i++) + delete []i->second; + clear(); + } +}; + +#if 0 +// cubic interpolation +static inline float cerp(float pm1, float p0, float p1, float p2, float t) +{ + return (-t*(t-1)*(t-2) * pm1 + 3*(t+1)*(t-1)*(t-2) * p0 - 3*(t+1)*t*(t-2) * p1 + (t+1)*t*(t-1) * p2) * (1.0 / 6.0); +} +#endif +/** + * Simple table-based lerping oscillator. Uses waveform of size 2^SIZE_BITS. + * Combine with waveform_family if bandlimited waveforms are needed. Because + * of linear interpolation, it's usually a good idea to use large tables + * (2048-4096 points), otherwise aliasing may be produced. + */ +template +struct waveform_oscillator: public simple_oscillator +{ + enum { SIZE = 1 << SIZE_BITS, MASK = SIZE - 1, SCALE = 1 << (32 - SIZE_BITS) }; + float *waveform; + inline float get() + { + uint32_t wpos = phase >> (32 - SIZE_BITS); + float value = dsp::lerp(waveform[wpos], waveform[(wpos + 1) & MASK], (phase & (SCALE - 1)) * (1.0f / SCALE)); + phase += phasedelta; + return value; + } + /// Add/substract two phase-shifted values + inline float get_phaseshifted(uint32_t shift, float mix) + { + uint32_t wpos = phase >> (32 - SIZE_BITS); + float value1 = dsp::lerp(waveform[wpos], waveform[(wpos + 1) & MASK], (phase & (SCALE - 1)) * (1.0f / SCALE)); + wpos = (phase + shift) >> (32 - SIZE_BITS); + float value2 = dsp::lerp(waveform[wpos], waveform[(wpos + 1) & MASK], ((phase + shift) & (SCALE - 1)) * (1.0f / SCALE)); + float value = value1 + mix * value2; + phase += phasedelta; + return value; + } +}; + +/** + * Simple triangle LFO without any smoothing or anything of this sort. + */ +struct triangle_lfo: public simple_oscillator +{ + inline float get() + { + uint32_t phase2 = phase; + // start at 90 degrees point of the "/\" wave (-1 to +1) + phase2 += 1<<30; + // if in second half, invert the wave (so it falls back into 0..0x7FFFFFFF) + phase2 ^= ((int32_t)phase2)>>31; + + float value = (phase2 >> 6) / 16777216.0 - 1.0; + phase += phasedelta; + return value; + } +}; + +/// Simple stupid inline function to normalize a waveform (by removing DC offset and ensuring max absolute value of 1). +static inline void normalize_waveform(float *table, unsigned int size) +{ + float dc = 0; + for (unsigned int i = 0; i < size; i++) + dc += table[i]; + dc /= size; + for (unsigned int i = 0; i < size; i++) + table[i] -= dc; + float thismax = 0; + for (unsigned int i = 0; i < size; i++) + thismax = std::max(thismax, fabsf(table[i])); + if (thismax < 0.000001f) + return; + double divv = 1.0 / thismax; + for (unsigned int i = 0; i < size; i++) + table[i] *= divv; +} + + + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/osctl.h b/plugins/ladspa_effect/calf/calf/osctl.h new file mode 100644 index 000000000..ff7a2fff2 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/osctl.h @@ -0,0 +1,502 @@ +/* Calf DSP Library + * Open Sound Control primitives + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_OSCTL_H +#define __CALF_OSCTL_H + +#include +#include +#include +#include +#include +#include + +namespace osctl +{ + +enum osc_type +{ + osc_i32 = 'i', + osc_f32 = 'f', + osc_string = 's', + osc_blob = 'b', + + // unsupported + osc_i64 = 'h', + osc_ts = 't', + osc_f64 = 'd', + osc_string_alt = 'S', + osc_char = 'c', + osc_rgba = 'r', + osc_midi = 'm', + osc_true = 'T', + osc_false = 'F', + osc_nil = 'N', + osc_inf = 'I', + osc_start_array = '[', + osc_end_array = ']' +}; + +extern const char *osc_type_name(osc_type type); + +struct osc_exception: public std::exception +{ + virtual const char *what() const throw() { return "OSC parsing error"; } +}; + +struct osc_read_exception: public std::exception +{ + virtual const char *what() const throw() { return "OSC buffer underflow"; } +}; + +struct osc_write_exception: public std::exception +{ + virtual const char *what() const throw() { return "OSC buffer overflow"; } +}; + +struct null_buffer +{ + static bool read(uint8_t *dest, uint32_t bytes) + { + return false; + } + static bool write(uint8_t *dest, uint32_t bytes) + { + return true; + } + static void clear() + { + } +}; + +struct raw_buffer +{ + uint8_t *ptr; + uint32_t pos, count, size; + + raw_buffer() + { + ptr = NULL; + pos = count = size = 0; + } + raw_buffer(uint8_t *_ptr, uint32_t _count, uint32_t _size) + { + set(_ptr, _count, _size); + } + inline void set(uint8_t *_ptr, uint32_t _count, uint32_t _size) + { + ptr = _ptr; + pos = 0; + count = _count; + size = _size; + } + bool read(uint8_t *dest, uint32_t bytes) + { + if (pos + bytes > count) + return false; + memcpy(dest, ptr + pos, bytes); + pos += bytes; + return true; + } + bool write(const uint8_t *src, uint32_t bytes) + { + if (count + bytes > size) + return false; + memcpy(ptr + count, src, bytes); + count += bytes; + return true; + } + int read_left() + { + return count - pos; + } + int write_left() + { + return size - count; + } + inline int write_misalignment() + { + return 4 - (count & 3); + } + void clear() + { + pos = 0; + count = 0; + } + int tell() + { + return pos; + } + void seek(int _pos) + { + pos = _pos; + } +}; + +struct string_buffer +{ + std::string data; + uint32_t pos, size; + + string_buffer() + { + pos = 0; + size = 1048576; + } + string_buffer(std::string _data, int _size = 1048576) + { + data = _data; + pos = 0; + size = _size; + } + bool read(uint8_t *dest, uint32_t bytes) + { + if (pos + bytes > data.length()) + return false; + memcpy(dest, &data[pos], bytes); + pos += bytes; + return true; + } + bool write(const uint8_t *src, uint32_t bytes) + { + if (data.length() + bytes > size) + return false; + uint32_t wpos = data.length(); + data.resize(wpos + bytes); + memcpy(&data[wpos], src, bytes); + return true; + } + inline int read_left() + { + return data.length() - pos; + } + inline int write_left() + { + return size - data.length(); + } + inline int write_misalignment() + { + return 4 - (data.length() & 3); + } + void clear() + { + data.clear(); + pos = 0; + } + int tell() + { + return pos; + } + void seek(int _pos) + { + pos = _pos; + } +}; + +template +struct osc_stream +{ + Buffer &buffer; + TypeBuffer *type_buffer; + bool error; + + osc_stream(Buffer &_buffer) : buffer(_buffer), type_buffer(NULL), error(false) {} + osc_stream(Buffer &_buffer, TypeBuffer &_type_buffer) : buffer(_buffer), type_buffer(&_type_buffer), error(false) {} + inline void pad() + { + uint32_t zero = 0; + write(&zero, buffer.write_misalignment()); + } + inline void read(void *dest, uint32_t bytes) + { + if (!buffer.read((uint8_t *)dest, bytes)) + { +#if 0 + if (Throw) + throw osc_read_exception(); + else +#endif + { + error = true; + memset(dest, 0, bytes); + } + } + } + inline void write(const void *src, uint32_t bytes) + { + if (!buffer.write((const uint8_t *)src, bytes)) + { +#if 0 + if (Throw) + throw osc_write_exception(); + else +#endif + error = true; + } + } + inline void clear() + { + buffer.clear(); + if (type_buffer) + type_buffer->clear(); + } + inline void write_type(char ch) + { + if (type_buffer) + type_buffer->write((uint8_t *)&ch, 1); + } +}; + +typedef osc_stream osc_strstream; +typedef osc_stream osc_typed_strstream; + +struct osc_inline_strstream: public string_buffer, public osc_strstream +{ + osc_inline_strstream() + : string_buffer(), osc_strstream(static_cast(*this)) + { + } +}; + +struct osc_str_typed_buffer_pair +{ + string_buffer buf_data, buf_types; +}; + +struct osc_inline_typed_strstream: public osc_str_typed_buffer_pair, public osc_typed_strstream +{ + osc_inline_typed_strstream() + : osc_str_typed_buffer_pair(), osc_typed_strstream(buf_data, buf_types) + { + } +}; + +template +inline osc_stream & +operator <<(osc_stream &s, uint32_t val) +{ + return s; +} + +template +inline osc_stream & +operator >>(osc_stream &s, uint32_t &val) +{ + return s; +} + +template +inline osc_stream & +operator >>(osc_stream &s, int32_t &val) +{ + return s; +} + +template +inline osc_stream & +operator <<(osc_stream &s, float val) +{ + return s; +} + +template +inline osc_stream & +operator >>(osc_stream &s, float &val) +{ + return s; +} + +template +inline osc_stream & +operator <<(osc_stream &s, const std::string &str) +{ + s.write(&str[0], str.length()); + s.pad(); + s.write_type(osc_string); + return s; +} + +template +inline osc_stream & +operator >>(osc_stream &s, std::string &str) +{ + // inefficient... + char five[5]; + five[4] = '\0'; + str.resize(0); + while(1) + { + s.read(five, 4); + if (five[0] == '\0') + break; + str += five; + if (!five[1] || !five[2] || !five[3]) + break; + } + return s; +} + +template +inline osc_stream & +read_buffer_from_osc_stream(osc_stream &s, DestBuffer &buf) +{ + return s; +} + +template +inline osc_stream & +write_buffer_to_osc_stream(osc_stream &s, SrcBuffer &buf) +{ + return s; +} + +template +inline osc_stream & +operator >>(osc_stream &s, raw_buffer &str) +{ + return read_buffer_from_osc_stream(s, str); +} + +template +inline osc_stream & +operator >>(osc_stream &s, string_buffer &str) +{ + return read_buffer_from_osc_stream(s, str); +} + +template +inline osc_stream & +operator <<(osc_stream &s, raw_buffer &str) +{ + return write_buffer_to_osc_stream(s, str); +} + +template +inline osc_stream & +operator <<(osc_stream &s, string_buffer &str) +{ + return write_buffer_to_osc_stream(s, str); +} + +// XXXKF: I don't support reading binary blobs yet +#if 0 +struct osc_net_bad_address: public std::exception +{ + std::string addr, error_msg; + osc_net_bad_address(const char *_addr) + { + addr = _addr; + error_msg = "Incorrect OSC URI: " + addr; + } + virtual const char *what() const throw() { return error_msg.c_str(); } + virtual ~osc_net_bad_address() throw () {} +}; + +struct osc_net_exception: public std::exception +{ + int net_errno; + std::string command, error_msg; + osc_net_exception(const char *cmd, int _errno = errno) + { + command = cmd; + net_errno = _errno; + error_msg = "OSC error in "+command+": "+strerror(_errno); + } + virtual const char *what() const throw() { return error_msg.c_str(); } + virtual ~osc_net_exception() throw () {} +}; + +struct osc_net_dns_exception: public std::exception +{ + int net_errno; + std::string command, error_msg; + virtual const char *what() const throw() { return error_msg.c_str(); } + virtual ~osc_net_dns_exception() throw () {} +}; +#endif + +template +struct osc_message_sink +{ + virtual void receive_osc_message(std::string address, std::string type_tag, OscStream &buffer)=0; + virtual ~osc_message_sink() {} +}; + +template +struct osc_message_dump: public osc_message_sink +{ + DumpStream &stream; + osc_message_dump(DumpStream &_stream) : stream(_stream) {} + + virtual void receive_osc_message(std::string address, std::string type_tag, OscStream &buffer) + { + int pos = buffer.buffer.tell(); + stream << "address: " << address << ", type tag: " << type_tag << std::endl; + for (unsigned int i = 0; i < type_tag.size(); i++) + { + stream << "Argument " << i << " is "; + switch(type_tag[i]) + { + case 'i': + { + uint32_t val; + buffer >> val; + stream << val; + break; + } + case 'f': + { + float val; + buffer >> val; + stream << val; + break; + } + case 's': + { + std::string val; + buffer >> val; + stream << val; + break; + } + case 'b': + { + osctl::string_buffer val; + buffer >> val; + stream << "blob (" << val.data.length() << " bytes)"; + break; + } + default: + { + stream << "unknown - cannot parse more arguments" << std::endl; + i = type_tag.size(); + break; + } + } + stream << std::endl; + } + stream << std::flush; + buffer.buffer.seek(pos); + } +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/plugininfo.h b/plugins/ladspa_effect/calf/calf/plugininfo.h new file mode 100644 index 000000000..46181998d --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/plugininfo.h @@ -0,0 +1,105 @@ +/* Calf DSP Library + * Plugin introspection interface + * + * Copyright (C) 2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __CALF_PLUGININFO_H +#define __CALF_PLUGININFO_H + +#include + +namespace calf_plugins { + +/// A sink to send information about an audio port +struct plain_port_info_iface +{ + /// Called if it's an input port + virtual plain_port_info_iface& input() { return *this; } + /// Called if it's an output port + virtual plain_port_info_iface& output() { return *this; } + virtual plain_port_info_iface& lv2_ttl(const std::string &text) { return *this; } + virtual ~plain_port_info_iface() {} +}; + +/// A sink to send information about a control port (very incomplete, missing stuff: units, integer, boolean, toggled, notAutomatic, notGUI...) +struct control_port_info_iface +{ + /// Called if it's an input port + virtual control_port_info_iface& input() { return *this; } + /// Called if it's an output port + virtual control_port_info_iface& output() { return *this; } + /// Called to mark the port as using linear range [from, to] + virtual control_port_info_iface& lin_range(double from, double to) { return *this; } + /// Called to mark the port as using log range [from, to] + virtual control_port_info_iface& log_range(double from, double to) { return *this; } + virtual control_port_info_iface& toggle() { return *this; } + virtual control_port_info_iface& trigger() { return *this; } + virtual control_port_info_iface& integer() { return *this; } + virtual control_port_info_iface& lv2_ttl(const std::string &text) { return *this; } + virtual control_port_info_iface& polymorphic() { return lv2_ttl("a poly:PolymorphicPort ;"); } + virtual control_port_info_iface& poly_audio() { return lv2_ttl("poly:supportsType lv2:AudioPort ;"); } + virtual ~control_port_info_iface() {} +}; + +/// A sink to send information about a plugin +struct plugin_info_iface +{ + /// Set plugin names (ID, name and label) + virtual void names(const std::string &name, const std::string &label, const std::string &category, const std::string µname = std::string()) {} + /// Add an audio port (returns a sink which accepts further description) + virtual plain_port_info_iface &audio_port(const std::string &id, const std::string &name, const std::string µname = std::string("N/A"))=0; + /// Add an event port (returns a sink which accepts further description) + virtual plain_port_info_iface &event_port(const std::string &id, const std::string &name, const std::string µname = std::string("N/A"))=0; + /// Add a control port (returns a sink which accepts further description) + virtual control_port_info_iface &control_port(const std::string &id, const std::string &name, double def_value, const std::string µname = "N/A")=0; + /// Add arbitrary TTL clauses + virtual void lv2_ttl(const std::string &text) {} + /// Add small plugin GUI + virtual void has_gui() { lv2_ttl("uiext:ui ;"); } + /// Called after plugin has reported all the information + virtual void finalize() {} + virtual ~plugin_info_iface() {} +}; + +struct plugin_port_type_grabber: public plugin_info_iface, public control_port_info_iface +{ + uint32_t ⌖ + uint32_t index; + + plain_port_info_iface pp; + control_port_info_iface cp; + + plugin_port_type_grabber(uint32_t &_target) : target(_target), index(0) { target = 0; } + + virtual plain_port_info_iface &audio_port(const std::string &id, const std::string &name, const std::string µname = std::string("N/A")) { target |= (1 << index); index++; return pp; } + virtual plain_port_info_iface &event_port(const std::string &id, const std::string &name, const std::string µname = std::string("N/A")) { index++; return pp; } + virtual control_port_info_iface &control_port(const std::string &id, const std::string &name, double def_value, const std::string µname = "N/A") { index++; return cp; } +}; + +/// A sink to send information about plugins +struct plugin_list_info_iface +{ + /// Add an empty plugin object and return the sink to be filled with information + virtual plugin_info_iface &plugin(const std::string &id) = 0; + virtual ~plugin_list_info_iface() {} +}; + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/preset.h b/plugins/ladspa_effect/calf/calf/preset.h new file mode 100644 index 000000000..c6a6ec4f8 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/preset.h @@ -0,0 +1,142 @@ +/* Calf DSP Library + * Preset management + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_PRESET_H +#define __CALF_PRESET_H + +#include +#include +#include +#include +#include +#include +#include "utils.h" + +namespace calf_plugins { + +class plugin_ctl_iface; + +/// Contents of single preset +struct plugin_preset +{ + /// Bank the preset belongs to (not used yet) + int bank; + /// Program number of the preset (not used yet) + int program; + /// Name of the preset + std::string name; + /// Name of the plugin the preset is for + std::string plugin; + /// Names of parameters in values array (for each item in param_names there should be a counterpart in values) + std::vector param_names; + /// Values of parameters + std::vector values; + /// DSSI configure-style variables + std::map variables; + + plugin_preset() : bank(0), program(0) {} + /// Export preset as XML + std::string to_xml(); + /// "Upload" preset content to the plugin + void activate(plugin_ctl_iface *plugin); + /// "Download" preset content from the plugin + void get_from(plugin_ctl_iface *plugin); + + std::string get_safe_name(); +}; + +/// Exception thrown by preset system +struct preset_exception +{ + std::string message, param, fulltext; + int error; + preset_exception(const std::string &_message, const std::string &_param, int _error) + : message(_message), param(_param), error(_error) + { + } + const char *what() { + if (error) + fulltext = message + " " + param + " (" + strerror(error) + ")"; + else + fulltext = message + " " + param; + return fulltext.c_str(); + } + ~preset_exception() + { + } +}; + +/// A vector of presets +typedef std::vector preset_vector; + +/// A single list of presets (usually there are two - @see get_builtin_presets(), get_user_presets() ) +struct preset_list +{ + /// Parser states + enum parser_state + { + START, ///< Beginning of parsing process (before root element) + LIST, ///< Inside root element + PRESET, ///< Inside preset definition + VALUE, ///< Inside (empty) param tag + VAR, ///< Inside (non-empty) var tag + } state; + + /// Contained presets (usually for all plugins) + preset_vector presets; + /// Temporary preset used during parsing process + plugin_preset parser_preset; + /// Preset number counters for DSSI (currently broken) + std::map last_preset_ids; + /// The key used in current tag (for state == VAR) + std::string current_key; + + /// Return the name of the built-in or user-defined preset file + static std::string get_preset_filename(bool builtin); + /// Load default preset list (built-in or user-defined) + bool load_defaults(bool builtin); + void parse(const std::string &data); + /// Load preset list from XML file + void load(const char *filename); + /// Save preset list as XML file + void save(const char *filename); + /// Append or replace a preset (replaces a preset with the same plugin and preset name) + void add(const plugin_preset &sp); + /// Get a sublist of presets for a given plugin (those with plugin_preset::plugin == plugin) + void get_for_plugin(preset_vector &vec, const char *plugin); + +protected: + /// Internal function: start element handler for expat + static void xml_start_element_handler(void *user_data, const char *name, const char *attrs[]); + /// Internal function: end element handler for expat + static void xml_end_element_handler(void *user_data, const char *name); + /// Internal function: character data (tag text content) handler for expat + static void xml_character_data_handler(void *user_data, const char *data, int len); +}; + +/// Return the current list of built-in (factory) presets (these are loaded from system-wide file) +extern preset_list &get_builtin_presets(); + +/// Return the current list of user-defined presets (these are loaded from ~/.calfpresets) +extern preset_list &get_user_presets(); + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/primitives.h b/plugins/ladspa_effect/calf/calf/primitives.h new file mode 100644 index 000000000..08f8e9012 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/primitives.h @@ -0,0 +1,516 @@ +/* Calf DSP Library + * DSP primitives. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_PRIMITIVES_H +#define __CALF_PRIMITIVES_H + +#include +#include +#include +#include +#include +#include + +namespace dsp { + +/// Set a float to zero +inline void zero(float &v) { + v = 0; +}; + +/// Set a double to zero +inline void zero(double &v) { + v = 0; +}; + +/// Set 64-bit unsigned integer value to zero +inline void zero(uint64_t &v) { v = 0; }; +/// Set 32-bit unsigned integer value to zero +inline void zero(uint32_t &v) { v = 0; }; +/// Set 16-bit unsigned integer value to zero +inline void zero(uint16_t &v) { v = 0; }; +/// Set 8-bit unsigned integer value to zero +inline void zero(uint8_t &v) { v = 0; }; +/// Set 64-bit signed integer value to zero +inline void zero(int64_t &v) { v = 0; }; +/// Set 32-bit signed integer value to zero +inline void zero(int32_t &v) { v = 0; }; +/// Set 16-bit signed integer value to zero +inline void zero(int16_t &v) { v = 0; }; +/// Set 8-bit signed integer value to zero +inline void zero(int8_t &v) { v = 0; }; + +/// Set array (buffer or anything similar) to vector of zeroes +template +void zero(T *data, unsigned int size) { + T value; + dsp::zero(value); + for (unsigned int i=0; istruct stereo_sample { + T left; + T right; + /// default constructor - preserves T's semantics (ie. no implicit initialization to 0) + inline stereo_sample() { + } + inline stereo_sample(T _left, T _right) { + left = _left; + right = _right; + } + inline stereo_sample(T _both) { + left = right = _both; + } + template + inline stereo_sample(const stereo_sample &value) { + left = value.left; + right = value.right; + } + inline stereo_sample& operator=(const T &value) { + left = right = value; + return *this; + } + template + inline stereo_sample& operator=(const stereo_sample &value) { + left = value.left; + right = value.right; + return *this; + } +/* + inline operator T() const { + return (left+right)/2; + } +*/ + inline stereo_sample& operator*=(const T &multiplier) { + left *= multiplier; + right *= multiplier; + return *this; + } + inline stereo_sample& operator+=(const stereo_sample &value) { + left += value.left; + right += value.right; + return *this; + } + inline stereo_sample& operator-=(const stereo_sample &value) { + left -= value.left; + right -= value.right; + return *this; + } + template inline stereo_sample operator*(const U &value) const { + return stereo_sample(left*value, right*value); + } + /*inline stereo_sample operator*(float value) const { + return stereo_sample(left*value, right*value); + } + inline stereo_sample operator*(double value) const { + return stereo_sample(left*value, right*value); + }*/ + inline stereo_sample operator+(const stereo_sample &value) { + return stereo_sample(left+value.left, right+value.right); + } + inline stereo_sample operator-(const stereo_sample &value) { + return stereo_sample(left-value.left, right-value.right); + } + inline stereo_sample operator+(const T &value) { + return stereo_sample(left+value, right+value); + } + inline stereo_sample operator-(const T &value) { + return stereo_sample(left-value, right-value); + } + inline stereo_sample operator+(float value) { + return stereo_sample(left+value, right+value); + } + inline stereo_sample operator-(float value) { + return stereo_sample(left-value, right-value); + } + inline stereo_sample operator+(double value) { + return stereo_sample(left+value, right+value); + } + inline stereo_sample operator-(double value) { + return stereo_sample(left-value, right-value); + } +}; + +/// Multiply constant by stereo_value +template +inline stereo_sample operator*(const T &value, const stereo_sample &value2) { + return stereo_sample(value2.left*value, value2.right*value); +} + +/// Add constant to stereo_value +template +inline stereo_sample operator+(const T &value, const stereo_sample &value2) { + return stereo_sample(value2.left+value, value2.right+value); +} + +/// Subtract stereo_value from constant (yields stereo_value of course) +template +inline stereo_sample operator-(const T &value, const stereo_sample &value2) { + return stereo_sample(value-value2.left, value-value2.right); +} + +/// Shift value right by 'bits' bits (multiply by 2^-bits) +template +inline stereo_sample shr(stereo_sample v, int bits = 1) { + v.left = shr(v.left, bits); + v.right = shr(v.right, bits); + return v; +} + +/// Set a stereo_sample value to zero +template +inline void zero(stereo_sample &v) { + dsp::zero(v.left); + dsp::zero(v.right); +} + +/// 'Small value' for integer and other types +template +inline T small_value() { + return 0; +} + +/// 'Small value' for floats (2^-24) - used for primitive underrun prevention. The value is pretty much arbitrary (allowing for 24-bit signals normalized to 1.0). +template<> +inline float small_value() { + return (1.0/16777216.0); // allows for 2^-24, should be enough for 24-bit DACs at least :) +} + +/// 'Small value' for doubles (2^-24) - used for primitive underrun prevention. The value is pretty much arbitrary. +template<> +inline double small_value() { + return (1.0/16777216.0); +} + +/// Convert a single value to single value = do nothing :) (but it's a generic with specialisation for stereo_sample) +template +inline float mono(T v) { + return v; +} + +/// Convert a stereo_sample to single value by averaging two channels +template +inline T mono(stereo_sample v) { + return shr(v.left+v.right); +} + +/// Clip a value to [min, max] +template +inline T clip(T value, T min, T max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + +/// Clip a double to [-1.0, +1.0] +inline double clip11(double value) { + double a = fabs(value); + if (a<=1) return value; + return (a<0) ? -1.0 : 1.0; +} + +/// Clip a float to [-1.0f, +1.0f] +inline float clip11(float value) { + float a = fabsf(value); + if (a<=1) return value; + return (a<0) ? -1.0f : 1.0f; +} + +/// Clip a double to [0.0, +1.0] +inline double clip01(double value) { + double a = fabs(value-0.5); + if (a<=0.5) return value; + return (a<0) ? -0.0 : 1.0; +} + +/// Clip a float to [0.0f, +1.0f] +inline float clip01(float value) { + float a = fabsf(value-0.5f); + if (a<=0.5f) return value; + return (a<0) ? -0.0f : 1.0f; +} + +// Linear interpolation (mix-way between v1 and v2). +template +inline T lerp(T v1, T v2, U mix) { + return v1+(v2-v1)*mix; +} + +// Linear interpolation for stereo values (mix-way between v1 and v2). +template +inline stereo_sample lerp(stereo_sample &v1, stereo_sample &v2, float mix) { + return stereo_sample(v1.left+(v2.left-v1.left)*mix, v1.right+(v2.right-v1.right)*mix); +} + +/** + * decay-only envelope (linear or exponential); deactivates itself when it goes below a set point (epsilon) + */ +class decay +{ + double value, initial; + unsigned int age, mask; + bool active; +public: + decay() { + active = false; + mask = 127; + initial = value = 0.0; + } + inline bool get_active() { + return active; + } + inline double get() { + return active ? value : 0.0; + } + inline void set(double v) { + initial = value = v; + active = true; + age = 0; + } + /// reinitialise envelope (must be called if shape changes from linear to exponential or vice versa in the middle of envelope) + inline void reinit() + { + initial = value; + age = 1; + } + inline void add(double v) { + if (active) + value += v; + else + value = v; + initial = value; + age = 0; + active = true; + } + static inline double calc_exp_constant(double times, double cycles) + { + if (cycles < 1.0) + cycles = 1.0; + return pow(times, 1.0 / cycles); + } + inline void age_exp(double constant, double epsilon) { + if (active) { + if (!(age & mask)) + value = initial * pow(constant, (double)age); + else + value *= constant; + if (value < epsilon) + active = false; + age++; + } + } + inline void age_lin(double constant, double epsilon) { + if (active) { + if (!(age & mask)) + value = initial - constant * age; + else + value -= constant; + if (value < epsilon) + active = false; + age++; + } + } + inline void deactivate() { + active = false; + value = 0; + } +}; + +class scheduler; + +class task { +public: + virtual void execute(scheduler *s)=0; + virtual void dispose() { delete this; } + virtual ~task() {} +}; + +/// this scheduler is based on std::multimap, so it isn't very fast, I guess +/// maybe some day it should be rewritten to use heapsort or something +/// work in progress, don't use! +class scheduler { + std::multimap timeline; + unsigned int time, next_task; + bool eob; + class end_buf_task: public task { + public: + scheduler *p; + end_buf_task(scheduler *_p) : p(_p) {} + virtual void execute(scheduler *s) { p->eob = true; } + virtual void dispose() { } + } eobt; +public: + + scheduler() + : time(0) + , next_task((unsigned)-1) + , eob(true) + , eobt (this) + { + time = 0; + next_task = (unsigned)-1; + eob = false; + } + inline bool is_next_tick() { + if (time < next_task) + return true; + do_tasks(); + } + inline void next_tick() { + time++; + } + void set(int pos, task *t) { + timeline.insert(std::pair(time+pos, t)); + next_task = timeline.begin()->first; + } + void do_tasks() { + std::multimap::iterator i = timeline.begin(); + while(i != timeline.end() && i->first == time) { + i->second->execute(this); + i->second->dispose(); + timeline.erase(i); + } + } + bool is_eob() { + return eob; + } + void set_buffer_size(int count) { + set(count, &eobt); + } +}; + +/** + * Force "small enough" float value to zero + */ +inline void sanitize(float &value) +{ + if (std::abs(value) < small_value()) + value = 0.f; +} + +/** + * Force "small enough" double value to zero + */ +inline void sanitize(double &value) +{ + if (std::abs(value) < small_value()) + value = 0.f; +} + +/** + * Force "small enough" stereo value to zero + */ +template +inline void sanitize(stereo_sample &value) +{ + sanitize(value.left); + sanitize(value.right); +} + +inline float fract16(unsigned int value) +{ + return (value & 0xFFFF) * (1.0 / 65536.0); +} + +/** + * typical precalculated sine table + */ +template +class sine_table +{ +public: + static bool initialized; + static T data[N+1]; + sine_table() { + if (initialized) + return; + initialized = true; + for (int i=0; i +bool sine_table::initialized = false; + +template +T sine_table::data[N+1]; + +/// fast float to int conversion using default rounding mode +inline int fastf2i_drm(float f) +{ +#ifdef __X86__ + volatile int v; + __asm ( "flds %1; fistpl %0" : "=m"(v) : "m"(f)); + return v; +#else + return (int)nearbyintf(f); +#endif +} + +/// Convert MIDI note to frequency in Hz. +inline float note_to_hz(double note, double detune_cents = 0.0) +{ + return 440 * pow(2.0, (note - 69 + detune_cents/100.0) / 12.0); +} + +/// Hermite interpolation between two points and slopes in normalized range (written after Wikipedia article) +/// @arg t normalized x coordinate (0-1 over the interval in question) +/// @arg p0 first point +/// @arg p1 second point +/// @arg m0 first slope (multiply by interval width when using over non-1-wide interval) +/// @arg m1 second slope (multiply by interval width when using over non-1-wide interval) +inline float normalized_hermite(float t, float p0, float p1, float m0, float m1) +{ + float t2 = t*t; + float t3 = t2*t; + return (2*t3 - 3*t2 + 1) * p0 + (t3 - 2*t2 + t) * m0 + (-2*t3 + 3*t2) * p1 + (t3-t2) * m1; +} + +/// Hermite interpolation between two points and slopes +/// @arg x point within interval (x0 <= x <= x1) +/// @arg x0 interval start +/// @arg x1 interval end +/// @arg p0 value at x0 +/// @arg p1 value at x1 +/// @arg m0 slope (steepness, tangent) at x0 +/// @arg m1 slope at x1 +inline float hermite_interpolation(float x, float x0, float x1, float p0, float p1, float m0, float m1) +{ + float width = x1 - x0; + float t = (x - x0) / width; + m0 *= width; + m1 *= width; + float t2 = t*t; + float t3 = t2*t; + + float ct0 = p0; + float ct1 = m0; + float ct2 = -3 * p0 - 2 * m0 + 3 * p1 - m1; + float ct3 = 2 * p0 + m0 - 2 * p1 + m1; + + return ct3 * t3 + ct2 * t2 + ct1 * t + ct0; + //return (2*t3 - 3*t2 + 1) * p0 + (t3 - 2*t2 + t) * m0 + (-2*t3 + 3*t2) * p1 + (t3-t2) * m1; +} + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/calf/synth.h b/plugins/ladspa_effect/calf/calf/synth.h new file mode 100644 index 000000000..dc5c8ebae --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/synth.h @@ -0,0 +1,228 @@ +/* Calf DSP Library + * Framework for synthesizer-like plugins. This is based + * on my earlier work on Drawbar electric organ emulator. + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_SYNTH_H +#define __CALF_SYNTH_H + +#include +#include +#include +#include "primitives.h" +#include "audio_fx.h" + +namespace dsp { + +/** + * A kind of set with fast non-ordered iteration, used for storing lists of pressed keys. + */ +class keystack { +private: + int dcount; + uint8_t active[128]; + uint8_t states[128]; +public: + keystack() { + memset(states, 0xFF, sizeof(states)); + dcount = 0; + } + void clear() { + for (int i=0; i= 0 && key <= 127); + if (states[key] != 0xFF) { + return true; + } + states[key] = dcount; + active[dcount++] = key; + return false; + } + bool pop(int key) { + if (states[key] == 0xFF) + return false; + int pos = states[key]; + if (pos != dcount-1) { + // reuse the popped item's stack position for stack top + int last = active[dcount-1]; + active[pos] = last; + // mark that position's new place on stack + states[last] = pos; + } + states[key] = 0xFF; + dcount--; + return true; + } + inline bool has(int key) { + return states[key] != 0xFF; + } + inline int count() { + return dcount; + } + inline bool empty() { + return (dcount == 0); + } + inline int nth(int n) { + return active[n]; + } +}; + +/** + * Convert MIDI note number to normalized UINT phase (where 1<<32 is full cycle). + * @param MIDI note number + * @param cents detune in cents (1/100 of a semitone) + * @param sr sample rate + */ +inline unsigned int midi_note_to_phase(int note, double cents, int sr) { + double incphase = 440*pow(2.0, (note-69)/12.0 + cents/1200.0)/sr; + if (incphase >= 1.0) incphase = fmod(incphase, 1.0); + incphase *= 65536.0*65536.0; + return (unsigned int)incphase; +} + +// Base class for all voice objects +class voice { +public: + int sample_rate; + bool released, sostenuto, stolen; + + voice() : sample_rate(-1), released(false), sostenuto(false), stolen(false) {} + + /// reset voice to default state (used when a voice is to be reused) + virtual void setup(int sr) { sample_rate = sr; } + /// reset voice to default state (used when a voice is to be reused) + virtual void reset()=0; + /// a note was pressed + virtual void note_on(int note, int vel)=0; + /// a note was released + virtual void note_off(int vel)=0; + /// check if voice can be removed from active voice list + virtual bool get_active()=0; + /// render voice data to buffer + virtual void render_to(float (*buf)[2], int nsamples)=0; + /// very fast note off + virtual void steal()=0; + /// return the note used by this voice + virtual int get_current_note()=0; + virtual float get_priority() { return stolen ? 20000 : (released ? 1 : (sostenuto ? 200 : 100)); } + /// empty virtual destructor + virtual ~voice() {} +}; + +/// An "optimized" voice class using fixed-size processing units +/// and fixed number of channels. The drawback is that voice +/// control is not sample-accurate, and no modulation input +/// is possible, but it should be good enough for most cases +/// (like Calf Organ). +template +class block_voice: public Base { +public: + // derived from Base + // enum { Channels = 2 }; + using Base::Channels; + // enum { BlockSize = 16 }; + using Base::BlockSize; + // float output_buffer[BlockSize][Channels]; + using Base::output_buffer; + // void render_block(); + using Base::render_block; + unsigned int read_ptr; + + block_voice() + { + read_ptr = BlockSize; + } + virtual void reset() + { + Base::reset(); + read_ptr = BlockSize; + } + virtual void render_to(float (*buf)[2], int nsamples) + { + int p = 0; + while(p < nsamples) + { + if (read_ptr == BlockSize) + { + render_block(); + read_ptr = 0; + } + int ncopy = std::min(BlockSize - read_ptr, nsamples - p); + for (int i = 0; i < ncopy; i++) + for (int c = 0; c < Channels; c++) + buf[p + i][c] += output_buffer[read_ptr + i][c]; + p += ncopy; + read_ptr += ncopy; + } + } +}; + +/// Base class for all kinds of polyphonic instruments, provides +/// somewhat reasonable voice management, pedal support - and +/// little else. It's implemented as a base class with virtual +/// functions, so there's some performance loss, but it shouldn't +/// be horrible. +/// @todo it would make sense to support all notes off controller too +struct basic_synth { +protected: + /// Current sample rate + int sample_rate; + /// Hold pedal state + bool hold; + /// Sostenuto pedal state + bool sostenuto; + /// Voices currently playing + std::list active_voices; + /// Voices allocated, but not used + std::stack unused_voices; + /// Gate values for all 128 MIDI notes + std::bitset<128> gate; + /// Maximum allocated number of channels + unsigned int polyphony_limit; + + void kill_note(int note, int vel, bool just_one); +public: + virtual void setup(int sr) { + sample_rate = sr; + hold = false; + sostenuto = false; + polyphony_limit = (unsigned)-1; + } + virtual void trim_voices(); + virtual dsp::voice *give_voice(); + virtual dsp::voice *alloc_voice()=0; + virtual dsp::voice *steal_voice(); + virtual void render_to(float (*output)[2], int nsamples); + virtual void note_on(int note, int vel); + virtual void percussion_note_on(int note, int vel) {} + virtual void control_change(int ctl, int val); + virtual void note_off(int note, int vel); + /// amt = -8192 to 8191 + virtual void pitch_bend(int amt) {} + virtual void on_pedal_release(); + virtual bool check_percussion() { return active_voices.empty(); } + virtual ~basic_synth(); +}; + +} + +#endif diff --git a/plugins/ladspa_effect/calf/calf/utils.h b/plugins/ladspa_effect/calf/calf/utils.h new file mode 100644 index 000000000..406c71ee7 --- /dev/null +++ b/plugins/ladspa_effect/calf/calf/utils.h @@ -0,0 +1,161 @@ +/* Calf DSP Library + * Utilities + * + * Copyright (C) 2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02111-1307, USA. + */ +#ifndef __CALF_UTILS_H +#define __CALF_UTILS_H + +#include +#include +#include +#include + +namespace calf_utils +{ + +/// Pthreads based mutex class +class ptmutex +{ +public: + pthread_mutex_t pm; + + ptmutex(int type = PTHREAD_MUTEX_RECURSIVE) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, type); + pthread_mutex_init(&pm, &attr); + pthread_mutexattr_destroy(&attr); + } + + bool lock() + { + return pthread_mutex_lock(&pm) == 0; + } + + bool trylock() + { + return pthread_mutex_trylock(&pm) == 0; + } + + void unlock() + { + pthread_mutex_unlock(&pm); + } + + ~ptmutex() + { + pthread_mutex_destroy(&pm); + } +}; + +/// Exception-safe mutex lock +class ptlock +{ + ptmutex &mutex; + bool locked; + +public: + ptlock(ptmutex &_m) : mutex(_m), locked(true) + { + mutex.lock(); + } + void unlock() + { + mutex.unlock(); + locked = false; + } + void unlocked() + { + locked = false; + } + ~ptlock() + { + if (locked) + mutex.unlock(); + } +}; + +/// Exception-safe temporary assignment +template +class scope_assign +{ + T &data, old_value; +public: + scope_assign(T &_data, T new_value) + : data(_data), old_value(_data) + { + data = new_value; + } + ~scope_assign() + { + data = old_value; + } +}; + +struct text_exception: public std::exception +{ + const char *text; + std::string container; +public: + text_exception(const std::string &t) : container(t) { text = container.c_str(); } + virtual const char *what() const throw () { return text; } + virtual ~text_exception() throw () {} +}; + +struct file_exception: public std::exception +{ + const char *text; + std::string message, filename, container; +public: + file_exception(const std::string &f) : message(strerror(errno)), filename(f), container(filename + ":" + message) { text = container.c_str(); } + file_exception(const std::string &f, const std::string &t) : message(t), filename(f), container(filename + ":" + message) { text = container.c_str(); } + virtual const char *what() const throw () { return text; } + virtual ~file_exception() throw () {} +}; + +/// String-to-string mapping +typedef std::map dictionary; + +/// Serialize a dictonary to a string +extern std::string encode_map(const dictionary &data); +/// Deserialize a dictonary from a string +extern void decode_map(dictionary &data, const std::string &src); + +/// int-to-string +extern std::string i2s(int value); + +/// float-to-string +extern std::string f2s(double value); + +/// float-to-string-that-doesn't-resemble-an-int +extern std::string ff2s(double value); + +/// Escape a string to be used in XML file +std::string xml_escape(const std::string &src); + +/// Load file from disk into a std::string blob, or throw file_exception +std::string load_file(const std::string &src); + +/// Indent a string by another string (prefix each line) +std::string indent(const std::string &src, const std::string &indent); + +}; + +#endif diff --git a/plugins/ladspa_effect/calf/giface.cpp b/plugins/ladspa_effect/calf/giface.cpp new file mode 100644 index 000000000..62d5157ed --- /dev/null +++ b/plugins/ladspa_effect/calf/giface.cpp @@ -0,0 +1,325 @@ +/* Calf DSP Library + * Module wrapper methods. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +using namespace std; +using namespace calf_utils; +using namespace calf_plugins; + +float parameter_properties::from_01(double value01) const +{ + double value = dsp::clip(value01, 0., 1.); + switch(flags & PF_SCALEMASK) + { + case PF_SCALE_DEFAULT: + case PF_SCALE_LINEAR: + case PF_SCALE_PERC: + default: + value = min + (max - min) * value01; + break; + case PF_SCALE_QUAD: + value = min + (max - min) * value01 * value01; + break; + case PF_SCALE_LOG: + value = min * pow(double(max / min), value01); + break; + case PF_SCALE_GAIN: + if (value01 < 0.00001) + value = min; + else { + float rmin = std::max(1.0f / 1024.0f, min); + value = rmin * pow(double(max / rmin), value01); + } + break; + case PF_SCALE_LOG_INF: + assert(step); + if (value01 > (step - 1.0) / step) + value = FAKE_INFINITY; + else + value = min * pow(double(max / min), value01 * step / (step - 1.0)); + break; + } + switch(flags & PF_TYPEMASK) + { + case PF_INT: + case PF_BOOL: + case PF_ENUM: + case PF_ENUM_MULTI: + if (value > 0) + value = (int)(value + 0.5); + else + value = (int)(value - 0.5); + break; + } + return value; +} + +double parameter_properties::to_01(float value) const +{ + switch(flags & PF_SCALEMASK) + { + case PF_SCALE_DEFAULT: + case PF_SCALE_LINEAR: + case PF_SCALE_PERC: + default: + return double(value - min) / (max - min); + case PF_SCALE_QUAD: + return sqrt(double(value - min) / (max - min)); + case PF_SCALE_LOG: + value /= min; + return log((double)value) / log((double)max / min); + case PF_SCALE_LOG_INF: + if (IS_FAKE_INFINITY(value)) + return max; + value /= min; + assert(step); + return (step - 1.0) * log((double)value) / (step * log((double)max / min)); + case PF_SCALE_GAIN: + if (value < 1.0 / 1024.0) // new bottom limit - 60 dB + return 0; + double rmin = std::max(1.0f / 1024.0f, min); + value /= rmin; + return log((double)value) / log(max / rmin); + } +} + +float parameter_properties::get_increment() const +{ + float increment = 0.01; + if (step > 1) + increment = 1.0 / (step - 1); + else + if (step > 0 && step < 1) + increment = step; + else + if ((flags & PF_TYPEMASK) != PF_FLOAT) + increment = 1.0 / (max - min); + return increment; +} + +int parameter_properties::get_char_count() const +{ + if ((flags & PF_SCALEMASK) == PF_SCALE_PERC) + return 6; + if ((flags & PF_SCALEMASK) == PF_SCALE_GAIN) { + char buf[256]; + size_t len = 0; + sprintf(buf, "%0.0f dB", 6.0 * log(min) / log(2)); + len = strlen(buf); + sprintf(buf, "%0.0f dB", 6.0 * log(max) / log(2)); + len = std::max(len, strlen(buf)) + 2; + return (int)len; + } + return std::max(to_string(min).length(), std::max(to_string(max).length(), to_string(min + (max-min) * 0.987654).length())); +} + +std::string parameter_properties::to_string(float value) const +{ + char buf[32]; + if ((flags & PF_SCALEMASK) == PF_SCALE_PERC) { + sprintf(buf, "%0.f%%", 100.0 * value); + return string(buf); + } + if ((flags & PF_SCALEMASK) == PF_SCALE_GAIN) { + if (value < 1.0 / 1024.0) // new bottom limit - 60 dB + return "-inf dB"; // XXXKF change to utf-8 infinity + sprintf(buf, "%0.1f dB", 6.0 * log(value) / log(2)); + return string(buf); + } + switch(flags & PF_TYPEMASK) + { + case PF_STRING: + return "N/A"; + case PF_INT: + case PF_BOOL: + case PF_ENUM: + case PF_ENUM_MULTI: + value = (int)value; + break; + } + + if ((flags & PF_SCALEMASK) == PF_SCALE_LOG_INF && IS_FAKE_INFINITY(value)) + sprintf(buf, "+inf"); // XXXKF change to utf-8 infinity + else + sprintf(buf, "%g", value); + + switch(flags & PF_UNITMASK) { + case PF_UNIT_DB: return string(buf) + " dB"; + case PF_UNIT_HZ: return string(buf) + " Hz"; + case PF_UNIT_SEC: return string(buf) + " s"; + case PF_UNIT_MSEC: return string(buf) + " ms"; + case PF_UNIT_CENTS: return string(buf) + " ct"; + case PF_UNIT_SEMITONES: return string(buf) + "#"; + case PF_UNIT_BPM: return string(buf) + " bpm"; + case PF_UNIT_RPM: return string(buf) + " rpm"; + case PF_UNIT_DEG: return string(buf) + " deg"; + case PF_UNIT_NOTE: + { + static const char *notes = "C C#D D#E F F#G G#A A#B "; + int note = (int)value; + if (note < 0 || note > 127) + return "---"; + return string(notes + 2 * (note % 12), 2) + i2s(note / 12 - 2); + } + } + + return string(buf); +} + +void calf_plugins::plugin_ctl_iface::clear_preset() { + int param_count = get_param_count(); + for (int i=0; i < param_count; i++) + { + parameter_properties &pp = *get_param_props(i); + if ((pp.flags & PF_TYPEMASK) == PF_STRING) + { + configure(pp.short_name, pp.choices ? pp.choices[0] : ""); + } + else + set_param_value(i, pp.def_value); + } +} + +const char *calf_plugins::load_gui_xml(const std::string &plugin_id) +{ +#if 0 + try { + return strdup(calf_utils::load_file((std::string(PKGLIBDIR) + "/gui-" + plugin_id + ".xml").c_str()).c_str()); + } + catch(file_exception e) +#endif + { + return NULL; + } +} + +bool calf_plugins::check_for_message_context_ports(parameter_properties *parameters, int count) +{ + for (int i = count - 1; i >= 0; i--) + { + if (parameters[i].flags & PF_PROP_MSGCONTEXT) + return true; + } + return false; +} + +bool calf_plugins::check_for_string_ports(parameter_properties *parameters, int count) +{ + for (int i = count - 1; i >= 0; i--) + { + if ((parameters[i].flags & PF_TYPEMASK) == PF_STRING) + return true; + if ((parameters[i].flags & PF_TYPEMASK) < PF_STRING) + return false; + } + return false; +} + +#if USE_DSSI +struct osc_cairo_control: public cairo_iface +{ + osctl::osc_inline_typed_strstream &os; + + osc_cairo_control(osctl::osc_inline_typed_strstream &_os) : os(_os) {} + virtual void set_source_rgba(float r, float g, float b, float a = 1.f) + { + os << (uint32_t)LGI_SET_RGBA << r << g << b << a; + } + virtual void set_line_width(float width) + { + os << (uint32_t)LGI_SET_WIDTH << width; + } +}; + +static void send_graph_via_osc(osctl::osc_client &client, const std::string &address, line_graph_iface *graph, std::vector ¶ms) +{ + osctl::osc_inline_typed_strstream os; + osc_cairo_control cairoctl(os); + for (size_t i = 0; i < params.size(); i++) + { + int index = params[i]; + os << (uint32_t)LGI_GRAPH; + os << (uint32_t)index; + for (int j = 0; ; j++) + { + float data[128]; + if (graph->get_graph(index, j, data, 128, &cairoctl)) + { + os << (uint32_t)LGI_SUBGRAPH; + os << (uint32_t)128; + for (int p = 0; p < 128; p++) + os << data[p]; + } + else + break; + } + for (int j = 0; ; j++) + { + float x, y; + int size = 3; + if (graph->get_dot(index, j, x, y, size, &cairoctl)) + os << (uint32_t)LGI_DOT << x << y << (uint32_t)size; + else + break; + } + for (int j = 0; ; j++) + { + float pos = 0; + bool vertical = false; + string legend; + if (graph->get_gridline(index, j, pos, vertical, legend, &cairoctl)) + os << (uint32_t)LGI_LEGEND << pos << (uint32_t)(vertical ? 1 : 0) << legend; + else + break; + } + os << (uint32_t)LGI_END_ITEM; + } + os << (uint32_t)LGI_END; + client.send(address, os); +} + +calf_plugins::dssi_feedback_sender::dssi_feedback_sender(const char *URI, line_graph_iface *_graph, calf_plugins::parameter_properties *props, int num_params) +{ + graph = _graph; + client = new osctl::osc_client; + client->bind("0.0.0.0", 0); + client->set_url(URI); + for (int i = 0; i < num_params; i++) + { + if (props[i].flags & PF_PROP_GRAPH) + indices.push_back(i); + } +} + +void calf_plugins::dssi_feedback_sender::update() +{ + send_graph_via_osc(*client, "/lineGraph", graph, indices); +} + +calf_plugins::dssi_feedback_sender::~dssi_feedback_sender() +{ + // this would not be received by GUI's main loop because it's already been terminated + // client->send("/iQuit"); + delete client; +} +#endif diff --git a/plugins/ladspa_effect/calf/modmatrix.cpp b/plugins/ladspa_effect/calf/modmatrix.cpp new file mode 100644 index 000000000..cba6ced8b --- /dev/null +++ b/plugins/ladspa_effect/calf/modmatrix.cpp @@ -0,0 +1,136 @@ +/* Calf DSP Library + * Modulation matrix boilerplate code. + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include + +using namespace std; +using namespace dsp; +using namespace calf_plugins; + +const char *mod_mapping_names[] = { "0..1", "-1..1", "-1..0", "x^2", "2x^2-1", "ASqr", "ASqrBip", "Para", NULL }; + +const float mod_matrix::scaling_coeffs[dsp::map_type_count][3] = { + { 0, 1, 0 }, + { -1, 2, 0 }, + { -1, 1, 0 }, + { 0, 0, 1 }, + { -1, 0, 1 }, + { 0, 2, -1 }, + { -1, 4, -2 }, + { 0, 4, -4 }, +}; + +mod_matrix::mod_matrix(modulation_entry *_matrix, unsigned int _rows, const char **_src_names, const char **_dest_names) +: matrix(_matrix) +, matrix_rows(_rows) +, mod_src_names(_src_names) +, mod_dest_names(_dest_names) +{ + table_column_info tci[6] = { + { "Source", TCT_ENUM, 0, 0, 0, mod_src_names }, + { "Mapping", TCT_ENUM, 0, 0, 0, mod_mapping_names }, + { "Modulator", TCT_ENUM, 0, 0, 0, mod_src_names }, + { "Amount", TCT_FLOAT, 0, 1, 1, NULL}, + { "Destination", TCT_ENUM, 0, 0, 0, mod_dest_names }, + { NULL } + }; + assert(sizeof(table_columns) == sizeof(tci)); + memcpy(table_columns, tci, sizeof(table_columns)); + for (unsigned int i = 0; i < matrix_rows; i++) + matrix[i].reset(); +} + +const table_column_info *mod_matrix::get_table_columns(int param) +{ + return table_columns; +} + +uint32_t mod_matrix::get_table_rows(int param) +{ + return matrix_rows; +} + +std::string mod_matrix::get_cell(int param, int row, int column) +{ + assert(row >= 0 && row < (int)matrix_rows); + modulation_entry &slot = matrix[row]; + switch(column) { + case 0: // source 1 + return mod_src_names[slot.src1]; + case 1: // mapping mode + return mod_mapping_names[slot.mapping]; + case 2: // source 2 + return mod_src_names[slot.src2]; + case 3: // amount + return calf_utils::f2s(slot.amount); + case 4: // destination + return mod_dest_names[slot.dest]; + default: + assert(0); + return ""; + } +} + +void mod_matrix::set_cell(int param, int row, int column, const std::string &src, std::string &error) +{ + assert(row >= 0 && row < (int)matrix_rows); + modulation_entry &slot = matrix[row]; + const char **arr = mod_src_names; + if (column == 1) + arr = mod_mapping_names; + if (column == 4) + arr = mod_dest_names; + switch(column) { + case 0: + case 1: + case 2: + case 4: + { + for (int i = 0; arr[i]; i++) + { + if (src == arr[i]) + { + if (column == 0) + slot.src1 = i; + else if (column == 1) + slot.mapping = (mapping_mode)i; + else if (column == 2) + slot.src2 = i; + else if (column == 4) + slot.dest = i; + error.clear(); + return; + } + } + error = "Invalid name: " + src; + return; + } + case 3: + { + stringstream ss(src); + ss >> slot.amount; + error.clear(); + return; + } + } +} + diff --git a/plugins/ladspa_effect/calf/modules.cpp b/plugins/ladspa_effect/calf/modules.cpp new file mode 100644 index 000000000..84d64a8ac --- /dev/null +++ b/plugins/ladspa_effect/calf/modules.cpp @@ -0,0 +1,614 @@ +/* Calf DSP Library + * Example audio modules - parameters and LADSPA wrapper instantiation + * + * Copyright (C) 2001-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#if USE_JACK +#include +#endif +#include +#include +#include + +using namespace dsp; +using namespace calf_plugins; + +const char *calf_plugins::calf_copyright_info = "(C) 2001-2008 Krzysztof Foltman, license: LGPL"; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(flanger) = {"In L", "In R", "Out L", "Out R"}; + +CALF_PORT_PROPS(flanger) = { + { 0.1, 0.1, 10, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC | PF_PROP_GRAPH, NULL, "min_delay", "Minimum delay" }, + { 0.5, 0.1, 10, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "mod_depth", "Modulation depth" }, + { 0.25, 0.01, 20, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "mod_rate", "Modulation rate" }, + { 0.90, -0.99, 0.99, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "feedback", "Feedback" }, + { 0, 0, 360, 9, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "stereo", "Stereo phase" }, + { 0, 0, 1, 2, PF_BOOL | PF_CTL_BUTTON , NULL, "reset", "Reset" }, + { 1, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "amount", "Amount" }, + { 1.0, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "dry", "Dry Amount" }, +}; + +CALF_PLUGIN_INFO(flanger) = { 0x847d, "Flanger", "Calf Flanger", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "FlangerPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(phaser) = {"In L", "In R", "Out L", "Out R"}; + +CALF_PORT_PROPS(phaser) = { + { 1000, 20, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ | PF_PROP_GRAPH, NULL, "base_freq", "Center Freq" }, + { 4000, 0, 10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "mod_depth", "Modulation depth" }, + { 0.25, 0.01, 20, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "mod_rate", "Modulation rate" }, + { 0.25, -0.99, 0.99, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "feedback", "Feedback" }, + { 6, 1, 12, 12, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "stages", "# Stages" }, + { 180, 0, 360, 9, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "stereo", "Stereo phase" }, + { 0, 0, 1, 2, PF_BOOL | PF_CTL_BUTTON , NULL, "reset", "Reset" }, + { 1, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "amount", "Amount" }, + { 1.0, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "dry", "Dry Amount" }, +}; + +CALF_PLUGIN_INFO(phaser) = { 0x8484, "Phaser", "Calf Phaser", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "PhaserPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(reverb) = {"In L", "In R", "Out L", "Out R"}; + +const char *reverb_room_sizes[] = { "Small", "Medium", "Large", "Tunnel-like", "Large/smooth", "Experimental" }; + +CALF_PORT_PROPS(reverb) = { + { 1.5, 0.4, 15.0, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_SEC, NULL, "decay_time", "Decay time" }, + { 5000, 2000,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "hf_damp", "High Frq Damp" }, + { 2, 0, 5, 0, PF_ENUM | PF_CTL_COMBO , reverb_room_sizes, "room_size", "Room size", }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "diffusion", "Diffusion" }, + { 0.25, 0, 2, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "amount", "Wet Amount" }, + { 1.0, 0, 2, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "dry", "Dry Amount" }, + { 0, 0, 50, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "predelay", "Pre Delay" }, + { 300, 20, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "bass_cut", "Bass Cut" }, + { 5000, 20, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "treble_cut", "Treble Cut" }, +}; + +CALF_PLUGIN_INFO(reverb) = { 0x847e, "Reverb", "Calf Reverb", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "ReverbPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(filter) = {"In L", "In R", "Out L", "Out R"}; + +const char *filter_choices[] = { + "12dB/oct Lowpass", + "24dB/oct Lowpass", + "36dB/oct Lowpass", + "12dB/oct Highpass", + "24dB/oct Highpass", + "36dB/oct Highpass", + "6dB/oct Bandpass", + "12dB/oct Bandpass", + "18dB/oct Bandpass", + "6dB/oct Bandreject", + "12dB/oct Bandreject", + "18dB/oct Bandreject", +}; + +CALF_PORT_PROPS(filter) = { + { 2000, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ | PF_PROP_GRAPH, NULL, "freq", "Frequency" }, + { 0.707, 0.707, 32, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "res", "Resonance" }, + { biquad_filter_module::mode_12db_lp, + biquad_filter_module::mode_12db_lp, + biquad_filter_module::mode_count - 1, + 1, PF_ENUM | PF_CTL_COMBO, filter_choices, "mode", "Mode" }, + { 20, 5, 100, 20, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "inertia", "Inertia"}, +}; + +CALF_PLUGIN_INFO(filter) = { 0x847f, "Filter", "Calf Filter", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "FilterPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(filterclavier) = {"In L", "In R", "Out L", "Out R"}; + +CALF_PORT_PROPS(filterclavier) = { + { 0, -48, 48, 48*2+1, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_SEMITONES, NULL, "transpose", "Transpose" }, + { 0, -100, 100, 0, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune", "Detune" }, + { 32, 0.707, 32, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "maxres", "Max. Resonance" }, + { biquad_filter_module::mode_6db_bp, + biquad_filter_module::mode_12db_lp, + biquad_filter_module::mode_count - 1, + 1, PF_ENUM | PF_CTL_COMBO | PF_PROP_GRAPH, filter_choices, "mode", "Mode" }, + { 20, 1, 2000, 20, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "inertia", "Portamento time"} +}; + +CALF_PLUGIN_INFO(filterclavier) = { 0x849f, "Filterclavier", "Calf Filterclavier", "Krzysztof Foltman / Hans Baier", calf_plugins::calf_copyright_info, "FilterclavierPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(vintage_delay) = {"In L", "In R", "Out L", "Out R"}; + +const char *vintage_delay_mixmodes[] = { + "Stereo", + "Ping-Pong", +}; + +const char *vintage_delay_fbmodes[] = { + "Plain", + "Tape", + "Old Tape", +}; + +CALF_PORT_PROPS(vintage_delay) = { + { 120, 30, 300,2701, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_BPM, NULL, "bpm", "Tempo" }, + { 4, 1, 16, 1, PF_INT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "subdiv", "Subdivide"}, + { 3, 1, 16, 1, PF_INT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "time_l", "Time L"}, + { 5, 1, 16, 1, PF_INT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "time_r", "Time R"}, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "feedback", "Feedback" }, + { 0.25, 0, 4, 100, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "amount", "Amount" }, + { 1, 0, 1, 0, PF_ENUM | PF_CTL_COMBO, vintage_delay_mixmodes, "mix_mode", "Mix mode" }, + { 1, 0, 2, 0, PF_ENUM | PF_CTL_COMBO, vintage_delay_fbmodes, "medium", "Medium" }, + { 1.0, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "dry", "Dry Amount" }, +}; + +CALF_PLUGIN_INFO(vintage_delay) = { 0x8482, "VintageDelay", "Calf Vintage Delay", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "DelayPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(rotary_speaker) = {"In L", "In R", "Out L", "Out R"}; + +const char *rotary_speaker_speed_names[] = { "Off", "Chorale", "Tremolo", "HoldPedal", "ModWheel", "Manual" }; + +CALF_PORT_PROPS(rotary_speaker) = { + { 2, 0, 5, 1.01, PF_ENUM | PF_CTL_COMBO, rotary_speaker_speed_names, "vib_speed", "Speed Mode" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "spacing", "Tap Spacing" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "shift", "Tap Offset" }, + { 0.10, 0, 1, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "mod_depth", "Mod Depth" }, + { 390, 10, 600, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_LOG | PF_UNIT_RPM, NULL, "treble_speed", "Treble Motor" }, + { 410, 10, 600, 0, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_LOG | PF_UNIT_RPM, NULL, "bass_speed", "Bass Motor" }, + { 0.7, 0, 1, 101, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "mic_distance", "Mic Distance" }, + { 0.3, 0, 1, 101, PF_FLOAT | PF_CTL_KNOB | PF_SCALE_PERC, NULL, "reflection", "Reflection" }, +}; + +CALF_PLUGIN_INFO(rotary_speaker) = { 0x8483, "RotarySpeaker", "Calf Rotary Speaker", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "SimulationPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(multichorus) = {"In L", "In R", "Out L", "Out R"}; + +CALF_PORT_PROPS(multichorus) = { + { 5, 0.1, 10, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC | PF_PROP_GRAPH, NULL, "min_delay", "Minimum delay" }, + { 6, 0.1, 10, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC| PF_PROP_GRAPH, NULL, "mod_depth", "Modulation depth" }, + { 0.5, 0.01, 20, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ| PF_PROP_GRAPH, NULL, "mod_rate", "Modulation rate" }, + { 180, 0, 360, 91, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "stereo", "Stereo phase" }, + { 4, 1, 8, 8, PF_INT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "voices", "Voices"}, + { 64, 0, 360, 91, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "vphase", "Inter-voice phase" }, + { 2, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "amount", "Amount" }, + { 1.0, 0, 4, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "dry", "Dry Amount" }, + { 100, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ | PF_PROP_GRAPH, NULL, "freq", "Center Frq 1" }, + { 5000, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ | PF_PROP_GRAPH, NULL, "freq2", "Center Frq 2" }, + { 0.125, 0.125, 8, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "q", "Q" }, + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "overlap", "Overlap" }, +}; + +CALF_PLUGIN_INFO(multichorus) = { 0x8501, "MultiChorus", "Calf MultiChorus", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "ChorusPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(compressor) = {"In L", "In R", "Out L", "Out R"}; + +const char *compressor_detection_names[] = { "RMS", "Peak" }; +const char *compressor_stereo_link_names[] = { "Average", "Maximum" }; +const char *compressor_weighting_names[] = { "Normal", "A-weighted", "Deesser (low)", "Deesser (med)", "Deesser (high)" }; + +CALF_PORT_PROPS(compressor) = { + { 0.125, 0.000976563, 1, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_DB, NULL, "threshold", "Threshold" }, + { 2, 1, 20, 21, PF_FLOAT | PF_SCALE_LOG_INF | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "ratio", "Ratio" }, + { 20, 0.01, 2000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "attack", "Attack" }, + { 250, 0.01, 2000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "release", "Release" }, + { 2, 1, 64, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_DB, NULL, "makeup", "Makeup Gain" }, + { 2.828427125, 1, 8, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_DB, NULL, "knee", "Knee" }, + { 0, 0, 1, 0, PF_ENUM | PF_CTL_COMBO, compressor_detection_names, "detection", "Detection" }, + { 0, 0, 1, 0, PF_ENUM | PF_CTL_COMBO, compressor_stereo_link_names, "stereo_link", "Stereo Link" }, + { 0, 0, 4, 0, PF_ENUM | PF_CTL_COMBO, compressor_weighting_names, "aweighting", "Weighting" }, + { 0, 0.03125, 1, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_METER | PF_CTLO_LABEL | PF_CTLO_REVERSE | PF_UNIT_DB | PF_PROP_OUTPUT | PF_PROP_OPTIONAL| PF_PROP_GRAPH, NULL, "compression", "Compression" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_METER | PF_CTLO_LABEL | PF_UNIT_DB | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "peak", "Peak Output" }, + { 0, 0, 1, 0, PF_BOOL | PF_CTL_LED | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "clip", "0dB" }, + { 0, 0, 1, 0, PF_BOOL | PF_CTL_TOGGLE, NULL, "bypass", "Bypass" }, + // { 2000, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ | PF_PROP_GRAPH, NULL, "deess_freq", "Frequency" }, + // { 0.707, 0.707, 32, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "deess_res", "Q" }, +}; + +CALF_PLUGIN_INFO(compressor) = { 0x8502, "Compressor", "Calf Compressor", "Thor Harald Johansen", calf_plugins::calf_copyright_info, "CompressorPlugin" }; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PORT_NAMES(monosynth) = { + "Out L", "Out R", +}; + +const char *monosynth_waveform_names[] = { "Sawtooth", "Square", "Pulse", "Sine", "Triangle", "Varistep", "Skewed Saw", "Skewed Square", + "Smooth Brass", "Bass", "Dark FM", "Multiwave", "Bell FM", "Dark Pad", "DCO Saw", "DCO Maze" }; +const char *monosynth_mode_names[] = { "0 : 0", "0 : 180", "0 : 90", "90 : 90", "90 : 270", "Random" }; +const char *monosynth_legato_names[] = { "Retrig", "Legato", "Fng Retrig", "Fng Legato" }; + +const char *monosynth_filter_choices[] = { + "12dB/oct Lowpass", + "24dB/oct Lowpass", + "2x12dB/oct Lowpass", + "12dB/oct Highpass", + "Lowpass+Notch", + "Highpass+Notch", + "6dB/oct Bandpass", + "2x6dB/oct Bandpass", +}; + +CALF_PLUGIN_INFO(monosynth) = { 0x8480, "Monosynth", "Calf Monosynth", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "SynthesizerPlugin" }; + +CALF_PORT_PROPS(monosynth) = { + { monosynth_metadata::wave_saw, 0, monosynth_metadata::wave_count - 1, 1, PF_ENUM | PF_CTL_COMBO | PF_PROP_GRAPH, monosynth_waveform_names, "o1_wave", "Osc1 Wave" }, + { monosynth_metadata::wave_sqr, 0, monosynth_metadata::wave_count - 1, 1, PF_ENUM | PF_CTL_COMBO | PF_PROP_GRAPH, monosynth_waveform_names, "o2_wave", "Osc2 Wave" }, + + { 0, -1, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "o1_pw", "Osc1 PW" }, + { 0, -1, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "o2_pw", "Osc2 PW" }, + + { 10, 0, 100, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "o12_detune", "O1<>2 Detune" }, + { 12, -24, 24, 0, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_SEMITONES, NULL, "o2_xpose", "Osc 2 transpose" }, + { 0, 0, 5, 0, PF_ENUM | PF_CTL_COMBO, monosynth_mode_names, "phase_mode", "Phase mode" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "o12_mix", "O1<>2 Mix" }, + { 1, 0, 7, 0, PF_ENUM | PF_CTL_COMBO | PF_PROP_GRAPH, monosynth_filter_choices, "filter", "Filter" }, + { 33, 10,16000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "cutoff", "Cutoff" }, + { 2, 0.7, 8, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB, NULL, "res", "Resonance" }, + { 0, -2400, 2400, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "filter_sep", "Separation" }, + { 8000, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "env2cutoff", "Env->Cutoff" }, + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "env2res", "Env->Res" }, + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "env2amp", "Env->Amp" }, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_a", "Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_d", "Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr_s", "Sustain" }, + { 0, -10000,10000, 21, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_f", "Fade" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_r", "Release" }, + + { 0, 0, 2, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "key_follow", "Key Follow" }, + { 0, 0, 3, 0, PF_ENUM | PF_CTL_COMBO, monosynth_legato_names, "legato", "Legato Mode" }, + { 1, 1, 2000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "portamento", "Portamento" }, + + { 0.5, 0, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "vel2filter", "Vel->Filter" }, + { 0, 0, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "vel2amp", "Vel->Amp" }, + + { 0.5, 0, 1, 100, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_PROP_OUTPUT_GAIN, NULL, "master", "Volume" }, + + { 200, 0, 2400, 25, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "pbend_range", "PBend Range" }, + + { 5, 0.01, 20, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "lfo_rate", "LFO Rate" }, + { 0.5, 0.1, 5, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_SEC, NULL, "lfo_delay", "LFO Delay" }, + { 0, -4800, 4800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "lfo2filter", "LFO->Filter" }, + { 100, 0, 1200, 0, PF_FLOAT | PF_SCALE_QUAD | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "lfo2pitch", "LFO->Pitch" }, + { 0, 0, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "lfo2pw", "LFO->PW" }, + { 1, 0, 1, 0.1, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "mwhl2lfo", "ModWheel->LFO" }, + + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "scale_detune", "Scale Detune" }, +}; + +//////////////////////////////////////////////////////////////////////////// + +CALF_PLUGIN_INFO(organ) = { 0x8481, "Organ", "Calf Organ", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "SynthesizerPlugin" }; + +const char **organ_metadata::get_default_configure_vars() +{ + static const char *data[] = { "map_curve", "2\n0 1\n1 1\n", NULL }; + return data; +} + +plugin_command_info *organ_metadata::get_commands() +{ + static plugin_command_info cmds[] = { + { "cmd_panic", "Panic!", "Stop all sounds and reset all controllers" }, + { NULL } + }; + return cmds; +} + +CALF_PORT_NAMES(organ) = {"Out L", "Out R"}; + +const char *organ_percussion_trigger_names[] = { "First note", "Each note", "Each, no retrig", "Polyphonic" }; + +const char *organ_wave_names[] = { + "Sin", + "S0", "S00", "S000", + "SSaw", "SSqr", "SPls", + "Saw", "Sqr", "Pls", + "S(", "Sq(", "S+", "Clvg", + "Bell", "Bell2", + "W1", "W2", "W3", "W4", "W5", "W6", "W7", "W8", "W9", + "DSaw", "DSqr", "DPls", + "P:SynStr","P:WideStr","P:Sine","P:Bell","P:Space","P:Voice","P:Hiss","P:Chant", +}; + +const char *organ_routing_names[] = { "Out", "Flt 1", "Flt 2" }; + +const char *organ_ampctl_names[] = { "None", "Direct", "Flt 1", "Flt 2", "All" }; + +const char *organ_vibrato_mode_names[] = { "None", "Direct", "Flt 1", "Flt 2", "Voice", "Global" }; + +const char *organ_filter_type_names[] = { "12dB/oct LP", "12dB/oct HP" }; + +const char *organ_filter_send_names[] = { "Output", "Filter 2" }; + +const char *organ_init_map_curve = "2\n0 1\n1 1\n"; + +CALF_PORT_PROPS(organ) = { + { 8, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l1", "16'" }, + { 8, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l2", "5 1/3'" }, + { 8, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l3", "8'" }, + { 0, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l4", "4'" }, + { 0, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l5", "2 2/3'" }, + { 0, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l6", "2'" }, + { 0, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l7", "1 3/5'" }, + { 0, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l8", "1 1/3'" }, + { 8, 0, 8, 80, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_FADER, NULL, "l9", "1'" }, + + { 1, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f1", "Freq 1" }, + { 3, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f2", "Freq 2" }, + { 2, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f3", "Freq 3" }, + { 4, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f4", "Freq 4" }, + { 6, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f5", "Freq 5" }, + { 8, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f6", "Freq 6" }, + { 10, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f7", "Freq 7" }, + { 12, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f8", "Freq 8" }, + { 16, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "f9", "Freq 9" }, + + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w1", "Wave 1" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w2", "Wave 2" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w3", "Wave 3" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w4", "Wave 4" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w5", "Wave 5" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w6", "Wave 6" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w7", "Wave 7" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w8", "Wave 8" }, + { 0, 0, organ_enums::wave_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_wave_names, "w9", "Wave 9" }, + + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune1", "Detune 1" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune2", "Detune 2" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune3", "Detune 3" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune4", "Detune 4" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune5", "Detune 5" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune6", "Detune 6" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune7", "Detune 7" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune8", "Detune 8" }, + { 0, -100,100, 401, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune9", "Detune 9" }, + + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase1", "Phase 1" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase2", "Phase 2" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase3", "Phase 3" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase4", "Phase 4" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase5", "Phase 5" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase6", "Phase 6" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase7", "Phase 7" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase8", "Phase 8" }, + { 0, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "phase9", "Phase 9" }, + + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan1", "Pan 1" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan2", "Pan 2" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan3", "Pan 3" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan4", "Pan 4" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan5", "Pan 5" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan6", "Pan 6" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan7", "Pan 7" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan8", "Pan 8" }, + { 0, -1, 1, 201, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB, NULL, "pan9", "Pan 9" }, + + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing1", "Routing 1" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing2", "Routing 2" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing3", "Routing 3" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing4", "Routing 4" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing5", "Routing 5" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing6", "Routing 6" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing7", "Routing 7" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing8", "Routing 8" }, + { 0, 0, 2, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, organ_routing_names, "routing9", "Routing 9" }, + + { 96, 0, 127, 128, PF_INT | PF_CTL_KNOB | PF_UNIT_NOTE, NULL, "foldnote", "Foldover" }, + + { 200, 10, 3000, 100, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "perc_decay", "P: Carrier Decay" }, + { 0.25, 0, 1, 100, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB, NULL, "perc_level", "P: Level" }, + { 0, 0, organ_enums::wave_count_small - 1, 1, PF_ENUM | PF_CTL_COMBO, organ_wave_names, "perc_waveform", "P: Carrier Wave" }, + { 6, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "perc_harmonic", "P: Carrier Frq" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "perc_vel2amp", "P: Vel->Amp" }, + + { 200, 10, 3000, 100, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "perc_fm_decay", "P: Modulator Decay" }, + { 0, 0, 4, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "perc_fm_depth", "P: FM Depth" }, + { 0, 0, organ_enums::wave_count_small - 1, 1, PF_ENUM | PF_CTL_COMBO, organ_wave_names, "perc_fm_waveform", "P: Modulator Wave" }, + { 6, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "perc_fm_harmonic", "P: Modulator Frq" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "perc_vel2fm", "P: Vel->FM" }, + + { 0, 0, organ_enums::perctrig_count - 1, 0, PF_ENUM | PF_CTL_COMBO, organ_percussion_trigger_names, "perc_trigger", "P: Trigger" }, + { 90, 0,360, 361, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "perc_stereo", "P: Stereo Phase" }, + + { 0, 0, 1, 0, PF_ENUM | PF_CTL_COMBO, organ_filter_send_names, "filter_chain", "Filter 1 To" }, + { 0, 0, 1, 0, PF_ENUM | PF_CTL_COMBO, organ_filter_type_names, "filter1_type", "Filter 1 Type" }, + { 0.1, 0, 1, 100, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_PROP_OUTPUT_GAIN | PF_PROP_GRAPH, NULL, "master", "Volume" }, + + { 2000, 20, 20000, 100, PF_FLOAT | PF_SCALE_LOG | PF_UNIT_HZ | PF_CTL_KNOB, NULL, "f1_cutoff", "F1 Cutoff" }, + { 2, 0.7, 8, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB, NULL, "f1_res", "F1 Res" }, + { 8000, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f1_env1", "F1 Env1" }, + { 0, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f1_env2", "F1 Env2" }, + { 0, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f1_env3", "F1 Env3" }, + { 0, 0, 2, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "f1_keyf", "F1 KeyFollow" }, + + { 2000, 20, 20000, 100, PF_FLOAT | PF_SCALE_LOG | PF_UNIT_HZ | PF_CTL_KNOB, NULL, "f2_cutoff", "F2 Cutoff" }, + { 2, 0.7, 8, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB, NULL, "f2_res", "F2 Res" }, + { 0, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f2_env1", "F2 Env1" }, + { 8000, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f2_env2", "F2 Env2" }, + { 0, -10800,10800, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "f2_env3", "F2 Env3" }, + { 0, 0, 2, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "f2_keyf", "F2 KeyFollow" }, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_a", "EG1 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_d", "EG1 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr_s", "EG1 Sustain" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_r", "EG1 Release" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr_v", "EG1 VelMod" }, + { 0, 0, organ_enums::ampctl_count - 1, + 0, PF_INT | PF_CTL_COMBO, organ_ampctl_names, "eg1_amp_ctl", "EG1 To Amp"}, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_a", "EG2 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_d", "EG2 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr2_s", "EG2 Sustain" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_r", "EG2 Release" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr2_v", "EG2 VelMod" }, + { 0, 0, organ_enums::ampctl_count - 1, + 0, PF_INT | PF_CTL_COMBO, organ_ampctl_names, "eg2_amp_ctl", "EG2 To Amp"}, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_a", "EG3 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_d", "EG3 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr3_s", "EG3 Sustain" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_r", "EG3 Release" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr3_v", "EG3 VelMod" }, + { 0, 0, organ_enums::ampctl_count - 1, + 0, PF_INT | PF_CTL_COMBO, organ_ampctl_names, "eg3_amp_ctl", "EG3 To Amp"}, + + { 6.6, 0.01, 80, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "vib_rate", "Vib Rate" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB , NULL, "vib_amt", "Vib Mod Amt" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB , NULL, "vib_wet", "Vib Wet" }, + { 180, 0, 360, 0, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_DEG, NULL, "vib_phase", "Vib Stereo" }, + { organ_enums::lfomode_global, 0, organ_enums::lfomode_count - 1, 0, PF_ENUM | PF_CTL_COMBO, organ_vibrato_mode_names, "vib_mode", "Vib Mode" }, +// { 0, 0, organ_enums::ampctl_count - 1, +// 0, PF_INT | PF_CTL_COMBO, organ_ampctl_names, "vel_amp_ctl", "Vel To Amp"}, + + { -12, -24, 24, 49, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_SEMITONES, NULL, "transpose", "Transpose" }, + { 0, -100, 100, 201, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "detune", "Detune" }, + + { 16, 1, 32, 32, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB, NULL, "polyphony", "Polyphony" }, + + { 1, 0, 1, 0, PF_BOOL | PF_CTL_TOGGLE, NULL, "quad_env", "Quadratic AmpEnv" }, + + { 200, 0, 2400, 25, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "pbend_range", "PBend Range" }, + + { 80, 20, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "bass_freq", "Bass Freq" }, + { 1, 0.1, 10, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "bass_gain", "Bass Gain" }, + { 12000, 20, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_HZ, NULL, "treble_freq", "Treble Freq" }, + { 1, 0.1, 10, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "treble_gain", "Treble Gain" }, + + { 0, 0, 0, 0, PF_STRING | PF_PROP_MSGCONTEXT, &organ_init_map_curve, "map_curve", "Key mapping curve" }, +}; + +//////////////////////////////////////////////////////////////////////////// + +const char *fluidsynth_init_soundfont = ""; + +const char *fluidsynth_interpolation_names[] = { "None (zero-hold)", "Linear", "Cubic", "7-point" }; + +CALF_PORT_NAMES(fluidsynth) = { + "Out L", "Out R", +}; + +CALF_PLUGIN_INFO(fluidsynth) = { 0x8700, "Fluidsynth", "Calf Fluidsynth", "FluidSynth Team / Krzysztof Foltman", calf_plugins::calf_copyright_info, "SynthesizerPlugin" }; + +CALF_PORT_PROPS(fluidsynth) = { + { 0.5, 0, 1, 100, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_PROP_OUTPUT_GAIN, NULL, "master", "Volume" }, + { 0, 0, 0, 0, PF_STRING | PF_PROP_MSGCONTEXT, &fluidsynth_init_soundfont, "soundfont", "Soundfont" }, + { 2, 0, 3, 0, PF_ENUM | PF_CTL_COMBO, fluidsynth_interpolation_names, "interpolation", "Interpolation" }, + { 1, 0, 1, 0, PF_BOOL | PF_CTL_TOGGLE, NULL, "reverb", "Enable Reverb" }, + { 1, 0, 1, 0, PF_BOOL | PF_CTL_TOGGLE, NULL, "chorus", "Enable Chorus" }, +}; + +//////////////////////////////////////////////////////////////////////////// + +const char *wavetable_names[] = { + "Shiny1", + "Shiny2", + "Rezo", + "Metal", + "Bell", + "Blah", + "Pluck", + "Stretch", + "Stretch 2", + "Hard Sync", + "Hard Sync 2", + "Soft Sync", + "Bell 2", + "Bell 3", + "Tine", + "Tine 2", + "Clav", + "Clav 2", + "Gtr", + "Gtr 2", + "Gtr 3", + "Gtr 4", + "Gtr 5", + "Reed", + "Reed 2", + "Silver", + "Brass", + "Multi", + "Multi 2", +}; + +const char *wavetable_init_soundfont = ""; + +CALF_PORT_NAMES(wavetable) = { + "Out L", "Out R", +}; + +CALF_PLUGIN_INFO(wavetable) = { 0x8701, "Wavetable", "Calf Wavetable", "Krzysztof Foltman", calf_plugins::calf_copyright_info, "SynthesizerPlugin" }; + +CALF_PORT_PROPS(wavetable) = { + { wavetable_metadata::wt_count - 1, 0, wavetable_metadata::wt_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, wavetable_names, "o1wave", "Osc1 Wave" }, + { 0.2, -1, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "o1offset", "Osc1 Ctl"}, + { 0, -48, 48, 48*2+1, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_SEMITONES, NULL, "o1trans", "Osc1 Transpose" }, + { 6, -100, 100, 0, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "o1detune", "Osc1 Detune" }, + { 0.1, 0, 1, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "o1level", "Osc1 Level" }, + + { 0, 0, wavetable_metadata::wt_count - 1, 0, PF_ENUM | PF_SCALE_LINEAR | PF_CTL_COMBO, wavetable_names, "o2wave", "Osc2 Wave" }, + { 0.4, -1, 1, 0, PF_FLOAT | PF_SCALE_PERC | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "o2offset", "Osc2 Ctl"}, + { 0, -48, 48, 48*2+1, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_SEMITONES, NULL, "o2trans", "Osc2 Transpose" }, + { -6, -100, 100, 0, PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "o2detune", "Osc2 Detune" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "o2level", "Osc2 Level" }, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_a", "EG1 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_d", "EG1 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr_s", "EG1 Sustain" }, + { 0, -10000,10000, 21, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_f", "EG1 Fade" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr_r", "EG1 Release" }, + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr_v", "EG1 VelMod" }, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_a", "EG2 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_d", "EG2 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr2_s", "EG2 Sustain" }, + { 0, -10000,10000, 21, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_f", "EG2 Fade" }, + { 50, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr2_r", "EG2 Release" }, + { 1, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr2_v", "EG2 VelMod" }, + + { 1, 1,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_a", "EG3 Attack" }, + { 350, 10,20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_d", "EG3 Decay" }, + { 0.5, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr3_s", "EG3 Sustain" }, + { 0, -10000,10000, 21, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_f", "EG3 Fade" }, + { 50, 10, 20000, 0, PF_FLOAT | PF_SCALE_LOG | PF_CTL_KNOB | PF_UNIT_MSEC, NULL, "adsr3_r", "EG3 Release" }, + { 0, 0, 1, 0, PF_FLOAT | PF_SCALE_PERC, NULL, "adsr3_v", "EG3 VelMod" }, + + { 200, 0, 2400, 25, PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_CENTS, NULL, "pbend_range", "PBend Range" }, +}; + +//////////////////////////////////////////////////////////////////////////// + +void calf_plugins::get_all_plugins(std::vector &plugins) +{ + #define PER_MODULE_ITEM(name, isSynth, jackname) plugins.push_back(new name##_metadata); + #define PER_SMALL_MODULE_ITEM(...) + #include +} + diff --git a/plugins/ladspa_effect/calf/modules_dsp.cpp b/plugins/ladspa_effect/calf/modules_dsp.cpp new file mode 100644 index 000000000..69293a4c9 --- /dev/null +++ b/plugins/ladspa_effect/calf/modules_dsp.cpp @@ -0,0 +1,638 @@ +/* Calf DSP Library + * Example audio modules - DSP code + * + * Copyright (C) 2001-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include +#if USE_JACK +#include +#endif +#include +#include +#include + +using namespace dsp; +using namespace calf_plugins; + +/// convert amplitude value to normalized grid-ish value (0dB = 0.5, 30dB = 1.0, -30 dB = 0.0, -60dB = -0.5, -90dB = -1.0) +static inline float dB_grid(float amp) +{ + return log(amp) * (1.0 / log(256.0)) + 0.4; +} + +template +static bool get_graph(Fx &fx, int subindex, float *data, int points) +{ + for (int i = 0; i < points; i++) + { + typedef std::complex cfloat; + double freq = 20.0 * pow (20000.0 / 20.0, i * 1.0 / points); + data[i] = dB_grid(fx.freq_gain(subindex, freq, fx.srate)); + } + return true; +} + +/// convert normalized grid-ish value back to amplitude value +static inline float dB_grid_inv(float pos) +{ + return pow(256.0, pos - 0.4); +} + +static void set_channel_color(cairo_iface *context, int channel) +{ + if (channel & 1) + context->set_source_rgba(0.75, 1, 0); + else + context->set_source_rgba(0, 1, 0.75); + context->set_line_width(1.5); +} + +static bool get_freq_gridline(int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context, bool use_frequencies = true) +{ + if (subindex < 0 ) + return false; + if (use_frequencies) + { + if (subindex < 28) + { + vertical = true; + if (subindex == 9) legend = "100 Hz"; + if (subindex == 18) legend = "1 kHz"; + if (subindex == 27) legend = "10 kHz"; + float freq = 100; + if (subindex < 9) + freq = 10 * (subindex + 1); + else if (subindex < 18) + freq = 100 * (subindex - 9 + 1); + else if (subindex < 27) + freq = 1000 * (subindex - 18 + 1); + else + freq = 10000 * (subindex - 27 + 1); + pos = log(freq / 20.0) / log(1000); + if (!legend.empty()) + context->set_source_rgba(0.25, 0.25, 0.25, 0.75); + else + context->set_source_rgba(0.25, 0.25, 0.25, 0.5); + return true; + } + subindex -= 28; + } + if (subindex >= 32) + return false; + float gain = 16.0 / (1 << subindex); + pos = dB_grid(gain); + if (pos < -1) + return false; + if (subindex != 4) + context->set_source_rgba(0.25, 0.25, 0.25, subindex & 1 ? 0.5 : 0.75); + if (!(subindex & 1)) + { + std::stringstream ss; + ss << (24 - 6 * subindex) << " dB"; + legend = ss.str(); + } + vertical = false; + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool frequency_response_line_graph::get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context) +{ + return get_freq_gridline(subindex, pos, vertical, legend, context); +} + +int frequency_response_line_graph::get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline) +{ + subindex_graph = 0; + subindex_dot = 0; + subindex_gridline = generation ? INT_MAX : 0; + return 1; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void flanger_audio_module::activate() { + left.reset(); + right.reset(); + last_r_phase = *params[par_stereo] * (1.f / 360.f); + left.reset_phase(0.f); + right.reset_phase(last_r_phase); + is_active = true; +} + +void flanger_audio_module::set_sample_rate(uint32_t sr) { + srate = sr; + left.setup(sr); + right.setup(sr); +} + +void flanger_audio_module::deactivate() { + is_active = false; +} + +bool flanger_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active) + return false; + if (index == par_delay && subindex < 2) + { + set_channel_color(context, subindex); + return ::get_graph(*this, subindex, data, points); + } + return false; +} + +float flanger_audio_module::freq_gain(int subindex, float freq, float srate) +{ + return (subindex ? right : left).freq_gain(freq, srate); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void phaser_audio_module::set_sample_rate(uint32_t sr) +{ + srate = sr; + left.setup(sr); + right.setup(sr); +} + +void phaser_audio_module::activate() +{ + is_active = true; + left.reset(); + right.reset(); + last_r_phase = *params[par_stereo] * (1.f / 360.f); + left.reset_phase(0.f); + right.reset_phase(last_r_phase); +} + +void phaser_audio_module::deactivate() +{ + is_active = false; +} + +bool phaser_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active) + return false; + if (subindex < 2) + { + set_channel_color(context, subindex); + return ::get_graph(*this, subindex, data, points); + } + return false; +} + +float phaser_audio_module::freq_gain(int subindex, float freq, float srate) +{ + return (subindex ? right : left).freq_gain(freq, srate); +} + +bool phaser_audio_module::get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context) +{ + return get_freq_gridline(subindex, pos, vertical, legend, context); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void reverb_audio_module::activate() +{ + reverb.reset(); +} + +void reverb_audio_module::deactivate() +{ +} + +void reverb_audio_module::set_sample_rate(uint32_t sr) +{ + srate = sr; + reverb.setup(sr); + amount.set_sample_rate(sr); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool filter_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active) + return false; + if (index == par_cutoff && !subindex) { + context->set_line_width(1.5); + return ::get_graph(*this, subindex, data, points); + } + return false; +} + +int filter_audio_module::get_changed_offsets(int generation, int &subindex_graph, int &subindex_dot, int &subindex_gridline) +{ + if (fabs(inertia_cutoff.get_last() - old_cutoff) + 100 * fabs(inertia_resonance.get_last() - old_resonance) + fabs(*params[par_mode] - old_mode) > 0.1f) + { + old_cutoff = inertia_cutoff.get_last(); + old_resonance = inertia_resonance.get_last(); + old_mode = *params[par_mode]; + last_generation++; + subindex_graph = 0; + subindex_dot = INT_MAX; + subindex_gridline = INT_MAX; + } + else { + subindex_graph = 0; + subindex_dot = subindex_gridline = generation ? INT_MAX : 0; + } + if (generation == last_calculated_generation) + subindex_graph = INT_MAX; + return last_generation; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool filterclavier_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active || index != par_mode) { + return false; + } + if (!subindex) { + context->set_line_width(1.5); + return ::get_graph(*this, subindex, data, points); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +rotary_speaker_audio_module::rotary_speaker_audio_module() +{ + mwhl_value = hold_value = 0.f; + phase_h = phase_l = 0.f; + aspeed_l = 1.f; + aspeed_h = 1.f; + dspeed = 0.f; +} + +void rotary_speaker_audio_module::set_sample_rate(uint32_t sr) +{ + srate = sr; + setup(); +} + +void rotary_speaker_audio_module::setup() +{ + crossover1l.set_lp_rbj(800.f, 0.7, (float)srate); + crossover1r.set_lp_rbj(800.f, 0.7, (float)srate); + crossover2l.set_hp_rbj(800.f, 0.7, (float)srate); + crossover2r.set_hp_rbj(800.f, 0.7, (float)srate); +} + +void rotary_speaker_audio_module::activate() +{ + phase_h = phase_l = 0.f; + maspeed_h = maspeed_l = 0.f; + setup(); +} + +void rotary_speaker_audio_module::deactivate() +{ +} + +void rotary_speaker_audio_module::control_change(int ctl, int val) +{ + if (vibrato_mode == 3 && ctl == 64) + { + hold_value = val / 127.f; + set_vibrato(); + return; + } + if (vibrato_mode == 4 && ctl == 1) + { + mwhl_value = val / 127.f; + set_vibrato(); + return; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +void multichorus_audio_module::activate() +{ + is_active = true; + params_changed(); +} + +void multichorus_audio_module::deactivate() +{ + is_active = false; +} + +void multichorus_audio_module::set_sample_rate(uint32_t sr) { + srate = sr; + left.setup(sr); + right.setup(sr); +} + +bool multichorus_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active) + return false; + int nvoices = (int)*params[par_voices]; + if (index == par_delay && subindex < 3) + { + if (subindex < 2) + set_channel_color(context, subindex); + else { + context->set_source_rgba(0, 1, 0); + context->set_line_width(1.0); + } + return ::get_graph(*this, subindex, data, points); + } + if (index == par_rate && subindex < nvoices) { + sine_multi_lfo &lfo = left.lfo; + for (int i = 0; i < points; i++) { + float phase = i * 2 * M_PI / points; + // original -65536 to 65535 value + float orig = subindex * lfo.voice_offset + ((lfo.voice_depth >> (30-13)) * 65536.0 * (0.95 * sin(phase) + 1)/ 8192.0) - 65536; + // scale to -1..1 + data[i] = orig / 65536.0; + } + return true; + } + return false; +} + +bool multichorus_audio_module::get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context) +{ + int voice = subindex >> 1; + int nvoices = (int)*params[par_voices]; + if ((index != par_rate && index != par_depth) || voice >= nvoices) + return false; + + float unit = (1 - *params[par_overlap]); + float scw = 1 + unit * (nvoices - 1); + set_channel_color(context, subindex); + sine_multi_lfo &lfo = (subindex & 1 ? right : left).lfo; + if (index == par_rate) + { + x = (double)(lfo.phase + lfo.vphase * voice) / 4096.0; + y = 0.95 * sin(x * 2 * M_PI); + y = (voice * unit + (y + 1) / 2) / scw * 2 - 1; + } + else + { + double ph = (double)(lfo.phase + lfo.vphase * voice) / 4096.0; + x = 0.5 + 0.5 * sin(ph * 2 * M_PI); + y = subindex & 1 ? -0.75 : 0.75; + x = (voice * unit + x) / scw; + } + return true; +} + +bool multichorus_audio_module::get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context) +{ + if (index == par_rate && !subindex) + { + pos = 0; + vertical = false; + return true; + } + if (index == par_delay) + return get_freq_gridline(subindex, pos, vertical, legend, context); + return false; +} + +float multichorus_audio_module::freq_gain(int subindex, float freq, float srate) +{ + if (subindex == 2) + return *params[par_amount] * left.post.freq_gain(freq, srate); + return (subindex ? right : left).freq_gain(freq, srate); +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +compressor_audio_module::compressor_audio_module() +{ + is_active = false; + srate = 0; + last_generation = 0; +} + +void compressor_audio_module::activate() +{ + is_active = true; + linSlope = 0.f; + peak = 0.f; + clip = 0.f; +} + +void compressor_audio_module::deactivate() +{ + is_active = false; +} + +void compressor_audio_module::set_sample_rate(uint32_t sr) +{ + srate = sr; + awL.set(sr); + awR.set(sr); +} + +bool compressor_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (!is_active) + return false; + if (subindex > 1) // 1 + return false; + for (int i = 0; i < points; i++) + { + float input = dB_grid_inv(-1.0 + i * 2.0 / (points - 1)); + float output = output_level(input); + if (subindex == 0) + data[i] = dB_grid(input); + else + data[i] = dB_grid(output); + } + if (subindex == (*params[param_bypass] > 0.5f ? 1 : 0)) + context->set_source_rgba(0.5, 0.5, 0.5, 0.5); + else { + context->set_source_rgba(0, 1, 0, 1); + context->set_line_width(2); + } + return true; +} + +bool compressor_audio_module::get_dot(int index, int subindex, float &x, float &y, int &size, cairo_iface *context) +{ + if (!is_active) + return false; + if (!subindex) + { + bool rms = *params[param_detection] == 0; + float det = rms ? sqrt(detected) : detected; + x = 0.5 + 0.5 * dB_grid(det); + y = dB_grid(*params[param_bypass] > 0.5f ? det : output_level(det)); + return *params[param_bypass] > 0.5f ? false : true; + } + return false; +} + +bool compressor_audio_module::get_gridline(int index, int subindex, float &pos, bool &vertical, std::string &legend, cairo_iface *context) +{ + bool tmp; + vertical = (subindex & 1) != 0; + bool result = get_freq_gridline(subindex >> 1, pos, tmp, legend, context, false); + if (result && vertical) { + if ((subindex & 4) && !legend.empty()) { + legend = ""; + } + else { + size_t pos = legend.find(" dB"); + if (pos != std::string::npos) + legend.erase(pos); + } + pos = 0.5 + 0.5 * pos; + } + return result; +} + +// In case of doubt: this function is written by Thor. I just moved it to this file, damaging +// the output of "git annotate" in the process. +uint32_t compressor_audio_module::process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) +{ + bool bypass = *params[param_bypass] > 0.5f; + + if(bypass) { + int count = numsamples * sizeof(float); + memcpy(outs[0], ins[0], count); + memcpy(outs[1], ins[1], count); + + if(params[param_compression] != NULL) { + *params[param_compression] = 1.f; + } + + if(params[param_clip] != NULL) { + *params[param_clip] = 0.f; + } + + if(params[param_peak] != NULL) { + *params[param_peak] = 0.f; + } + + return inputs_mask; + } + + bool rms = *params[param_detection] == 0; + bool average = *params[param_stereo_link] == 0; + int aweighting = fastf2i_drm(*params[param_aweighting]); + float linThreshold = *params[param_threshold]; + ratio = *params[param_ratio]; + float attack = *params[param_attack]; + float attack_coeff = std::min(1.f, 1.f / (attack * srate / 4000.f)); + float release = *params[param_release]; + float release_coeff = std::min(1.f, 1.f / (release * srate / 4000.f)); + makeup = *params[param_makeup]; + knee = *params[param_knee]; + + float linKneeSqrt = sqrt(knee); + linKneeStart = linThreshold / linKneeSqrt; + adjKneeStart = linKneeStart*linKneeStart; + float linKneeStop = linThreshold * linKneeSqrt; + + threshold = log(linThreshold); + kneeStart = log(linKneeStart); + kneeStop = log(linKneeStop); + compressedKneeStop = (kneeStop - threshold) / ratio + threshold; + + if (aweighting >= 2) + { + bpL.set_highshelf_rbj(5000, 0.707, 10 << (aweighting - 2), srate); + bpR.copy_coeffs(bpL); + bpL.sanitize(); + bpR.sanitize(); + } + + numsamples += offset; + + float compression = 1.f; + + peak -= peak * 5.f * numsamples / srate; + + clip -= std::min(clip, numsamples); + + while(offset < numsamples) { + float left = ins[0][offset]; + float right = ins[1][offset]; + + if(aweighting == 1) { + left = awL.process(left); + right = awR.process(right); + } + else if(aweighting >= 2) { + left = bpL.process(left); + right = bpR.process(right); + } + + float absample = average ? (fabs(left) + fabs(right)) * 0.5f : std::max(fabs(left), fabs(right)); + if(rms) absample *= absample; + + linSlope += (absample - linSlope) * (absample > linSlope ? attack_coeff : release_coeff); + + float gain = 1.f; + + if(linSlope > 0.f) { + gain = output_gain(linSlope, rms); + } + + compression = gain; + gain *= makeup; + + float outL = ins[0][offset] * gain; + float outR = ins[1][offset] * gain; + + outs[0][offset] = outL; + outs[1][offset] = outR; + + ++offset; + + float maxLR = std::max(fabs(outL), fabs(outR)); + + if(maxLR > 1.f) clip = srate >> 3; /* blink clip LED for 125 ms */ + + if(maxLR > peak) { + peak = maxLR; + } + } + + detected = linSlope; + + if(params[param_compression] != NULL) { + *params[param_compression] = compression; + } + + if(params[param_clip] != NULL) { + *params[param_clip] = clip; + } + + if(params[param_peak] != NULL) { + *params[param_peak] = peak; + } + + return inputs_mask; +} diff --git a/plugins/ladspa_effect/calf/modules_small.cpp b/plugins/ladspa_effect/calf/modules_small.cpp new file mode 100644 index 000000000..529751a3a --- /dev/null +++ b/plugins/ladspa_effect/calf/modules_small.cpp @@ -0,0 +1,1916 @@ +/* Calf DSP Library + * Small modules for modular synthesizers + * + * Copyright (C) 2001-2008 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef ENABLE_EXPERIMENTAL + +#if USE_LV2 +#define LV2_SMALL_WRAPPER(mod, name) static calf_plugins::lv2_small_wrapper lv2_small_##mod(name); +#else +#define LV2_SMALL_WRAPPER(...) +#endif + +#define SMALL_WRAPPERS(mod, name) LV2_SMALL_WRAPPER(mod, name) + +#if USE_LV2 + +using namespace calf_plugins; +using namespace dsp; +using namespace std; + +template LV2_Descriptor lv2_small_wrapper::descriptor; +template uint32_t lv2_small_wrapper::poly_port_types; + +namespace small_plugins +{ + +class filter_base: public null_small_audio_module +{ +public: + enum { in_signal, in_cutoff, in_resonance, in_count }; + enum { out_signal, out_count}; + float *ins[in_count]; + float *outs[out_count]; + static void port_info(plugin_info_iface *pii) + { + pii->audio_port("in", "In").input(); + pii->control_port("cutoff", "Cutoff", 1000).input().log_range(20, 20000); + pii->control_port("res", "Resonance", 0.707).input().log_range(0.707, 20); + pii->audio_port("out", "Out").output(); + } + dsp::biquad_d1 filter; + + void activate() { + filter.reset(); + } + inline void process_inner(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[out_signal][i] = filter.process(ins[in_signal][i]); + filter.sanitize(); + } +}; + +class lp_filter_audio_module: public filter_base +{ +public: + inline void process(uint32_t count) { + filter.set_lp_rbj(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), *ins[in_resonance], srate); + process_inner(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("lowpass12", "12dB/oct RBJ Lowpass", "lv2:LowpassPlugin"); + port_info(pii); + } +}; + +class hp_filter_audio_module: public filter_base +{ +public: + inline void process(uint32_t count) { + filter.set_hp_rbj(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), *ins[in_resonance], srate); + process_inner(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("highpass12", "12dB/oct RBJ Highpass", "lv2:HighpassPlugin"); + port_info(pii); + } +}; + +class bp_filter_audio_module: public filter_base +{ +public: + inline void process(uint32_t count) { + filter.set_bp_rbj(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), *ins[in_resonance], srate); + process_inner(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("bandpass6", "6dB/oct RBJ Bandpass", "lv2:BandpassPlugin"); + port_info(pii); + } +}; + +class br_filter_audio_module: public filter_base +{ +public: + inline void process(uint32_t count) { + filter.set_br_rbj(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), *ins[in_resonance], srate); + process_inner(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("notch6", "6dB/oct RBJ Bandpass", "lv2:FilterPlugin"); + port_info(pii); + } +}; + +class onepole_filter_base: public null_small_audio_module +{ +public: + enum { in_signal, in_cutoff, in_count }; + enum { out_signal, out_count}; + float *ins[in_count]; + float *outs[out_count]; + dsp::onepole filter; + static parameter_properties param_props[]; + + static void port_info(plugin_info_iface *pii) + { + pii->audio_port("In", "in").input(); + pii->control_port("Cutoff", "cutoff", 1000).input().log_range(20, 20000); + pii->audio_port("Out", "out").output(); + } + /// do not export mode and inertia as CVs, as those are settings and not parameters + void activate() { + filter.reset(); + } +}; + +class onepole_lp_filter_audio_module: public onepole_filter_base +{ +public: + void process(uint32_t count) { + filter.set_lp(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), srate); + for (uint32_t i = 0; i < count; i++) + outs[0][i] = filter.process_lp(ins[0][i]); + filter.sanitize(); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("lowpass6", "6dB/oct Lowpass Filter", "lv2:LowpassPlugin"); + port_info(pii); + } +}; + +class onepole_hp_filter_audio_module: public onepole_filter_base +{ +public: + void process(uint32_t count) { + filter.set_hp(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), srate); + for (uint32_t i = 0; i < count; i++) + outs[0][i] = filter.process_hp(ins[0][i]); + filter.sanitize(); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("highpass6", "6dB/oct Highpass Filter", "lv2:HighpassPlugin"); + port_info(pii); + } +}; + +class onepole_ap_filter_audio_module: public onepole_filter_base +{ +public: + void process(uint32_t count) { + filter.set_ap(clip(*ins[in_cutoff], 0.0001, 0.48 * srate), srate); + for (uint32_t i = 0; i < count; i++) + outs[0][i] = filter.process_ap(ins[0][i]); + filter.sanitize(); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("allpass", "1-pole 1-zero Allpass Filter", "lv2:AllpassPlugin"); + port_info(pii); + } +}; + +/// This works for 1 or 2 operands only... +template +class audio_operator_audio_module: public small_audio_module_base +{ +public: + static void port_info(plugin_info_iface *pii) + { + if (Inputs == 1) + pii->audio_port("in", "In", "").input(); + else + { + pii->audio_port("in_1", "In 1", "").input(); + pii->audio_port("in_2", "In 2", "").input(); + } + pii->audio_port("out", "Out", "").output(); + } +}; + +class min_audio_module: public audio_operator_audio_module<2> +{ +public: + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[0][i] = std::min(ins[0][i], ins[1][i]); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("min", "Minimum (A)", "kf:MathOperatorPlugin", "min"); + port_info(pii); + } +}; + +class max_audio_module: public audio_operator_audio_module<2> +{ +public: + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[0][i] = std::max(ins[0][i], ins[1][i]); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("max", "Maximum (A)", "kf:MathOperatorPlugin", "max"); + port_info(pii); + } +}; + +class minus_audio_module: public audio_operator_audio_module<2> +{ +public: + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[0][i] = ins[0][i] - ins[1][i]; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("minus", "Subtract (A)", "kf:MathOperatorPlugin", "-"); + port_info(pii); + } +}; + +class mul_audio_module: public audio_operator_audio_module<2> +{ +public: + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[0][i] = ins[0][i] * ins[1][i]; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("mul", "Multiply (A)", "kf:MathOperatorPlugin", "*"); + port_info(pii); + } +}; + +class neg_audio_module: public audio_operator_audio_module<1> +{ +public: + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + outs[0][i] = -ins[0][i]; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("neg", "Negative (A)", "kf:MathOperatorPlugin", "-"); + port_info(pii); + } +}; + +template struct polymorphic_process; + +template struct polymorphic_process +{ + static inline void run(float **ins, float **outs, uint32_t count, uint32_t poly_port_types) { + if (poly_port_types < 2) // control to control or audio to control + *outs[0] = T::process_single(*ins[0]); + else if (poly_port_types == 2) { + outs[0][0] = T::process_single(ins[0][0]); // same as above, but the index might not be 0 in later versions + for (uint32_t i = 1; i < count; i++) + outs[0][i] = outs[0][0]; + } + else { // audio to audio + for (uint32_t i = 0; i < count; i++) + outs[0][i] = T::process_single(ins[0][i]); + } + }; +}; + +template struct polymorphic_process +{ + static inline void run(float **ins, float **outs, uint32_t count, uint32_t poly_port_types) { + poly_port_types &= ~1; + if (poly_port_types < 4) // any to control + *outs[0] = T::process_single(*ins[0], *ins[1]); + else if (poly_port_types == 4) { // control+control to audio + outs[0][0] = T::process_single(*ins[0], *ins[1]); // same as above, but the index might not be 0 in later versions + for (uint32_t i = 1; i < count; i++) + outs[0][i] = outs[0][0]; + } + else { // {control+audio or audio+control or audio+audio} to audio + // use masks to force 0 for index for control ports + uint32_t mask1 = null_small_audio_module::port_audio_mask(0, poly_port_types); + uint32_t mask2 = null_small_audio_module::port_audio_mask(1, poly_port_types); + for (uint32_t i = 0; i < count; i++) + outs[0][i] = T::process_single(ins[0][i & mask1], ins[1][i & mask2]); + } + }; +}; + +/// This works for 1 or 2 operands only... +template +class control_operator_audio_module: public small_audio_module_base +{ +public: + using small_audio_module_base::ins; + using small_audio_module_base::outs; + using small_audio_module_base::poly_port_types; + static void port_info(plugin_info_iface *pii, control_port_info_iface *cports[Inputs + 1], float in1 = 0, float in2 = 0) + { + int idx = 0; + if (Inputs == 1) + cports[idx++] = &pii->control_port("in", "In", in1, "").polymorphic().poly_audio().input(); + else + { + cports[idx++] = &pii->control_port("in_1", "In 1", in1, "").polymorphic().poly_audio().input(); + cports[idx++] = &pii->control_port("in_2", "In 2", in2, "").polymorphic().poly_audio().input(); + } + cports[idx++] = &pii->control_port("out", "Out", 0, "").poly_audio().output(); + } + + template inline void do_process(uint32_t count) { + polymorphic_process::run(ins, outs, count, poly_port_types); + } +}; + +class minus_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return x - y; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("minus_c", "Subtract (C)", "kf:MathOperatorPlugin", "-"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + } +}; + +class mul_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return x * y; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("mul_c", "Multiply (C)", "kf:MathOperatorPlugin", "*"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + } +}; + +class neg_c_audio_module: public control_operator_audio_module<1> +{ +public: + static inline float process_single(float x) { + return -x; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("neg_c", "Negative (C)", "kf:MathOperatorPlugin", "-"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + } +}; + +class min_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return std::min(x, y); + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("min_c", "Minimum (C)", "kf:MathOperatorPlugin", "min"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + } +}; + +class max_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return std::max(x, y); + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("max_c", "Maximum (C)", "kf:MathOperatorPlugin", "max"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + } +}; + +class less_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return x < y; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("less_c", "Less than (C)", "kf:MathOperatorPlugin", "<"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + cports[2]->toggle(); + } +}; + +class level2edge_c_audio_module: public control_operator_audio_module<1> +{ +public: + bool last_value; + void activate() { + last_value = false; + } + void process(uint32_t count) { + *outs[0] = (*ins[0] > 0 && !last_value) ? 1.f : 0.f; + last_value = *ins[0] > 0; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("level2edge_c", "Level to edge (C)", "kf:BooleanPlugin"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + cports[0]->toggle(); + cports[1]->toggle().trigger(); + } +}; + +class int_c_audio_module: public control_operator_audio_module<1> +{ +public: + static inline float process_single(float x) { + return (int)x; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("int_c", "Integer value (C)", "kf:IntegerPlugin"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + cports[0]->integer(); + cports[1]->integer(); + } +}; + +class bitwise_op_c_module_base: public control_operator_audio_module<2> +{ +public: + static void port_info(plugin_info_iface *pii) + { + pii->control_port("in_1", "In 1", 0, "").polymorphic().poly_audio().integer().input(); + pii->control_port("in_2", "In 2", 0, "").polymorphic().poly_audio().integer().input(); + pii->control_port("out", "Out", 0, "").polymorphic().poly_audio().integer().output(); + } +}; +class bit_and_c_audio_module: public bitwise_op_c_module_base +{ +public: + static inline float process_single(float x, float y) { + return ((int)x) & ((int)y); + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("bit_and_c", "Bitwise AND (C)", "kf:IntegerPlugin"); + port_info(pii); + } +}; + +class bit_or_c_audio_module: public bitwise_op_c_module_base +{ +public: + static inline float process_single(float x, float y) { + return ((int)x) | ((int)y); + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("bit_or_c", "Bitwise OR (C)", "kf:IntegerPlugin"); + port_info(pii); + } +}; + +class bit_xor_c_audio_module: public bitwise_op_c_module_base +{ +public: + static inline float process_single(float x, float y) { + return ((int)x) ^ ((int)y); + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("bit_xor_c", "Bitwise XOR (C)", "kf:IntegerPlugin"); + port_info(pii); + } +}; + +class flipflop_c_audio_module: public control_operator_audio_module<1> +{ +public: + bool last_value, output; + void activate() { + last_value = false; + output = false; + } + void process(uint32_t count) { + if (*ins[0] > 0 && !last_value) + output = !output; + *outs[0] = output ? 1.f : 0.f; + last_value = *ins[0] > 0; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("flipflop_c", "T Flip-Flop (C)", "kf:BooleanPlugin"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + cports[0]->toggle().trigger(); + cports[1]->toggle(); + } +}; + +class logical_and_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return (x > 0 && y > 0) ? 1.f : 0.f; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("logical_and_c", "Logical AND (C)", "kf:BooleanPlugin", "&&"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + cports[0]->toggle(); + cports[1]->toggle(); + cports[2]->toggle(); + } +}; + +class logical_or_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return (x > 0 || y > 0) ? 1.f : 0.f; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("logical_or_c", "Logical OR (C)", "kf:BooleanPlugin", "||"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + cports[0]->toggle(); + cports[1]->toggle(); + cports[2]->toggle(); + } +}; + +class logical_xor_c_audio_module: public control_operator_audio_module<2> +{ +public: + static inline float process_single(float x, float y) { + return ((x > 0) != (y > 0)) ? 1.f : 0.f; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("logical_xor_c", "Logical XOR (C)", "kf:BooleanPlugin", "xor"); + control_port_info_iface *cports[3]; + port_info(pii, cports); + cports[0]->toggle(); + cports[1]->toggle(); + cports[2]->toggle(); + } +}; + +class logical_not_c_audio_module: public control_operator_audio_module<1> +{ +public: + static inline float process_single(float x) { + return (x <= 0) ? 1.f : 0.f; + } + void process(uint32_t count) { + do_process(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("logical_not_c", "Logical NOT (C)", "kf:BooleanPlugin", "!"); + control_port_info_iface *cports[2]; + port_info(pii, cports); + cports[0]->toggle(); + cports[1]->toggle(); + } +}; + +/// converter of trigger signals from audio to control rate +class trigger_a2c_audio_module: public null_small_audio_module +{ +public: + enum { in_count = 1, out_count = 1 }; + float *ins[in_count]; + float *outs[out_count]; + void process(uint32_t count) { + for (uint32_t i = 0; i < count; i++) + { + if (ins[0][i] > 0) + { + *outs[0] = 1.f; + return; + } + } + *outs[0] = 0.f; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("trigger_a2c", "Audio-to-control OR", "kf:BooleanPlugin", "ta2c"); + pii->audio_port("in", "In").input(); + pii->control_port("out", "Out", 0.f).output().toggle(); + } +}; + +/// Monostable multivibrator like 74121 or 74123, with reset input, progress output (0 to 1), "finished" signal, configurable to allow or forbid retriggering. +class timer_c_audio_module: public null_small_audio_module +{ +public: + enum { in_trigger, in_time, in_reset, in_allow_retrig, in_count }; + enum { out_running, out_finished, out_progress, out_count }; + float *ins[in_count]; + float *outs[out_count]; + bool running, finished, old_trigger; + double state; + + void activate() + { + state = 0.f; + running = false; + finished = false; + old_trigger = false; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("timer_c", "Timer (C)", "lv2:UtilityPlugin"); + pii->control_port("trigger", "Trigger", 0.f).input().toggle().trigger(); + pii->control_port("time", "Time", 0.f).input(); + pii->control_port("reset", "Reset", 0).input().toggle(); + pii->control_port("allow_retrig", "Allow retrig", 0).input().toggle(); + pii->control_port("running", "Running", 0.f).output().toggle(); + pii->control_port("finished", "Finished", 0.f).output().toggle(); + pii->control_port("progress", "Progress", 0.f).output().lin_range(0, 1); + } + void process(uint32_t count) + { + // This is a branch city, which is definitely a bad thing. + // Perhaps I'll add a bunch of __builtin_expect hints some day, but somebody would have to start using it first. + if (*ins[in_reset] > 0) + { + state = 0.0; + running = finished = false; + } + else + if (!old_trigger && *ins[in_trigger] > 0 && (!running || *ins[in_allow_retrig] > 0)) + { + state = 0.0; + running = true; + finished = false; + } + else + if (running) + { + float rate = (1.0 / std::max(0.0000001f, *ins[in_time])); + state += rate * odsr * count; + if (state >= 1.0) + { + running = false; + finished = true; + state = 1.0; + } + } + old_trigger = *ins[in_trigger] > 0; + *outs[out_running] = running ? 1.f : 0.f; + *outs[out_finished] = finished ? 1.f : 0.f; + *outs[out_progress] = state; + } +}; + +/// 4-input priority multiplexer - without inertia. Outputs the first input if gate_1 is TRUE, else second input if gate_2 is TRUE, else... else "Else" input +class prio_mux_c_audio_module: public null_small_audio_module +{ +public: + enum { in_in1, in_gate1, in_in2, in_gate2, in_in3, in_gate3, in_in4, in_gate4, in_else, in_count }; + enum { out_value, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("prio_mux_c", "Priority Multiplexer (C)", "kf:BooleanPlugin"); + for (int i = 1; i <= 4; i++) + { + stringstream numb; + numb << i; + string num = numb.str(); + pii->control_port("in_"+num, "In "+num, 0.f).input(); + pii->control_port("gate_"+num, "Gate "+num, 0.f).input().toggle(); + } + pii->control_port("in_else", "Else", 0.f).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + for (int i = 0; i < 4; i++) + { + if (*ins[i * 2 + in_gate1] > 0) + { + *outs[out_value] = *ins[i * 2 + in_in1]; + return; + } + } + *outs[out_value] = *ins[in_else]; + } +}; + +/// 8-input priority encoder - outputs the index of the first port whose value is >0. 'Any' output is set whenever any of gates is set (which tells +/// apart no inputs set and 0th input set). +template +class prio_enc_c_audio_module: public null_small_audio_module +{ +public: + enum { in_gate1, in_count = in_gate1 + N}; + enum { out_value, out_any, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + char buf[32], buf2[64]; + sprintf(buf, "prio_enc%d_c", N); + sprintf(buf2, "%d-input Priority Encoder (C)", N); + pii->names(buf, buf2, "kf:IntegerPlugin"); + for (int i = 0; i < N; i++) + { + stringstream numb; + numb << i; + string num = numb.str(); + pii->control_port("gate_"+num, "Gate "+num, 0.f).input().toggle(); + } + pii->control_port("out", "Out", -1).output().integer(); + pii->control_port("any", "Any", -1).output().toggle(); + } + void process(uint32_t count) + { + for (int i = 0; i < N; i++) + { + if (*ins[in_gate1 + i] > 0) + { + *outs[out_value] = i; + *outs[out_any] = 1; + return; + } + } + *outs[out_value] = 0; + *outs[out_any] = 0; + } +}; + +typedef prio_enc_c_audio_module<8> prio_enc8_c_audio_module; + +/// 8-input integer multiplexer, outputs the input selected by ((int)select input & 7) +template +class mux_c_audio_module: public null_small_audio_module +{ +public: + enum { in_select, in_in1, in_count = in_in1 + N}; + enum { out_value, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + char buf[32], buf2[64]; + sprintf(buf, "mux%d_c", N); + sprintf(buf2, "%d-input Multiplexer (C)", N); + pii->names(buf, buf2, "kf:IntegerPlugin"); + pii->control_port("select", "Select", 0.f).input().integer().lin_range(0, N - 1); + for (int i = 0; i < N; i++) + { + stringstream numb; + numb << i; + string num = numb.str(); + pii->control_port("in_"+num, "In "+num, 0.f).input(); + } + pii->control_port("out", "Out", -1).output(); + } + void process(uint32_t count) + { + *outs[out_value] = *ins[in_in1 + ((N - 1) & (int)*ins[in_select])]; + } +}; + +typedef mux_c_audio_module<4> mux4_c_audio_module; +typedef mux_c_audio_module<8> mux8_c_audio_module; +typedef mux_c_audio_module<16> mux16_c_audio_module; + +/// Linear-to-exponential mapper +class map_lin2exp_audio_module: public null_small_audio_module +{ +public: + enum { in_signal, in_from_min, in_from_max, in_to_min, in_to_max, in_count }; + enum { out_signal, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("lin2exp", "Lin-Exp Mapper", "lv2:UtilityPlugin"); + pii->control_port("in", "In", 0.f).input(); + pii->control_port("from_min", "Min (from)", 0).input(); + pii->control_port("from_max", "Max (from)", 1).input(); + pii->control_port("to_min", "Min (to)", 20).input(); + pii->control_port("to_max", "Max (to)", 20000).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + float normalized = (*ins[in_signal] - *ins[in_from_min]) / (*ins[in_from_max] - *ins[in_from_min]); + *outs[out_signal] = *ins[in_to_min] * pow(*ins[in_to_max] / *ins[in_to_min], normalized); + } +}; + +/// Schmitt trigger - http://en.wikipedia.org/wiki/Schmitt_trigger - also outputs a change signal (positive spike whenever output value is changed) +class schmitt_c_audio_module: public null_small_audio_module +{ +public: + enum { in_signal, in_low, in_high, in_count }; + enum { out_signal, out_change, out_count }; + float *ins[in_count]; + float *outs[out_count]; + bool state; + + void activate() + { + state = false; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("schmitt_c", "Schmitt Trigger (C)", "kf:BooleanPlugin"); + pii->control_port("in", "In", 0.f).input(); + pii->control_port("low", "Low threshold", 0).input(); + pii->control_port("high", "High threshold", 0.5).input(); + pii->control_port("out", "Out", 0.f).output(); + pii->control_port("change", "Change", 0.f).output(); + } + void process(uint32_t count) + { + float value = *ins[in_signal]; + bool new_state = state; + if (value <= *ins[in_low]) + new_state = false; + if (value >= *ins[in_high]) + new_state = true; + *outs[out_signal] = new_state ? 1.f : 0.f; + *outs[out_change] = (new_state != state) ? 1.f : 0.f; + state = new_state; + } +}; + +/// Stateless 'between' operator (lo <= in <= hi) +class between_c_audio_module: public null_small_audio_module +{ +public: + enum { in_signal, in_low, in_high, in_count }; + enum { out_signal, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("between_c", "Between (C)", "kf:MathOperatorPlugin"); + pii->control_port("in", "In", 0.f).input(); + pii->control_port("low", "Low threshold", 0).input(); + pii->control_port("high", "High threshold", 1).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + float value = *ins[in_signal]; + *outs[out_signal] = (value >= *ins[in_low] && value <= *ins[in_high]) ? 1.f : 0.f; + } +}; + +/// Clip to range +class clip_c_audio_module: public null_small_audio_module +{ +public: + enum { in_signal, in_min, in_max, in_count }; + enum { out_signal, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("clip_c", "Clip (C)", "kf:MathOperatorPlugin", "clip"); + pii->control_port("in", "In", 0.f).input(); + pii->control_port("min", "Min", 0).input(); + pii->control_port("max", "Max", 1).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + float value = *ins[in_signal]; + *outs[out_signal] = std::min(*ins[in_max], std::max(value, *ins[in_min])); + } +}; + +/// Two input control crossfader +class crossfader2_c_audio_module: public null_small_audio_module +{ +public: + enum { in_a, in_b, in_ctl, in_count }; + enum { out_value, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("crossfader2_c", "2-in crossfader (C)", "kf:MathOperatorPlugin", "xfC"); + pii->control_port("in_a", "In A", 0.f).input(); + pii->control_port("in_b", "In B", 0).input(); + pii->control_port("mix", "B in mix", 0.5).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + *outs[out_value] = *ins[in_a] + (*ins[in_b] - *ins[in_a]) * dsp::clip(*ins[in_ctl], 0.f, 1.f); + } +}; + +/// 2-input multiplexer (if-then-else) +class ifthenelse_c_audio_module: public null_small_audio_module +{ +public: + enum { in_if, in_then, in_else, in_count }; + enum { out_value, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("ifthenelse_c", "If-Then-Else (C)", "kf:BooleanPlugin", "if"); + pii->control_port("if", "If", 0.f).input().toggle(); + pii->control_port("then", "Then", 0).input(); + pii->control_port("else", "Else", 0).input(); + pii->control_port("out", "Out", 0.f).output(); + } + void process(uint32_t count) + { + *outs[out_value] = *ins[in_if] > 0 ? *ins[in_then] : *ins[in_else]; + } +}; + +/// Integer counter with definable ranges +class counter_c_audio_module: public null_small_audio_module +{ +public: + enum { in_clock, in_min, in_max, in_steps, in_reset, in_count }; + enum { out_value, out_carry, out_count }; + float *ins[in_count]; + float *outs[out_count]; + bool state; + int value; + + void activate() + { + state = false; + value = 0; + } + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("counter_c", "Counter (C)", "kf:IntegerPlugin", "cnt"); + pii->control_port("clock", "Clock", 0.f).input().toggle().trigger(); + pii->control_port("min", "Min", 0).input(); + pii->control_port("max", "Max", 15).input(); + pii->control_port("steps", "Steps", 16).input().integer(); + pii->control_port("reset", "Reset", 0).input().toggle(); + pii->control_port("out", "Out", 0.f).output(); + pii->control_port("carry", "Carry", 0.f).output().toggle().trigger(); + } + void process(uint32_t count) + { + // Yes, this is slower than it should be. I will bother optimizing it when someone starts actually using it. + if (*ins[in_reset] > 0 || *ins[in_steps] < 2.0) + { + state = false; + value = 0; + *outs[out_value] = *ins[in_min]; + *outs[out_carry] = 0.f; + return; + } + *outs[out_carry] = 0; + if (!state && *ins[in_clock] > 0) + { + value++; + state = true; + if (value >= (int)*ins[in_steps]) + { + value = 0; + *outs[out_carry] = 1; + } + } + else + state = *ins[in_clock] > 0; + *outs[out_value] = *ins[in_min] + (*ins[in_max] - *ins[in_min]) * value / (int)(*ins[in_steps] - 1); + } +}; + +/// Two input audio crossfader +class crossfader2_a_audio_module: public null_small_audio_module +{ +public: + enum { in_a, in_b, in_ctl, in_count }; + enum { out_value, out_count }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + pii->names("crossfader2_a", "2-in crossfader (A)", "kf:MathOperatorPlugin", "xfA"); + pii->audio_port("in_a", "In A").input(); + pii->audio_port("in_b", "In B").input(); + pii->audio_port("mix", "B in mix").input(); + pii->audio_port("out", "Out").output(); + } + void process(uint32_t count) + { + for (uint32_t i = 0; i < count; i++) + outs[out_value][i] = ins[in_a][i] + (ins[in_b][i] - ins[in_a][i]) * dsp::clip(ins[in_ctl][i], 0.f, 1.f); + } +}; + +/// Base class for LFOs with frequency and reset inputs +class freq_phase_lfo_base: public small_audio_module_base<2, 1> +{ +public: + enum { in_freq, in_reset }; + double phase; + inline void activate() + { + phase = 0; + } + static void port_info(plugin_info_iface *pii) + { + pii->control_port("freq", "Frequency", 1).input().log_range(0.02, 100); + pii->control_port("reset", "Reset", 0).input().toggle(); + pii->control_port("out", "Out", 0).output(); + } + inline void check_inputs() + { + if (*ins[in_reset]) + phase = 0; + } + inline void advance(uint32_t count) + { + phase += count * *ins[in_freq] * odsr; + if (phase >= 1.0) + phase = fmod(phase, 1.0); + } +}; + +class square_lfo_audio_module: public freq_phase_lfo_base +{ +public: + void process(uint32_t count) + { + check_inputs(); + *outs[0] = (phase < 0.5) ? -1 : +1; + advance(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("square_lfo", "Square LFO", "lv2:OscillatorPlugin"); + port_info(pii); + } +}; + +class saw_lfo_audio_module: public freq_phase_lfo_base +{ +public: + void process(uint32_t count) + { + check_inputs(); + *outs[0] = -1 + 2 * phase; + advance(count); + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("saw_lfo", "Saw LFO", "lv2:OscillatorPlugin"); + port_info(pii); + } +}; + +class pulse_lfo_audio_module: public freq_phase_lfo_base +{ +public: + inline void activate() + { + phase = 1.0; + } + void process(uint32_t count) + { + check_inputs(); + double oldphase = phase; + advance(count); + *outs[0] = (phase < oldphase) ? 1.f : 0.f; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("pulse_lfo", "Pulse LFO", "lv2:OscillatorPlugin"); + port_info(pii); + } +}; + +#define SMALL_OSC_TABLE_BITS 12 + +class freq_only_osc_base_common: public null_small_audio_module +{ +public: + typedef waveform_family waves_type; + enum { in_freq, in_count }; + enum { out_signal, out_count}; + enum { wave_size = 1 << SMALL_OSC_TABLE_BITS }; + float *ins[in_count]; + float *outs[out_count]; + waves_type *waves; + waveform_oscillator osc; + + /// Fill the array with the original, non-bandlimited, waveform + virtual void get_original_waveform(float data[wave_size]) = 0; + /// This function needs to be virtual to ensure a separate wave family for each class (but not each instance) + virtual waves_type *get_waves() = 0; + void activate() { + waves = get_waves(); + } + void process(uint32_t count) + { + osc.set_freq_odsr(*ins[in_freq], odsr); + osc.waveform = waves->get_level(osc.phasedelta); + if (osc.waveform) + { + for (uint32_t i = 0; i < count; i++) + outs[out_signal][i] = osc.get(); + } + else + dsp::zero(outs[out_signal], count); + } + static void port_info(plugin_info_iface *pii) + { + pii->control_port("freq", "Frequency", 440).input().log_range(20, 20000); + pii->audio_port("out", "Out").output(); + } + /// Generate a wave family (bandlimit levels) from the original wave + waves_type *create_waves() { + waves_type *ptr = new waves_type; + float source[wave_size]; + get_original_waveform(source); + bandlimiter bl; + ptr->make(bl, source); + return ptr; + } +}; + +template +class freq_only_osc_base: public freq_only_osc_base_common +{ + virtual waves_type *get_waves() { + static waves_type *waves = NULL; + if (!waves) + waves = create_waves(); + return waves; + } +}; + +class square_osc_audio_module: public freq_only_osc_base +{ +public: + virtual void get_original_waveform(float data[wave_size]) { + for (int i = 0; i < wave_size; i++) + data[i] = (i < wave_size / 2) ? +1 : -1; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("squareosc", "Square Oscillator", "lv2:OscillatorPlugin"); + port_info(pii); + } +}; + +class saw_osc_audio_module: public freq_only_osc_base +{ +public: + virtual void get_original_waveform(float data[wave_size]) { + for (int i = 0; i < wave_size; i++) + data[i] = (i * 2.0 / wave_size) - 1; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("sawosc", "Saw Oscillator", "lv2:OscillatorPlugin"); + port_info(pii); + } +}; + +class print_c_audio_module: public small_audio_module_base<1, 0> +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("print_c", "Print To Console (C)", "lv2:UtilityPlugin"); + pii->control_port("in", "In", 0).input(); + } + void process(uint32_t) + { + printf("%f\n", *ins[0]); + } +}; + +class print_e_audio_module: public small_audio_module_base<1, 0> +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("print_e", "Print To Console (E)", "lv2:UtilityPlugin"); + pii->event_port("in", "In").input(); + } + void dump(LV2_Event_Buffer *event_data) + { + event_port_read_iterator ei(event_data); + while(ei) + { + const lv2_event &event = *ei++; + printf("Event at %d.%d, type %d, size %d:", event.frames, event.subframes, (int)event.type, (int)event.size); + uint32_t size = std::min((uint16_t)16, event.size); + + for (uint32_t j = 0; j < size; j++) + printf(" %02X", (uint32_t)event.data[j]); + if (event.size > size) + printf("...\n"); + else + printf("\n"); + } + } + void process(uint32_t) + { + LV2_Event_Buffer *event_data = (LV2_Event_Buffer *)ins[0]; + dump(event_data); + } +}; + +class print_em_audio_module: public print_e_audio_module +{ +public: + LV2_Event_Buffer *events; + static void plugin_info(plugin_info_iface *pii) + { + pii->names("print_em", "Print To Console (EM)", "lv2:UtilityPlugin"); + pii->lv2_ttl("lv2:requiredFeature ;"); + pii->lv2_ttl("lv2:requiredFeature lv2ctx:MessageContext ;"); + pii->lv2_ttl("lv2ctx:requiredContext lv2ctx:MessageContext ;"); + pii->event_port("in", "In").input().lv2_ttl("lv2ctx:context lv2ctx:MessageContext ;"); + } + void process(uint32_t) + { + } + static uint32_t message_run(LV2_Handle instance, const void *valid_inputs, void *outputs_written) + { + print_em_audio_module *self = (print_em_audio_module *)instance; + if (lv2_contexts_port_is_valid(valid_inputs, 0)) + { + printf("message_run (events = %p, count = %d)\n", self->events, self->events->event_count); + self->dump(self->events); + } + return 0; + } + static void message_connect_port(LV2_Handle instance, uint32_t port, void* data) + { + print_em_audio_module *self = (print_em_audio_module *)instance; + printf("message_connect_port %d -> %p\n", port, data); + assert(!port); + self->events = (LV2_Event_Buffer *)data; + } + static inline const void *ext_data(const char *URI) { + static LV2MessageContext ctx_ext_data = { message_run, message_connect_port }; + printf("URI=%s\n", URI); + if (!strcmp(URI, LV2_CONTEXT_MESSAGE)) + { + return &ctx_ext_data; + } + return NULL; + } +}; + +class copy_em_audio_module: public small_audio_module_base<0, 0> +{ +public: + LV2_Event_Buffer *events_in, *events_out; + static void plugin_info(plugin_info_iface *pii) + { + pii->names("copy_em", "Message pass-through (EM)", "lv2:UtilityPlugin"); + pii->lv2_ttl("lv2:requiredFeature lv2ctx:MessageContext ;"); + pii->lv2_ttl("lv2:requiredFeature ;"); + pii->lv2_ttl("lv2:requiredContext lv2ctx:MessageContext ;"); + pii->event_port("in", "In").input().lv2_ttl("lv2ctx:context lv2ctx:MessageContext ;"); + pii->event_port("out", "Out").output().lv2_ttl("lv2ctx:context lv2ctx:MessageContext ;"); + } + void process(uint32_t) + { + } + static uint32_t message_run(LV2_Handle instance, const void *valid_inputs, void *outputs_written) + { + copy_em_audio_module *self = (copy_em_audio_module *)instance; + return self->message_run(valid_inputs, outputs_written); + } + uint32_t message_run(const void *inputs_written, void *outputs_written) + { + if (lv2_contexts_port_is_valid(inputs_written, 0)) + { + event_port_read_iterator ri(events_in); + event_port_write_iterator wi(events_out); + if (events_in->size > events_out->capacity) + { + printf("Buffer capacity exceeded!\n"); + return false; + } + while(ri) + { + const lv2_event &event = *ri++; + *wi++ = event; + } + if (events_in->event_count != 0) + { + lv2_contexts_set_port_valid(outputs_written, 1); + return 1; + } + } + lv2_contexts_unset_port_valid(outputs_written, 1); + return 0; + } + static void message_connect_port(LV2_Handle instance, uint32_t port, void* data) + { + copy_em_audio_module *self = (copy_em_audio_module *)instance; + printf("message_connect_port %d -> %p\n", port, data); + if (port == 0) self->events_in = (LV2_Event_Buffer *)data; + if (port == 1) self->events_out = (LV2_Event_Buffer *)data; + } + static inline const void *ext_data(const char *URI) { + static LV2MessageContext ctx_ext_data = { message_run, message_connect_port }; + if (!strcmp(URI, LV2_CONTEXT_MESSAGE)) + { + printf("URI=%s\n", URI); + return &ctx_ext_data; + } + return NULL; + } +}; + +template +class miditypefilter_m_audio_module: public midi_mixin > +{ +public: + static inline void extra_inputs(plugin_info_iface *pii) + { + } + static inline const char *plugin_symbol() { return Range::strings()[0]; } + static inline const char *plugin_name() { return Range::strings()[1]; } + static inline const char *port_symbol() { return Range::strings()[2]; } + static inline const char *port_name() { return Range::strings()[3]; } + static void plugin_info(plugin_info_iface *pii) + { + pii->names(Range::plugin_symbol(), Range::plugin_name(), "kf:MIDIPlugin"); + pii->event_port("in", "In").input(); + Range::extra_inputs(pii); + pii->event_port(Range::port_symbol(), Range::port_name()).output(); + pii->event_port("others", "Others").output(); + } + void process(uint32_t) + { + event_port_read_iterator ri((LV2_Event_Buffer *)this->ins[0]); + event_port_write_iterator wi((LV2_Event_Buffer *)this->outs[0]); + event_port_write_iterator wi2((LV2_Event_Buffer *)this->outs[1]); + while(ri) + { + const lv2_event &event = *ri++; + if (event.type == this->midi_event_type && event.size && Range::is_in_range(event.data, this->ins)) + *wi++ = event; + else + *wi2++ = event; + } + } +}; + +class notefilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0x80 && data[0] <= 0x9F; } + static inline const char **strings() { static const char *s[] = { "notefilter_m", "Note Filter", "note", "Note" }; return s;} +}; + +class pcfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0xA0 && data[0] <= 0xAF; } + static inline const char **strings() { static const char *s[] = { "pcfilter_m", "Program Change Filter", "pc", "PC" }; return s;} +}; + +class ccfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0xB0 && data[0] <= 0xBF; } + static inline const char **strings() { static const char *s[] = { "ccfilter_m", "Control Change Filter", "cc", "CC" }; return s;} +}; + +class pressurefilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0xC0 && data[0] <= 0xDF; } + static inline const char **strings() { static const char *s[] = { "pressurefilter_m", "Pressure Filter", "pressure", "Pressure" }; return s;} +}; + +class pitchbendfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0xE0 && data[0] <= 0xEF; } + static inline const char **strings() { static const char *s[] = { "pitchbendfilter_m", "Pitch Bend Filter", "pbend", "Pitch Bend" }; return s;} +}; + +class systemfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline bool is_in_range(const uint8_t *data, float **) { return data[0] >= 0xF0; } + static inline const char **strings() { static const char *s[] = { "systemfilter_m", "System Msg Filter", "system", "System" }; return s;} +}; + +class channelfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline void extra_inputs(plugin_info_iface *pii) + { + pii->control_port("min", "Min Channel", 1).input().integer().lin_range(1, 16); + pii->control_port("max", "Max Channel", 16).input().integer().lin_range(1, 16); + } + static inline bool is_in_range(const uint8_t *data, float **ins) { + int chnl = 1 + (data[0] & 0xF); + return data[0] < 0xF0 && chnl >= *ins[1] && chnl <= *ins[2]; + } + static inline const char **strings() { static const char *s[] = { "channelfilter_m", "Channel Range Filter", "range", "Range" }; return s;} +}; + +class keyfilter_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline void extra_inputs(plugin_info_iface *pii) + { + pii->control_port("min", "Min Note", 0).input().integer().lin_range(0, 127); + pii->control_port("max", "Max Note", 127).input().integer().lin_range(0, 127); + } + static inline bool is_in_range(const uint8_t *data, float **ins) { + // XXXKF doesn't handle polyphonic aftertouch + return (data[0] >= 0x80 && data[0] <= 0x9F) && data[0] >= *ins[1] && data[1] <= *ins[2]; + } + static inline const char **strings() { static const char *s[] = { "keyfilter_m", "Key Range Filter", "range", "Range" }; return s;} +}; + +class key_less_than_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline void extra_inputs(plugin_info_iface *pii) + { + pii->control_port("threshold", "Threshold", 60).input().integer().lin_range(0, 128); + } + static inline bool is_in_range(const uint8_t *data, float **ins) { + // XXXKF doesn't handle polyphonic aftertouch + return (data[0] >= 0x80 && data[0] <= 0x9F) && data[1] < *ins[1]; + } + static inline const char **strings() { static const char *s[] = { "key_less_than_m", "Key Less-Than Filter", "less", "Less" }; return s;} +}; + +class channel_less_than_m_audio_module: public miditypefilter_m_audio_module +{ +public: + static inline void extra_inputs(plugin_info_iface *pii) + { + pii->control_port("threshold", "Threshold", 10).input().integer().lin_range(1, 16); + } + static inline bool is_in_range(const uint8_t *data, float **ins) { + // XXXKF doesn't handle polyphonic aftertouch + return (data[0] < 0xF0) && (1 + (data[0] & 0xF)) < *ins[1]; + } + static inline const char **strings() { static const char *s[] = { "channel_less_than_m", "Channel Less-Than Filter", "less", "Less" }; return s;} +}; + +class transpose_m_audio_module: public midi_mixin > +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("transpose_m", "Transpose", "kf:MIDIPlugin"); + pii->event_port("in", "In").input(); + pii->control_port("transpose", "Transpose", 12).input().integer(); + pii->event_port("out", "Out").output(); + } + void process(uint32_t) + { + event_port_read_iterator ri((LV2_Event_Buffer *)ins[0]); + event_port_write_iterator wi((LV2_Event_Buffer *)outs[0]); + while(ri) + { + const lv2_event &event = *ri++; + if (event.type == this->midi_event_type && event.size == 3 && (event.data[0] >= 0x80 && event.data[0] <= 0x9F)) + { + int new_note = event.data[1] + (int)*ins[1]; + // ignore out-of-range notes + if (new_note >= 0 && new_note <= 127) + { + // it is not possible to create copies here because they are variable length and would "nicely" overwrite the stack + // so write the original event instead, and then modify the pitch + *wi = event; + wi->data[1] = new_note; + wi++; + } + } + else + *wi++ = event; + } + } +}; + +class setchannel_m_audio_module: public midi_mixin > +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("setchannel_m", "Set Channel", "kf:MIDIPlugin"); + pii->event_port("in", "In").input(); + pii->control_port("channel", "Channel", 1).input().integer().lin_range(1, 16); + pii->event_port("out", "Out").output(); + } + void process(uint32_t) + { + event_port_read_iterator ri((LV2_Event_Buffer *)ins[0]); + event_port_write_iterator wi((LV2_Event_Buffer *)outs[0]); + while(ri) + { + const lv2_event &event = *ri++; + if (event.type == this->midi_event_type && (event.data[0] >= 0x80 && event.data[0] <= 0xEF)) + { + *wi = event; + // modify channel number in the first byte + wi->data[0] = (wi->data[0] & 0xF0) | (((int)*ins[1] - 1) & 0xF); + wi++; + } + else + *wi++ = event; + } + } +}; + +class eventmerge_e_audio_module: public event_mixin > +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("eventmerge_e", "Event Merge (E)", "lv2:UtilityPlugin"); + pii->event_port("in_1", "In 1").input(); + pii->event_port("in_2", "In 2").input(); + pii->event_port("out", "Out").output(); + } + void process(uint32_t) + { + event_port_merge_iterator ri((const LV2_Event_Buffer *)ins[0], (const LV2_Event_Buffer *)ins[1]); + event_port_write_iterator wi((LV2_Event_Buffer *)outs[0]); + while(ri) + *wi++ = *ri++; + } +}; + +class print_a_audio_module: public small_audio_module_base<1, 0> +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("print_a", "Print To Console (A)", "lv2:UtilityPlugin"); + pii->audio_port("in", "In").input(); + } + void process(uint32_t) + { + printf("%f\n", *ins[0]); + } +}; + +template +class quadpower_base: public null_small_audio_module +{ +public: + enum { in_value, in_factor, in_count , out_count = 4 }; + float *ins[in_count]; + float *outs[out_count]; + + static void plugin_info(plugin_info_iface *pii) + { + const char *names[8] = {"xa", "x*a^1", "xaa", "x*a^2", "xaaa", "x*a^3", "xaaaa", "x*a^4" }; + if (audio) + pii->names("quadpower_a", "Quad Power (A)", "kf:MathOperatorPlugin"); + else + pii->names("quadpower_c", "Quad Power (C)", "kf:MathOperatorPlugin"); + if (audio) + pii->audio_port("x", "x").input(); + else + pii->control_port("x", "x", 1).input(); + pii->control_port("a", "a", 1).input(); + for (int i = 0; i < 8; i += 2) + if (audio) + pii->audio_port(names[i], names[i+1]).output(); + else + pii->control_port(names[i], names[i+1], 0).output(); + } +}; + +class quadpower_a_audio_module: public quadpower_base +{ +public: + void process(uint32_t count) + { + float a = *ins[in_factor]; + for (uint32_t i = 0; i < count; i++) + { + float x = ins[in_value][i]; + outs[0][i] = x * a; + outs[1][i] = x * a * a; + outs[2][i] = x * a * a * a; + outs[3][i] = x * a * a * a * a; + } + } +}; + +class quadpower_c_audio_module: public quadpower_base +{ +public: + void process(uint32_t) + { + float x = *ins[in_value]; + float a = *ins[in_factor]; + *outs[0] = x * a; + *outs[1] = x * a * a; + *outs[2] = x * a * a * a; + *outs[3] = x * a * a * a * a; + } +}; + +template +class inertia_c_module_base: public small_audio_module_base<3, 1> +{ +public: + enum { in_value, in_inertia, in_immediate }; + bool reset; + inertia state; + inertia_c_module_base() + : state(Ramp(1)) + {} + void activate() + { + reset = true; + } + static void port_info(plugin_info_iface *pii) + { + pii->control_port("in", "In", 0).input(); + pii->control_port("time", "Inertia time", 100).input(); + pii->control_port("reset", "Reset", 0).input().toggle().trigger(); + pii->control_port("out", "Out", 0).output(); + } + void process(uint32_t count) + { + float value = *ins[in_value]; + if (reset || *ins[in_immediate] > 0) + { + *outs[0] = value; + state.set_now(value); + reset = false; + } + else + { + if (value != state.get_last()) + { + state.ramp.set_length(dsp::clip((int)(srate * 0.001 * *ins[in_inertia]), 1, 10000000)); + } + state.set_inertia(value); + *outs[0] = state.get_last(); + if (count) + state.step_many(count); + } + } +}; + +class linear_inertia_c_audio_module: public inertia_c_module_base +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("linear_inertia_c", "Linear Inertia (C)", "lv2:FilterPlugin"); + port_info(pii); + } +}; + +class exp_inertia_c_audio_module: public inertia_c_module_base +{ +public: + static void plugin_info(plugin_info_iface *pii) + { + pii->names("exp_inertia_c", "Exponential Inertia (C)", "lv2:FilterPlugin"); + port_info(pii); + } +}; + +class sample_hold_base: public small_audio_module_base<2, 1> +{ +public: + enum { in_value, in_gate }; + static void port_info(plugin_info_iface *pii, const char *clock_symbol, const char *clock_name) + { + pii->control_port("in", "In", 0).input(); + pii->control_port(clock_symbol, clock_name, 0).input().toggle().trigger(); + pii->control_port("out", "Out", 0).output(); + } +}; + +class sample_hold_edge_c_audio_module: public sample_hold_base +{ +public: + bool prev_clock; + float value; + void activate() + { + prev_clock = false; + value = 0; + } + void process(uint32_t count) + { + if (!prev_clock && *ins[in_gate] > 0) + value = *ins[in_value]; + prev_clock = *ins[in_gate] > 0; + *outs[0] = value; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("sample_hold_edge", "Sample&Hold (Edge, C)", "lv2:UtilityPlugin"); + port_info(pii, "clock", "Clock"); + } +}; + +class sample_hold_level_c_audio_module: public sample_hold_base +{ +public: + float value; + void activate() + { + value = 0; + } + void process(uint32_t count) + { + if (*ins[in_gate] > 0) + value = *ins[in_value]; + *outs[0] = value; + } + static void plugin_info(plugin_info_iface *pii) + { + pii->names("sample_hold_level", "Sample&Hold (Level, C)", "lv2:UtilityPlugin"); + port_info(pii, "gate", "Gate"); + } +}; + +class msgread_e_audio_module: public message_mixin > +{ +public: + uint32_t set_float_msg, float_type; + static void plugin_info(plugin_info_iface *pii) + { + pii->names("msgread_e", "Msg Read", "lv2:UtilityPlugin"); + pii->has_gui(); + pii->event_port("in", "In").input(); + pii->control_port("out", "Out", 0).output(); + } + virtual void map_uris() + { + message_mixin >::map_uris(); + set_float_msg = map_uri("http://lv2plug.in/ns/dev/msg", "http://foltman.com/garbage/setFloat"); + float_type = map_uri("http://lv2plug.in/ns/dev/types", "http://lv2plug.in/ns/dev/types#float"); + } + void process(uint32_t count) + { + event_port_read_iterator ri((const LV2_Event_Buffer *)ins[0]); + while(ri) + { + const lv2_event *event = &*ri++; + if (event->type == message_event_type) + { + struct payload { + uint32_t selector; + uint32_t serial_no; + uint32_t data_size; + uint32_t parg_count; + uint32_t data_type; + float data_value; + uint32_t narg_count; + }; + const payload *p = (const payload *)event->data; + if (p->selector == set_float_msg) { + assert(p->parg_count == 1); + assert(p->data_size == 16); + assert(p->data_type == float_type); + *outs[0] = p->data_value; + assert(p->narg_count == 0); // this is just for testing - passing + } + } + } + } +}; + +}; + +#define PER_SMALL_MODULE_ITEM(name, id) SMALL_WRAPPERS(name, id) +#include + +const LV2_Descriptor *calf_plugins::lv2_small_descriptor(uint32_t index) +{ + #define PER_SMALL_MODULE_ITEM(name, id) if (!(index--)) return &::lv2_small_##name.descriptor; + #include + return NULL; +} +#endif +#endif + +void calf_plugins::get_all_small_plugins(plugin_list_info_iface *iface) +{ +#if USE_LV2 + #define PER_SMALL_MODULE_ITEM(name, id) { plugin_info_iface *pii = &iface->plugin(id); small_plugins::name##_audio_module::plugin_info(pii); pii->finalize(); } + #include +#endif +} + diff --git a/plugins/ladspa_effect/calf/monosynth.cpp b/plugins/ladspa_effect/calf/monosynth.cpp new file mode 100644 index 000000000..ef64c6f99 --- /dev/null +++ b/plugins/ladspa_effect/calf/monosynth.cpp @@ -0,0 +1,663 @@ +/* Calf DSP Library + * Example audio modules - monosynth + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include +#if USE_JACK +#include +#endif +#include +#include + +using namespace dsp; +using namespace calf_plugins; +using namespace std; + +float silence[4097]; + +static const char *monosynth_mod_src_names[] = { + "None", + "Velocity", + "Pressure", + "ModWheel", + "Envelope", + "LFO", + NULL +}; + +static const char *monosynth_mod_dest_names[] = { + "None", + "Attenuation", + "Osc Mix Ratio (%)", + "Cutoff [ct]", + "Resonance", + "O1: Detune [ct]", + "O2: Detune [ct]", + "O1: PW (%)", + "O2: PW (%)", + NULL +}; + +monosynth_audio_module::monosynth_audio_module() +: mod_matrix(mod_matrix_data, mod_matrix_slots, monosynth_mod_src_names, monosynth_mod_dest_names) +, inertia_cutoff(1) +, inertia_pitchbend(1) +, inertia_pressure(64) +{ +} + +void monosynth_audio_module::activate() { + running = false; + output_pos = 0; + queue_note_on = -1; + stop_count = 0; + inertia_pitchbend.set_now(1.f); + lfo_bend = 1.0; + modwheel_value = 0.f; + modwheel_value_int = 0; + inertia_cutoff.set_now(*params[par_cutoff]); + inertia_pressure.set_now(0); + filter.reset(); + filter2.reset(); + stack.clear(); + last_pwshift1 = last_pwshift2 = 0; +} + +waveform_family *monosynth_audio_module::waves; + +void monosynth_audio_module::precalculate_waves(progress_report_iface *reporter) +{ + float data[1 << MONOSYNTH_WAVE_BITS]; + bandlimiter bl; + + if (waves) + return; + + static waveform_family waves_data[wave_count]; + waves = waves_data; + + enum { S = 1 << MONOSYNTH_WAVE_BITS, HS = S / 2, QS = S / 4, QS3 = 3 * QS }; + float iQS = 1.0 / QS; + + if (reporter) + reporter->report_progress(0, "Precalculating waveforms"); + + // yes these waves don't have really perfect 1/x spectrum because of aliasing + // (so what?) + for (int i = 0 ; i < HS; i++) + data[i] = (float)(i * 1.0 / HS), + data[i + HS] = (float)(i * 1.0 / HS - 1.0f); + waves[wave_saw].make(bl, data); + + // this one is dummy, fake and sham, we're using a difference of two sawtooths for square wave due to PWM + for (int i = 0 ; i < S; i++) + data[i] = (float)(i < HS ? -1.f : 1.f); + waves[wave_sqr].make(bl, data, 4); + + for (int i = 0 ; i < S; i++) + data[i] = (float)(i < (64 * S / 2048)? -1.f : 1.f); + waves[wave_pulse].make(bl, data); + + for (int i = 0 ; i < S; i++) + data[i] = (float)sin(i * M_PI / HS); + waves[wave_sine].make(bl, data); + + for (int i = 0 ; i < QS; i++) { + data[i] = i * iQS, + data[i + QS] = 1 - i * iQS, + data[i + HS] = - i * iQS, + data[i + QS3] = -1 + i * iQS; + } + waves[wave_triangle].make(bl, data); + + for (int i = 0, j = 1; i < S; i++) { + data[i] = -1 + j * 1.0 / HS; + if (i == j) + j *= 2; + } + waves[wave_varistep].make(bl, data); + + for (int i = 0; i < S; i++) { + data[i] = (min(1.f, (float)(i / 64.f))) * (1.0 - i * 1.0 / S) * (-1 + fmod (i * i * 8/ (S * S * 1.0), 2.0)); + } + waves[wave_skewsaw].make(bl, data); + for (int i = 0; i < S; i++) { + data[i] = (min(1.f, (float)(i / 64.f))) * (1.0 - i * 1.0 / S) * (fmod (i * i * 8/ (S * S * 1.0), 2.0) < 1.0 ? -1.0 : +1.0); + } + waves[wave_skewsqr].make(bl, data); + + if (reporter) + reporter->report_progress(50, "Precalculating waveforms"); + + for (int i = 0; i < S; i++) { + if (i < QS3) { + float p = i * 1.0 / QS3; + data[i] = sin(M_PI * p * p * p); + } else { + float p = (i - QS3 * 1.0) / QS; + data[i] = -0.5 * sin(3 * M_PI * p * p); + } + } + waves[wave_test1].make(bl, data); + for (int i = 0; i < S; i++) { + data[i] = exp(-i * 1.0 / HS) * sin(i * M_PI / HS) * cos(2 * M_PI * i / HS); + } + normalize_waveform(data, S); + waves[wave_test2].make(bl, data); + for (int i = 0; i < S; i++) { + //int ii = (i < HS) ? i : S - i; + int ii = HS; + data[i] = (ii * 1.0 / HS) * sin(i * 3 * M_PI / HS + 2 * M_PI * sin(M_PI / 4 + i * 4 * M_PI / HS)) * sin(i * 5 * M_PI / HS + 2 * M_PI * sin(M_PI / 8 + i * 6 * M_PI / HS)); + } + waves[wave_test3].make(bl, data); + for (int i = 0; i < S; i++) { + data[i] = sin(i * 2 * M_PI / HS + sin(i * 2 * M_PI / HS + 0.5 * M_PI * sin(i * 18 * M_PI / HS)) * sin(i * 1 * M_PI / HS + 0.5 * M_PI * sin(i * 11 * M_PI / HS))); + } + waves[wave_test4].make(bl, data); + for (int i = 0; i < S; i++) { + data[i] = sin(i * 2 * M_PI / HS + 0.2 * M_PI * sin(i * 13 * M_PI / HS) + 0.1 * M_PI * sin(i * 37 * M_PI / HS)) * sin(i * M_PI / HS + 0.2 * M_PI * sin(i * 15 * M_PI / HS)); + } + waves[wave_test5].make(bl, data); + for (int i = 0; i < S; i++) { + if (i < HS) + data[i] = sin(i * 2 * M_PI / HS); + else + if (i < 3 * S / 4) + data[i] = sin(i * 4 * M_PI / HS); + else + if (i < 7 * S / 8) + data[i] = sin(i * 8 * M_PI / HS); + else + data[i] = sin(i * 8 * M_PI / HS) * (S - i) / (S / 8); + } + waves[wave_test6].make(bl, data); + for (int i = 0; i < S; i++) { + int j = i >> (MONOSYNTH_WAVE_BITS - 11); + data[i] = (j ^ 0x1D0) * 1.0 / HS - 1; + } + waves[wave_test7].make(bl, data); + for (int i = 0; i < S; i++) { + int j = i >> (MONOSYNTH_WAVE_BITS - 11); + data[i] = -1 + 0.66 * (3 & ((j >> 8) ^ (j >> 10) ^ (j >> 6))); + } + waves[wave_test8].make(bl, data); + if (reporter) + reporter->report_progress(100, ""); + +} + +bool monosynth_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + monosynth_audio_module::precalculate_waves(NULL); + // printf("get_graph %d %p %d wave1=%d wave2=%d\n", index, data, points, wave1, wave2); + if (index == par_wave1 || index == par_wave2) { + if (subindex) + return false; + enum { S = 1 << MONOSYNTH_WAVE_BITS }; + float value = *params[index]; + int wave = dsp::clip(dsp::fastf2i_drm(value), 0, (int)wave_count - 1); + + uint32_t shift = index == par_wave1 ? last_pwshift1 : last_pwshift2; + if (!running) + shift = (int32_t)(0x78000000 * (*params[index == par_wave1 ? par_pw1 : par_pw2])); + int flag = (wave == wave_sqr); + + shift = (flag ? S/2 : 0) + (shift >> (32 - MONOSYNTH_WAVE_BITS)); + int sign = flag ? -1 : 1; + if (wave == wave_sqr) + wave = wave_saw; + float *waveform = waves[wave].original; + for (int i = 0; i < points; i++) + data[i] = (sign * waveform[i * S / points] + waveform[(i * S / points + shift) & (S - 1)]) / (sign == -1 ? 1 : 2); + return true; + } + if (index == par_filtertype) { + if (!running) + return false; + if (subindex > (is_stereo_filter() ? 1 : 0)) + return false; + for (int i = 0; i < points; i++) + { + typedef complex cfloat; + double freq = 20.0 * pow (20000.0 / 20.0, i * 1.0 / points); + + dsp::biquad_d1_lerp &f = subindex ? filter2 : filter; + float level = f.freq_gain(freq, srate); + if (!is_stereo_filter()) + level *= filter2.freq_gain(freq, srate); + level *= fgain; + + data[i] = log(level) / log(1024.0) + 0.5; + } + return true; + } + return get_static_graph(index, subindex, *params[index], data, points, context); +} + +void monosynth_audio_module::calculate_buffer_oscs(float lfo) +{ + int flag1 = (wave1 == wave_sqr); + int flag2 = (wave2 == wave_sqr); + int32_t shift1 = last_pwshift1; + int32_t shift2 = last_pwshift2; + int32_t shift_target1 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw1] + lfo * *params[par_lfopw] + 0.01f * moddest[moddest_o1pw])); + int32_t shift_target2 = (int32_t)(0x78000000 * dsp::clip11(*params[par_pw2] + lfo * *params[par_lfopw] + 0.01f * moddest[moddest_o2pw])); + int32_t shift_delta1 = ((shift_target1 >> 1) - (last_pwshift1 >> 1)) >> (step_shift - 1); + int32_t shift_delta2 = ((shift_target2 >> 1) - (last_pwshift2 >> 1)) >> (step_shift - 1); + last_lfov = lfo; + last_pwshift1 = shift_target1; + last_pwshift2 = shift_target2; + + shift1 += (flag1 << 31); + shift2 += (flag2 << 31); + float mix1 = 1 - 2 * flag1, mix2 = 1 - 2 * flag2; + + float new_xfade = dsp::clip(xfade + 0.01f * moddest[moddest_oscmix], 0.f, 1.f); + float cur_xfade = last_xfade; + float xfade_step = (new_xfade - cur_xfade) * (1.0 / step_size); + + for (uint32_t i = 0; i < step_size; i++) + { + float osc1val = osc1.get_phaseshifted(shift1, mix1); + float osc2val = osc2.get_phaseshifted(shift2, mix2); + float wave = osc1val + (osc2val - osc1val) * cur_xfade; + buffer[i] = wave; + shift1 += shift_delta1; + shift2 += shift_delta2; + cur_xfade += xfade_step; + } + last_xfade = new_xfade; +} + +void monosynth_audio_module::calculate_buffer_ser() +{ + filter.big_step(1.0 / step_size); + filter2.big_step(1.0 / step_size); + for (uint32_t i = 0; i < step_size; i++) + { + float wave = buffer[i] * fgain; + wave = filter.process(wave); + wave = filter2.process(wave); + buffer[i] = wave; + fgain += fgain_delta; + } +} + +void monosynth_audio_module::calculate_buffer_single() +{ + filter.big_step(1.0 / step_size); + for (uint32_t i = 0; i < step_size; i++) + { + float wave = buffer[i] * fgain; + wave = filter.process(wave); + buffer[i] = wave; + fgain += fgain_delta; + } +} + +void monosynth_audio_module::calculate_buffer_stereo() +{ + filter.big_step(1.0 / step_size); + filter2.big_step(1.0 / step_size); + for (uint32_t i = 0; i < step_size; i++) + { + float wave1 = buffer[i] * fgain; + float wave2 = phaseshifter.process_ap(wave1); + buffer[i] = fgain * filter.process(wave1); + buffer2[i] = fgain * filter2.process(wave2); + fgain += fgain_delta; + } +} + +void monosynth_audio_module::lookup_waveforms() +{ + osc1.waveform = waves[wave1 == wave_sqr ? wave_saw : wave1].get_level(osc1.phasedelta); + osc2.waveform = waves[wave2 == wave_sqr ? wave_saw : wave2].get_level(osc2.phasedelta); + if (!osc1.waveform) osc1.waveform = silence; + if (!osc2.waveform) osc2.waveform = silence; + prev_wave1 = wave1; + prev_wave2 = wave2; +} + +void monosynth_audio_module::delayed_note_on() +{ + force_fadeout = false; + stop_count = 0; + porta_time = 0.f; + start_freq = freq; + target_freq = freq = 440 * pow(2.0, (queue_note_on - 69) / 12.0); + velocity = queue_vel; + ampctl = 1.0 + (queue_vel - 1.0) * *params[par_vel2amp]; + fltctl = 1.0 + (queue_vel - 1.0) * *params[par_vel2filter]; + set_frequency(); + lookup_waveforms(); + lfo_clock = 0.f; + + if (!running) + { + if (legato >= 2) + porta_time = -1.f; + last_xfade = xfade; + osc1.reset(); + osc2.reset(); + filter.reset(); + filter2.reset(); + lfo.reset(); + switch((int)*params[par_oscmode]) + { + case 1: + osc2.phase = 0x80000000; + break; + case 2: + osc2.phase = 0x40000000; + break; + case 3: + osc1.phase = osc2.phase = 0x40000000; + break; + case 4: + osc1.phase = 0x40000000; + osc2.phase = 0xC0000000; + break; + case 5: + // rand() is crap, but I don't have any better RNG in Calf yet + osc1.phase = rand() << 16; + osc2.phase = rand() << 16; + break; + default: + break; + } + envelope.note_on(); + running = true; + } + if (legato >= 2 && !gate) + porta_time = -1.f; + gate = true; + stopping = false; + if (!(legato & 1) || envelope.released()) { + envelope.note_on(); + } + envelope.advance(); + queue_note_on = -1; + float modsrc[modsrc_count] = { 1, velocity, inertia_pressure.get_last(), modwheel_value, 0, 0.5+0.5*last_lfov}; + calculate_modmatrix(moddest, moddest_count, modsrc); +} + +void monosynth_audio_module::set_sample_rate(uint32_t sr) { + srate = sr; + crate = sr / step_size; + odcr = (float)(1.0 / crate); + phaseshifter.set_ap(1000.f, sr); + fgain = 0.f; + fgain_delta = 0.f; + inertia_cutoff.ramp.set_length(crate / 30); // 1/30s + inertia_pitchbend.ramp.set_length(crate / 30); // 1/30s +} + +void monosynth_audio_module::calculate_step() +{ + if (queue_note_on != -1) + delayed_note_on(); + else if (stopping) + { + running = false; + dsp::zero(buffer, step_size); + if (is_stereo_filter()) + dsp::zero(buffer2, step_size); + envelope.advance(); + return; + } + lfo.set_freq(*params[par_lforate], crate); + float porta_total_time = *params[par_portamento] * 0.001f; + + if (porta_total_time >= 0.00101f && porta_time >= 0) { + // XXXKF this is criminal, optimize! + float point = porta_time / porta_total_time; + if (point >= 1.0f) { + freq = target_freq; + porta_time = -1; + } else { + freq = start_freq + (target_freq - start_freq) * point; + // freq = start_freq * pow(target_freq / start_freq, point); + porta_time += odcr; + } + } + float lfov = lfo.get() * std::min(1.0f, lfo_clock / *params[par_lfodelay]); + lfov = lfov * dsp::lerp(1.f, modwheel_value, *params[par_mwhl_lfo]); + lfo_clock += odcr; + if (fabs(*params[par_lfopitch]) > small_value()) + lfo_bend = pow(2.0f, *params[par_lfopitch] * lfov * (1.f / 1200.0f)); + inertia_pitchbend.step(); + set_frequency(); + envelope.advance(); + float env = envelope.value; + + // mod matrix + // this should be optimized heavily; I think I'll do it when MIDI in Ardour 3 gets stable :> + float modsrc[modsrc_count] = { 1, velocity, inertia_pressure.get(), modwheel_value, env, 0.5+0.5*lfov}; + calculate_modmatrix(moddest, moddest_count, modsrc); + + inertia_cutoff.set_inertia(*params[par_cutoff]); + cutoff = inertia_cutoff.get() * pow(2.0f, (lfov * *params[par_lfofilter] + env * fltctl * *params[par_envmod] + moddest[moddest_cutoff]) * (1.f / 1200.f)); + if (*params[par_keyfollow] > 0.01f) + cutoff *= pow(freq / 264.f, *params[par_keyfollow]); + cutoff = dsp::clip(cutoff , 10.f, 18000.f); + float resonance = *params[par_resonance]; + float e2r = *params[par_envtores]; + float e2a = *params[par_envtoamp]; + resonance = resonance * (1 - e2r) + (0.7 + (resonance - 0.7) * env * env) * e2r + moddest[moddest_resonance]; + float cutoff2 = dsp::clip(cutoff * separation, 10.f, 18000.f); + float newfgain = 0.f; + if (filter_type != last_filter_type) + { + filter.y2 = filter.y1 = filter.x2 = filter.x1 = filter.y1; + filter2.y2 = filter2.y1 = filter2.x2 = filter2.x1 = filter2.y1; + last_filter_type = filter_type; + } + switch(filter_type) + { + case flt_lp12: + filter.set_lp_rbj(cutoff, resonance, srate); + filter2.set_null(); + newfgain = min(0.7f, 0.7f / resonance) * ampctl; + break; + case flt_hp12: + filter.set_hp_rbj(cutoff, resonance, srate); + filter2.set_null(); + newfgain = min(0.7f, 0.7f / resonance) * ampctl; + break; + case flt_lp24: + filter.set_lp_rbj(cutoff, resonance, srate); + filter2.set_lp_rbj(cutoff2, resonance, srate); + newfgain = min(0.5f, 0.5f / resonance) * ampctl; + break; + case flt_lpbr: + filter.set_lp_rbj(cutoff, resonance, srate); + filter2.set_br_rbj(cutoff2, 1.0 / resonance, srate); + newfgain = min(0.5f, 0.5f / resonance) * ampctl; + break; + case flt_hpbr: + filter.set_hp_rbj(cutoff, resonance, srate); + filter2.set_br_rbj(cutoff2, 1.0 / resonance, srate); + newfgain = min(0.5f, 0.5f / resonance) * ampctl; + break; + case flt_2lp12: + filter.set_lp_rbj(cutoff, resonance, srate); + filter2.set_lp_rbj(cutoff2, resonance, srate); + newfgain = min(0.7f, 0.7f / resonance) * ampctl; + break; + case flt_bp6: + filter.set_bp_rbj(cutoff, resonance, srate); + filter2.set_null(); + newfgain = ampctl; + break; + case flt_2bp6: + filter.set_bp_rbj(cutoff, resonance, srate); + filter2.set_bp_rbj(cutoff2, resonance, srate); + newfgain = ampctl; + break; + } + float aenv = env; + if (*params[par_envtoamp] > 0.f) + newfgain *= 1.0 - (1.0 - aenv) * e2a; + if (moddest[moddest_attenuation] != 0.f) + newfgain *= dsp::clip(1 - moddest[moddest_attenuation] * moddest[moddest_attenuation], 0.f, 1.f); + fgain_delta = (newfgain - fgain) * (1.0 / step_size); + calculate_buffer_oscs(lfov); + switch(filter_type) + { + case flt_lp24: + case flt_lpbr: + case flt_hpbr: // Oomek's wish + calculate_buffer_ser(); + break; + case flt_lp12: + case flt_hp12: + case flt_bp6: + calculate_buffer_single(); + break; + case flt_2lp12: + case flt_2bp6: + calculate_buffer_stereo(); + break; + } + if ((envelope.state == adsr::STOP && !gate) || force_fadeout || (envelope.state == adsr::RELEASE && *params[par_envtoamp] <= 0.f)) + { + enum { ramp = step_size * 4 }; + for (int i = 0; i < step_size; i++) + buffer[i] *= (ramp - i - stop_count) * (1.0f / ramp); + if (is_stereo_filter()) + for (int i = 0; i < step_size; i++) + buffer2[i] *= (ramp - i - stop_count) * (1.0f / ramp); + stop_count += step_size; + if (stop_count >= ramp) + stopping = true; + } +} + +void monosynth_audio_module::note_on(int note, int vel) +{ + queue_note_on = note; + last_key = note; + queue_vel = vel / 127.f; + stack.push(note); +} + +void monosynth_audio_module::note_off(int note, int vel) +{ + stack.pop(note); + // If releasing the currently played note, try to get another one from note stack. + if (note == last_key) { + if (stack.count()) + { + last_key = note = stack.nth(stack.count() - 1); + start_freq = freq; + target_freq = freq = dsp::note_to_hz(note); + porta_time = 0; + set_frequency(); + if (!(legato & 1)) { + envelope.note_on(); + stopping = false; + running = true; + } + return; + } + gate = false; + envelope.note_off(); + } +} + +void monosynth_audio_module::channel_pressure(int value) +{ + inertia_pressure.set_inertia(value * (1.0 / 127.0)); +} + +void monosynth_audio_module::control_change(int controller, int value) +{ + switch(controller) + { + case 1: + modwheel_value_int = (modwheel_value_int & 127) | (value << 7); + modwheel_value = modwheel_value_int / 16383.0; + break; + case 33: + modwheel_value_int = (modwheel_value_int & (127 << 7)) | value; + modwheel_value = modwheel_value_int / 16383.0; + break; + case 120: // all sounds off + force_fadeout = true; + // fall through + case 123: // all notes off + gate = false; + queue_note_on = -1; + envelope.note_off(); + stack.clear(); + break; + } +} + +void monosynth_audio_module::deactivate() +{ + gate = false; + running = false; + stopping = false; + envelope.reset(); + stack.clear(); +} + +uint32_t monosynth_audio_module::process(uint32_t offset, uint32_t nsamples, uint32_t inputs_mask, uint32_t outputs_mask) { + if (!running && queue_note_on == -1) { + for (uint32_t i = 0; i < nsamples / step_size; i++) + envelope.advance(); + return 0; + } + uint32_t op = offset; + uint32_t op_end = offset + nsamples; + while(op < op_end) { + if (output_pos == 0) { + if (running || queue_note_on != -1) + calculate_step(); + else { + envelope.advance(); + dsp::zero(buffer, step_size); + } + } + if(op < op_end) { + uint32_t ip = output_pos; + uint32_t len = std::min(step_size - output_pos, op_end - op); + if (is_stereo_filter()) + for(uint32_t i = 0 ; i < len; i++) { + float vol = master.get(); + outs[0][op + i] = buffer[ip + i] * vol, + outs[1][op + i] = buffer2[ip + i] * vol; + } + else + for(uint32_t i = 0 ; i < len; i++) + outs[0][op + i] = outs[1][op + i] = buffer[ip + i] * master.get(); + op += len; + output_pos += len; + if (output_pos == step_size) + output_pos = 0; + } + } + + return 3; +} + diff --git a/plugins/ladspa_effect/calf/organ.cpp b/plugins/ladspa_effect/calf/organ.cpp new file mode 100644 index 000000000..661b73b15 --- /dev/null +++ b/plugins/ladspa_effect/calf/organ.cpp @@ -0,0 +1,857 @@ +/* Calf DSP Library + * Example audio modules - organ + * + * Copyright (C) 2001-2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#if USE_JACK +#include +#endif +#include +#include +#include + +using namespace std; +using namespace dsp; +using namespace calf_plugins; + +////////////////////////////////////////////////////////////////////////////////////////////////////////// + +bool organ_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) +{ + if (index == par_master) { + organ_voice_base::precalculate_waves(progress_report); + if (subindex) + return false; + float *waveforms[9]; + int S[9], S2[9]; + enum { small_waves = organ_voice_base::wave_count_small}; + for (int i = 0; i < 9; i++) + { + int wave = dsp::clip((int)(parameters->waveforms[i]), 0, (int)organ_voice_base::wave_count - 1); + if (wave >= small_waves) + { + waveforms[i] = organ_voice_base::get_big_wave(wave - small_waves).original; + S[i] = ORGAN_BIG_WAVE_SIZE; + S2[i] = ORGAN_WAVE_SIZE / 64; + } + else + { + waveforms[i] = organ_voice_base::get_wave(wave).original; + S[i] = S2[i] = ORGAN_WAVE_SIZE; + } + } + for (int i = 0; i < points; i++) + { + float sum = 0.f; + for (int j = 0; j < 9; j++) + { + float shift = parameters->phase[j] * S[j] / 360.0; + sum += parameters->drawbars[j] * waveforms[j][int(parameters->harmonics[j] * i * S2[j] / points + shift) & (S[j] - 1)]; + } + data[i] = sum * 2 / (9 * 8); + } + return true; + } + return false; +} + +//////////////////////////////////////////////////////////////////////////// + +organ_voice_base::small_wave_family (*organ_voice_base::waves)[organ_voice_base::wave_count_small]; +organ_voice_base::big_wave_family (*organ_voice_base::big_waves)[organ_voice_base::wave_count_big]; + +static void smoothen(bandlimiter &bl, float tmp[ORGAN_WAVE_SIZE]) +{ + bl.compute_spectrum(tmp); + for (int i = 1; i <= ORGAN_WAVE_SIZE / 2; i++) { + bl.spectrum[i] *= 1.0 / sqrt(i); + bl.spectrum[ORGAN_WAVE_SIZE - i] *= 1.0 / sqrt(i); + } + bl.compute_waveform(tmp); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); +} + +static void phaseshift(bandlimiter &bl, float tmp[ORGAN_WAVE_SIZE]) +{ + bl.compute_spectrum(tmp); + for (int i = 1; i <= ORGAN_WAVE_SIZE / 2; i++) { + float frac = i * 2.0 / ORGAN_WAVE_SIZE; + float phase = M_PI / sqrt(frac) ; + complex shift = complex(cos(phase), sin(phase)); + bl.spectrum[i] *= shift; + bl.spectrum[ORGAN_WAVE_SIZE - i] *= conj(shift); + } + bl.compute_waveform(tmp); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); +} + +static void padsynth(bandlimiter blSrc, bandlimiter &blDest, organ_voice_base::big_wave_family &result, int bwscale = 20, float bell_factor = 0, bool foldover = false) +{ + // kept in a vector to avoid putting large arrays on stack + vector >orig_spectrum; + orig_spectrum.resize(ORGAN_WAVE_SIZE / 2); + for (int i = 0; i < ORGAN_WAVE_SIZE / 2; i++) + { + orig_spectrum[i] = blSrc.spectrum[i]; +// printf("@%d = %f\n", i, abs(orig_spectrum[i])); + } + + int periods = (1 << ORGAN_BIG_WAVE_SHIFT) * ORGAN_BIG_WAVE_SIZE / ORGAN_WAVE_SIZE; + for (int i = 0; i <= ORGAN_BIG_WAVE_SIZE / 2; i++) { + blDest.spectrum[i] = 0; + } + int MAXHARM = (ORGAN_WAVE_SIZE >> (1 + ORGAN_BIG_WAVE_SHIFT)); + for (int i = 1; i <= MAXHARM; i++) { + //float esc = 0.25 * (1 + 0.5 * log(i)); + float esc = 0.5; + float amp = abs(blSrc.spectrum[i]); + // fade out starting from half + if (i >= MAXHARM / 2) { + float pos = (i - MAXHARM/2) * 1.0 / (MAXHARM / 2); + amp *= 1.0 - pos; + amp *= 1.0 - pos; + } + int bw = 1 + 20 * i; + float sum = 1; + int delta = 1; + if (bw > 20) delta = bw / 20; + for (int j = delta; j <= bw; j+=delta) + { + float p = j * 1.0 / bw; + sum += 2 * exp(-p * p * esc); + } + if (sum < 0.0001) + continue; + amp *= (ORGAN_BIG_WAVE_SIZE / ORGAN_WAVE_SIZE); + amp /= sum; + int orig = i * periods + bell_factor * cos(i); + if (orig > 0 && orig < ORGAN_BIG_WAVE_SIZE / 2) + blDest.spectrum[orig] += amp; + for (int j = delta; j <= bw; j += delta) + { + float p = j * 1.0 / bw; + float val = amp * exp(-p * p * esc); + int dist = j * bwscale / 40; + int pos = orig + dist; + if (pos < 1 || pos >= ORGAN_BIG_WAVE_SIZE / 2) + continue; + int pos2 = orig - dist; + if (pos2 < 1 || pos2 >= ORGAN_BIG_WAVE_SIZE / 2) + continue; + blDest.spectrum[pos] += val; + if (j) + blDest.spectrum[pos2] += val; + } + } + for (int i = 1; i <= ORGAN_BIG_WAVE_SIZE / 2; i++) { + float phase = M_PI * 2 * (rand() & 255) / 256; + complex shift = complex(cos(phase), sin(phase)); + blDest.spectrum[i] *= shift; +// printf("@%d = %f\n", i, abs(blDest.spectrum[i])); + + blDest.spectrum[ORGAN_BIG_WAVE_SIZE - i] = conj(blDest.spectrum[i]); + } + // same as above - put large array on heap to avoid stack overflow in ingen + vector tmp; + tmp.resize(ORGAN_BIG_WAVE_SIZE); + blDest.compute_waveform(tmp.data()); + normalize_waveform(tmp.data(), ORGAN_BIG_WAVE_SIZE); + blDest.compute_spectrum(tmp.data()); + + // limit is 1/2 of the number of harmonics of the original wave + result.make_from_spectrum(blDest, foldover, ORGAN_WAVE_SIZE >> (1 + ORGAN_BIG_WAVE_SHIFT)); + memcpy(result.original, result.begin()->second, sizeof(result.original)); + #if 0 + blDest.compute_waveform(result); + normalize_waveform(result, ORGAN_BIG_WAVE_SIZE); + result[ORGAN_BIG_WAVE_SIZE] = result[0]; + for (int i =0 ; ireport_progress(floor(progress / totalwaves), "Precalculating large waveforms"); } } while(0) + + +void organ_voice_base::precalculate_waves(progress_report_iface *reporter) +{ + static bool inited = false; + if (!inited) + { + static organ_voice_base::small_wave_family waves[organ_voice_base::wave_count_small]; + static organ_voice_base::big_wave_family big_waves[organ_voice_base::wave_count_big]; + organ_voice_base::waves = &waves; + organ_voice_base::big_waves = &big_waves; + + float progress = 0.0; + int totalwaves = 1 + wave_count_big; + if (reporter) + reporter->report_progress(0, "Precalculating small waveforms"); + float tmp[ORGAN_WAVE_SIZE]; + static bandlimiter bl; + static bandlimiter blBig; + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = sin(i * 2 * M_PI / ORGAN_WAVE_SIZE); + waves[wave_sine].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 16)) ? 1 : 0; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_pulse].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = i < (ORGAN_WAVE_SIZE / 2) ? sin(i * 2 * 2 * M_PI / ORGAN_WAVE_SIZE) : 0; + waves[wave_sinepl1].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = i < (ORGAN_WAVE_SIZE / 3) ? sin(i * 3 * 2 * M_PI / ORGAN_WAVE_SIZE) : 0; + waves[wave_sinepl2].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = i < (ORGAN_WAVE_SIZE / 4) ? sin(i * 4 * 2 * M_PI / ORGAN_WAVE_SIZE) : 0; + waves[wave_sinepl3].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 2)) ? 1 : -1; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_sqr].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = -1 + (i * 2.0 / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_saw].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 2)) ? 1 : -1; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + smoothen(bl, tmp); + waves[wave_ssqr].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = -1 + (i * 2.0 / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + smoothen(bl, tmp); + waves[wave_ssaw].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 16)) ? 1 : 0; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + smoothen(bl, tmp); + waves[wave_spls].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = i < (ORGAN_WAVE_SIZE / 1.5) ? sin(i * 1.5 * 2 * M_PI / ORGAN_WAVE_SIZE) : 0; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_sinepl05].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = i < (ORGAN_WAVE_SIZE / 1.5) ? (i < ORGAN_WAVE_SIZE / 3 ? -1 : +1) : 0; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_sqr05].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = sin(i * M_PI / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_halfsin].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = sin(i * 3 * M_PI / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_clvg].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.3 * sin(6*ph) + 0.2 * sin(11*ph) + 0.2 * cos(17*ph) - 0.2 * cos(19*ph); + tmp[i] = sin(5*ph + fm) + 0.7 * cos(7*ph - fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_bell].make(bl, tmp, true); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.3 * sin(3*ph) + 0.3 * sin(11*ph) + 0.3 * cos(17*ph) - 0.3 * cos(19*ph) + 0.3 * cos(25*ph) - 0.3 * cos(31*ph) + 0.3 * cos(37*ph); + tmp[i] = sin(3*ph + fm) + cos(7*ph - fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_bell2].make(bl, tmp, true); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.5 * sin(3*ph) + 0.3 * sin(5*ph) + 0.3 * cos(6*ph) - 0.3 * cos(9*ph); + tmp[i] = sin(4*ph + fm) + cos(ph - fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w1].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + tmp[i] = sin(ph) * sin(2 * ph) * sin(4 * ph) * sin(8 * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w2].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + tmp[i] = sin(ph) * sin(3 * ph) * sin(5 * ph) * sin(7 * ph) * sin(9 * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w3].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + tmp[i] = sin(ph + 2 * sin(ph + 2 * sin(ph))); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w4].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + tmp[i] = ph * sin(ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w5].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + tmp[i] = ph * sin(ph) + (2 * M_PI - ph) * sin(2 * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w6].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 1.0 / ORGAN_WAVE_SIZE; + tmp[i] = exp(-ph * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w7].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 1.0 / ORGAN_WAVE_SIZE; + tmp[i] = exp(-ph * sin(2 * M_PI * ph)); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w8].make(bl, tmp); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 1.0 / ORGAN_WAVE_SIZE; + tmp[i] = sin(2 * M_PI * ph * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + waves[wave_w9].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = -1 + (i * 2.0 / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + phaseshift(bl, tmp); + waves[wave_dsaw].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 2)) ? 1 : -1; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + phaseshift(bl, tmp); + waves[wave_dsqr].make(bl, tmp); + + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = (i < (ORGAN_WAVE_SIZE / 16)) ? 1 : 0; + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + phaseshift(bl, tmp); + waves[wave_dpls].make(bl, tmp); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = -1 + (i * 2.0 / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_strings - wave_count_small], 15); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = -1 + (i * 2.0 / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_strings2 - wave_count_small], 40); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + tmp[i] = sin(i * 2 * M_PI / ORGAN_WAVE_SIZE); + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_sinepad - wave_count_small], 20); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.3 * sin(6*ph) + 0.2 * sin(11*ph) + 0.2 * cos(17*ph) - 0.2 * cos(19*ph); + tmp[i] = sin(5*ph + fm) + 0.7 * cos(7*ph - fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_bellpad - wave_count_small], 30, 30, true); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.3 * sin(3*ph) + 0.2 * sin(4*ph) + 0.2 * cos(5*ph) - 0.2 * cos(6*ph); + tmp[i] = sin(2*ph + fm) + 0.7 * cos(3*ph - fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_space - wave_count_small], 30, 30); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = 0.5 * sin(ph) + 0.5 * sin(2*ph) + 0.5 * sin(3*ph); + tmp[i] = sin(ph + fm) + 0.5 * cos(7*ph - 2 * fm) + 0.25 * cos(13*ph - 4 * fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_choir - wave_count_small], 50, 10); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = sin(ph) ; + tmp[i] = sin(ph + fm) + 0.25 * cos(11*ph - 2 * fm) + 0.125 * cos(23*ph - 2 * fm) + 0.0625 * cos(49*ph - 2 * fm); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_choir2 - wave_count_small], 50, 10); + + LARGE_WAVEFORM_PROGRESS(); + for (int i = 0; i < ORGAN_WAVE_SIZE; i++) + { + float ph = i * 2 * M_PI / ORGAN_WAVE_SIZE; + float fm = sin(ph) ; + tmp[i] = sin(ph + 4 * fm) + 0.5 * sin(2 * ph + 4 * ph); + } + normalize_waveform(tmp, ORGAN_WAVE_SIZE); + bl.compute_spectrum(tmp); + padsynth(bl, blBig, big_waves[wave_choir3 - wave_count_small], 50, 10); + LARGE_WAVEFORM_PROGRESS(); + + inited = true; + } +} + +organ_voice_base::organ_voice_base(organ_parameters *_parameters, int &_sample_rate_ref, bool &_released_ref) +: parameters(_parameters) +, sample_rate_ref(_sample_rate_ref) +, released_ref(_released_ref) +{ + note = -1; +} + +void organ_voice_base::render_percussion_to(float (*buf)[2], int nsamples) +{ + if (note == -1) + return; + + if (!pamp.get_active()) + return; + if (parameters->percussion_level < small_value()) + return; + float level = parameters->percussion_level * 9; + static float zeros[ORGAN_WAVE_SIZE]; + // XXXKF the decay needs work! + double age_const = parameters->perc_decay_const; + double fm_age_const = parameters->perc_fm_decay_const; + int timbre = parameters->get_percussion_wave(); + if (timbre < 0 || timbre >= wave_count_small) + return; + int timbre2 = parameters->get_percussion_fm_wave(); + if (timbre2 < 0 || timbre2 >= wave_count_small) + timbre2 = wave_sine; + float *fmdata = (*waves)[timbre2].get_level(moddphase.get()); + if (!fmdata) + fmdata = zeros; + float *data = (*waves)[timbre].get_level(dpphase.get()); + if (!data) { + pamp.deactivate(); + return; + } + float s = parameters->percussion_stereo * ORGAN_WAVE_SIZE * (0.5 / 360.0); + for (int i = 0; i < nsamples; i++) { + float fm = wave(fmdata, modphase); + fm *= ORGAN_WAVE_SIZE * parameters->percussion_fm_depth * fm_amp.get(); + modphase += moddphase; + fm_amp.age_exp(fm_age_const, 1.0 / 32768.0); + + float lamp = level * pamp.get(); + buf[i][0] += lamp * wave(data, pphase + dsp::fixed_point(fm - s)); + buf[i][1] += lamp * wave(data, pphase + dsp::fixed_point(fm + s)); + if (released_ref) + pamp.age_lin(rel_age_const,0.0); + else + pamp.age_exp(age_const, 1.0 / 32768.0); + pphase += dpphase; + } +} + +void organ_vibrato::reset() +{ + for (int i = 0; i < VibratoSize; i++) + vibrato_x1[i][0] = vibrato_y1[i][0] = vibrato_x1[i][1] = vibrato_y1[i][1] = 0.f; + vibrato[0].a0 = vibrato[1].a0 = 0; + lfo_phase = 0.f; +} + +void organ_vibrato::process(organ_parameters *parameters, float (*data)[2], unsigned int len, float sample_rate) +{ + float lfo1 = lfo_phase < 0.5 ? 2 * lfo_phase : 2 - 2 * lfo_phase; + float lfo_phase2 = lfo_phase + parameters->lfo_phase * (1.0 / 360.0); + if (lfo_phase2 >= 1.0) + lfo_phase2 -= 1.0; + float lfo2 = lfo_phase2 < 0.5 ? 2 * lfo_phase2 : 2 - 2 * lfo_phase2; + lfo_phase += parameters->lfo_rate * len / sample_rate; + if (lfo_phase >= 1.0) + lfo_phase -= 1.0; + if (!len) + return; + float olda0[2] = {vibrato[0].a0, vibrato[1].a0}; + vibrato[0].set_ap(3000 + 7000 * parameters->lfo_amt * lfo1 * lfo1, sample_rate); + vibrato[1].set_ap(3000 + 7000 * parameters->lfo_amt * lfo2 * lfo2, sample_rate); + float ilen = 1.0 / len; + float deltaa0[2] = {(vibrato[0].a0 - olda0[0])*ilen, (vibrato[1].a0 - olda0[1])*ilen}; + + float vib_wet = parameters->lfo_wet; + for (int c = 0; c < 2; c++) + { + for (unsigned int i = 0; i < len; i++) + { + float v = data[i][c]; + float v0 = v; + float coeff = olda0[c] + deltaa0[c] * i; + for (int t = 0; t < VibratoSize; t++) + v = vibrato[c].process_ap(v, vibrato_x1[t][c], vibrato_y1[t][c], coeff); + + data[i][c] += (v - v0) * vib_wet; + } + for (int t = 0; t < VibratoSize; t++) + { + sanitize(vibrato_x1[t][c]); + sanitize(vibrato_y1[t][c]); + } + } +} + +void organ_voice::update_pitch() +{ + organ_voice_base::update_pitch(); + dphase.set(dsp::midi_note_to_phase(note, 100 * parameters->global_transpose + parameters->global_detune, sample_rate) * inertia_pitchbend.get_last()); +} + +void organ_voice::render_block() { + if (note == -1) + return; + + dsp::zero(&output_buffer[0][0], Channels * BlockSize); + dsp::zero(&aux_buffers[1][0][0], 2 * Channels * BlockSize); + if (!amp.get_active()) + { + if (use_percussion()) + render_percussion_to(output_buffer, BlockSize); + return; + } + + inertia_pitchbend.set_inertia(parameters->pitch_bend); + inertia_pitchbend.step(); + update_pitch(); + dsp::fixed_point tphase, tdphase; + unsigned int foldvalue = parameters->foldvalue * inertia_pitchbend.get_last(); + int vibrato_mode = fastf2i_drm(parameters->lfo_mode); + for (int h = 0; h < 9; h++) + { + float amp = parameters->drawbars[h]; + if (amp < small_value()) + continue; + float *data; + dsp::fixed_point hm = dsp::fixed_point(parameters->multiplier[h]); + int waveid = (int)parameters->waveforms[h]; + if (waveid < 0 || waveid >= wave_count) + waveid = 0; + + uint32_t rate = (dphase * hm).get(); + if (waveid >= wave_count_small) + { + float *data = (*big_waves)[waveid - wave_count_small].get_level(rate >> (ORGAN_BIG_WAVE_BITS - ORGAN_WAVE_BITS + ORGAN_BIG_WAVE_SHIFT)); + if (!data) + continue; + hm.set(hm.get() >> ORGAN_BIG_WAVE_SHIFT); + dsp::fixed_point tphase, tdphase; + tphase.set(((phase * hm).get()) + parameters->phaseshift[h]); + tdphase.set(rate >> ORGAN_BIG_WAVE_SHIFT); + float ampl = amp * 0.5f * (1 - parameters->pan[h]); + float ampr = amp * 0.5f * (1 + parameters->pan[h]); + float (*out)[Channels] = aux_buffers[dsp::fastf2i_drm(parameters->routing[h])]; + + for (int i=0; i < (int)BlockSize; i++) { + float wv = big_wave(data, tphase); + out[i][0] += wv * ampl; + out[i][1] += wv * ampr; + tphase += tdphase; + } + } + else + { + unsigned int foldback = 0; + while (rate > foldvalue) + { + rate >>= 1; + foldback++; + } + hm.set(hm.get() >> foldback); + data = (*waves)[waveid].get_level(rate); + if (!data) + continue; + tphase.set((uint32_t)((phase * hm).get()) + parameters->phaseshift[h]); + tdphase.set((uint32_t)rate); + float ampl = amp * 0.5f * (1 - parameters->pan[h]); + float ampr = amp * 0.5f * (1 + parameters->pan[h]); + float (*out)[Channels] = aux_buffers[dsp::fastf2i_drm(parameters->routing[h])]; + + for (int i=0; i < (int)BlockSize; i++) { + float wv = wave(data, tphase); + out[i][0] += wv * ampl; + out[i][1] += wv * ampr; + tphase += tdphase; + } + } + } + + bool is_quad = parameters->quad_env >= 0.5f; + + expression.set_inertia(parameters->cutoff); + phase += dphase * BlockSize; + float escl[EnvCount], eval[EnvCount]; + for (int i = 0; i < EnvCount; i++) + escl[i] = (1.f + parameters->envs[i].velscale * (velocity - 1.f)); + + if (is_quad) + { + for (int i = 0; i < EnvCount; i++) + eval[i] = envs[i].value * envs[i].value * escl[i]; + } + else + { + for (int i = 0; i < EnvCount; i++) + eval[i] = envs[i].value * escl[i]; + } + for (int i = 0; i < FilterCount; i++) + { + float mod = parameters->filters[i].envmod[0] * eval[0] ; + mod += parameters->filters[i].keyf * 100 * (note - 60); + for (int j = 1; j < EnvCount; j++) + { + mod += parameters->filters[i].envmod[j] * eval[j]; + } + if (i) mod += expression.get() * 1200 * 4; + float fc = parameters->filters[i].cutoff * pow(2.0f, mod * (1.f / 1200.f)); + if (i == 0 && parameters->filter1_type >= 0.5f) + filterL[i].set_hp_rbj(dsp::clip(fc, 10, 18000), parameters->filters[i].resonance, sample_rate); + else + filterL[i].set_lp_rbj(dsp::clip(fc, 10, 18000), parameters->filters[i].resonance, sample_rate); + filterR[i].copy_coeffs(filterL[i]); + } + float amp_pre[ampctl_count - 1], amp_post[ampctl_count - 1]; + for (int i = 0; i < ampctl_count - 1; i++) + { + amp_pre[i] = 1.f; + amp_post[i] = 1.f; + } + bool any_running = false; + for (int i = 0; i < EnvCount; i++) + { + float pre = eval[i]; + envs[i].advance(); + int mode = fastf2i_drm(parameters->envs[i].ampctl); + if (!envs[i].stopped()) + any_running = true; + if (mode == ampctl_none) + continue; + float post = (is_quad ? envs[i].value : 1) * envs[i].value * escl[i]; + amp_pre[mode - 1] *= pre; + amp_post[mode - 1] *= post; + } + if (vibrato_mode >= lfomode_direct && vibrato_mode <= lfomode_filter2) + vibrato.process(parameters, aux_buffers[vibrato_mode - lfomode_direct], BlockSize, sample_rate); + if (!any_running) + finishing = true; + // calculate delta from pre and post + for (int i = 0; i < ampctl_count - 1; i++) + amp_post[i] = (amp_post[i] - amp_pre[i]) * (1.0 / BlockSize); + float a0 = amp_pre[0], a1 = amp_pre[1], a2 = amp_pre[2], a3 = amp_pre[3]; + float d0 = amp_post[0], d1 = amp_post[1], d2 = amp_post[2], d3 = amp_post[3]; + if (parameters->filter_chain >= 0.5f) + { + for (int i=0; i < (int) BlockSize; i++) { + output_buffer[i][0] = a3 * (a0 * output_buffer[i][0] + a2 * filterL[1].process(a1 * filterL[0].process(aux_buffers[1][i][0]) + aux_buffers[2][i][0])); + output_buffer[i][1] = a3 * (a0 * output_buffer[i][1] + a2 * filterR[1].process(a1 * filterR[0].process(aux_buffers[1][i][1]) + aux_buffers[2][i][1])); + a0 += d0, a1 += d1, a2 += d2, a3 += d3; + } + } + else + { + for (int i=0; i < (int) BlockSize; i++) { + output_buffer[i][0] = a3 * (a0 * output_buffer[i][0] + a1 * filterL[0].process(aux_buffers[1][i][0]) + a2 * filterL[1].process(aux_buffers[2][i][0])); + output_buffer[i][1] = a3 * (a0 * output_buffer[i][1] + a1 * filterR[0].process(aux_buffers[1][i][1]) + a2 * filterR[1].process(aux_buffers[2][i][1])); + a0 += d0, a1 += d1, a2 += d2, a3 += d3; + } + } + filterL[0].sanitize(); + filterR[0].sanitize(); + filterL[1].sanitize(); + filterR[1].sanitize(); + if (vibrato_mode == lfomode_voice) + vibrato.process(parameters, output_buffer, BlockSize, sample_rate); + + if (finishing) + { + for (int i = 0; i < (int) BlockSize; i++) { + output_buffer[i][0] *= amp.get(); + output_buffer[i][1] *= amp.get(); + amp.age_lin((1.0/44100.0)/0.03,0.0); + } + } + + if (use_percussion()) + render_percussion_to(output_buffer, BlockSize); +} + +void drawbar_organ::update_params() +{ + parameters->perc_decay_const = dsp::decay::calc_exp_constant(1.0 / 1024.0, 0.001 * parameters->percussion_time * sample_rate); + parameters->perc_fm_decay_const = dsp::decay::calc_exp_constant(1.0 / 1024.0, 0.001 * parameters->percussion_fm_time * sample_rate); + for (int i = 0; i < 9; i++) + { + parameters->multiplier[i] = parameters->harmonics[i] * pow(2.0, parameters->detune[i] * (1.0 / 1200.0)); + parameters->phaseshift[i] = int(parameters->phase[i] * 65536 / 360) << 16; + } + double dphase = dsp::midi_note_to_phase((int)parameters->foldover, 0, sample_rate); + parameters->foldvalue = (int)(dphase); +} + +void drawbar_organ::pitch_bend(int amt) +{ + parameters->pitch_bend = pow(2.0, (amt * parameters->pitch_bend_range) / (1200.0 * 8192.0)); + for (list::iterator i = active_voices.begin(); i != active_voices.end(); i++) + { + organ_voice *v = dynamic_cast(*i); + v->update_pitch(); + } + percussion.update_pitch(); +} + +void organ_audio_module::execute(int cmd_no) +{ + switch(cmd_no) + { + case 0: + panic_flag = true; + break; + } +} + +void organ_voice_base::perc_note_on(int note, int vel) +{ + perc_reset(); + released_ref = false; + this->note = note; + if (parameters->percussion_level > 0) + pamp.set(1.0f + (vel - 127) * parameters->percussion_vel2amp / 127.0); + update_pitch(); + float (*kt)[2] = parameters->percussion_keytrack; + // assume last point (will be put there by padding) + fm_keytrack = kt[ORGAN_KEYTRACK_POINTS - 1][1]; + // yes binary search would be nice if we had more than those crappy 4 points + for (int i = 0; i < ORGAN_KEYTRACK_POINTS - 1; i++) + { + float &lower = kt[i][0], upper = kt[i + 1][0]; + if (note >= lower && note < upper) + { + fm_keytrack = kt[i][1] + (note - lower) * (kt[i + 1][1] - kt[i][1]) / (upper - lower); + break; + } + } + fm_amp.set(fm_keytrack * (1.0f + (vel - 127) * parameters->percussion_vel2fm / 127.0)); +} + +char *organ_audio_module::configure(const char *key, const char *value) +{ + if (!strcmp(key, "map_curve")) + { + var_map_curve = value; + stringstream ss(value); + int i = 0; + float x = 0, y = 1; + if (*value) + { + int points; + ss >> points; + for (i = 0; i < points; i++) + { + static const int whites[] = { 0, 2, 4, 5, 7, 9, 11 }; + ss >> x >> y; + int wkey = (int)(x * 71); + x = whites[wkey % 7] + 12 * (wkey / 7); + parameters->percussion_keytrack[i][0] = x; + parameters->percussion_keytrack[i][1] = y; + // cout << "(" << x << ", " << y << ")" << endl; + } + } + // pad with constant Y + for (; i < ORGAN_KEYTRACK_POINTS; i++) { + parameters->percussion_keytrack[i][0] = x; + parameters->percussion_keytrack[i][1] = y; + } + return NULL; + } + cout << "Set unknown configure value " << key << " to " << value << endl; + return NULL; +} + +void organ_audio_module::send_configures(send_configure_iface *sci) +{ + sci->send_configure("map_curve", var_map_curve.c_str()); +} + +void organ_audio_module::deactivate() +{ + +} + +void drawbar_organ::render_separate(float *output[], int nsamples) +{ + float buf[4096][2]; + dsp::zero(&buf[0][0], 2 * nsamples); + basic_synth::render_to(buf, nsamples); + if (dsp::fastf2i_drm(parameters->lfo_mode) == organ_voice_base::lfomode_global) + { + for (int i = 0; i < nsamples; i += 64) + global_vibrato.process(parameters, buf + i, std::min(64, nsamples - i), sample_rate); + } + if (percussion.get_active()) + percussion.render_percussion_to(buf, nsamples); + float gain = parameters->master * (1.0 / 8); + eq_l.set(parameters->bass_freq, parameters->bass_gain, parameters->treble_freq, parameters->treble_gain, sample_rate); + eq_r.copy_coeffs(eq_l); + for (int i=0; i +#include +#include +#include +#include + +using namespace calf_plugins; + +#if USE_LADSPA +template +LADSPA_Descriptor ladspa_wrapper::descriptor; + +template +LADSPA_Descriptor ladspa_wrapper::descriptor_for_dssi; + +#if USE_DSSI + +template +DSSI_Descriptor ladspa_wrapper::dssi_descriptor; + +template +DSSI_Program_Descriptor ladspa_wrapper::dssi_default_program; + +template +std::vector *ladspa_wrapper::presets; + +template +std::vector *ladspa_wrapper::preset_descs; +#endif +#endif + +#if USE_LV2 +// instantiate descriptor templates +template LV2_Descriptor calf_plugins::lv2_wrapper::descriptor; +template LV2_Calf_Descriptor calf_plugins::lv2_wrapper::calf_descriptor; +template LV2MessageContext calf_plugins::lv2_wrapper::message_context; + +extern "C" { + +const LV2_Descriptor *lv2_descriptor(uint32_t index) +{ + #define PER_MODULE_ITEM(name, isSynth, jackname) if (!(index--)) return &lv2_wrapper::get().descriptor; + #include +#ifdef ENABLE_EXPERIMENTAL + return lv2_small_descriptor(index); +#else + return NULL; +#endif +} + +}; + +#endif + +#if USE_LADSPA +extern "C" { + +const LADSPA_Descriptor *ladspa_descriptor(unsigned long Index) +{ + #define PER_MODULE_ITEM(name, isSynth, jackname) if (!isSynth && !(Index--)) return &ladspa_wrapper::get().descriptor; + #include + return NULL; +} + +}; + +#if USE_DSSI +extern "C" { + +const DSSI_Descriptor *dssi_descriptor(unsigned long Index) +{ + #define PER_MODULE_ITEM(name, isSynth, jackname) if (!(Index--)) return &calf_plugins::ladspa_wrapper::get().dssi_descriptor; + #include + return NULL; +} + +}; +#endif + +#endif + diff --git a/plugins/ladspa_effect/calf/synth.cpp b/plugins/ladspa_effect/calf/synth.cpp new file mode 100644 index 000000000..633650e31 --- /dev/null +++ b/plugins/ladspa_effect/calf/synth.cpp @@ -0,0 +1,228 @@ +/* Calf DSP Library + * Generic polyphonic synthesizer framework. + * + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#if USE_JACK +#include +#endif +#include +#include + +using namespace dsp; +using namespace std; + +void basic_synth::kill_note(int note, int vel, bool just_one) +{ + for (list::iterator it = active_voices.begin(); it != active_voices.end(); it++) { + // preserve sostenuto notes + if ((*it)->get_current_note() == note && !(sostenuto && (*it)->sostenuto)) { + (*it)->note_off(vel); + if (just_one) + return; + } + } +} + +dsp::voice *basic_synth::give_voice() +{ + if (active_voices.size() >= polyphony_limit) + { + dsp::voice *stolen = steal_voice(); + if (stolen) + return stolen; + } + if (unused_voices.empty()) + return alloc_voice(); + else { + dsp::voice *v = unused_voices.top(); + unused_voices.pop(); + v->reset(); + return v; + } +} + +dsp::voice *basic_synth::steal_voice() +{ + std::list::iterator found = active_voices.end(); + float priority = 10000; + //int idx = 0; + for(std::list::iterator i = active_voices.begin(); i != active_voices.end(); i++) + { + //printf("Voice %d priority %f at %p\n", idx++, (*i)->get_priority(), *i); + if ((*i)->get_priority() < priority) + { + priority = (*i)->get_priority(); + found = i; + } + } + //printf("Found: %p\n\n", *found); + if (found == active_voices.end()) + return NULL; + + (*found)->steal(); + return NULL; +} + +void basic_synth::trim_voices() +{ + // count stealable voices + unsigned int count = 0; + for(std::list::iterator i = active_voices.begin(); i != active_voices.end(); i++) + { + if ((*i)->get_priority() < 10000) + count++; + } + // printf("Count=%d limit=%d\n", count, polyphony_limit); + // steal any voices above polyphony limit + if (count > polyphony_limit) { + for (unsigned int i = 0; i < count - polyphony_limit; i++) + steal_voice(); + } +} + +void basic_synth::note_on(int note, int vel) +{ + if (!vel) { + note_off(note, 0); + return; + } + bool perc = check_percussion(); + dsp::voice *v = give_voice(); + v->setup(sample_rate); + v->released = false; + v->sostenuto = false; + gate.set(note); + v->note_on(note, vel); + active_voices.push_back(v); + if (perc) { + percussion_note_on(note, vel); + } +} + +void basic_synth::note_off(int note, int vel) +{ + gate.reset(note); + if (!hold) + kill_note(note, vel, false); +} + +#define for_all_voices(iter) for (std::list::iterator iter = active_voices.begin(); iter != active_voices.end(); iter++) + +void basic_synth::on_pedal_release() +{ + for_all_voices(i) + { + int note = (*i)->get_current_note(); + if (note < 0 || note > 127) + continue; + bool still_held = gate[note]; + // sostenuto pedal released + if ((*i)->sostenuto && !sostenuto) + { + // mark note as non-sostenuto + (*i)->sostenuto = false; + // if key still pressed or hold pedal used, hold the note (as non-sostenuto so it can be released later by releasing the key or pedal) + // if key has been released and hold pedal is not depressed, release the note + if (!still_held && !hold) + (*i)->note_off(127); + } + else if (!hold && !still_held && !(*i)->released) + { + (*i)->released = true; + (*i)->note_off(127); + } + } +} + +void basic_synth::control_change(int ctl, int val) +{ + if (ctl == 64) { // HOLD controller + bool prev = hold; + hold = (val >= 64); + if (!hold && prev && !sostenuto) { + on_pedal_release(); + } + } + if (ctl == 66) { // SOSTENUTO controller + bool prev = sostenuto; + sostenuto = (val >= 64); + if (sostenuto && !prev) { + // SOSTENUTO was pressed - move all notes onto sustain stack + for_all_voices(i) { + (*i)->sostenuto = true; + } + } + if (!sostenuto && prev) { + // SOSTENUTO was released - release all keys which were previously held + on_pedal_release(); + } + } + if (ctl == 123 || ctl == 120) { // all notes off, all sounds off + vector notes; + notes.reserve(128); + if (ctl == 120) { // for "all sounds off", automatically release hold and sostenuto pedal + control_change(66, 0); + control_change(64, 0); + } + for_all_voices(i) + { + if (ctl == 123) + (*i)->note_off(127); + else + (*i)->steal(); + } + } + if (ctl == 121) { + control_change(1, 0); + control_change(7, 100); + control_change(10, 64); + control_change(11, 127); + // release hold..hold2 + for (int i = 64; i <= 69; i++) + control_change(i, 0); + } +} + +void basic_synth::render_to(float (*output)[2], int nsamples) +{ + // render voices, eliminate ones that aren't sounding anymore + for (list::iterator i = active_voices.begin(); i != active_voices.end();) { + dsp::voice *v = *i; + v->render_to(output, nsamples); + if (!v->get_active()) { + i = active_voices.erase(i); + unused_voices.push(v); + continue; + } + i++; + } +} + +basic_synth::~basic_synth() +{ + while(!unused_voices.empty()) { + delete unused_voices.top(); + unused_voices.pop(); + } + for (list::iterator i = active_voices.begin(); i != active_voices.end(); i++) + delete *i; +} + diff --git a/plugins/ladspa_effect/calf/utils.cpp b/plugins/ladspa_effect/calf/utils.cpp new file mode 100644 index 000000000..338587f2c --- /dev/null +++ b/plugins/ladspa_effect/calf/utils.cpp @@ -0,0 +1,132 @@ +/* Calf DSP Library + * Various utility functions. + * Copyright (C) 2007 Krzysztof Foltman + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include +#include + +using namespace std; +using namespace osctl; + +namespace calf_utils { + +string encode_map(const dictionary &data) +{ + osctl::string_buffer sb; + osc_stream str(sb); + str << (uint32_t)data.size(); + for(dictionary::const_iterator i = data.begin(); i != data.end(); i++) + { + str << i->first << i->second; + } + return sb.data; +} + +void decode_map(dictionary &data, const string &src) +{ + osctl::string_buffer sb(src); + osc_stream str(sb); + uint32_t count = 0; + str >> count; + string tmp, tmp2; + data.clear(); + for (uint32_t i = 0; i < count; i++) + { + str >> tmp; + str >> tmp2; + data[tmp] = tmp2; + } +} + +std::string xml_escape(const std::string &src) +{ + string dest; + for (size_t i = 0; i < src.length(); i++) { + // XXXKF take care of string encoding + if (src[i] < 0 || src[i] == '"' || src[i] == '<' || src[i] == '>' || src[i] == '&') + dest += "&"+i2s((uint8_t)src[i])+";"; + else + dest += src[i]; + } + return dest; +} + +std::string load_file(const std::string &src) +{ + std::string str; + FILE *f = fopen(src.c_str(), "rb"); +#if 0 + if (!f) + throw file_exception(src); +#endif + while(!feof(f)) + { + char buffer[1024]; + int len = fread(buffer, 1, sizeof(buffer), f); +#if 0 + if (len < 0) + throw file_exception(src); +#endif + str += string(buffer, len); + } + return str; +} + +std::string i2s(int value) +{ + char buf[32]; + sprintf(buf, "%d", value); + + return std::string(buf); +} + +std::string f2s(double value) +{ + stringstream ss; + ss << value; + return ss.str(); +} + +std::string ff2s(double value) +{ + string s = f2s(value); + if (s.find('.') == string::npos) + s += ".0"; + return s; +} + +std::string indent(const std::string &src, const std::string &indent) +{ + std::string dest; + size_t pos = 0; + do { + size_t epos = src.find("\n", pos); + if (epos == string::npos) + break; + dest += indent + src.substr(pos, epos - pos) + "\n"; + pos = epos + 1; + } while(pos < src.length()); + if (pos < src.length()) + dest += indent + src.substr(pos); + return dest; +} + +}