├── .github └── workflows │ ├── build_linux.yaml │ ├── build_macos.yaml │ └── build_windows.yaml ├── .gitignore ├── .gitmodules ├── AUTHORS ├── CMakeLists.txt ├── CMakePresets.json ├── COPYING ├── ci ├── build.sh └── config_cmake.sh └── plugin └── Source ├── AlignedArray.hpp ├── AudioFFT.cpp ├── AudioFFT.h ├── FFTConvolver.cpp ├── FFTConvolver.h ├── PluginEditor.cpp ├── PluginEditor.h ├── PluginProcessor.cpp ├── PluginProcessor.h ├── TwoStageFFTConvolver.cpp ├── TwoStageFFTConvolver.h ├── Utilities.cpp ├── Utilities.h ├── dwgs.cpp ├── dwgs.h ├── fft.cpp ├── fft.h ├── filter.cpp ├── filter.h ├── hammer.cpp ├── hammer.h ├── qiano.cpp ├── qiano.h ├── reverb.cpp ├── reverb.h ├── utils.cpp └── utils.h /.github/workflows/build_linux.yaml: -------------------------------------------------------------------------------- 1 | name: Build Linux 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - release 7 | jobs: 8 | build: 9 | name: Build Linux 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Fix up git URLs 13 | run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig 14 | shell: bash 15 | - uses: actions/checkout@v1 16 | with: 17 | token: ${{ secrets.ACCESS_TOKEN }} 18 | submodules: true 19 | - name: "Run script" 20 | run: | 21 | export OS="linux" 22 | ./ci/build.sh 23 | shell: bash 24 | env: 25 | APIKEY: ${{ secrets.APIKEY }} 26 | -------------------------------------------------------------------------------- /.github/workflows/build_macos.yaml: -------------------------------------------------------------------------------- 1 | name: Build macOS 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build macOS 6 | runs-on: macos-latest 7 | steps: 8 | - name: Fix up git URLs 9 | run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig 10 | shell: bash 11 | - uses: actions/checkout@v1 12 | with: 13 | token: ${{ secrets.ACCESS_TOKEN }} 14 | submodules: true 15 | - uses: maxim-lobanov/setup-xcode@v1 16 | with: 17 | xcode-version: latest-stable 18 | - name: "Run script" 19 | run: | 20 | export OS="mac" 21 | ./ci/build.sh 22 | shell: bash 23 | env: 24 | APPLICATION: ${{ secrets.APPLICATION }} 25 | INSTALLER: ${{ secrets.INSTALLER }} 26 | APPLE_PASS: ${{ secrets.APPLE_PASS }} 27 | APPLE_USER: ${{ secrets.APPLE_USER }} 28 | APIKEY: ${{ secrets.APIKEY }} 29 | -------------------------------------------------------------------------------- /.github/workflows/build_windows.yaml: -------------------------------------------------------------------------------- 1 | name: Build Windows 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build Windows 6 | runs-on: windows-latest 7 | steps: 8 | - name: Fix up git URLs 9 | run: echo -e '[url "https://github.com/"]\n insteadOf = "git@github.com:"' >> ~/.gitconfig 10 | shell: bash 11 | - uses: actions/checkout@v1 12 | with: 13 | token: ${{ secrets.ACCESS_TOKEN }} 14 | submodules: true 15 | - name: "Run script" 16 | run: | 17 | export OS="win" 18 | ./ci/build.sh 19 | shell: bash 20 | env: 21 | APIKEY: ${{ secrets.APIKEY }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | JuceLibraryCode 35 | Builds 36 | ci/bin -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/juce"] 2 | path = modules/juce 3 | url = git@github.com:juce-framework/JUCE.git 4 | [submodule "modules/gin"] 5 | path = modules/gin 6 | url = git@github.com:FigBug/Gin.git 7 | [submodule "modules/plugin_sdk"] 8 | path = modules/plugin_sdk 9 | url = git@github.com:TurnipHat/plugin_sdk.git 10 | [submodule "modules/simde"] 11 | path = modules/simde 12 | url = https://github.com/simd-everywhere/simde-no-tests.git 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Clayton Otey (wdwonka@users.sourceforge.net) 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.24.0 FATAL_ERROR) 2 | 3 | # 4 | # Set for each plugin 5 | # 6 | set (PLUGIN_NAME Piano) 7 | set (PLUGIN_VERSION 1.0.1) 8 | set (BUNDLE_ID com.socalabs.Piano) 9 | set (AU_ID PianoAU) 10 | set (LV2_URI https://socalabs.com/piano/) 11 | set (PLUGIN_CODE SLpn) 12 | 13 | set (CMAKE_POLICY_DEFAULT_CMP0077 NEW) 14 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 15 | set (CMAKE_SUPPRESS_REGENERATION true) 16 | set (CMAKE_SKIP_INSTALL_RULES YES) 17 | set_property (GLOBAL PROPERTY DEBUG_CONFIGURATIONS "Debug") 18 | 19 | set (CMAKE_C_FLAGS_DEVELOPMENT ${CMAKE_C_FLAGS_RELEASE}) 20 | set (CMAKE_CXX_FLAGS_DEVELOPMENT ${CMAKE_CXX_FLAGS_RELEASE}) 21 | 22 | project (${PLUGIN_NAME} VERSION ${PLUGIN_VERSION} LANGUAGES CXX C HOMEPAGE_URL "https://socalabs.com/") 23 | 24 | include (CMakeDependentOption) 25 | 26 | set_property (DIRECTORY APPEND PROPERTY LABELS ${PLUGIN_NAME}) 27 | 28 | set_property (DIRECTORY APPEND PROPERTY LABELS SocaLabs) 29 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PLUGIN_NAME}_Standalone) 30 | 31 | set (CMAKE_OSX_DEPLOYMENT_TARGET 11.0) 32 | 33 | set (CMAKE_EXPORT_COMPILE_COMMANDS ON) 34 | set (CMAKE_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION ON) 35 | set (CMAKE_CXX_STANDARD 20) 36 | set (CMAKE_CXX_STANDARD_REQUIRED ON) 37 | set (CMAKE_CXX_EXTENSIONS OFF) 38 | set (CMAKE_OBJCXX_STANDARD 20) 39 | set (CMAKE_OBJCXX_STANDARD_REQUIRED ON) 40 | set (CMAKE_CXX_VISIBILITY_PRESET hidden) 41 | set (CMAKE_VISIBILITY_INLINES_HIDDEN ON) 42 | set (CMAKE_MINSIZEREL_POSTFIX -rm) 43 | set (CMAKE_RELWITHDEBINFO_POSTFIX -rd) 44 | set (CMAKE_OPTIMIZE_DEPENDENCIES OFF) 45 | 46 | set (BUILD_SHARED_LIBS OFF) 47 | 48 | if(APPLE) 49 | set (CMAKE_OSX_ARCHITECTURES arm64 x86_64) 50 | endif() 51 | 52 | if (WIN32) 53 | set (FORMATS Standalone VST VST3 LV2) 54 | else() 55 | set (FORMATS Standalone VST VST3 AU LV2) 56 | endif() 57 | 58 | set_property (GLOBAL PROPERTY USE_FOLDERS YES) 59 | set_property (GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER utility) 60 | set_property (GLOBAL PROPERTY REPORT_UNDEFINED_PROPERTIES "${CMAKE_BINARY_DIR}/undefined_properties.log") 61 | set_property (GLOBAL PROPERTY JUCE_COPY_PLUGIN_AFTER_BUILD YES) 62 | 63 | set_property (DIRECTORY APPEND PROPERTY LABELS External) 64 | 65 | # JUCE 66 | 67 | set (JUCE_MODULES_ONLY OFF) 68 | set (JUCE_ENABLE_MODULE_SOURCE_GROUPS ON) 69 | set (JUCE_BUILD_EXTRAS OFF) 70 | set (JUCE_BUILD_EXAMPLES OFF) 71 | 72 | add_subdirectory (modules/juce) 73 | 74 | set_property (DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/modules/juce" APPEND PROPERTY LABELS JUCE) 75 | 76 | # 77 | 78 | # Gin modules 79 | 80 | foreach(module_name IN ITEMS gin gin_dsp gin_simd gin_graphics gin_gui gin_metadata gin_network gin_plugin gin_webp) 81 | juce_add_module ( 82 | "${CMAKE_CURRENT_LIST_DIR}/modules/gin/modules/${module_name}" 83 | ) 84 | 85 | set_property (TARGET "${module_name}" APPEND PROPERTY LABELS Gin) 86 | endforeach() 87 | 88 | # Binary Data 89 | 90 | juce_set_vst2_sdk_path (${CMAKE_SOURCE_DIR}/modules/plugin_sdk/vstsdk2.4) 91 | 92 | juce_add_plugin (${PLUGIN_NAME} 93 | PRODUCT_NAME ${PLUGIN_NAME} 94 | VERSION ${PLUGIN_VERSION} 95 | COMPANY_NAME SocaLabs 96 | COMPANY_WEBSITE "https://socalabs.com/" 97 | BUNDLE_ID ${BUNDLE_ID} 98 | FORMATS ${FORMATS} 99 | PLUGIN_MANUFACTURER_CODE Soca 100 | PLUGIN_CODE ${PLUGIN_CODE} 101 | IS_SYNTH ON 102 | NEEDS_MIDI_INPUT ON 103 | EDITOR_WANTS_KEYBOARD_FOCUS ON 104 | VST2_CATEGORY kPlugCategSynth 105 | VST3_CATEGORIES Instrument 106 | AU_MAIN_TYPE kAudioUnitType_MusicDevice 107 | AU_EXPORT_PREFIX ${AU_ID} 108 | AU_SANDBOX_SAFE FALSE 109 | LV2URI ${LV2_URI}) 110 | 111 | file (GLOB_RECURSE source_files CONFIGURE_DEPENDS 112 | ${CMAKE_CURRENT_SOURCE_DIR}/plugin/*.cpp 113 | ${CMAKE_CURRENT_SOURCE_DIR}/plugin/*.c 114 | ${CMAKE_CURRENT_SOURCE_DIR}/plugin/*.cc 115 | ${CMAKE_CURRENT_SOURCE_DIR}/plugin/*.h) 116 | 117 | target_sources (${PLUGIN_NAME} PRIVATE ${source_files}) 118 | source_group (TREE ${CMAKE_CURRENT_SOURCE_DIR}/plugin PREFIX Source FILES ${source_files}) 119 | 120 | target_link_libraries (${PLUGIN_NAME} PRIVATE 121 | gin 122 | gin_dsp 123 | gin_simd 124 | gin_graphics 125 | gin_gui 126 | gin_plugin 127 | 128 | juce::juce_audio_basics 129 | juce::juce_audio_devices 130 | juce::juce_audio_formats 131 | juce::juce_audio_plugin_client 132 | juce::juce_audio_processors 133 | juce::juce_audio_utils 134 | juce::juce_core 135 | juce::juce_cryptography 136 | juce::juce_data_structures 137 | juce::juce_events 138 | juce::juce_graphics 139 | juce::juce_gui_basics 140 | juce::juce_gui_extra 141 | 142 | juce::juce_recommended_config_flags 143 | ) 144 | 145 | target_include_directories (${PLUGIN_NAME} PRIVATE modules/fmt/include) 146 | target_include_directories (${PLUGIN_NAME} PRIVATE modules/ASIO/common) 147 | target_include_directories (${PLUGIN_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/Definitions) 148 | 149 | juce_generate_juce_header (${PLUGIN_NAME}) 150 | 151 | target_compile_definitions (${PLUGIN_NAME} PRIVATE 152 | JUCE_DISPLAY_SPLASH_SCREEN=0 153 | JUCE_COREGRAPHICS_DRAW_ASYNC=1 154 | JUCE_MODAL_LOOPS_PERMITTED=1 155 | JUCE_WEB_BROWSER=0 156 | JUCE_USE_FLAC=0 157 | JUCE_USE_CURL=1 158 | JUCE_USE_MP3AUDIOFORMAT=0 159 | JUCE_USE_LAME_AUDIO_FORMAT=0 160 | JUCE_USE_WINDOWS_MEDIA_FORMAT=0 161 | DONT_SET_USING_JUCE_NAMESPACE=1 162 | JUCE_IGNORE_VST3_MISMATCHED_PARAMETER_ID_WARNING=1 163 | JucePlugin_PreferredChannelConfigurations={0,2} 164 | _CRT_SECURE_NO_WARNINGS 165 | JUCE_FORCE_USE_LEGACY_PARAM_IDS 166 | ) 167 | set(TARGET ${PLUGIN_NAME} PRIVATE CMAKE_CXX_STANDARD 20) 168 | 169 | if (APPLE) 170 | foreach(t ${FORMATS} "All" "") 171 | set(tgt ${CMAKE_PROJECT_NAME}) 172 | if (NOT t STREQUAL "") 173 | set(tgt ${tgt}_${t}) 174 | endif() 175 | if (TARGET ${tgt}) 176 | set_target_properties(${tgt} PROPERTIES 177 | XCODE_ATTRIBUTE_CLANG_LINK_OBJC_RUNTIME NO 178 | #XCODE_ATTRIBUTE_DEPLOYMENT_POSTPROCESSING[variant=Release] YES 179 | XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] "YES" 180 | ) 181 | if (NOT t STREQUAL "All") 182 | target_compile_options(${tgt} PRIVATE 183 | -Wall -Wstrict-aliasing -Wunused-parameter -Wconditional-uninitialized -Woverloaded-virtual -Wreorder -Wconstant-conversion -Wbool-conversion -Wextra-semi 184 | -Wunreachable-code -Winconsistent-missing-destructor-override -Wshift-sign-overflow -Wnullable-to-nonnull-conversion -Wuninitialized -Wno-missing-field-initializers 185 | -Wno-ignored-qualifiers -Wno-missing-braces -Wno-char-subscripts -Wno-unused-private-field -fno-aligned-allocation -Wunused-private-field -Wunreachable-code 186 | -Wenum-compare -Wshadow -Wfloat-conversion -Wshadow-uncaptured-local -Wshadow-field -Wsign-compare -Wdeprecated-this-capture -Wimplicit-float-conversion 187 | -ffast-math -fno-finite-math-only) 188 | endif() 189 | endif() 190 | endforeach() 191 | endif() 192 | 193 | if (WIN32) 194 | foreach(t ${FORMATS} "All" "") 195 | set(tgt ${CMAKE_PROJECT_NAME}) 196 | if (NOT t STREQUAL "") 197 | set(tgt ${tgt}_${t}) 198 | endif() 199 | if (TARGET ${tgt}) 200 | set_property(TARGET ${tgt} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /INCREMENTAL:NO") 201 | set_target_properties(${tgt} PROPERTIES LINK_FLAGS "/ignore:4099") 202 | endif() 203 | endforeach() 204 | endif() 205 | 206 | if(UNIX AND NOT APPLE) 207 | target_link_libraries (${PLUGIN_NAME} PRIVATE curl) 208 | endif() 209 | 210 | 211 | if(WIN32) 212 | set (dest "Program Files") 213 | else() 214 | set (dest "Applications") 215 | endif() 216 | 217 | install (TARGETS ${PLUGIN_NAME} DESTINATION "${dest}") 218 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmakeMinimumRequired": { 3 | "major": 3, 4 | "minor": 24, 5 | "patch": 0 6 | }, 7 | "version": 5, 8 | "include": [ 9 | "modules/gin/ci/toolchains/xcode.json", 10 | "modules/gin/ci/toolchains/vs.json", 11 | "modules/gin/ci/toolchains/gcc.json", 12 | "modules/gin/ci/toolchains/clang.json" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 309 | 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Library General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | PLUGIN="Piano" 4 | 5 | # linux specific stiff 6 | if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 7 | sudo apt-get update 8 | sudo apt-get install clang git ninja-build ladspa-sdk freeglut3-dev g++ libasound2-dev libcurl4-openssl-dev libfreetype6-dev libjack-jackd2-dev libx11-dev libxcomposite-dev libxcursor-dev libxinerama-dev libxrandr-dev mesa-common-dev webkit2gtk-4.0 juce-tools xvfb 9 | fi 10 | 11 | # mac specific stuff 12 | if [ "$(uname)" == "Darwin" ]; then 13 | # Create a temp keychain 14 | if [ -n "$GITHUB_ACTIONS" ]; then 15 | echo "Create a keychain" 16 | security create-keychain -p nr4aGPyz Keys.keychain 17 | 18 | echo $APPLICATION | base64 -D -o /tmp/Application.p12 19 | echo $INSTALLER | base64 -D -o /tmp/Installer.p12 20 | 21 | security import /tmp/Application.p12 -t agg -k Keys.keychain -P aym9PKWB -A -T /usr/bin/codesign 22 | security import /tmp/Installer.p12 -t agg -k Keys.keychain -P aym9PKWB -A -T /usr/bin/codesign 23 | 24 | security list-keychains -s Keys.keychain 25 | security default-keychain -s Keys.keychain 26 | security unlock-keychain -p nr4aGPyz Keys.keychain 27 | security set-keychain-settings -l -u -t 13600 Keys.keychain 28 | security set-key-partition-list -S apple-tool:,apple: -s -k nr4aGPyz Keys.keychain 29 | fi 30 | DEV_APP_ID="Developer ID Application: Roland Rabien (3FS7DJDG38)" 31 | DEV_INST_ID="Developer ID Installer: Roland Rabien (3FS7DJDG38)" 32 | fi 33 | 34 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 35 | cd "$ROOT" 36 | echo "$ROOT" 37 | 38 | BRANCH=${GITHUB_REF##*/} 39 | echo "$BRANCH" 40 | 41 | cd "$ROOT/ci" 42 | rm -Rf bin 43 | mkdir bin 44 | 45 | # Build mac version 46 | if [ "$(uname)" == "Darwin" ]; then 47 | cd "$ROOT" 48 | cmake --preset xcode 49 | cmake --build --preset xcode --config Release 50 | 51 | cp -R "$ROOT/Builds/xcode/${PLUGIN}_artefacts/Release/AU/$PLUGIN.component" "$ROOT/ci/bin" 52 | cp -R "$ROOT/Builds/xcode/${PLUGIN}_artefacts/Release/VST/$PLUGIN.vst" "$ROOT/ci/bin" 53 | cp -R "$ROOT/Builds/xcode/${PLUGIN}_artefacts/Release/VST3/$PLUGIN.vst3" "$ROOT/ci/bin" 54 | 55 | cd "$ROOT/ci/bin" 56 | codesign -s "$DEV_APP_ID" -v $PLUGIN.vst --options=runtime --timestamp --force 57 | codesign -s "$DEV_APP_ID" -v $PLUGIN.vst3 --options=runtime --timestamp --force 58 | codesign -s "$DEV_APP_ID" -v $PLUGIN.component --options=runtime --timestamp --force 59 | 60 | # Notarize 61 | cd "$ROOT/ci/bin" 62 | zip -r ${PLUGIN}_Mac.zip $PLUGIN.vst $PLUGIN.vst3 $PLUGIN.component 63 | 64 | if [[ -n "$APPLE_USER" ]]; then 65 | xcrun notarytool submit --verbose --apple-id "$APPLE_USER" --password "$APPLE_PASS" --team-id "3FS7DJDG38" --wait --timeout 30m ${PLUGIN}_Mac.zip 66 | fi 67 | 68 | rm ${PLUGIN}_Mac.zip 69 | xcrun stapler staple $PLUGIN.vst 70 | xcrun stapler staple $PLUGIN.vst3 71 | xcrun stapler staple $PLUGIN.component 72 | zip -r ${PLUGIN}_Mac.zip $PLUGIN.vst $PLUGIN.vst3 $PLUGIN.component 73 | 74 | if [ "$BRANCH" = "release" ]; then 75 | curl -F "files=@${PLUGIN}_Mac.zip" "https://socalabs.com/files/set.php?key=$APIKEY" 76 | fi 77 | fi 78 | 79 | # Build linux version 80 | if [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 81 | cd "$ROOT" 82 | 83 | cmake --preset ninja-clang 84 | cmake --build --preset ninja-clang --config Release 85 | 86 | cp -R "$ROOT/Builds/ninja-clang/${PLUGIN}_artefacts/Release/LV2/$PLUGIN.lv2" "$ROOT/ci/bin" 87 | cp -R "$ROOT/Builds/ninja-clang/${PLUGIN}_artefacts/Release/VST/lib$PLUGIN.so" "$ROOT/ci/bin/$PLUGIN.so" 88 | cp -R "$ROOT/Builds/ninja-clang/${PLUGIN}_artefacts/Release/VST3/$PLUGIN.vst3" "$ROOT/ci/bin" 89 | 90 | cd "$ROOT/ci/bin" 91 | 92 | # Upload 93 | cd "$ROOT/ci/bin" 94 | zip -r ${PLUGIN}_Linux.zip $PLUGIN.so $PLUGIN.vst3 $PLUGIN.lv2 95 | 96 | if [ "$BRANCH" = "release" ]; then 97 | curl -F "files=@${PLUGIN}_Linux.zip" "https://socalabs.com/files/set.php?key=$APIKEY" 98 | fi 99 | fi 100 | 101 | # Build Win version 102 | if [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then 103 | cd "$ROOT" 104 | 105 | cmake --preset vs 106 | cmake --build --preset vs --config Release 107 | 108 | cd "$ROOT/ci/bin" 109 | 110 | cp -R "$ROOT/Builds/vs/${PLUGIN}_artefacts/Release/VST/$PLUGIN.dll" "$ROOT/ci/bin" 111 | cp -R "$ROOT/Builds/vs/${PLUGIN}_artefacts/Release/VST3/$PLUGIN.vst3" "$ROOT/ci/bin" 112 | 113 | 7z a ${PLUGIN}_Win.zip $PLUGIN.dll $PLUGIN.vst3 114 | 115 | if [ "$BRANCH" = "release" ]; then 116 | curl -F "files=@${PLUGIN}_Win.zip" "https://socalabs.com/files/set.php?key=$APIKEY" 117 | fi 118 | fi 119 | -------------------------------------------------------------------------------- /ci/config_cmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | ROOT=$(cd "$(dirname "$0")/.."; pwd) 4 | cd "$ROOT" 5 | 6 | export PATH=$PATH:"/c/Program Files/CMake/bin" 7 | 8 | if [ "$(uname)" == "Darwin" ]; then 9 | TOOLCHAIN="xcode" 10 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then 11 | TOOLCHAIN="ninja-clang" 12 | elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then 13 | TOOLCHAIN="vs" 14 | fi 15 | 16 | cmake --preset $TOOLCHAIN -D BUILD_EXTRAS=OFF -D JUCE_COPY_PLUGIN_AFTER_BUILD=ON 17 | -------------------------------------------------------------------------------- /plugin/Source/AlignedArray.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | template 8 | class AlignedArray { 9 | public: 10 | ~AlignedArray() { 11 | Free(); 12 | } 13 | void Free() { 14 | if (ptr_) { 15 | #ifdef _WIN32 16 | _aligned_free(ptr_); 17 | #else 18 | free(ptr_); 19 | #endif 20 | ptr_ = nullptr; 21 | size_ = 0; 22 | } 23 | } 24 | 25 | void Reset(size_t size) { 26 | Free(); 27 | #ifdef _WIN32 28 | ptr_ = (T*)_aligned_malloc(size * sizeof(T), aligenment); 29 | if (!ptr_) assert(false); 30 | #else 31 | auto result = posix_memalign((void**)&ptr_, aligenment, size * sizeof(T)); 32 | if (result != 0) assert(false); 33 | #endif 34 | std::fill_n(ptr_, size, T{}); 35 | size_ = size; 36 | } 37 | 38 | T& operator[](size_t i) { 39 | assert(i < size_); 40 | return ptr_[i]; 41 | } 42 | 43 | T operator[](size_t i) const { 44 | assert(i < size_); 45 | return ptr_[i]; 46 | } 47 | 48 | T* Get() { 49 | return ptr_; 50 | } 51 | 52 | const T* Get() const { 53 | return ptr_; 54 | } 55 | 56 | private: 57 | T* ptr_{ nullptr }; 58 | size_t size_{ 0 }; 59 | }; 60 | -------------------------------------------------------------------------------- /plugin/Source/AudioFFT.cpp: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2013 HiFi-LoFi 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is furnished 9 | // to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // ================================================================================== 21 | 22 | #include "AudioFFT.h" 23 | #include "FFTConvolver.h" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | 30 | namespace audiofft 31 | { 32 | 33 | namespace internal 34 | { 35 | static bool IsPowerOf2 (size_t val) 36 | { 37 | return (val == 1 || (val & (val-1)) == 0); 38 | } 39 | 40 | 41 | template 42 | void ConvertBuffer(TypeDest* dest, const TypeSrc* src, size_t len) 43 | { 44 | for (size_t i=0; i(src[i]); 47 | } 48 | } 49 | 50 | 51 | template 52 | void ScaleBuffer(TypeDest* dest, const TypeSrc* src, const TypeFactor factor, size_t len) 53 | { 54 | for (size_t i=0; i(static_cast(src[i]) * factor); 57 | } 58 | } 59 | 60 | } // End of namespace internal 61 | 62 | 63 | // ================================================================ 64 | 65 | 66 | #ifdef AUDIOFFT_OOURA_USED 67 | 68 | 69 | OouraFFT::OouraFFT() : 70 | AudioFFTBase(), 71 | _size(0), 72 | _ip(), 73 | _w(), 74 | _buffer() 75 | { 76 | } 77 | 78 | 79 | void OouraFFT::init(size_t size) 80 | { 81 | assert(internal::IsPowerOf2(size)); 82 | 83 | if (_size != size) 84 | { 85 | _ip.resize(2 + static_cast(std::sqrt(static_cast(size)))); 86 | _w.resize(size / 2); 87 | _buffer.resize(size); 88 | _size = size; 89 | 90 | const int size4 = static_cast(_size) / 4; 91 | makewt(size4, _ip.data(), _w.data()); 92 | makect(size4, _ip.data(), _w.data() + size4); 93 | } 94 | } 95 | 96 | 97 | void OouraFFT::fft(const float* data, float* re, float* im) 98 | { 99 | // Convert into the format as required by the Ooura FFT 100 | internal::ConvertBuffer(&_buffer[0], data, _size); 101 | 102 | rdft(static_cast(_size), +1, _buffer.data(), _ip.data(), _w.data()); 103 | 104 | // Convert back to split-complex 105 | { 106 | double* b = &_buffer[0]; 107 | double* bEnd = b + _size; 108 | float *r = re; 109 | float *i = im; 110 | while (b != bEnd) 111 | { 112 | *(r++) = static_cast(*(b++)); 113 | *(i++) = static_cast(-(*(b++))); 114 | } 115 | } 116 | const size_t size2 = _size / 2; 117 | re[size2] = -im[0]; 118 | im[0] = 0.0; 119 | im[size2] = 0.0; 120 | } 121 | 122 | 123 | void OouraFFT::ifft(float* data, const float* re, const float* im) 124 | { 125 | // Convert into the format as required by the Ooura FFT 126 | { 127 | double* b = &_buffer[0]; 128 | double* bEnd = b + _size; 129 | const float *r = re; 130 | const float *i = im; 131 | while (b != bEnd) 132 | { 133 | *(b++) = static_cast(*(r++)); 134 | *(b++) = -static_cast(*(i++)); 135 | } 136 | _buffer[1] = re[_size / 2]; 137 | } 138 | 139 | rdft(static_cast(_size), -1, _buffer.data(), _ip.data(), _w.data()); 140 | 141 | // Convert back to split-complex 142 | internal::ScaleBuffer(data, &_buffer[0], 2.0 / static_cast(_size), _size); 143 | } 144 | 145 | 146 | void OouraFFT::rdft(int n, int isgn, double *a, int *ip, double *w) 147 | { 148 | int nw = ip[0]; 149 | int nc = ip[1]; 150 | 151 | if (isgn >= 0) 152 | { 153 | if (n > 4) 154 | { 155 | bitrv2(n, ip + 2, a); 156 | cftfsub(n, a, w); 157 | rftfsub(n, a, nc, w + nw); 158 | } 159 | else if (n == 4) 160 | { 161 | cftfsub(n, a, w); 162 | } 163 | double xi = a[0] - a[1]; 164 | a[0] += a[1]; 165 | a[1] = xi; 166 | } 167 | else 168 | { 169 | a[1] = 0.5 * (a[0] - a[1]); 170 | a[0] -= a[1]; 171 | if (n > 4) 172 | { 173 | rftbsub(n, a, nc, w + nw); 174 | bitrv2(n, ip + 2, a); 175 | cftbsub(n, a, w); 176 | } 177 | else if (n == 4) 178 | { 179 | cftfsub(n, a, w); 180 | } 181 | } 182 | } 183 | 184 | 185 | /* -------- initializing routines -------- */ 186 | 187 | void OouraFFT::makewt(int nw, int *ip, double *w) 188 | { 189 | int j, nwh; 190 | double delta, x, y; 191 | 192 | ip[0] = nw; 193 | ip[1] = 1; 194 | if (nw > 2) { 195 | nwh = nw >> 1; 196 | delta = atan(1.0) / nwh; 197 | w[0] = 1; 198 | w[1] = 0; 199 | w[nwh] = cos(delta * nwh); 200 | w[nwh + 1] = w[nwh]; 201 | if (nwh > 2) { 202 | for (j = 2; j < nwh; j += 2) { 203 | x = cos(delta * j); 204 | y = sin(delta * j); 205 | w[j] = x; 206 | w[j + 1] = y; 207 | w[nw - j] = y; 208 | w[nw - j + 1] = x; 209 | } 210 | bitrv2(nw, ip + 2, w); 211 | } 212 | } 213 | } 214 | 215 | 216 | void OouraFFT::makect(int nc, int *ip, double *c) 217 | { 218 | int j, nch; 219 | double delta; 220 | 221 | ip[1] = nc; 222 | if (nc > 1) { 223 | nch = nc >> 1; 224 | delta = atan(1.0) / nch; 225 | c[0] = cos(delta * nch); 226 | c[nch] = 0.5 * c[0]; 227 | for (j = 1; j < nch; j++) { 228 | c[j] = 0.5 * cos(delta * j); 229 | c[nc - j] = 0.5 * sin(delta * j); 230 | } 231 | } 232 | } 233 | 234 | 235 | /* -------- child routines -------- */ 236 | 237 | 238 | void OouraFFT::bitrv2(int n, int *ip, double *a) 239 | { 240 | int j, j1, k, k1, l, m, m2; 241 | double xr, xi, yr, yi; 242 | 243 | ip[0] = 0; 244 | l = n; 245 | m = 1; 246 | while ((m << 3) < l) { 247 | l >>= 1; 248 | for (j = 0; j < m; j++) { 249 | ip[m + j] = ip[j] + l; 250 | } 251 | m <<= 1; 252 | } 253 | m2 = 2 * m; 254 | if ((m << 3) == l) { 255 | for (k = 0; k < m; k++) { 256 | for (j = 0; j < k; j++) { 257 | j1 = 2 * j + ip[k]; 258 | k1 = 2 * k + ip[j]; 259 | xr = a[j1]; 260 | xi = a[j1 + 1]; 261 | yr = a[k1]; 262 | yi = a[k1 + 1]; 263 | a[j1] = yr; 264 | a[j1 + 1] = yi; 265 | a[k1] = xr; 266 | a[k1 + 1] = xi; 267 | j1 += m2; 268 | k1 += 2 * m2; 269 | xr = a[j1]; 270 | xi = a[j1 + 1]; 271 | yr = a[k1]; 272 | yi = a[k1 + 1]; 273 | a[j1] = yr; 274 | a[j1 + 1] = yi; 275 | a[k1] = xr; 276 | a[k1 + 1] = xi; 277 | j1 += m2; 278 | k1 -= m2; 279 | xr = a[j1]; 280 | xi = a[j1 + 1]; 281 | yr = a[k1]; 282 | yi = a[k1 + 1]; 283 | a[j1] = yr; 284 | a[j1 + 1] = yi; 285 | a[k1] = xr; 286 | a[k1 + 1] = xi; 287 | j1 += m2; 288 | k1 += 2 * m2; 289 | xr = a[j1]; 290 | xi = a[j1 + 1]; 291 | yr = a[k1]; 292 | yi = a[k1 + 1]; 293 | a[j1] = yr; 294 | a[j1 + 1] = yi; 295 | a[k1] = xr; 296 | a[k1 + 1] = xi; 297 | } 298 | j1 = 2 * k + m2 + ip[k]; 299 | k1 = j1 + m2; 300 | xr = a[j1]; 301 | xi = a[j1 + 1]; 302 | yr = a[k1]; 303 | yi = a[k1 + 1]; 304 | a[j1] = yr; 305 | a[j1 + 1] = yi; 306 | a[k1] = xr; 307 | a[k1 + 1] = xi; 308 | } 309 | } else { 310 | for (k = 1; k < m; k++) { 311 | for (j = 0; j < k; j++) { 312 | j1 = 2 * j + ip[k]; 313 | k1 = 2 * k + ip[j]; 314 | xr = a[j1]; 315 | xi = a[j1 + 1]; 316 | yr = a[k1]; 317 | yi = a[k1 + 1]; 318 | a[j1] = yr; 319 | a[j1 + 1] = yi; 320 | a[k1] = xr; 321 | a[k1 + 1] = xi; 322 | j1 += m2; 323 | k1 += m2; 324 | xr = a[j1]; 325 | xi = a[j1 + 1]; 326 | yr = a[k1]; 327 | yi = a[k1 + 1]; 328 | a[j1] = yr; 329 | a[j1 + 1] = yi; 330 | a[k1] = xr; 331 | a[k1 + 1] = xi; 332 | } 333 | } 334 | } 335 | } 336 | 337 | 338 | void OouraFFT::cftfsub(int n, double *a, double *w) 339 | { 340 | int j, j1, j2, j3, l; 341 | double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 342 | 343 | l = 2; 344 | if (n > 8) { 345 | cft1st(n, a, w); 346 | l = 8; 347 | while ((l << 2) < n) { 348 | cftmdl(n, l, a, w); 349 | l <<= 2; 350 | } 351 | } 352 | if ((l << 2) == n) { 353 | for (j = 0; j < l; j += 2) { 354 | j1 = j + l; 355 | j2 = j1 + l; 356 | j3 = j2 + l; 357 | x0r = a[j] + a[j1]; 358 | x0i = a[j + 1] + a[j1 + 1]; 359 | x1r = a[j] - a[j1]; 360 | x1i = a[j + 1] - a[j1 + 1]; 361 | x2r = a[j2] + a[j3]; 362 | x2i = a[j2 + 1] + a[j3 + 1]; 363 | x3r = a[j2] - a[j3]; 364 | x3i = a[j2 + 1] - a[j3 + 1]; 365 | a[j] = x0r + x2r; 366 | a[j + 1] = x0i + x2i; 367 | a[j2] = x0r - x2r; 368 | a[j2 + 1] = x0i - x2i; 369 | a[j1] = x1r - x3i; 370 | a[j1 + 1] = x1i + x3r; 371 | a[j3] = x1r + x3i; 372 | a[j3 + 1] = x1i - x3r; 373 | } 374 | } else { 375 | for (j = 0; j < l; j += 2) { 376 | j1 = j + l; 377 | x0r = a[j] - a[j1]; 378 | x0i = a[j + 1] - a[j1 + 1]; 379 | a[j] += a[j1]; 380 | a[j + 1] += a[j1 + 1]; 381 | a[j1] = x0r; 382 | a[j1 + 1] = x0i; 383 | } 384 | } 385 | } 386 | 387 | 388 | void OouraFFT::cftbsub(int n, double *a, double *w) 389 | { 390 | int j, j1, j2, j3, l; 391 | double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 392 | 393 | l = 2; 394 | if (n > 8) { 395 | cft1st(n, a, w); 396 | l = 8; 397 | while ((l << 2) < n) { 398 | cftmdl(n, l, a, w); 399 | l <<= 2; 400 | } 401 | } 402 | if ((l << 2) == n) { 403 | for (j = 0; j < l; j += 2) { 404 | j1 = j + l; 405 | j2 = j1 + l; 406 | j3 = j2 + l; 407 | x0r = a[j] + a[j1]; 408 | x0i = -a[j + 1] - a[j1 + 1]; 409 | x1r = a[j] - a[j1]; 410 | x1i = -a[j + 1] + a[j1 + 1]; 411 | x2r = a[j2] + a[j3]; 412 | x2i = a[j2 + 1] + a[j3 + 1]; 413 | x3r = a[j2] - a[j3]; 414 | x3i = a[j2 + 1] - a[j3 + 1]; 415 | a[j] = x0r + x2r; 416 | a[j + 1] = x0i - x2i; 417 | a[j2] = x0r - x2r; 418 | a[j2 + 1] = x0i + x2i; 419 | a[j1] = x1r - x3i; 420 | a[j1 + 1] = x1i - x3r; 421 | a[j3] = x1r + x3i; 422 | a[j3 + 1] = x1i + x3r; 423 | } 424 | } else { 425 | for (j = 0; j < l; j += 2) { 426 | j1 = j + l; 427 | x0r = a[j] - a[j1]; 428 | x0i = -a[j + 1] + a[j1 + 1]; 429 | a[j] += a[j1]; 430 | a[j + 1] = -a[j + 1] - a[j1 + 1]; 431 | a[j1] = x0r; 432 | a[j1 + 1] = x0i; 433 | } 434 | } 435 | } 436 | 437 | 438 | void OouraFFT::cft1st(int n, double *a, double *w) 439 | { 440 | int j, k1, k2; 441 | double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; 442 | double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 443 | 444 | x0r = a[0] + a[2]; 445 | x0i = a[1] + a[3]; 446 | x1r = a[0] - a[2]; 447 | x1i = a[1] - a[3]; 448 | x2r = a[4] + a[6]; 449 | x2i = a[5] + a[7]; 450 | x3r = a[4] - a[6]; 451 | x3i = a[5] - a[7]; 452 | a[0] = x0r + x2r; 453 | a[1] = x0i + x2i; 454 | a[4] = x0r - x2r; 455 | a[5] = x0i - x2i; 456 | a[2] = x1r - x3i; 457 | a[3] = x1i + x3r; 458 | a[6] = x1r + x3i; 459 | a[7] = x1i - x3r; 460 | wk1r = w[2]; 461 | x0r = a[8] + a[10]; 462 | x0i = a[9] + a[11]; 463 | x1r = a[8] - a[10]; 464 | x1i = a[9] - a[11]; 465 | x2r = a[12] + a[14]; 466 | x2i = a[13] + a[15]; 467 | x3r = a[12] - a[14]; 468 | x3i = a[13] - a[15]; 469 | a[8] = x0r + x2r; 470 | a[9] = x0i + x2i; 471 | a[12] = x2i - x0i; 472 | a[13] = x0r - x2r; 473 | x0r = x1r - x3i; 474 | x0i = x1i + x3r; 475 | a[10] = wk1r * (x0r - x0i); 476 | a[11] = wk1r * (x0r + x0i); 477 | x0r = x3i + x1r; 478 | x0i = x3r - x1i; 479 | a[14] = wk1r * (x0i - x0r); 480 | a[15] = wk1r * (x0i + x0r); 481 | k1 = 0; 482 | for (j = 16; j < n; j += 16) { 483 | k1 += 2; 484 | k2 = 2 * k1; 485 | wk2r = w[k1]; 486 | wk2i = w[k1 + 1]; 487 | wk1r = w[k2]; 488 | wk1i = w[k2 + 1]; 489 | wk3r = wk1r - 2 * wk2i * wk1i; 490 | wk3i = 2 * wk2i * wk1r - wk1i; 491 | x0r = a[j] + a[j + 2]; 492 | x0i = a[j + 1] + a[j + 3]; 493 | x1r = a[j] - a[j + 2]; 494 | x1i = a[j + 1] - a[j + 3]; 495 | x2r = a[j + 4] + a[j + 6]; 496 | x2i = a[j + 5] + a[j + 7]; 497 | x3r = a[j + 4] - a[j + 6]; 498 | x3i = a[j + 5] - a[j + 7]; 499 | a[j] = x0r + x2r; 500 | a[j + 1] = x0i + x2i; 501 | x0r -= x2r; 502 | x0i -= x2i; 503 | a[j + 4] = wk2r * x0r - wk2i * x0i; 504 | a[j + 5] = wk2r * x0i + wk2i * x0r; 505 | x0r = x1r - x3i; 506 | x0i = x1i + x3r; 507 | a[j + 2] = wk1r * x0r - wk1i * x0i; 508 | a[j + 3] = wk1r * x0i + wk1i * x0r; 509 | x0r = x1r + x3i; 510 | x0i = x1i - x3r; 511 | a[j + 6] = wk3r * x0r - wk3i * x0i; 512 | a[j + 7] = wk3r * x0i + wk3i * x0r; 513 | wk1r = w[k2 + 2]; 514 | wk1i = w[k2 + 3]; 515 | wk3r = wk1r - 2 * wk2r * wk1i; 516 | wk3i = 2 * wk2r * wk1r - wk1i; 517 | x0r = a[j + 8] + a[j + 10]; 518 | x0i = a[j + 9] + a[j + 11]; 519 | x1r = a[j + 8] - a[j + 10]; 520 | x1i = a[j + 9] - a[j + 11]; 521 | x2r = a[j + 12] + a[j + 14]; 522 | x2i = a[j + 13] + a[j + 15]; 523 | x3r = a[j + 12] - a[j + 14]; 524 | x3i = a[j + 13] - a[j + 15]; 525 | a[j + 8] = x0r + x2r; 526 | a[j + 9] = x0i + x2i; 527 | x0r -= x2r; 528 | x0i -= x2i; 529 | a[j + 12] = -wk2i * x0r - wk2r * x0i; 530 | a[j + 13] = -wk2i * x0i + wk2r * x0r; 531 | x0r = x1r - x3i; 532 | x0i = x1i + x3r; 533 | a[j + 10] = wk1r * x0r - wk1i * x0i; 534 | a[j + 11] = wk1r * x0i + wk1i * x0r; 535 | x0r = x1r + x3i; 536 | x0i = x1i - x3r; 537 | a[j + 14] = wk3r * x0r - wk3i * x0i; 538 | a[j + 15] = wk3r * x0i + wk3i * x0r; 539 | } 540 | } 541 | 542 | 543 | void OouraFFT::cftmdl(int n, int l, double *a, double *w) 544 | { 545 | int j, j1, j2, j3, k, k1, k2, m, m2; 546 | double wk1r, wk1i, wk2r, wk2i, wk3r, wk3i; 547 | double x0r, x0i, x1r, x1i, x2r, x2i, x3r, x3i; 548 | 549 | m = l << 2; 550 | for (j = 0; j < l; j += 2) { 551 | j1 = j + l; 552 | j2 = j1 + l; 553 | j3 = j2 + l; 554 | x0r = a[j] + a[j1]; 555 | x0i = a[j + 1] + a[j1 + 1]; 556 | x1r = a[j] - a[j1]; 557 | x1i = a[j + 1] - a[j1 + 1]; 558 | x2r = a[j2] + a[j3]; 559 | x2i = a[j2 + 1] + a[j3 + 1]; 560 | x3r = a[j2] - a[j3]; 561 | x3i = a[j2 + 1] - a[j3 + 1]; 562 | a[j] = x0r + x2r; 563 | a[j + 1] = x0i + x2i; 564 | a[j2] = x0r - x2r; 565 | a[j2 + 1] = x0i - x2i; 566 | a[j1] = x1r - x3i; 567 | a[j1 + 1] = x1i + x3r; 568 | a[j3] = x1r + x3i; 569 | a[j3 + 1] = x1i - x3r; 570 | } 571 | wk1r = w[2]; 572 | for (j = m; j < l + m; j += 2) { 573 | j1 = j + l; 574 | j2 = j1 + l; 575 | j3 = j2 + l; 576 | x0r = a[j] + a[j1]; 577 | x0i = a[j + 1] + a[j1 + 1]; 578 | x1r = a[j] - a[j1]; 579 | x1i = a[j + 1] - a[j1 + 1]; 580 | x2r = a[j2] + a[j3]; 581 | x2i = a[j2 + 1] + a[j3 + 1]; 582 | x3r = a[j2] - a[j3]; 583 | x3i = a[j2 + 1] - a[j3 + 1]; 584 | a[j] = x0r + x2r; 585 | a[j + 1] = x0i + x2i; 586 | a[j2] = x2i - x0i; 587 | a[j2 + 1] = x0r - x2r; 588 | x0r = x1r - x3i; 589 | x0i = x1i + x3r; 590 | a[j1] = wk1r * (x0r - x0i); 591 | a[j1 + 1] = wk1r * (x0r + x0i); 592 | x0r = x3i + x1r; 593 | x0i = x3r - x1i; 594 | a[j3] = wk1r * (x0i - x0r); 595 | a[j3 + 1] = wk1r * (x0i + x0r); 596 | } 597 | k1 = 0; 598 | m2 = 2 * m; 599 | for (k = m2; k < n; k += m2) { 600 | k1 += 2; 601 | k2 = 2 * k1; 602 | wk2r = w[k1]; 603 | wk2i = w[k1 + 1]; 604 | wk1r = w[k2]; 605 | wk1i = w[k2 + 1]; 606 | wk3r = wk1r - 2 * wk2i * wk1i; 607 | wk3i = 2 * wk2i * wk1r - wk1i; 608 | for (j = k; j < l + k; j += 2) { 609 | j1 = j + l; 610 | j2 = j1 + l; 611 | j3 = j2 + l; 612 | x0r = a[j] + a[j1]; 613 | x0i = a[j + 1] + a[j1 + 1]; 614 | x1r = a[j] - a[j1]; 615 | x1i = a[j + 1] - a[j1 + 1]; 616 | x2r = a[j2] + a[j3]; 617 | x2i = a[j2 + 1] + a[j3 + 1]; 618 | x3r = a[j2] - a[j3]; 619 | x3i = a[j2 + 1] - a[j3 + 1]; 620 | a[j] = x0r + x2r; 621 | a[j + 1] = x0i + x2i; 622 | x0r -= x2r; 623 | x0i -= x2i; 624 | a[j2] = wk2r * x0r - wk2i * x0i; 625 | a[j2 + 1] = wk2r * x0i + wk2i * x0r; 626 | x0r = x1r - x3i; 627 | x0i = x1i + x3r; 628 | a[j1] = wk1r * x0r - wk1i * x0i; 629 | a[j1 + 1] = wk1r * x0i + wk1i * x0r; 630 | x0r = x1r + x3i; 631 | x0i = x1i - x3r; 632 | a[j3] = wk3r * x0r - wk3i * x0i; 633 | a[j3 + 1] = wk3r * x0i + wk3i * x0r; 634 | } 635 | wk1r = w[k2 + 2]; 636 | wk1i = w[k2 + 3]; 637 | wk3r = wk1r - 2 * wk2r * wk1i; 638 | wk3i = 2 * wk2r * wk1r - wk1i; 639 | for (j = k + m; j < l + (k + m); j += 2) { 640 | j1 = j + l; 641 | j2 = j1 + l; 642 | j3 = j2 + l; 643 | x0r = a[j] + a[j1]; 644 | x0i = a[j + 1] + a[j1 + 1]; 645 | x1r = a[j] - a[j1]; 646 | x1i = a[j + 1] - a[j1 + 1]; 647 | x2r = a[j2] + a[j3]; 648 | x2i = a[j2 + 1] + a[j3 + 1]; 649 | x3r = a[j2] - a[j3]; 650 | x3i = a[j2 + 1] - a[j3 + 1]; 651 | a[j] = x0r + x2r; 652 | a[j + 1] = x0i + x2i; 653 | x0r -= x2r; 654 | x0i -= x2i; 655 | a[j2] = -wk2i * x0r - wk2r * x0i; 656 | a[j2 + 1] = -wk2i * x0i + wk2r * x0r; 657 | x0r = x1r - x3i; 658 | x0i = x1i + x3r; 659 | a[j1] = wk1r * x0r - wk1i * x0i; 660 | a[j1 + 1] = wk1r * x0i + wk1i * x0r; 661 | x0r = x1r + x3i; 662 | x0i = x1i - x3r; 663 | a[j3] = wk3r * x0r - wk3i * x0i; 664 | a[j3 + 1] = wk3r * x0i + wk3i * x0r; 665 | } 666 | } 667 | } 668 | 669 | 670 | void OouraFFT::rftfsub(int n, double *a, int nc, double *c) 671 | { 672 | int j, k, kk, ks, m; 673 | double wkr, wki, xr, xi, yr, yi; 674 | 675 | m = n >> 1; 676 | ks = 2 * nc / m; 677 | kk = 0; 678 | for (j = 2; j < m; j += 2) { 679 | k = n - j; 680 | kk += ks; 681 | wkr = 0.5 - c[nc - kk]; 682 | wki = c[kk]; 683 | xr = a[j] - a[k]; 684 | xi = a[j + 1] + a[k + 1]; 685 | yr = wkr * xr - wki * xi; 686 | yi = wkr * xi + wki * xr; 687 | a[j] -= yr; 688 | a[j + 1] -= yi; 689 | a[k] += yr; 690 | a[k + 1] -= yi; 691 | } 692 | } 693 | 694 | 695 | void OouraFFT::rftbsub(int n, double *a, int nc, double *c) 696 | { 697 | int j, k, kk, ks, m; 698 | double wkr, wki, xr, xi, yr, yi; 699 | 700 | a[1] = -a[1]; 701 | m = n >> 1; 702 | ks = 2 * nc / m; 703 | kk = 0; 704 | for (j = 2; j < m; j += 2) { 705 | k = n - j; 706 | kk += ks; 707 | wkr = 0.5 - c[nc - kk]; 708 | wki = c[kk]; 709 | xr = a[j] - a[k]; 710 | xi = a[j + 1] + a[k + 1]; 711 | yr = wkr * xr + wki * xi; 712 | yi = wkr * xi - wki * xr; 713 | a[j] -= yr; 714 | a[j + 1] = yi - a[j + 1]; 715 | a[k] += yr; 716 | a[k + 1] = yi - a[k + 1]; 717 | } 718 | a[m + 1] = -a[m + 1]; 719 | } 720 | 721 | #endif // AUDIOFFT_OOURA_USED 722 | 723 | 724 | // ================================================================ 725 | 726 | 727 | #ifdef AUDIOFFT_APPLE_ACCELERATE_USED 728 | 729 | AppleAccelerateFFT::AppleAccelerateFFT() : 730 | _size(0), 731 | _powerOf2(0), 732 | _fftSetup(0), 733 | _re(), 734 | _im() 735 | { 736 | } 737 | 738 | 739 | AppleAccelerateFFT::~AppleAccelerateFFT() 740 | { 741 | AppleAccelerateFFT::init(0); 742 | } 743 | 744 | 745 | void AppleAccelerateFFT::init(size_t size) 746 | { 747 | assert(internal::IsPowerOf2(size)); 748 | 749 | if (_fftSetup) 750 | { 751 | vDSP_destroy_fftsetup(_fftSetup); 752 | _size = 0; 753 | _powerOf2 = 0; 754 | _fftSetup = 0; 755 | _re.clear(); 756 | _im.clear(); 757 | } 758 | 759 | if (size > 0) 760 | { 761 | _size = size; 762 | _powerOf2 = 0; 763 | while ((1 << _powerOf2) < _size) 764 | { 765 | ++_powerOf2; 766 | } 767 | _fftSetup = vDSP_create_fftsetup(_powerOf2, FFT_RADIX2); 768 | _re.resize(_size / 2); 769 | _im.resize(_size / 2); 770 | } 771 | } 772 | 773 | 774 | void AppleAccelerateFFT::fft(const float* data, float* re, float* im) 775 | { 776 | const size_t size2 = _size / 2; 777 | DSPSplitComplex splitComplex; 778 | splitComplex.realp = re; 779 | splitComplex.imagp = im; 780 | vDSP_ctoz(reinterpret_cast(data), 2, &splitComplex, 1, size2); 781 | vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_FORWARD); 782 | const float factor = 0.5f; 783 | vDSP_vsmul(re, 1, &factor, re, 1, size2); 784 | vDSP_vsmul(im, 1, &factor, im, 1, size2); 785 | re[size2] = im[0]; 786 | im[0] = 0.0f; 787 | im[size2] = 0.0f; 788 | } 789 | 790 | 791 | void AppleAccelerateFFT::ifft(float* data, const float* re, const float* im) 792 | { 793 | const size_t size2 = _size / 2; 794 | ::memcpy(_re.data(), re, size2 * sizeof(float)); 795 | ::memcpy(_im.data(), im, size2 * sizeof(float)); 796 | _im[0] = re[size2]; 797 | DSPSplitComplex splitComplex; 798 | splitComplex.realp = _re.data(); 799 | splitComplex.imagp = _im.data(); 800 | vDSP_fft_zrip(_fftSetup, &splitComplex, 1, _powerOf2, FFT_INVERSE); 801 | vDSP_ztoc(&splitComplex, 1, reinterpret_cast(data), 2, size2); 802 | const float factor = 1.0f / static_cast(_size); 803 | vDSP_vsmul(data, 1, &factor, data, 1, _size); 804 | } 805 | 806 | 807 | #endif // AUDIOFFT_APPLE_ACCELERATE_USED 808 | 809 | 810 | // ================================================================ 811 | 812 | 813 | #ifdef AUDIOFFT_FFTW3_USED 814 | 815 | 816 | FFTW3FFT::FFTW3FFT() : 817 | _size(0), 818 | _complexSize(0), 819 | _planForward(0), 820 | _planBackward(0), 821 | _data(0), 822 | _re(0), 823 | _im(0) 824 | { 825 | } 826 | 827 | 828 | FFTW3FFT::~FFTW3FFT() 829 | { 830 | FFTW3FFT::init(0); 831 | } 832 | 833 | 834 | void FFTW3FFT::init(size_t size) 835 | { 836 | assert(internal::IsPowerOf2(size)); 837 | 838 | if (_size != size) 839 | { 840 | if (_size > 0) 841 | { 842 | fftwf_destroy_plan(_planForward); 843 | fftwf_destroy_plan(_planBackward); 844 | _planForward = 0; 845 | _planBackward = 0; 846 | _size = 0; 847 | _complexSize = 0; 848 | 849 | if (_data) 850 | { 851 | fftwf_free(_data); 852 | _data = 0; 853 | } 854 | 855 | if (_re) 856 | { 857 | fftwf_free(_re); 858 | _re = 0; 859 | } 860 | 861 | if (_im) 862 | { 863 | fftwf_free(_im); 864 | _im = 0; 865 | } 866 | } 867 | 868 | if (size > 0) 869 | { 870 | _size = size; 871 | _complexSize = ComplexSize(_size); 872 | const size_t complexSize = ComplexSize(_size); 873 | _data = reinterpret_cast(fftwf_malloc(_size * sizeof(float))); 874 | _re = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float))); 875 | _im = reinterpret_cast(fftwf_malloc(complexSize * sizeof(float))); 876 | 877 | fftw_iodim dim; 878 | dim.n = static_cast(size); 879 | dim.is = 1; 880 | dim.os = 1; 881 | _planForward = fftwf_plan_guru_split_dft_r2c(1, &dim, 0, 0, _data, _re, _im, FFTW_MEASURE); 882 | _planBackward = fftwf_plan_guru_split_dft_c2r(1, &dim, 0, 0, _re, _im, _data, FFTW_MEASURE); 883 | } 884 | } 885 | } 886 | 887 | 888 | void FFTW3FFT::fft(const float* data, float* re, float* im) 889 | { 890 | ::memcpy(_data, data, _size * sizeof(float)); 891 | fftwf_execute_split_dft_r2c(_planForward, _data, _re, _im); 892 | ::memcpy(re, _re, _complexSize * sizeof(float)); 893 | ::memcpy(im, _im, _complexSize * sizeof(float)); 894 | } 895 | 896 | 897 | void FFTW3FFT::ifft(float* data, const float* re, const float* im) 898 | { 899 | ::memcpy(_re, re, _complexSize * sizeof(float)); 900 | ::memcpy(_im, im, _complexSize * sizeof(float)); 901 | fftwf_execute_split_dft_c2r(_planBackward, _re, _im, _data); 902 | internal::ScaleBuffer(data, _data, 1.0f / static_cast(_size), _size); 903 | } 904 | 905 | #endif // AUDIOFFT_FFTW3_USED 906 | 907 | } // End of namespace 908 | -------------------------------------------------------------------------------- /plugin/Source/AudioFFT.h: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2013 HiFi-LoFi 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is furnished 9 | // to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // ================================================================================== 21 | 22 | #ifndef _AUDIOFFT_H 23 | #define _AUDIOFFT_H 24 | 25 | //#define FFTCONVOLVER_USE_SSE 1 26 | //#define AUDIOFFT_APPLE_ACCELERATE 1 27 | 28 | 29 | 30 | /** 31 | * AudioFFT provides real-to-complex/complex-to-real FFT routines. 32 | * 33 | * Features: 34 | * 35 | * - Real-complex FFT and complex-real inverse FFT for power-of-2-sized real data. 36 | * 37 | * - Uniform interface to different FFT implementations (currently Ooura, FFTW3 and Apple Accelerate). 38 | * 39 | * - Complex data is handled in "split-complex" format, i.e. there are separate 40 | * arrays for the real and imaginary parts which can be useful for SIMD optimizations 41 | * (split-complex arrays have to be of length (size/2+1) representing bins from DC 42 | * to Nyquist frequency). 43 | * 44 | * - Output is "ready to use" (all scaling etc. is already handled internally). 45 | * 46 | * - No allocations/deallocations after the initialization which makes it usable 47 | * for real-time audio applications (that's what I wrote it for and using it). 48 | * 49 | * 50 | * How to use it in your project: 51 | * 52 | * - Add the .h and .cpp file to your project - that's all. 53 | * 54 | * - To get extra speed, you can link FFTW3 to your project and define 55 | * AUDIOFFT_FFTW3 (however, please check whether your project suits the 56 | * according license). 57 | * 58 | * - To get the best speed on Apple platforms, you can link the Apple 59 | * Accelerate framework to your project and define 60 | * AUDIOFFT_APPLE_ACCELERATE (however, please check whether your 61 | * project suits the according license). 62 | * 63 | * 64 | * Remarks: 65 | * 66 | * - AudioFFT is not intended to be the fastest FFT, but to be a fast-enough 67 | * FFT suitable for most audio applications. 68 | * 69 | * - AudioFFT uses the quite liberal MIT license. 70 | * 71 | * 72 | * Example usage: 73 | * @code 74 | * #include "AudioFFT.h" 75 | * 76 | * void Example() 77 | * { 78 | * const size_t fftSize = 1024; // Needs to be power of 2! 79 | * 80 | * std::vector input(fftSize, 0.0f); 81 | * std::vector re(fftaudio::AudioFFT::ComplexSize(fftSize)); 82 | * std::vector im(fftaudio::AudioFFT::ComplexSize(fftSize)); 83 | * std::vector output(fftSize); 84 | * 85 | * audiofft::AudioFFT fft; 86 | * fft.init(1024); 87 | * fft.fft(input.data(), re.data(), im.data()); 88 | * fft.ifft(output.data(), re.data(), im.data()); 89 | * } 90 | * @endcode 91 | */ 92 | 93 | 94 | #include 95 | 96 | 97 | #if defined(AUDIOFFT_APPLE_ACCELERATE) 98 | #define AUDIOFFT_APPLE_ACCELERATE_USED 99 | 100 | #ifndef __has_extension 101 | #define __has_extension(x) 0 102 | #endif 103 | #define vImage_Utilities_h 104 | #define vImage_CVUtilities_h 105 | 106 | #include 107 | #include 108 | #elif defined (AUDIOFFT_FFTW3) 109 | #define AUDIOFFT_FFTW3_USED 110 | #include 111 | #else 112 | #ifndef AUDIOFFT_OOURA 113 | #define AUDIOFFT_OOURA 114 | #endif 115 | #define AUDIOFFT_OOURA_USED 116 | #include 117 | #endif 118 | 119 | 120 | namespace audiofft 121 | { 122 | 123 | /** 124 | * @class AudioFFTBase 125 | * @brief Interface class for FFT implementations 126 | */ 127 | class AudioFFTBase 128 | { 129 | public: 130 | /** 131 | * @brief Constructor 132 | */ 133 | AudioFFTBase() 134 | { 135 | } 136 | 137 | /** 138 | * @brief Destructor 139 | */ 140 | virtual ~AudioFFTBase() 141 | { 142 | } 143 | 144 | /** 145 | * @brief Initializes the FFT object 146 | * @param size Size of the real input (must be power 2) 147 | */ 148 | virtual void init(size_t size) = 0; 149 | 150 | /** 151 | * @brief Performs the forward FFT 152 | * @param data The real input data (has to be of the length as specified in init()) 153 | * @param re The real part of the complex output (has to be of length as returned by ComplexSize()) 154 | * @param im The imaginary part of the complex output (has to be of length as returned by ComplexSize()) 155 | */ 156 | virtual void fft(const float* data, float* re, float* im) = 0; 157 | 158 | /** 159 | * @brief Performs the inverse FFT 160 | * @param data The real output data (has to be of the length as specified in init()) 161 | * @param re The real part of the complex input (has to be of length as returned by ComplexSize()) 162 | * @param im The imaginary part of the complex input (has to be of length as returned by ComplexSize()) 163 | */ 164 | virtual void ifft(float* data, const float* re, const float* im) = 0; 165 | 166 | /** 167 | * @brief Calculates the necessary size of the real/imaginary complex arrays 168 | * @param size The size of the real data 169 | * @return The size of the real/imaginary complex arrays 170 | */ 171 | static size_t ComplexSize(size_t size) 172 | { 173 | return (size / 2) + 1; 174 | } 175 | 176 | private: 177 | AudioFFTBase(const AudioFFTBase&); 178 | AudioFFTBase& operator=(const AudioFFTBase&); 179 | }; 180 | 181 | 182 | // ================================================================ 183 | 184 | 185 | #ifdef AUDIOFFT_APPLE_ACCELERATE_USED 186 | 187 | 188 | /** 189 | * @class AppleAccelerateFFT 190 | * @brief FFT implementation using the Apple Accelerate framework internally 191 | */ 192 | class AppleAccelerateFFT : public AudioFFTBase 193 | { 194 | public: 195 | AppleAccelerateFFT(); 196 | virtual ~AppleAccelerateFFT(); 197 | virtual void init(size_t size); 198 | virtual void fft(const float* data, float* re, float* im); 199 | virtual void ifft(float* data, const float* re, const float* im); 200 | 201 | private: 202 | size_t _size; 203 | size_t _powerOf2; 204 | FFTSetup _fftSetup; 205 | std::vector _re; 206 | std::vector _im; 207 | 208 | // Prevent uncontrolled usage 209 | AppleAccelerateFFT(const AppleAccelerateFFT&); 210 | AppleAccelerateFFT& operator=(const AppleAccelerateFFT&); 211 | }; 212 | 213 | typedef AppleAccelerateFFT AudioFFT; 214 | 215 | #endif // AUDIOFFT_APPLE_ACCELERATE_USED 216 | 217 | 218 | // ================================================================ 219 | 220 | 221 | #ifdef AUDIOFFT_FFTW3_USED 222 | 223 | /** 224 | * @class FFTW3FFT 225 | * @brief FFT implementation using FFTW3 internally (see fftw.org) 226 | */ 227 | class FFTW3FFT : public AudioFFTBase 228 | { 229 | public: 230 | FFTW3FFT(); 231 | virtual ~FFTW3FFT(); 232 | virtual void init(size_t size); 233 | virtual void fft(const float* data, float* re, float* im); 234 | virtual void ifft(float* data, const float* re, const float* im); 235 | 236 | private: 237 | size_t _size; 238 | size_t _complexSize; 239 | fftwf_plan _planForward; 240 | fftwf_plan _planBackward; 241 | float* _data; 242 | float* _re; 243 | float* _im; 244 | 245 | // Prevent uncontrolled usage 246 | FFTW3FFT(const FFTW3FFT&); 247 | FFTW3FFT& operator=(const FFTW3FFT&); 248 | }; 249 | 250 | typedef FFTW3FFT AudioFFT; 251 | 252 | #endif // AUDIOFFT_FFTW3_USED 253 | 254 | 255 | // ================================================================ 256 | 257 | 258 | #ifdef AUDIOFFT_OOURA_USED 259 | 260 | /** 261 | * @class OouraFFT 262 | * @brief FFT implementation based on the great radix-4 routines by Takuya Ooura 263 | */ 264 | class OouraFFT : public AudioFFTBase 265 | { 266 | public: 267 | OouraFFT(); 268 | virtual void init(size_t size); 269 | virtual void fft(const float* data, float* re, float* im); 270 | virtual void ifft(float* data, const float* re, const float* im); 271 | 272 | private: 273 | size_t _size; 274 | std::vector _ip; 275 | std::vector _w; 276 | std::vector _buffer; 277 | 278 | // The original FFT routines by Takuya Ooura (see http://momonga.t.u-tokyo.ac.jp/~ooura/fft.html) 279 | void rdft(int n, int isgn, double *a, int *ip, double *w); 280 | void makewt(int nw, int *ip, double *w); 281 | void makect(int nc, int *ip, double *c); 282 | void bitrv2(int n, int *ip, double *a); 283 | void cftfsub(int n, double *a, double *w); 284 | void cftbsub(int n, double *a, double *w); 285 | void rftfsub(int n, double *a, int nc, double *c); 286 | void rftbsub(int n, double *a, int nc, double *c); 287 | void cft1st(int n, double *a, double *w); 288 | void cftmdl(int n, int l, double *a, double *w); 289 | 290 | // Prevent uncontrolled usage 291 | OouraFFT(const OouraFFT&); 292 | OouraFFT& operator=(const OouraFFT&); 293 | }; 294 | 295 | typedef OouraFFT AudioFFT; 296 | 297 | #endif // AUDIOFFT_OOURA_USED 298 | 299 | } // End of namespace 300 | 301 | #endif // Header guard 302 | -------------------------------------------------------------------------------- /plugin/Source/FFTConvolver.cpp: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2012 HiFi-LoFi 3 | // 4 | // This is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // ================================================================================== 17 | 18 | #include "FFTConvolver.h" 19 | 20 | #include 21 | #include 22 | 23 | #if defined (FFTCONVOLVER_USE_SSE) 24 | #include 25 | #endif 26 | 27 | 28 | namespace fftconvolver 29 | { 30 | 31 | FFTConvolver::FFTConvolver() : 32 | _blockSize(0), 33 | _segSize(0), 34 | _segCount(0), 35 | _fftComplexSize(0), 36 | _segments(), 37 | _segmentsIR(), 38 | _fftBuffer(), 39 | _fft(), 40 | _preMultiplied(), 41 | _conv(), 42 | _overlap(), 43 | _current(0), 44 | _inputBuffer(), 45 | _inputBufferFill(0) 46 | { 47 | } 48 | 49 | 50 | FFTConvolver::~FFTConvolver() 51 | { 52 | reset(); 53 | } 54 | 55 | 56 | void FFTConvolver::reset() 57 | { 58 | for (size_t i=0; i<_segCount; ++i) 59 | { 60 | delete _segments[i]; 61 | delete _segmentsIR[i]; 62 | } 63 | 64 | _blockSize = 0; 65 | _segSize = 0; 66 | _segCount = 0; 67 | _fftComplexSize = 0; 68 | _segments.clear(); 69 | _segmentsIR.clear(); 70 | _fftBuffer.clear(); 71 | _fft.init(0); 72 | _preMultiplied.clear(); 73 | _conv.clear(); 74 | _overlap.clear(); 75 | _current = 0; 76 | _inputBuffer.clear(); 77 | _inputBufferFill = 0; 78 | } 79 | 80 | 81 | bool FFTConvolver::init(size_t blockSize, const Sample* ir, size_t irLen) 82 | { 83 | reset(); 84 | 85 | if (blockSize == 0) 86 | { 87 | return false; 88 | } 89 | 90 | // Ignore zeros at the end of the impulse response because they only waste computation time 91 | while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f) 92 | { 93 | --irLen; 94 | } 95 | 96 | if (irLen == 0) 97 | { 98 | return true; 99 | } 100 | 101 | _blockSize = NextPowerOf2(blockSize); 102 | _segSize = 2 * _blockSize; 103 | _segCount = static_cast(::ceil(static_cast(irLen) / static_cast(_blockSize))); 104 | _fftComplexSize = audiofft::AudioFFT::ComplexSize(_segSize); 105 | 106 | // FFT 107 | _fft.init(_segSize); 108 | _fftBuffer.resize(_segSize); 109 | 110 | // Prepare segments 111 | for (size_t i=0; i<_segCount; ++i) 112 | { 113 | _segments.push_back(new SplitComplex(_fftComplexSize)); 114 | } 115 | 116 | // Prepare IR 117 | for (size_t i=0; i<_segCount; ++i) 118 | { 119 | SplitComplex* segment = new SplitComplex(_fftComplexSize); 120 | const size_t remaining = irLen - (i * _blockSize); 121 | const size_t sizeCopy = (remaining >= _blockSize) ? _blockSize : remaining; 122 | CopyAndPad(_fftBuffer, &ir[i*_blockSize], sizeCopy); 123 | _fft.fft(_fftBuffer.data(), segment->re(), segment->im()); 124 | _segmentsIR.push_back(segment); 125 | } 126 | 127 | // Prepare convolution buffers 128 | _preMultiplied.resize(_fftComplexSize); 129 | _conv.resize(_fftComplexSize); 130 | _overlap.resize(_blockSize); 131 | 132 | // Prepare input buffer 133 | _inputBuffer.resize(_blockSize); 134 | _inputBufferFill = 0; 135 | 136 | // Reset current position 137 | _current = 0; 138 | 139 | return true; 140 | } 141 | 142 | 143 | void FFTConvolver::process(const Sample* input, Sample* output, size_t len) 144 | { 145 | if (_segCount == 0) 146 | { 147 | ::memset(output, 0, len * sizeof(Sample)); 148 | return; 149 | } 150 | 151 | size_t processed = 0; 152 | while (processed < len) 153 | { 154 | const bool inputBufferWasEmpty = (_inputBufferFill == 0); 155 | const size_t processing = std::min(len-processed, _blockSize-_inputBufferFill); 156 | const size_t inputBufferPos = _inputBufferFill; 157 | ::memcpy(_inputBuffer.data()+inputBufferPos, input+processed, processing * sizeof(Sample)); 158 | 159 | // Forward FFT 160 | CopyAndPad(_fftBuffer, &_inputBuffer[0], _blockSize); 161 | _fft.fft(_fftBuffer.data(), _segments[_current]->re(), _segments[_current]->im()); 162 | 163 | // Complex multiplication 164 | if (inputBufferWasEmpty) 165 | { 166 | _preMultiplied.setZero(); 167 | for (size_t i=1; i<_segCount; ++i) 168 | { 169 | const size_t indexIr = i; 170 | const size_t indexAudio = (_current + i) % _segCount; 171 | ComplexMultiplyAccumulate(_preMultiplied, *_segmentsIR[indexIr], *_segments[indexAudio]); 172 | } 173 | } 174 | _conv.copyFrom(_preMultiplied); 175 | ComplexMultiplyAccumulate(_conv, *_segments[_current], *_segmentsIR[0]); 176 | 177 | // Backward FFT 178 | _fft.ifft(_fftBuffer.data(), _conv.re(), _conv.im()); 179 | 180 | // Add overlap 181 | Sum(output+processed, _fftBuffer.data()+inputBufferPos, _overlap.data()+inputBufferPos, processing); 182 | 183 | // Input buffer full => Next block 184 | _inputBufferFill += processing; 185 | if (_inputBufferFill == _blockSize) 186 | { 187 | // Input buffer is empty again now 188 | _inputBuffer.setZero(); 189 | _inputBufferFill = 0; 190 | 191 | // Save the overlap 192 | ::memcpy(_overlap.data(), _fftBuffer.data()+_blockSize, _blockSize * sizeof(Sample)); 193 | 194 | // Update current segment 195 | _current = (_current > 0) ? (_current - 1) : (_segCount - 1); 196 | } 197 | 198 | processed += processing; 199 | } 200 | } 201 | 202 | } // End of namespace fftconvolver 203 | -------------------------------------------------------------------------------- /plugin/Source/FFTConvolver.h: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2012 HiFi-LoFi 3 | // 4 | // This is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // ================================================================================== 17 | 18 | #ifndef _FFTCONVOLVER_FFTCONVOLVER_H 19 | #define _FFTCONVOLVER_FFTCONVOLVER_H 20 | 21 | 22 | #include "AudioFFT.h" 23 | #include "Utilities.h" 24 | 25 | #include 26 | 27 | 28 | namespace fftconvolver 29 | { 30 | 31 | /** 32 | * @class FFTConvolver 33 | * @brief Implementation of a partitioned FFT convolution algorithm with uniform block size 34 | * 35 | * Some notes on how to use it: 36 | * 37 | * - After initialization with an impulse response, subsequent data portions of 38 | * arbitrary length can be convolved. The convolver internally can handle 39 | * this by using appropriate buffering. 40 | * 41 | * - The convolver works without "latency" (except for the required 42 | * processing time, of course), i.e. the output always is the convolved 43 | * input for each processing call. 44 | * 45 | * - The convolver is suitable for real-time processing which means that no 46 | * "unpredictable" operations like allocations, locking, API calls, etc. are 47 | * performed during processing (all necessary allocations and preparations take 48 | * place during initialization). 49 | */ 50 | 51 | 52 | class FFTConvolver 53 | { 54 | public: 55 | FFTConvolver(); 56 | virtual ~FFTConvolver(); 57 | 58 | /** 59 | * @brief Initializes the convolver 60 | * @param blockSize Block size internally used by the convolver (partition size) 61 | * @param ir The impulse response 62 | * @param irLen Length of the impulse response 63 | * @return true: Success - false: Failed 64 | */ 65 | bool init(size_t blockSize, const Sample* ir, size_t irLen); 66 | 67 | /** 68 | * @brief Convolves the the given input samples and immediately outputs the result 69 | * @param input The input samples 70 | * @param output The convolution result 71 | * @param len Number of input/output samples 72 | */ 73 | void process(const Sample* input, Sample* output, size_t len); 74 | 75 | /** 76 | * @brief Resets the convolver and discards the set impulse response 77 | */ 78 | void reset(); 79 | 80 | private: 81 | size_t _blockSize; 82 | size_t _segSize; 83 | size_t _segCount; 84 | size_t _fftComplexSize; 85 | std::vector _segments; 86 | std::vector _segmentsIR; 87 | SampleBuffer _fftBuffer; 88 | audiofft::AudioFFT _fft; 89 | SplitComplex _preMultiplied; 90 | SplitComplex _conv; 91 | SampleBuffer _overlap; 92 | size_t _current; 93 | SampleBuffer _inputBuffer; 94 | size_t _inputBufferFill; 95 | 96 | // Prevent uncontrolled usage 97 | FFTConvolver(const FFTConvolver&); 98 | FFTConvolver& operator=(const FFTConvolver&); 99 | }; 100 | 101 | } // End of namespace fftconvolver 102 | 103 | #endif // Header guard 104 | -------------------------------------------------------------------------------- /plugin/Source/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | 4 | class PianoHorizontalFader : public gin::HorizontalFader 5 | { 6 | public: 7 | PianoHorizontalFader (gin::Parameter* p, bool c) 8 | : gin::HorizontalFader (p, c) 9 | { 10 | } 11 | 12 | void resized() override 13 | { 14 | juce::Rectangle r = getLocalBounds(); 15 | 16 | name.setBounds (r.removeFromLeft (100)); 17 | value.setBounds (r.removeFromRight (40)); 18 | fader.setBounds (r.reduced (2)); 19 | } 20 | }; 21 | 22 | //============================================================================== 23 | PianoAudioProcessorEditor::PianoAudioProcessorEditor (PianoAudioProcessor& p_) 24 | : ProcessorEditor (p_), proc (p_) 25 | { 26 | setName ("main"); 27 | 28 | keyboard.setName ("keys"); 29 | keyboard.setOpaque (false); 30 | keyboard.setMidiChannel (1); 31 | keyboard.setKeyWidth (10); 32 | keyboard.setAvailableRange (PIANO_MIN_NOTE, PIANO_MAX_NOTE); 33 | keyboard.setScrollButtonsVisible (false); 34 | addAndMakeVisible (keyboard); 35 | 36 | for (auto pp : proc.getPluginParameters()) 37 | { 38 | auto pc = new PianoHorizontalFader (pp, false); 39 | 40 | addAndMakeVisible (pc); 41 | controls.add (pc); 42 | } 43 | 44 | setGridSize (19, 4); 45 | } 46 | 47 | PianoAudioProcessorEditor::~PianoAudioProcessorEditor() 48 | { 49 | } 50 | 51 | //============================================================================== 52 | void PianoAudioProcessorEditor::paint (juce::Graphics& g) 53 | { 54 | ProcessorEditor::paint (g); 55 | } 56 | 57 | void PianoAudioProcessorEditor::resized() 58 | { 59 | ProcessorEditor::resized (); 60 | 61 | keyboard.setBounds (getGridArea (0, 3, 19, 1)); 62 | keyboard.setKeyWidth ( 10.0f ); 63 | auto ratio = keyboard.getWidth () / keyboard.getTotalKeyboardWidth (); 64 | keyboard.setKeyWidth ( 10.0f * ratio ); 65 | 66 | auto r = getGridArea (0, 0, 19, 3); 67 | 68 | // faders 69 | auto h = 20; 70 | auto w = r.getWidth() / 4; 71 | auto rc = r.removeFromLeft (w); 72 | auto perCol = int (std::ceil (controls.size () / 4.0f)); 73 | 74 | int i = 0; 75 | for (auto c : controls) 76 | { 77 | c->setBounds (rc.removeFromTop (h)); 78 | i++; 79 | 80 | if (i == perCol) 81 | { 82 | i = 0; 83 | rc = r.removeFromLeft (w); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /plugin/Source/PluginEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | 6 | //============================================================================== 7 | class PianoAudioProcessorEditor : public gin::ProcessorEditor 8 | { 9 | public: 10 | PianoAudioProcessorEditor (PianoAudioProcessor&); 11 | ~PianoAudioProcessorEditor() override; 12 | 13 | //============================================================================== 14 | void paint (juce::Graphics&) override; 15 | void resized() override; 16 | 17 | private: 18 | gin::Layout layout {*this}; 19 | 20 | PianoAudioProcessor& proc; 21 | 22 | juce::MidiKeyboardComponent keyboard { proc.keyState, juce::MidiKeyboardComponent::horizontalKeyboard }; 23 | 24 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PianoAudioProcessorEditor) 25 | }; 26 | -------------------------------------------------------------------------------- /plugin/Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "PluginEditor.h" 3 | #include 4 | #include 5 | 6 | static float userValue (int32_t index, float value) 7 | { 8 | float v = 0; 9 | 10 | switch (index) 11 | { 12 | case pYoungsModulus: 13 | v = 200 * exp (4.0f * (value - 0.5f)); 14 | break; 15 | case pStringDensity: 16 | v = 7850 * exp (4.0f * (value - 0.5f)); 17 | break; 18 | case pHammerMass: 19 | v = exp (4.0f * (value - 0.5f)); 20 | break; 21 | case pStringTension: 22 | v = 800.0f * exp (3.0f * (value - 0.5f)); 23 | break; 24 | case pStringLength: 25 | v = exp (2.0f * (value - 0.25f)); 26 | break; 27 | case pStringRadius: 28 | v = exp (2.0f * (value - 0.25f)); 29 | break; 30 | case pHammerCompliance: 31 | v = 2.0f * value; 32 | break; 33 | case pHammerSpringConstant: 34 | v = (2.0f * value); 35 | break; 36 | case pHammerHysteresis: 37 | v = exp (4.0f * (value - 0.5f)); 38 | break; 39 | case pBridgeImpedance: 40 | v = 8000.0f * exp (12.0f * (value - 0.5f)); 41 | break; 42 | case pBridgeHorizontalImpedance: 43 | v = 60000.0f * exp (12.0f * (value - 0.5f)); 44 | break; 45 | case pVerticalHorizontalImpedance: 46 | v = 400.0f * exp (12.0f * (value - 0.5f)); 47 | break; 48 | case pHammerPosition: 49 | v = 0.05f + value * 0.15f; 50 | break; 51 | case pSoundboardSize: 52 | v = value; 53 | break; 54 | case pStringDecay: 55 | v = 0.25f * exp (6.0f * (value - 0.25f)); 56 | break; 57 | case pStringLopass: 58 | v = 5.85f * exp (6.0f * (value - 0.5f)); 59 | break; 60 | case pDampedStringDecay: 61 | v = 8.0f * exp (6.0f * (value - 0.5f)); 62 | break; 63 | case pDampedStringLopass: 64 | v = 25.0f * exp (6.0f * (value - 0.5f)); 65 | break; 66 | case pSoundboardDecay: 67 | v = 20.0f * exp (4.0f * (value - 0.5f)); 68 | break; 69 | case pSoundboardLopass: 70 | v = 20.0f * exp (4.0f * (value - 0.5f)); 71 | break; 72 | case pLongitudinalGamma: 73 | v = 1e-2f * exp (10.0f * (value - 0.5f)); 74 | break; 75 | case pLongitudinalGammaQuadratic: 76 | v = 1.0e-2f * exp (8.0f * (value - 0.5f)); 77 | break; 78 | case pLongitudinalGammaDamped: 79 | v = 5e-2f * exp (10.0f * (value - 0.5f)); 80 | break; 81 | case pLongitudinalGammaQuadraticDamped: 82 | v = 3.0e-2f * exp (8.0f * (value - 0.5f)); 83 | break; 84 | case pLongitudinalMix: 85 | v = (value == 0.0f) ? 0.0f : 1e0f * exp (16.0f * (value - 0.5f)); 86 | break; 87 | case pLongitudinalTransverseMix: 88 | v = (value == 0.0f) ? 0.0f : 1e0f * exp (16.0f * (value - 0.5f)); 89 | break; 90 | case pVolume: 91 | v = 5e-3f * exp (8.0f * (value - 0.5f)); 92 | break; 93 | case pMaxVelocity: 94 | v = 10 * exp (8.0f * (value - 0.5f)); 95 | break; 96 | case pStringDetuning: 97 | v = 1.0f * exp (10.0f * (value - 0.5f)); 98 | break; 99 | case pBridgeMass: 100 | v = 10.0f * exp (10.0f * (value - 0.5f)); 101 | break; 102 | case pBridgeSpring: 103 | v = 1e5f * exp (20.0f * (value - 0.5f)); 104 | break; 105 | case pDwgs4: 106 | v = lrintf (value); 107 | break; 108 | case pDownsample: 109 | v = 1 + lrintf(value); 110 | break; 111 | case pLongModes: 112 | v = 1 + lrintf(value); 113 | break; 114 | } 115 | return v; 116 | } 117 | 118 | //============================================================================== 119 | PianoAudioProcessor::PianoAudioProcessor() 120 | : gin::Processor (false, gin::ProcessorOptions().withAdditionalCredits({"Clayton Otey"})) 121 | { 122 | setLatencySamples (interalBlockSize); 123 | 124 | auto textFunction = [this] (const gin::Parameter& p, float v) 125 | { 126 | int idx = params.indexOf (&p); 127 | return juce::String (userValue (idx, v), 1); 128 | }; 129 | 130 | params.add (addExtParam ("YoungsModulus", "Youngs Modulus", "", "GPa", { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 131 | params.add (addExtParam ("StringDensity", "String Density", "", "kg/m^3" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 132 | params.add (addExtParam ("HammerMass", "Hammer Mass", "", "kg" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 133 | params.add (addExtParam ("StringTension", "String Tension", "", "kg*m/s^2" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 134 | params.add (addExtParam ("StringLength", "String Length", "", "m" , { 0.0f, 1.0f }, 0.25f, {0.0f}, textFunction)); 135 | params.add (addExtParam ("StringRadius", "String Radius", "", "m" , { 0.0f, 1.0f }, 0.25f, {0.0f}, textFunction)); 136 | params.add (addExtParam ("HammerCompliance", "Hammer Compliance", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 137 | params.add (addExtParam ("HammerSpringConstant", "Hammer Spring Constant", "", "kg/s^2" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 138 | params.add (addExtParam ("HammerHysteresis", "Hammer Hysteresis", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 139 | params.add (addExtParam ("HammerPosition", "Hammer Position", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 140 | params.add (addExtParam ("BridgeImpedance", "Bridge Impedance", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 141 | params.add (addExtParam ("BridgeHorizontalImpedance", "Bridge Horizontal Impedance", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 142 | params.add (addExtParam ("VerticalHorizontalImpedance", "Vertical Horizontal Impedance", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 143 | params.add (addExtParam ("SoundboardSize", "Soundboard Size", "", "" , { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 144 | params.add (addExtParam ("StringDecay", "String Decay", "", "" , { 0.0f, 1.0f }, 0.25f, {0.0f}, textFunction)); 145 | params.add (addExtParam ("StringLopass", "String Lopass", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 146 | params.add (addExtParam ("DampedStringDecay", "Damped String Decay", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 147 | params.add (addExtParam ("DampedStringLopass", "Damped String Lopass", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 148 | params.add (addExtParam ("SoundboardDecay", "Soundboard Decay", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 149 | params.add (addExtParam ("SoundboardLopass", "Soundboard Lopass", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 150 | params.add (addExtParam ("LongitudinalGamma", "Longitudinal Gamma", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 151 | params.add (addExtParam ("LongitudinalGammaQuadratic", "Longitudinal Gamma Quadratic", "", "" , { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 152 | params.add (addExtParam ("LongitudinalGammaDamped", "Longitudinal Gamma Damped", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 153 | params.add (addExtParam ("LongitudinalGammaQuadDamped", "Longitudinal Gamma Quad Damped", "", "" , { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 154 | params.add (addExtParam ("LongitudinalMix", "Longitudinal Mix", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 155 | params.add (addExtParam ("LongitudinalTransverseMix", "Longitudinal Transverse Mix", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 156 | params.add (addExtParam ("Volume", "Volume", "", "" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 157 | params.add (addExtParam ("MaxVelocity", "Max Velocity", "", "m/s" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 158 | params.add (addExtParam ("StringDetuning", "String Detuning", "", "%" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 159 | params.add (addExtParam ("BridgeMass", "Bridge Mass", "", "kg" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 160 | params.add (addExtParam ("BridgeSpring", "Bridge Spring", "", "kg/s^2" , { 0.0f, 1.0f }, 0.5f, {0.0f}, textFunction)); 161 | params.add (addExtParam ("Dwgs4", "Dwgs4", "", "" , { 0.0f, 1.0f }, 1.0f, {0.0f}, textFunction)); 162 | params.add (addExtParam ("Downsample", "Downsample", "", "" , { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 163 | params.add (addExtParam ("LongModes", "Long Modes", "", "", { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 164 | params.add (addExtParam ("MaxTime", "Max Time", "", "", { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 165 | params.add (addExtParam ("MultiThread", "MultiThread", "", "", { 0.0f, 1.0f }, 0.0f, {0.0f}, textFunction)); 166 | 167 | init(); 168 | } 169 | 170 | PianoAudioProcessor::~PianoAudioProcessor() 171 | { 172 | } 173 | 174 | //============================================================================== 175 | void PianoAudioProcessor::stateUpdated() 176 | { 177 | } 178 | 179 | void PianoAudioProcessor::updateState() 180 | { 181 | } 182 | 183 | //============================================================================== 184 | void PianoAudioProcessor::reset() 185 | { 186 | Processor::reset(); 187 | } 188 | 189 | void PianoAudioProcessor::prepareToPlay (double newSampleRate, int newSamplesPerBlock) 190 | { 191 | Processor::prepareToPlay (newSampleRate, newSamplesPerBlock); 192 | 193 | piano = std::make_unique(); 194 | piano->init (float (newSampleRate), interalBlockSize); 195 | 196 | fifoIn.setSize (2, newSamplesPerBlock * 2 + interalBlockSize); 197 | fifoOut.setSize (2, newSamplesPerBlock * 2 + interalBlockSize); 198 | 199 | fifoOut.clear(); 200 | fifoOut.writeSilence (interalBlockSize); 201 | } 202 | 203 | void PianoAudioProcessor::releaseResources() 204 | { 205 | } 206 | 207 | void PianoAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midi) 208 | { 209 | juce::ScopedNoDenormals noDenormals; 210 | 211 | buffer.clear (); 212 | auto numSamples = buffer.getNumSamples(); 213 | 214 | keyState.processNextMidiBuffer (midi, 0, numSamples, true); 215 | 216 | if (piano) 217 | { 218 | int idx = 0; 219 | for (auto p : params) 220 | piano->setParameter (idx++, p->getValue()); 221 | 222 | fifoIn.write (buffer, midi); 223 | 224 | while (fifoIn.getNumSamplesAvailable() >= interalBlockSize) 225 | { 226 | juce::MidiBuffer workMidi; 227 | fifoIn.read (workBuffer, workMidi); 228 | 229 | std::span leftBlock{ workBuffer.getWritePointer(0), static_cast(workBuffer.getNumSamples()) }; 230 | std::span rightBlock{ workBuffer.getWritePointer(1), static_cast(workBuffer.getNumSamples()) }; 231 | 232 | piano->process (leftBlock, midi); 233 | 234 | std::copy (leftBlock.begin(), leftBlock.end(), rightBlock.begin()); 235 | 236 | fifoOut.write (workBuffer, workMidi); 237 | } 238 | 239 | // write the output 240 | midi.clear(); 241 | fifoOut.read (buffer, midi); 242 | } 243 | } 244 | 245 | //============================================================================== 246 | bool PianoAudioProcessor::hasEditor() const 247 | { 248 | return true; 249 | } 250 | 251 | juce::AudioProcessorEditor* PianoAudioProcessor::createEditor() 252 | { 253 | return new PianoAudioProcessorEditor (*this); 254 | } 255 | 256 | //============================================================================== 257 | // This creates new instances of the plugin.. 258 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 259 | { 260 | return new PianoAudioProcessor(); 261 | } 262 | -------------------------------------------------------------------------------- /plugin/Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "qiano.h" 5 | 6 | //============================================================================== 7 | class PianoAudioProcessor : public gin::Processor 8 | { 9 | public: 10 | //============================================================================== 11 | PianoAudioProcessor(); 12 | ~PianoAudioProcessor() override; 13 | 14 | void stateUpdated() override; 15 | void updateState() override; 16 | 17 | //============================================================================== 18 | void reset() override; 19 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 20 | void releaseResources() override; 21 | 22 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 23 | 24 | //============================================================================== 25 | juce::AudioProcessorEditor* createEditor() override; 26 | bool hasEditor() const override; 27 | 28 | juce::MidiKeyboardState keyState; 29 | 30 | std::unique_ptr piano; 31 | 32 | juce::Array params; 33 | 34 | constexpr static int interalBlockSize = 32; 35 | 36 | gin::AudioMidiFifo fifoIn {2, interalBlockSize}; 37 | gin::AudioMidiFifo fifoOut {2, interalBlockSize}; 38 | juce::AudioSampleBuffer workBuffer {2, interalBlockSize}; 39 | 40 | //============================================================================== 41 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PianoAudioProcessor) 42 | }; 43 | -------------------------------------------------------------------------------- /plugin/Source/TwoStageFFTConvolver.cpp: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2012 HiFi-LoFi 3 | // 4 | // This is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // ================================================================================== 17 | 18 | #include "TwoStageFFTConvolver.h" 19 | 20 | #include 21 | #include 22 | 23 | 24 | namespace fftconvolver 25 | { 26 | 27 | TwoStageFFTConvolver::TwoStageFFTConvolver() : 28 | _headBlockSize(0), 29 | _tailBlockSize(0), 30 | _headConvolver(), 31 | _tailConvolver0(), 32 | _tailOutput0(), 33 | _tailPrecalculated0(0), 34 | _tailConvolver(), 35 | _tailOutput(), 36 | _tailPrecalculated(0), 37 | _tailInput(), 38 | _tailInputFill(0), 39 | _precalculatedPos(0), 40 | _backgroundProcessingInput() 41 | { 42 | } 43 | 44 | 45 | TwoStageFFTConvolver::~TwoStageFFTConvolver() 46 | { 47 | reset(); 48 | } 49 | 50 | 51 | void TwoStageFFTConvolver::reset() 52 | { 53 | _headBlockSize = 0; 54 | _tailBlockSize = 0; 55 | _headConvolver.reset(); 56 | _tailConvolver0.reset(); 57 | _tailOutput0.clear(); 58 | _tailPrecalculated0.clear(); 59 | _tailConvolver.reset(); 60 | _tailOutput.clear(); 61 | _tailPrecalculated.clear(); 62 | _tailInput.clear(); 63 | _tailInputFill = 0; 64 | _tailInputFill = 0; 65 | _precalculatedPos = 0; 66 | _backgroundProcessingInput.clear(); 67 | } 68 | 69 | 70 | bool TwoStageFFTConvolver::init(size_t headBlockSize, 71 | size_t tailBlockSize, 72 | const Sample* ir, 73 | size_t irLen) 74 | { 75 | reset(); 76 | 77 | if (headBlockSize == 0 || tailBlockSize == 0) 78 | { 79 | return false; 80 | } 81 | 82 | headBlockSize = std::max(size_t(1), headBlockSize); 83 | if (headBlockSize > tailBlockSize) 84 | { 85 | assert(false); 86 | std::swap(headBlockSize, tailBlockSize); 87 | } 88 | 89 | // Ignore zeros at the end of the impulse response because they only waste computation time 90 | while (irLen > 0 && ::fabs(ir[irLen-1]) < 0.000001f) 91 | { 92 | --irLen; 93 | } 94 | 95 | if (irLen == 0) 96 | { 97 | return true; 98 | } 99 | 100 | _headBlockSize = NextPowerOf2(headBlockSize); 101 | _tailBlockSize = NextPowerOf2(tailBlockSize); 102 | 103 | const size_t headIrLen = std::min(irLen, _tailBlockSize); 104 | _headConvolver.init(_headBlockSize, ir, headIrLen); 105 | 106 | if (irLen > _tailBlockSize) 107 | { 108 | const size_t conv1IrLen = std::min(irLen-_tailBlockSize, _tailBlockSize); 109 | _tailConvolver0.init(_headBlockSize, ir+_tailBlockSize, conv1IrLen); 110 | _tailOutput0.resize(_tailBlockSize); 111 | _tailPrecalculated0.resize(_tailBlockSize); 112 | } 113 | 114 | if (irLen > 2 * _tailBlockSize) 115 | { 116 | const size_t tailIrLen = irLen - (2*_tailBlockSize); 117 | _tailConvolver.init(_tailBlockSize, ir+(2*_tailBlockSize), tailIrLen); 118 | _tailOutput.resize(_tailBlockSize); 119 | _tailPrecalculated.resize(_tailBlockSize); 120 | _backgroundProcessingInput.resize(_tailBlockSize); 121 | } 122 | 123 | if (_tailPrecalculated0.size() > 0 || _tailPrecalculated.size() > 0) 124 | { 125 | _tailInput.resize(_tailBlockSize); 126 | } 127 | _tailInputFill = 0; 128 | _precalculatedPos = 0; 129 | 130 | return true; 131 | } 132 | 133 | 134 | void TwoStageFFTConvolver::process(const Sample* input, Sample* output, size_t len) 135 | { 136 | // Head 137 | _headConvolver.process(input, output, len); 138 | 139 | // Tail 140 | if (_tailInput.size() > 0) 141 | { 142 | size_t processed = 0; 143 | while (processed < len) 144 | { 145 | const size_t remaining = len - processed; 146 | const size_t processing = std::min(remaining, _headBlockSize - (_tailInputFill % _headBlockSize)); 147 | assert(_tailInputFill + processing <= _tailBlockSize); 148 | 149 | // Sum head and tail 150 | const size_t sumBegin = processed; 151 | const size_t sumEnd = processed + processing; 152 | { 153 | // Sum: 1st tail block 154 | if (_tailPrecalculated0.size() > 0) 155 | { 156 | size_t precalculatedPos = _precalculatedPos; 157 | for (size_t i=sumBegin; i 0) 166 | { 167 | size_t precalculatedPos = _precalculatedPos; 168 | for (size_t i=sumBegin; i 0 && _tailInputFill % _headBlockSize == 0) 185 | { 186 | assert(_tailInputFill >= _headBlockSize); 187 | const size_t blockOffset = _tailInputFill - _headBlockSize; 188 | _tailConvolver0.process(_tailInput.data()+blockOffset, _tailOutput0.data()+blockOffset, _headBlockSize); 189 | if (_tailInputFill == _tailBlockSize) 190 | { 191 | SampleBuffer::Swap(_tailPrecalculated0, _tailOutput0); 192 | } 193 | } 194 | 195 | // Convolution: 2nd-Nth tail block (might be done in some background thread) 196 | if (_tailPrecalculated.size() > 0 && 197 | _tailInputFill == _tailBlockSize && 198 | _backgroundProcessingInput.size() == _tailBlockSize && 199 | _tailOutput.size() == _tailBlockSize) 200 | { 201 | waitForBackgroundProcessing(); 202 | SampleBuffer::Swap(_tailPrecalculated, _tailOutput); 203 | _backgroundProcessingInput.copyFrom(_tailInput); 204 | startBackgroundProcessing(); 205 | } 206 | 207 | if (_tailInputFill == _tailBlockSize) 208 | { 209 | _tailInputFill = 0; 210 | _precalculatedPos = 0; 211 | } 212 | 213 | processed += processing; 214 | } 215 | } 216 | } 217 | 218 | 219 | void TwoStageFFTConvolver::startBackgroundProcessing() 220 | { 221 | doBackgroundProcessing(); 222 | } 223 | 224 | 225 | void TwoStageFFTConvolver::waitForBackgroundProcessing() 226 | { 227 | } 228 | 229 | 230 | void TwoStageFFTConvolver::doBackgroundProcessing() 231 | { 232 | _tailConvolver.process(_backgroundProcessingInput.data(), _tailOutput.data(), _tailBlockSize); 233 | } 234 | 235 | } // End of namespace fftconvolver 236 | -------------------------------------------------------------------------------- /plugin/Source/TwoStageFFTConvolver.h: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2012 HiFi-LoFi 3 | // 4 | // This is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // ================================================================================== 17 | 18 | #ifndef _FFTCONVOLVER_TWOSTAGEFFTCONVOLVER_H 19 | #define _FFTCONVOLVER_TWOSTAGEFFTCONVOLVER_H 20 | 21 | #include "FFTConvolver.h" 22 | #include "Utilities.h" 23 | 24 | 25 | namespace fftconvolver 26 | { 27 | 28 | /** 29 | * @class TwoStageFFTConvolver 30 | * @brief FFT convolver using two different block sizes 31 | * 32 | * The 2-stage convolver consists internally of two convolvers: 33 | * 34 | * - A head convolver, which processes the only the begin of the impulse response. 35 | * 36 | * - A tail convolver, which processes the rest and major amount of the impulse response. 37 | * 38 | * Using a short block size for the head convolver and a long block size for 39 | * the tail convolver results in much less CPU usage, while keeping the 40 | * calculation time of each processing call short. 41 | * 42 | * Furthermore, this convolver class provides virtual methods which provide the 43 | * possibility to move the tail convolution into the background (e.g. by using 44 | * multithreading, see startBackgroundProcessing()/waitForBackgroundProcessing()). 45 | * 46 | * As well as the basic FFTConvolver class, the 2-stage convolver is suitable 47 | * for real-time processing which means that no "unpredictable" operations like 48 | * allocations, locking, API calls, etc. are performed during processing (all 49 | * necessary allocations and preparations take place during initialization). 50 | */ 51 | class TwoStageFFTConvolver 52 | { 53 | public: 54 | TwoStageFFTConvolver(); 55 | virtual ~TwoStageFFTConvolver(); 56 | 57 | /** 58 | * @brief Initialization the convolver 59 | * @param headBlockSize The head block size 60 | * @param tailBlockSize the tail block size 61 | * @param ir The impulse response 62 | * @param irLen Length of the impulse response in samples 63 | * @return true: Success - false: Failed 64 | */ 65 | bool init(size_t headBlockSize, size_t tailBlockSize, const Sample* ir, size_t irLen); 66 | 67 | /** 68 | * @brief Convolves the the given input samples and immediately outputs the result 69 | * @param input The input samples 70 | * @param output The convolution result 71 | * @param len Number of input/output samples 72 | */ 73 | void process(const Sample* input, Sample* output, size_t len); 74 | 75 | /** 76 | * @brief Resets the convolver and discards the set impulse response 77 | */ 78 | void reset(); 79 | 80 | protected: 81 | /** 82 | * @brief Method called by the convolver if work for background processing is available 83 | * 84 | * The default implementation just calls doBackgroundProcessing() to perform the "bulk" 85 | * convolution. However, if you want to perform the majority of work in some background 86 | * thread (which is recommended), you can overload this method and trigger the execution 87 | * of doBackgroundProcessing() really in some background thread. 88 | */ 89 | virtual void startBackgroundProcessing(); 90 | 91 | /** 92 | * @brief Called by the convolver if it expects the result of its previous call to startBackgroundProcessing() 93 | * 94 | * After returning from this method, all background processing has to be completed. 95 | */ 96 | virtual void waitForBackgroundProcessing(); 97 | 98 | /** 99 | * @brief Actually performs the background processing work 100 | */ 101 | void doBackgroundProcessing(); 102 | 103 | private: 104 | size_t _headBlockSize; 105 | size_t _tailBlockSize; 106 | FFTConvolver _headConvolver; 107 | FFTConvolver _tailConvolver0; 108 | SampleBuffer _tailOutput0; 109 | SampleBuffer _tailPrecalculated0; 110 | FFTConvolver _tailConvolver; 111 | SampleBuffer _tailOutput; 112 | SampleBuffer _tailPrecalculated; 113 | SampleBuffer _tailInput; 114 | size_t _tailInputFill; 115 | size_t _precalculatedPos; 116 | SampleBuffer _backgroundProcessingInput; 117 | 118 | // Prevent uncontrolled usage 119 | TwoStageFFTConvolver(const TwoStageFFTConvolver&); 120 | TwoStageFFTConvolver& operator=(const TwoStageFFTConvolver&); 121 | }; 122 | 123 | } // End of namespace fftconvolver 124 | 125 | #endif // Header guard 126 | -------------------------------------------------------------------------------- /plugin/Source/Utilities.cpp: -------------------------------------------------------------------------------- 1 | // ================================================================================== 2 | // Copyright (c) 2012 HiFi-LoFi 3 | // 4 | // This is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | // ================================================================================== 17 | 18 | #include "Utilities.h" 19 | 20 | 21 | namespace fftconvolver 22 | { 23 | 24 | bool SSEEnabled() 25 | { 26 | #if defined(FFTCONVOLVER_USE_SSE) 27 | return true; 28 | #else 29 | return false; 30 | #endif 31 | } 32 | 33 | 34 | void Sum(Sample* FFTCONVOLVER_RESTRICT result, 35 | const Sample* FFTCONVOLVER_RESTRICT a, 36 | const Sample* FFTCONVOLVER_RESTRICT b, 37 | size_t len) 38 | { 39 | const size_t end4 = 4 * (len / 4); 40 | for (size_t i=0; i. 16 | // ================================================================================== 17 | 18 | #ifndef _FFTCONVOLVER_UTILITIES_H 19 | #define _FFTCONVOLVER_UTILITIES_H 20 | 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | 29 | namespace fftconvolver 30 | { 31 | 32 | #if defined(__SSE__) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) 33 | #if !defined(FFTCONVOLVER_USE_SSE) && !defined(FFTCONVOLVER_DONT_USE_SSE) 34 | #define FFTCONVOLVER_USE_SSE 35 | #endif 36 | #endif 37 | 38 | 39 | #if defined (FFTCONVOLVER_USE_SSE) 40 | #include 41 | #endif 42 | 43 | 44 | #if defined(__GNUC__) 45 | #define FFTCONVOLVER_RESTRICT __restrict__ 46 | #else 47 | #define FFTCONVOLVER_RESTRICT 48 | #endif 49 | 50 | 51 | /** 52 | * @brief Returns whether SSE optimization for the convolver is enabled 53 | * @return true: Enabled - false: Disabled 54 | */ 55 | bool SSEEnabled(); 56 | 57 | 58 | /** 59 | * @class Buffer 60 | * @brief Simple buffer implementation (uses 16-byte alignment if SSE optimization is enabled) 61 | */ 62 | template 63 | class Buffer 64 | { 65 | public: 66 | explicit Buffer(size_t initialSize = 0) : 67 | _data (nullptr), 68 | _size (0) 69 | { 70 | resize(initialSize); 71 | } 72 | 73 | virtual ~Buffer() 74 | { 75 | clear(); 76 | } 77 | 78 | void clear() 79 | { 80 | deallocate(_data); 81 | _data = nullptr; 82 | _size = 0; 83 | } 84 | 85 | void resize(size_t size) 86 | { 87 | if (_size != size) 88 | { 89 | clear(); 90 | 91 | if (size > 0) 92 | { 93 | assert(!_data && _size == 0); 94 | _data = allocate(size); 95 | _size = size; 96 | } 97 | } 98 | setZero(); 99 | } 100 | 101 | size_t size() const 102 | { 103 | return _size; 104 | } 105 | 106 | void setZero() 107 | { 108 | ::memset(_data, 0, _size * sizeof(T)); 109 | } 110 | 111 | void copyFrom(const Buffer& other) 112 | { 113 | assert(_size == other._size); 114 | if (this != &other) 115 | { 116 | ::memcpy(_data, other._data, _size * sizeof(T)); 117 | } 118 | } 119 | 120 | T& operator[](size_t index) 121 | { 122 | assert(_data && index < _size); 123 | return _data[index]; 124 | } 125 | 126 | const T& operator[](size_t index) const 127 | { 128 | assert(_data && index < _size); 129 | return _data[index]; 130 | } 131 | 132 | operator bool() const 133 | { 134 | return (_data != 0 && _size > 0); 135 | } 136 | 137 | T* data() 138 | { 139 | return _data; 140 | } 141 | 142 | const T* data() const 143 | { 144 | return _data; 145 | } 146 | 147 | static void Swap(Buffer& a, Buffer& b) 148 | { 149 | std::swap(a._data, b._data); 150 | std::swap(a._size, b._size); 151 | } 152 | 153 | private: 154 | T* allocate(size_t size) 155 | { 156 | #if defined(FFTCONVOLVER_USE_SSE) 157 | return static_cast(_mm_malloc(size * sizeof(T), 16)); 158 | #else 159 | return new T[size]; 160 | #endif 161 | } 162 | 163 | void deallocate(T* ptr) 164 | { 165 | #if defined(FFTCONVOLVER_USE_SSE) 166 | _mm_free(ptr); 167 | #else 168 | delete [] ptr; 169 | #endif 170 | } 171 | 172 | T* _data; 173 | size_t _size; 174 | 175 | // Prevent uncontrolled usage 176 | Buffer(const Buffer&); 177 | Buffer& operator=(const Buffer&); 178 | }; 179 | 180 | 181 | /** 182 | * @brief Type of one sample 183 | */ 184 | typedef float Sample; 185 | 186 | 187 | /** 188 | * @brief Buffer for samples 189 | */ 190 | typedef Buffer SampleBuffer; 191 | 192 | 193 | /** 194 | * @class SplitComplex 195 | * @brief Buffer for split-complex representation of FFT results 196 | * 197 | * The split-complex representation stores the real and imaginary parts 198 | * of FFT results in two different memory buffers which is useful e.g. for 199 | * SIMD optimizations. 200 | */ 201 | class SplitComplex 202 | { 203 | public: 204 | explicit SplitComplex(size_t initialSize = 0) : 205 | _size(0), 206 | _re(), 207 | _im() 208 | { 209 | resize(initialSize); 210 | } 211 | 212 | ~SplitComplex() 213 | { 214 | clear(); 215 | } 216 | 217 | void clear() 218 | { 219 | _re.clear(); 220 | _im.clear(); 221 | _size = 0; 222 | } 223 | 224 | void resize(size_t newSize) 225 | { 226 | _re.resize(newSize); 227 | _im.resize(newSize); 228 | _size = newSize; 229 | } 230 | 231 | void setZero() 232 | { 233 | _re.setZero(); 234 | _im.setZero(); 235 | } 236 | 237 | void copyFrom(const SplitComplex& other) 238 | { 239 | _re.copyFrom(other._re); 240 | _im.copyFrom(other._im); 241 | } 242 | 243 | Sample* re() 244 | { 245 | return _re.data(); 246 | } 247 | 248 | const Sample* re() const 249 | { 250 | return _re.data(); 251 | } 252 | 253 | Sample* im() 254 | { 255 | return _im.data(); 256 | } 257 | 258 | const Sample* im() const 259 | { 260 | return _im.data(); 261 | } 262 | 263 | size_t size() const 264 | { 265 | return _size; 266 | } 267 | 268 | private: 269 | size_t _size; 270 | SampleBuffer _re; 271 | SampleBuffer _im; 272 | 273 | // Prevent uncontrolled usage 274 | SplitComplex(const SplitComplex&); 275 | SplitComplex& operator=(const SplitComplex&); 276 | }; 277 | 278 | 279 | /** 280 | * @brief Returns the next power of 2 of a given number 281 | * @param val The number 282 | * @return The next power of 2 283 | */ 284 | template 285 | T NextPowerOf2(const T& val) 286 | { 287 | T nextPowerOf2 = 1; 288 | while (nextPowerOf2 < val) 289 | { 290 | nextPowerOf2 *= 2; 291 | } 292 | return nextPowerOf2; 293 | } 294 | 295 | 296 | /** 297 | * @brief Sums two given sample arrays 298 | * @param result The result array 299 | * @param a The 1st array 300 | * @param b The 2nd array 301 | * @param len The length of the arrays 302 | */ 303 | void Sum(Sample* FFTCONVOLVER_RESTRICT result, 304 | const Sample* FFTCONVOLVER_RESTRICT a, 305 | const Sample* FFTCONVOLVER_RESTRICT b, 306 | size_t len); 307 | 308 | 309 | /** 310 | * @brief Copies a source array into a destination buffer and pads the destination buffer with zeros 311 | * @param dest The destination buffer 312 | * @param src The source array 313 | * @param srcSize The size of the source array 314 | */ 315 | template 316 | void CopyAndPad(Buffer& dest, const T* src, size_t srcSize) 317 | { 318 | assert(dest.size() >= srcSize); 319 | ::memcpy(dest.data(), src, srcSize * sizeof(T)); 320 | ::memset(dest.data() + srcSize, 0, (dest.size()-srcSize) * sizeof(T)); 321 | } 322 | 323 | 324 | /** 325 | * @brief Adds the complex product of two split-complex buffers to a result buffer 326 | * @param result The result buffer 327 | * @param a The 1st factor of the complex product 328 | * @param b The 2nd factor of the complex product 329 | */ 330 | void ComplexMultiplyAccumulate(SplitComplex& result, const SplitComplex& a, const SplitComplex& b); 331 | 332 | 333 | /** 334 | * @brief Adds the complex product of two split-complex arrays to a result array 335 | * @param re The real part of the result buffer 336 | * @param im The imaginary part of the result buffer 337 | * @param reA The real part of the 1st factor of the complex product 338 | * @param imA The imaginary part of the 1st factor of the complex product 339 | * @param reB The real part of the 2nd factor of the complex product 340 | * @param imB The imaginary part of the 2nd factor of the complex product 341 | */ 342 | void ComplexMultiplyAccumulate(Sample* FFTCONVOLVER_RESTRICT re, 343 | Sample* FFTCONVOLVER_RESTRICT im, 344 | const Sample* FFTCONVOLVER_RESTRICT reA, 345 | const Sample* FFTCONVOLVER_RESTRICT imA, 346 | const Sample* FFTCONVOLVER_RESTRICT reB, 347 | const Sample* FFTCONVOLVER_RESTRICT imB, 348 | const size_t len); 349 | 350 | } // End of namespace fftconvolver 351 | 352 | #endif // Header guard 353 | -------------------------------------------------------------------------------- /plugin/Source/dwgs.cpp: -------------------------------------------------------------------------------- 1 | #include "dwgs.h" 2 | #include 3 | #include 4 | #include "utils.h" 5 | #include 6 | using namespace std; 7 | 8 | 9 | /* 10 | 11 | pin - 0-|--1 -- 2 bridge - soundboard 12 | | 13 | 3 14 | hammer 15 | 16 | The hammer is imparted with an initial velocity. 17 | The hammer velocity becomes an input load using the impedances at the junction. 18 | The 0 & 1 strings carry the velocity wave. 19 | The 0 string is terminated at an infinite impedance pin. 20 | The 1 string interacts with the soundboard by inducing a load 21 | There are no additional ingoing waves into the strings from either the hammer or the bridge 22 | 23 | */ 24 | 25 | //#define MERGE_FILTER 1 26 | //#define LONGMODE_DEBUG 1 27 | //#define LONG_DEBUG 1 28 | //#define STRING_DEBUG 1 29 | //#define DEBUG_4 30 | 31 | 32 | vec4 dwgs::tran2long4 (int delay) 33 | { 34 | if(nLongModes == 0) { 35 | vec4 out = {0}; 36 | return out; 37 | } 38 | 39 | alignas(32) float out[4]; 40 | float *x; 41 | int cur; 42 | int n; 43 | float *wave10; 44 | 45 | /********* bottom *********/ 46 | x = d1.x; 47 | /* wave10[-del0] = x[cursor - del1] */ 48 | n = delTab + 4; 49 | cur = (d1.cursor + DelaySize - delay + del0 - del1 + 1 ) % DelaySize; 50 | wave10 = wave0.Get() + delTab + 4; 51 | 52 | if(n <= cur) 53 | { 54 | float *x1 = x + cur; 55 | memcpy (wave10-n, x1-n, size_t (n) * sizeof(float)); 56 | /* 57 | for(int i=-1; i>=-n; i--) { 58 | wave10[i] = x1[i]; 59 | } 60 | */ 61 | } 62 | else 63 | { 64 | float *x1 = x + cur; 65 | memcpy(wave10-cur, x1-cur, size_t (cur) * sizeof(float)); 66 | /* 67 | for(int i=-1; i>=-cur; i--) { 68 | wave10[i] = x1[i]; 69 | } 70 | */ 71 | 72 | int cur2 = cur + DelaySize; 73 | x1 = x + cur2; 74 | memcpy(wave10-n, x1-n, size_t (n-cur) * sizeof(float)); 75 | /* 76 | for(int i=-cur-1; i>=-n; i--) { 77 | wave10[i] = x1[i]; 78 | } 79 | */ 80 | } 81 | 82 | /********* top *********/ 83 | 84 | for(int j=0; j<4; j++) { 85 | 86 | x = d2.x; 87 | cur = (d2.cursor + DelaySize - delay - 4 + j + del4) % DelaySize; 88 | 89 | float* wave10 = wave0.Get() + j; 90 | #ifdef STRING_DEBUG 91 | for(int i=0; i<=delTab; i++) 92 | printf("%g ",wave10[i]); 93 | printf("\n"); 94 | #endif 95 | 96 | 97 | n = del0 + del2 + del4 + 5; 98 | if (n <= cur) 99 | { 100 | float *x1 = x + cur; 101 | ms4 (wave10, x1 - n + 1, wave.Get(), n); 102 | 103 | /* 104 | for(int i=0; i>2)<<2) + 3,n); 122 | //cerr << "n4/n/cursor = " << n4 << "/" << n << "/" << cur << "\n"; 123 | 124 | for(int i=cur+1; i<=n4; i++) { 125 | //wave1[i] = x1[-i]; 126 | wave[i] = square(wave10[i] - x1[-i]); 127 | cur++; 128 | } 129 | 130 | if(cur + 1 < n) { 131 | ms4(wave10+cur+1,x1-n+1,wave.Get()+cur+1,n-cur-1); 132 | /* 133 | for(int i=cur+1; idownsample = downsample; 198 | this->upsample = upsample; 199 | float resample = (float)upsample / (float)downsample; 200 | 201 | 202 | this->L = L; 203 | this->f = f; 204 | this->inpos = inpos; 205 | 206 | if(f > 400) { 207 | M = 1; 208 | } else { 209 | M = 4; 210 | } 211 | 212 | for(int m=0;mB = B; 216 | this->longFreq1 = longFreq1; 217 | 218 | 219 | float deltot = Fs/downsample/f*upsample; 220 | this->omega = float (TWOPI) / deltot; 221 | dDispersion = M*dispersion[0].phasedelay(omega); 222 | 223 | //logf("hammer delay = %g\n", inpos*deltot); 224 | del0 = (int)(0.5 * (inpos*deltot)); 225 | del1 = (int)((inpos*deltot) - 1.0); 226 | if(del1 < 2) abort(); 227 | dHammer = inpos * deltot - del1; 228 | int dd = std::min(4, del1 - 2); 229 | dHammer += dd; 230 | del1 -= dd; 231 | 232 | hammerDelay.create(dHammer,(int)dHammer); 233 | del5 = (int)dHammer; 234 | 235 | // XXX resize arrays 236 | del2 = (int)(0.5*(deltot - inpos * deltot) - 1.0); 237 | del3 = (int)(0.5*(deltot - inpos * deltot) - dDispersion - 2.0); 238 | 239 | if(del2 < 1) abort(); 240 | if(del3 < 1) abort(); 241 | 242 | float delHalf = 0.5f * deltot; 243 | 244 | float delHammerHalf = 0.5f * inpos * deltot; 245 | 246 | dTop = delHalf - delHammerHalf - del2; 247 | dd = std::min(4, del2 - 1); 248 | dTop += dd; 249 | del2 -= dd; 250 | //logf("hammer = %g\n",dHammer); 251 | //logf("top = %g\n",dTop); 252 | //logf("dispersion(%g) = %g\n",omega,dDispersion); 253 | 254 | del4 = (int)dTop; 255 | 256 | fracDelayTop.create(dTop,(int)dTop); 257 | 258 | dBottomAndLoss = delHalf - delHammerHalf - del3 - dDispersion; 259 | dd = std::min(4, del3 - 1); 260 | dBottomAndLoss += dd; 261 | del3 -= dd; 262 | 263 | d0.setDelay(0); 264 | d1.setDelay(del1-1); 265 | d2.setDelay(del2-1); 266 | d3.setDelay(del3-1); 267 | 268 | delTab = del0 + del2 + del4; 269 | float delta = dTop - (int)dTop; 270 | float delta2 = delHalf - delTab - delta; 271 | 272 | //logf("%d %d %d %d %g %g\n", del0, del1, del2, del3, delta, delta2); 273 | 274 | // posix_memalign ((void**)&wave0, 32, size_t (delTab + 8) * sizeof(float)); 275 | // posix_memalign ((void**)&wave1, 32, size_t (delTab + 8) * sizeof(float)); 276 | // posix_memalign ((void**)&wave, 32, size_t (delTab + 8) * sizeof(float)); 277 | // posix_memalign ((void**)&Fl, 32, size_t (delTab + 8) * sizeof(float)); 278 | // memset (wave0, 0, size_t (delTab + 8) * sizeof(float)); 279 | // memset (wave1, 0, size_t(delTab + 8) * sizeof(float)); 280 | // memset (Fl, 0, size_t (delTab + 8) * sizeof(float)); 281 | wave0.Reset(delTab + 8); 282 | wave1.Reset(delTab + 8); 283 | wave.Reset(delTab + 8); 284 | Fl.Reset(delTab + 8); 285 | 286 | //logf("dwgs top %d %d %d %d %g %g %g %g %g %g %g %g\n",del0,del1,del2,del3,dHammer+1+del2+dTop,del1+del3+dDispersion+lowpassdelay+dBottom, dTop, dBottom, dHammer, inpos*deltot, lowpassdelay, dDispersion); 287 | 288 | nLongModes = (int)(0.5f * Fs / downsample / longFreq1 - 0.5); 289 | nLongModes = (int)(0.5f * Fs / longmodes / longFreq1 - 0.5); 290 | //logf("nlongmodes = %d\n", nLongModes); 291 | if(nLongModes >= nMaxLongModes) abort(); 292 | 293 | //nLongModes = 1; 294 | gammaL /= resample; 295 | 296 | for(int k=1; k <= nLongModes; k++) { 297 | float omegak = float (TWOPI) * k * longFreq1 / (Fs * resample); 298 | float gammak = gammaL * (1.0f + gammaL2 * k * k); 299 | fLong[k] = omegak / float (TWOPI); 300 | longModeResonator[k].create(omegak, gammak); 301 | 302 | //logf("fL%d=%g L%d/fT = %g\n",k,k*longFreq1,k,k*longFreq1/f ); 303 | #ifdef LONGMODE_DEBUG 304 | printf("%g ",omegak); 305 | #endif 306 | if(delTab) { 307 | float n = float (PI) * k / delHalf; 308 | // if(modeTable[k]) delete modeTable[k]; 309 | // if (modeTable[k]) _aligned_free(modeTable[k]); 310 | // posix_memalign ((void**)&modeTable[k], 32, size_t (delTab + 8) * sizeof (float)); 311 | modeTable[k].Reset(delTab + 8); 312 | 313 | for (int i = 0; i <= delTab; i++) 314 | { 315 | float d = i + delta; 316 | float s = sin(d*n); 317 | modeTable[k][i+3] = s; 318 | } 319 | //logf("maxd = %g/ %g\n",delTab+delta,delHalf); 320 | modeTable[k][0] = 0; 321 | modeTable[k][1] = 0; 322 | modeTable[k][2] = 0; 323 | modeTable[k][3] *= (0.5f + delta); 324 | modeTable[k][delTab+3] *= (0.5f + delta2); 325 | } 326 | } 327 | #ifdef LONGMODE_DEBUG 328 | printf("\n"); 329 | for(int k=1; k <= nLongModes; k++) { 330 | float gammak = gammaL * (1.0 + gammaL2 * k * k); 331 | printf("%g ",gammak); 332 | } 333 | printf("\n"); 334 | #endif 335 | 336 | damper(c1,c3,gammaL,gammaL2,128); 337 | } 338 | 339 | // XXX dwgresonator create 340 | void dwgs::damper (float c1, float c3, float gammaL, float gammaL2, int nDamper) 341 | { 342 | if (this->c1 == 0) 343 | { 344 | this->c1 = c1; 345 | this->c3 = c3; 346 | this->nDamper = 0; 347 | 348 | loss.create(f,c1,c3,(float)upsample/(float)downsample); 349 | float lowpassdelay = loss.phasedelay(omega); 350 | float dBottom = dBottomAndLoss - lowpassdelay; 351 | //logf("bottom = %g\n",dBottom); 352 | fracDelayBottom.create(dBottom,std::min(5,(int)dBottom)); 353 | 354 | } 355 | else 356 | { 357 | c1M = pow (c1 / this->c1, 4.0 / nDamper); 358 | c3M = pow (c3 / this->c3, 4.0 / nDamper); 359 | this->nDamper = nDamper; 360 | } 361 | 362 | 363 | #ifdef MERGE_FILTER 364 | fracDelayBottom.merge(loss); 365 | for(int m=0;m decimation * magic) { 386 | decimation *= 2; 387 | } else { 388 | break; 389 | } 390 | } while(true); 391 | 392 | return decimation; 393 | } 394 | 395 | int dwgs::getMinUpsample(int downsample, float Fs, float f, float inpos, float B) 396 | { 397 | int upsample = 1; 398 | 399 | int M; 400 | if(f > 400) { 401 | M = 1; 402 | } else { 403 | M = 4; 404 | } 405 | 406 | do { 407 | float resample = (float)upsample / (float)downsample; 408 | float deltot = (Fs*resample) / f; 409 | del1 = (int)((inpos*deltot) - 1.0); 410 | 411 | for (int m = 0; m < M; m++) 412 | dispersion[m].create(B,f,M,downsample,upsample); 413 | 414 | float omega = float (TWOPI) / deltot; 415 | dDispersion = M * dispersion[0].phasedelay (omega); 416 | del3 = (int)(0.5f * (deltot - inpos * deltot) - dDispersion - 2.0); 417 | 418 | if (del1 < 2 || del3 < 4) 419 | upsample *= 2; 420 | else 421 | break; 422 | 423 | } while (true); 424 | 425 | return upsample; 426 | } 427 | 428 | float dwgs::input_velocity() 429 | { 430 | return a0_3 + a1_2; 431 | } 432 | 433 | float dwgs::next_input_velocity() 434 | { 435 | return d2.probe() + d1.probe(); 436 | } 437 | 438 | // returns input to soundboard 439 | float dwgs::go_string() 440 | { 441 | if (nDamper > 0) 442 | { 443 | if((nDamper & 3) == 0) 444 | { 445 | c1 *= c1M; 446 | c3 *= c3M; 447 | loss.create(f,c1,c3,(float)upsample/(float)downsample); 448 | float lowpassdelay = loss.phasedelay(omega); 449 | float dBottom = dBottomAndLoss - lowpassdelay; 450 | fracDelayBottom.create(dBottom,std::min(5,(int)dBottom)); 451 | } 452 | nDamper--; 453 | } 454 | float a; 455 | 456 | a = d0.goDelay(a0_2); 457 | a = hammerDelay.filter(a); 458 | a1_2 = d1.goDelay(-a); 459 | a0_3 = d2.goDelay(a0_4); 460 | a1_4 = d3.goDelay(a1_3); 461 | 462 | //cout << "0 del =" << a1_2 << " / " << a0_3 << " / " << a1_4 << "\n"; 463 | 464 | a = a1_4; 465 | #ifndef MERGE_FILTER 466 | for(int m=0;m 0) { 550 | if((nDamper & 3) == 0) { 551 | c1 *= c1M; 552 | c3 *= c3M; 553 | loss.create(f,c1,c3,(float)upsample/(float)downsample); 554 | float lowpassdelay = loss.phasedelay(omega); 555 | float dBottom = dBottomAndLoss - lowpassdelay; 556 | fracDelayBottom.create(dBottom,std::min(5,(int)dBottom)); 557 | } 558 | nDamper-=4; 559 | } 560 | 561 | vec4 v; 562 | 563 | v = d0.goDelay4(v0_2); 564 | v = hammerDelay.filter4(v); 565 | v1_2 = d1.goDelay4 (simde_x_mm_negate_ps (v)); 566 | v1_3 = v1_2; 567 | v1_4 = d3.goDelay4(v1_3); 568 | v = v1_4; 569 | #ifndef MERGE_FILTER 570 | for(int m=0;m=-del0; i--) { 675 | wave10[i] -= x1[i]; 676 | } 677 | } else { 678 | float *x1 = x + cur; 679 | for(int i=0; i>=-cur; i--) { 680 | wave10[i] -= x1[i]; 681 | } 682 | int cur2 = cur + DelaySize; 683 | x1 = x + cur2; 684 | for(int i=-cur-1; i>=-del0; i--) { 685 | wave10[i] -= x1[i]; 686 | } 687 | } 688 | 689 | x = d3.x; 690 | cur = (d3.cursor + DelaySize - delay) % DelaySize; 691 | n = del2 + del4; 692 | wave10 = wave.Get() + n; 693 | if(n < cur) { 694 | float *x1 = x + cur; 695 | for(int i=-1; i>=-n; i--) { 696 | wave10[i] -= x1[i]; 697 | } 698 | } else { 699 | float *x1 = x + cur; 700 | for(int i=-1; i>=-cur; i--) { 701 | wave10[i] -= x1[i]; 702 | } 703 | int cur2 = cur + DelaySize; 704 | x1 = x + cur2; 705 | for(int i=-cur-1; i>=-n; i--) { 706 | wave10[i] -= x1[i]; 707 | } 708 | } 709 | 710 | #ifdef STRING_DEBUG 711 | for(int i=0; i<=delTab; i++) { 712 | printf("%g ",wave[i]); 713 | } 714 | printf("\n"); 715 | #endif 716 | 717 | for(int i=0; i<=delTab; i++) { 718 | wave[i] = square(wave[i]); 719 | } 720 | 721 | Fl[3] = 2.0f * (wave[1] - wave[0]); 722 | for(int i=1; i wave; 47 | AlignedArray wave0; 48 | AlignedArray wave1; 49 | AlignedArray Fl; 50 | 51 | float F[nMaxLongModes]{}; 52 | vec4 F4[nMaxLongModes]{}; 53 | 54 | float L{}; 55 | float omega{}; 56 | float f{}; 57 | float inpos{}; 58 | float B{}; 59 | float longFreq1{}; 60 | int nLongModes{}; 61 | int upsample{}; 62 | 63 | int nDamper{}; 64 | float c1{}; 65 | float c3{}; 66 | float c1M{}; 67 | float c3M{}; 68 | 69 | Loss loss{}; 70 | Thiran fracDelayTop; 71 | Thiran fracDelayBottom; 72 | Thiran hammerDelay; 73 | ThiranDispersion dispersion[4]; 74 | 75 | // float *modeTable[nMaxLongModes]; 76 | // float *modeTable4[nMaxLongModes]; 77 | AlignedArray modeTable[nMaxLongModes]; 78 | float fLong[nMaxLongModes]{}; 79 | DWGResonator longModeResonator[nMaxLongModes]; 80 | 81 | float dDispersion{}; 82 | float dTop{}; 83 | float dHammer{}; 84 | float dBottomAndLoss{}; 85 | 86 | float a0_0{}; 87 | float a0_1{}, a0_2{}, a0_3{}, a0_4{}, a0_5{}; 88 | float a1_1{}, a1_2{}, a1_3{}, a1_4{}, a1_5{}; 89 | 90 | vec4 v0_0{}; 91 | vec4 v0_1{}, v0_2{}, v0_3{}, v0_4{}, v0_5{}; 92 | vec4 v1_1{}, v1_2{}, v1_3{}, v1_4{}, v1_5{}; 93 | 94 | 95 | int del0{}, del1{}, del2{}, del3{}, del4{}, del5{}; 96 | Delay d0; 97 | Delay d1; 98 | Delay d2; 99 | Delay d3; 100 | 101 | 102 | int M{}; 103 | }; 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /plugin/Source/fft.cpp: -------------------------------------------------------------------------------- 1 | #include "fft.h" 2 | 3 | void fft128(t_fft *x) 4 | { 5 | fft<128,1>(x); 6 | } 7 | 8 | void ifft128(t_fft *x) 9 | { 10 | fft<128,-1>(x); 11 | } 12 | 13 | void fft256(t_fft *x) 14 | { 15 | fft<256,1>(x); 16 | } 17 | 18 | void ifft256(t_fft *x) 19 | { 20 | fft<256,-1>(x); 21 | } 22 | 23 | void fft384(t_fft *x) 24 | { 25 | fft<384,1>(x); 26 | } 27 | 28 | void fft512(t_fft *x) 29 | { 30 | fft<512,1>(x); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /plugin/Source/filter.cpp: -------------------------------------------------------------------------------- 1 | #include "filter.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "utils.h" 7 | #include "AudioFFT.h" 8 | #include "filter.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | inline void complex_divide(float Hn[2], float Hd[2], float H[2]) 15 | { 16 | float d2 = Hd[0]*Hd[0] + Hd[1]*Hd[1]; 17 | H[0] = (Hn[0]*Hd[0] + Hn[1]*Hd[1])/d2; 18 | H[1] = (Hn[1]*Hd[0] - Hn[0]*Hd[1])/d2; 19 | } 20 | 21 | float Filter::phasedelay(float omega) 22 | { 23 | float Hn[2]; 24 | float Hd[2]; 25 | float H[2]; 26 | 27 | Hn[0] = 0.0; Hn[1] = 0.0; 28 | Hd[0] = 0.0; Hd[1] = 0.0; 29 | 30 | for(int k=0;k<=n;k++) { 31 | int j = n - k; 32 | float c = cos(k * omega); 33 | float s = sin(k * omega); 34 | Hn[0] += c * b[j]; 35 | Hn[1] += s * b[j]; 36 | Hd[0] -= c * a[j]; 37 | Hd[1] -= s * a[j]; 38 | } 39 | complex_divide(Hn,Hd,H); 40 | float arg = atan2(H[1],H[0]); 41 | if (arg < 0) 42 | arg = arg + 2 * float (PI); 43 | 44 | return arg/omega; 45 | } 46 | 47 | void Filter::merge(const Filter &f) 48 | { 49 | int n1 = n; 50 | n = n1 + f.n; 51 | 52 | float* aa = (float*)malloc (size_t (n1 + 1) * sizeof(float)); 53 | float* bb = (float*)malloc (size_t (n1 + 1) * sizeof(float)); 54 | memcpy (aa, a.Get(), size_t (n1+1) * sizeof(float)); 55 | memcpy (bb, b.Get(), size_t (n1+1) * sizeof(float)); 56 | memset (a.Get(), 0, size_t (n+1) * sizeof(float)); 57 | memset (b.Get(), 0, size_t (n+1) * sizeof(float)); 58 | 59 | for(int j=0;j<=n1;j++) { 60 | for(int k=0;k<=f.n;k++) { 61 | b[j+k] += bb[n1-j]*f.b[f.n-k]; 62 | a[j+k] -= aa[n1-j]*f.a[f.n-k]; 63 | } 64 | } 65 | 66 | free(aa); 67 | free(bb); 68 | init(upsample); 69 | } 70 | 71 | static float Db (float B, float f, int M) 72 | { 73 | float C1,C2,k1,k2,k3; 74 | if (M == 4) 75 | { 76 | C1 = 0.069618f; 77 | C2 = 2.0427f; 78 | k1 = -0.00050469f; 79 | k2 = -0.0064264f; 80 | k3 = -2.8743f; 81 | } 82 | else 83 | { 84 | C1 = 0.071089f; 85 | C2 = 2.1074f; 86 | k1 = -0.0026580f; 87 | k2 = -0.014811f; 88 | k3 = -2.9018f; 89 | } 90 | 91 | float logB = log(B); 92 | float kd = exp(k1*logB*logB + k2*logB + k3); 93 | float Cd = exp(C1*logB+C2); 94 | float halfstep = pow (2.0f, 1.0f / 12.0f); 95 | float Ikey = log (f * halfstep / 27.5f) / log (halfstep); 96 | float D = exp (Cd - Ikey * kd); 97 | return D; 98 | } 99 | 100 | Filter::~Filter() 101 | { 102 | // _aligned_free(b); 103 | // _aligned_free(a); 104 | // _aligned_free(x); 105 | // _aligned_free(y); 106 | } 107 | 108 | Filter::Filter(int nmax_) 109 | { 110 | nmax = nmax_; 111 | int n4 = nmax / 4; 112 | // posix_memalign((void**)&b,32,size_t(n4+3)*sizeof(vec4)); 113 | // posix_memalign((void**)&a,32,size_t(n4+3)*sizeof(vec4)); 114 | // posix_memalign((void**)&x,32,2*size_t(n4+3)*sizeof(vec4)); 115 | // posix_memalign((void**)&y,32,2*size_t(n4+3)*sizeof(vec4)); 116 | 117 | // memset(x,0,2*size_t(n4+3)*sizeof(vec4)); 118 | // memset(y,0,2*size_t(n4+3)*sizeof(vec4)); 119 | // memset(b,0,size_t(n4+3)*sizeof(vec4)); 120 | // memset(a,0,size_t(n4+3)*sizeof(vec4)); 121 | b.Reset((n4 + 3) * sizeof(vec4) / sizeof(float)); 122 | a.Reset(n4 + 3 * sizeof(vec4) / sizeof(float)); 123 | x.Reset(2 * (n4 + 3) * sizeof(vec4) / sizeof(float)); 124 | y.Reset(2 * (n4 + 3) * sizeof(vec4) / sizeof(float)); 125 | 126 | int xsize = 2*4*(n4 + 2); 127 | xend = x.Get() + xsize; 128 | yend = y.Get() + xsize; 129 | xc = x.Get(); 130 | yc = y.Get(); 131 | 132 | xskip = xsize; 133 | } 134 | 135 | void Filter::init(int upsample_) 136 | { 137 | upsample = upsample_; 138 | bend = b.Get() + n; 139 | aend = a.Get() + n - 1; 140 | aend4 = a.Get() + n - 4; 141 | 142 | vec4 v0 = {a[3], a[3], a[3], 0}; 143 | a0 = v0; 144 | 145 | vec4 v1 = {a[2], a[2], 0, a[1]}; 146 | a1 = v1; 147 | 148 | vec4 v2 = {a[1], 0, a[1], a[2] + a[1] * a[1]}; 149 | a2 = v2; 150 | 151 | vec4 v3 = {0, a[1], a[1] * a[1] + a[2], a[1] * (a[1] * a[1] + 2 * a[2]) + a[3]}; 152 | a3 = v3; 153 | 154 | // reverse order 155 | for(int i=0; i<=n/2; i++) { 156 | float tmp = a[i]; 157 | a[i] = a[n-i]; 158 | a[n-i] = tmp; 159 | 160 | tmp = b[i]; 161 | b[i] = b[n-i]; 162 | b[n-i] = tmp; 163 | } 164 | 165 | a[n+1] = 0; 166 | b[n+1] = 0; 167 | 168 | } 169 | 170 | float Filter::filter(float in) 171 | { 172 | float *b = this->b.Get(); 173 | float *x = this->xc - n; 174 | if(x < this->x.Get()) x += xskip; 175 | 176 | *xc = in; 177 | xc++; 178 | if(xc>=xend) xc = this->x.Get(); 179 | 180 | 181 | float out = 0; 182 | while(b <= bend) { 183 | if(x >= xend) x -= xskip; 184 | out += *b * *x; 185 | b += upsample; 186 | x += upsample; 187 | } 188 | 189 | float *a = this->a.Get(); 190 | float *y = this->yc - n; 191 | if(y < this->y.Get()) y += xskip; 192 | while(a <= aend) { 193 | if(y >= yend) y -= xskip; 194 | out += *a * *y; 195 | a += upsample; 196 | y += upsample; 197 | } 198 | 199 | *yc = out; 200 | yc++; 201 | if(yc>=yend) yc = this->y.Get(); 202 | 203 | return out; 204 | } 205 | 206 | vec4 Filter::filter4(vec4 in) 207 | { 208 | float *b = this->b.Get(); 209 | float *x = this->xc - n; 210 | if(x < this->x.Get()) x += xskip; 211 | 212 | simde_mm_store_ps(xc, in); 213 | if(xc == this->x.Get()) 214 | simde_mm_store_ps(xend, in); 215 | xc+=4; 216 | if(xc>=xend) xc = this->x.Get(); 217 | 218 | vec4 out = {0}; 219 | 220 | while(b <= bend) { 221 | if(x >= xend) x = this->x.Get(); 222 | out = simde_mm_fmadd_ps(simde_mm_broadcast_ss(b), simde_mm_loadu_ps(x), out); 223 | b+=upsample; 224 | x+=upsample; 225 | } 226 | 227 | vec4 outa = {0}; 228 | float *a = this->a.Get(); 229 | float *y = this->yc - n; 230 | if(y < this->y.Get()) y += xskip; 231 | 232 | while(a <= aend4) 233 | { 234 | if(y >= yend) y = this->y.Get(); 235 | outa = simde_mm_fmadd_ps(simde_mm_broadcast_ss(a), simde_mm_loadu_ps(y), outa); 236 | a+=upsample; 237 | y+=upsample; 238 | } 239 | 240 | out = simde_mm_add_ps (out, outa); 241 | 242 | y = yc - 4; 243 | if(y < this->y.Get()) y += xskip; 244 | vec4 y4 = simde_mm_loadu_ps(y); 245 | 246 | out = simde_mm_fmadd_ps (simde_mm_shuffle_ps (y4, y4, SIMDE_MM_SHUFFLE(0,3,2,1)), a0, out); 247 | out = simde_mm_fmadd_ps (simde_mm_shuffle_ps (y4, out, SIMDE_MM_SHUFFLE(2,0,3,2)), a1, out); 248 | out = simde_mm_fmadd_ps (simde_mm_shuffle_ps (y4, out, SIMDE_MM_SHUFFLE(1,1,0,3)), a2, out); 249 | out = simde_mm_fmadd_ps (simde_mm_shuffle_ps (out, out, SIMDE_MM_SHUFFLE(0,0,0,0)), a3, out); 250 | 251 | simde_mm_store_ps(yc, out); 252 | 253 | if (yc == this->y.Get()) 254 | simde_mm_store_ps(yend, out); 255 | 256 | yc += 4; 257 | 258 | if (yc>=yend) 259 | yc = this->y.Get(); 260 | 261 | return out; 262 | } 263 | 264 | 265 | 266 | void Thiran::create(float D, int N, int upsample) 267 | { 268 | if(N < 1) { 269 | n = 0; 270 | a[0] = -1; 271 | b[0] = 1; 272 | init(); 273 | return; 274 | } 275 | n = N*upsample; 276 | 277 | memset (a.Get(), 0, size_t (n+1) * sizeof(float)); 278 | memset (b.Get(), 0, size_t (n+1) * sizeof(float)); 279 | 280 | int choose = 1; 281 | for(int k=0;k<=N;k++) { 282 | float ak = choose; 283 | for(int n=0;n<=N;n++) { 284 | ak *= ((float)D-(float)(N-n)); 285 | ak /= ((float)D-(float)(N-k-n)); 286 | } 287 | a[upsample*(k)] = -ak; 288 | b[upsample*(N-k)] = ak; 289 | choose = (-choose * (N-k)) / (k+1); 290 | } 291 | 292 | init(upsample); 293 | } 294 | 295 | void ThiranDispersion::create(float B, float f, int M, int downsample, int upsample) 296 | { 297 | int N = 2; 298 | float D; 299 | D = Db(B,f,M); 300 | D /= downsample; 301 | 302 | if(D<=1.0) 303 | { 304 | n = 2*upsample; 305 | a[0] = -1; 306 | a[upsample] = 0; 307 | a[2*upsample] = 0; 308 | b[0] = 1; 309 | b[upsample] = 0; 310 | b[2*upsample] = 0; 311 | init(upsample); 312 | } 313 | else 314 | { 315 | Thiran::create (D,N,upsample); 316 | } 317 | } 318 | 319 | void BiquadHP::create(float omega, float Q) 320 | { 321 | float A = 1.0f / (2.0f * tan (0.5f * omega)); 322 | float A2 = A * A; 323 | float AoQ = A / Q; 324 | float d = (4 * A2 + 2 * AoQ + 1); 325 | 326 | a[0] = -1.0; 327 | a[1] = (8*A2-2) / d; 328 | a[2] = -(4*A2 - 2*AoQ + 1) / d; 329 | 330 | b[0] = 4*A2/d; 331 | b[1] = -8*A2/d; 332 | b[2] = 4*A2/d; 333 | 334 | n = 2; 335 | init(); 336 | } 337 | 338 | DWGResonator::DWGResonator() 339 | { 340 | x1 = 0; 341 | x2 = 0; 342 | } 343 | 344 | vec4 DWGResonator::go4(vec4 vin) 345 | { 346 | alignas(32) float out[4]; 347 | alignas(32) float in[4]; 348 | simde_mm_store_ps(in, vin); 349 | 350 | for (int i = 0; i < 4; i++) 351 | { 352 | float x1t = g * x1; 353 | float v = c * (x1t + x2); 354 | x1 = v - x2 + b1t * in[i]; 355 | x2 = x1t + v; 356 | out[i] = x2; 357 | } 358 | 359 | return simde_mm_load_ps(out); 360 | } 361 | 362 | float DWGResonator::go(float in) 363 | { 364 | float x1t = g * x1; 365 | float v = c * (x1t + x2); 366 | x1 = v - x2 + b1t * in; 367 | x2 = x1t + v; 368 | 369 | return x2; 370 | } 371 | 372 | void DWGResonator::create(float omega, float gamma) 373 | { 374 | g = exp(-2*gamma); 375 | c = sqrt(1/(1+(square(tan(omega) * (1+g)) + square(1-g)) / (4*g))); 376 | if(omega > HALFPI) c = -c; 377 | b1t = sqrt((1-c)/(1+c)); 378 | //x1 = 0; 379 | //x2 = 0; 380 | } 381 | 382 | 383 | void MSD2Filter::filter(float in[2], float out[2]) 384 | { 385 | out[0] = f11 * in[0] + f12 * in[1]; 386 | out[1] = f21 * in[0] + f22 * in[1]; 387 | } 388 | 389 | void MSD2Filter::filter4(vec4 in[2], vec4 out[2]) 390 | { 391 | out[0] = simde_mm_add_ps (simde_mm_mul_ps (simde_mm_broadcast_ss (&f11), in[0]), simde_mm_mul_ps (simde_mm_broadcast_ss(&f12), in[1])); 392 | out[1] = simde_mm_add_ps (simde_mm_mul_ps (simde_mm_broadcast_ss (&f21), in[0]), simde_mm_mul_ps (simde_mm_broadcast_ss(&f22), in[1])); 393 | } 394 | 395 | void MSD2Filter::create(float Fs, 396 | float m1, float k1, float R1, 397 | float m2, float k2, float R2, 398 | float R12, float k12, float Zn, float Z) 399 | { 400 | float det = (R1 + Zn) * (R2 + Zn) - R12 * R12; 401 | f11 = 2.0f * Z * (R2 + Zn) / det; 402 | f12 = -2.0f * Z * R12 / det; 403 | f21 = -2.0f * Z * R12 / det; 404 | f22 = 2.0f * Z * (R1 + Zn) / det; 405 | } 406 | 407 | vec8 ResampleFIR::filter8(vec8 in) 408 | { 409 | float *b = this->b; 410 | float *x = this->xc - (ResampleFilterSize - 1); 411 | if(x < this->x) x += xsize; 412 | 413 | simde_mm256_store_ps(xc, in); 414 | 415 | if(xc == this->x) 416 | simde_mm256_store_ps(xend, in); 417 | 418 | xc += 8; 419 | 420 | if (xc>=xend) 421 | xc = this->x; 422 | 423 | vec8 out = {0}; 424 | 425 | while (b <= bend) 426 | { 427 | if (x >= xend) 428 | x = this->x; 429 | 430 | out = simde_mm256_fmadd_ps (simde_mm256_broadcast_ss (b), simde_mm256_loadu_ps (x), out); 431 | b++; 432 | x++; 433 | } 434 | 435 | return out; 436 | } 437 | 438 | ResampleFIR::ResampleFIR() 439 | { 440 | xsize = ResampleFilterSize * 4; 441 | memset (x, 0, size_t (xsize + 16) * sizeof (float)); 442 | xc = x; 443 | 444 | bend = this->b + ResampleFilterSize - 1; 445 | xend = this->x + xsize; 446 | bInit = false; 447 | } 448 | 449 | int ResampleFIR::getDelay() 450 | { 451 | return ResampleFilterSize / 2; 452 | } 453 | 454 | bool ResampleFIR::isCreated() 455 | { 456 | return bInit; 457 | } 458 | 459 | void ResampleFIR::create(int resample) 460 | { 461 | bInit = true; 462 | float f[ResampleFilterSize]; 463 | float im[ResampleFilterSize]; 464 | 465 | memset(f,0,ResampleFilterSize*sizeof(float)); 466 | memset(im,0,ResampleFilterSize*sizeof(float)); 467 | 468 | for(int i=0; i 6 | #include 7 | #include 8 | #include "AlignedArray.hpp" 9 | 10 | enum { 11 | MaxFilterUpsample = 8 12 | }; 13 | 14 | 15 | class Filter { 16 | public: 17 | Filter(int n); 18 | ~Filter(); 19 | 20 | void merge(const Filter &f); 21 | vec4 filter4(vec4 in); 22 | float filter(float in); 23 | float phasedelay(float omega); 24 | void init(int upsample = 1); 25 | 26 | AlignedArray x; 27 | AlignedArray y; 28 | AlignedArray b; 29 | AlignedArray a; 30 | 31 | float *bend{}; 32 | float *aend{}; 33 | float *aend4{}; 34 | 35 | float *xc{}; 36 | float *yc{}; 37 | 38 | float *xend{}; 39 | float *yend{}; 40 | 41 | vec4 a0; 42 | vec4 a1; 43 | vec4 a2; 44 | vec4 a3; 45 | 46 | int upsample{}; 47 | int xskip{}; 48 | int n{}; 49 | int nmax{}; 50 | }; 51 | 52 | class BiquadHP : public Filter 53 | { 54 | public: 55 | BiquadHP() : Filter(2) {} 56 | void create(float omega, float Q); 57 | }; 58 | 59 | class Thiran : public Filter 60 | { 61 | public: 62 | Thiran (int n_) : Filter (n_) {} 63 | void create (float D, int N, int upsample = 1); 64 | }; 65 | 66 | // nmax = 16 (max upsample is 8) 67 | class ThiranDispersion : public Thiran { 68 | public: 69 | ThiranDispersion() : Thiran(16) {} 70 | void create(float B, float f, int M, int downsample = 1, int upsample = 1); 71 | }; 72 | 73 | 74 | class Loss : public Filter 75 | { 76 | public: 77 | Loss() : Filter(1) {} 78 | /* 79 | float filter(float in) { 80 | float out = b[1] * in + a[0] * y[0]; 81 | y = out; 82 | return out; 83 | } 84 | */ 85 | 86 | void create(float f0, float c1, float c3, float upsample = 1) { 87 | f0 /= upsample; 88 | c1 /= upsample; 89 | c3 *= upsample; 90 | float g = 1.0f - c1 / f0; 91 | float c = 4.0f * c3 + f0; 92 | float a1 = (-c + sqrt (c * c - 16.0f * c3 *c3)) / (4.0f * c3); 93 | b[0] = g*(1+a1); 94 | b[1] = 0; 95 | a[0] = -1; 96 | a[1] = -a1; 97 | n = 1; 98 | init(1); 99 | } 100 | }; 101 | 102 | template 103 | class Delay 104 | { 105 | public: 106 | int di{}; 107 | int d1{}; 108 | int cursor{}; 109 | 110 | float* x; 111 | alignas(32) float x_[size+16]; 112 | 113 | enum { 114 | mask = size-1 }; 115 | 116 | Delay(int offset = 0) { 117 | x = x_ + 8; 118 | cursor = offset; 119 | clear(); 120 | } 121 | 122 | void setDelay (int di_) 123 | { 124 | di = di_; 125 | d1 = (cursor+size-di)&(mask); 126 | } 127 | 128 | void clear() 129 | { 130 | memset(x_,0,(size+16)*sizeof(float)); 131 | } 132 | 133 | float probe() 134 | { 135 | float y0; 136 | y0 = x[d1]; 137 | return y0; 138 | } 139 | 140 | float goDelay(float in) 141 | { 142 | float y0; 143 | x[cursor] = in; 144 | if(cursor <= 3) { 145 | x[cursor + size] = in; 146 | } 147 | cursor = (cursor + 1) & mask; 148 | y0 = x[d1]; 149 | d1 = (d1 + 1) & mask; 150 | return y0; 151 | } 152 | 153 | vec4 probe4() 154 | { 155 | alignas(32) float out[4]; 156 | for (int i = 2; i < 4; i++) 157 | { 158 | int d = d1 + i - 2; 159 | out[i] = x[(d + size) & mask]; 160 | } 161 | d1 = (d1 + 2) & mask; 162 | 163 | return simde_mm_load_ps (out); 164 | } 165 | 166 | void backup() 167 | { 168 | d1 = (d1 - 1 + size) & mask; 169 | } 170 | 171 | void backupCursor() 172 | { 173 | cursor = (cursor - 1 + size) & mask; 174 | } 175 | 176 | vec4 goDelay4(vec4 in) 177 | { 178 | if (cursor == 0) 179 | simde_mm_store_ps((x + size), in); 180 | 181 | simde_mm_store_ps((x + cursor), in); 182 | vec4 y0 = simde_mm_loadu_ps(x + d1); 183 | d1 = (d1 + 4) & mask; 184 | cursor = (cursor + 4) & mask; 185 | return y0; 186 | } 187 | 188 | }; 189 | 190 | 191 | class DWGResonator { 192 | public: 193 | DWGResonator(); 194 | void create(float omega, float gamma); 195 | float go(float in); 196 | vec4 go4(vec4 vin); 197 | 198 | // variables 199 | float g; 200 | float c; 201 | float b1t; 202 | 203 | // state 204 | float x1; 205 | float x2; 206 | }; 207 | 208 | class MSD2Filter { 209 | public: 210 | MSD2Filter() : f11(4), f12(4), f21(4), f22(4) {} 211 | void filter(float in[2], float out[2]); 212 | void filter4(vec4 in[2], vec4 out[2]); 213 | 214 | void create(float Fs, 215 | float m1, float k1, float R1, 216 | float m2, float k2, float R2, 217 | float R12, float k12, float Zn, float Z); 218 | 219 | float f11,f12,f21,f22; 220 | }; 221 | 222 | enum { 223 | ResampleFilterSize = 64 224 | }; 225 | 226 | class ResampleFIR { 227 | public: 228 | ResampleFIR(); 229 | bool isCreated(); 230 | int getDelay(); 231 | void create(int resample); 232 | vec8 filter8(vec8 in); 233 | alignas(32) float b[ResampleFilterSize]; 234 | alignas(32) float x[ResampleFilterSize*4+16]; 235 | float *xc; 236 | float *xend; 237 | float *bend; 238 | int xsize; 239 | bool bInit; 240 | }; 241 | 242 | #endif 243 | -------------------------------------------------------------------------------- /plugin/Source/hammer.cpp: -------------------------------------------------------------------------------- 1 | #include "hammer.h" 2 | #include 3 | #include 4 | #include "utils.h" 5 | 6 | enum { 7 | S = 3 8 | }; 9 | 10 | Hammer::Hammer() 11 | { 12 | x = 0; 13 | a = 0.0; 14 | F = 0.0; 15 | upprev = 0; 16 | } 17 | 18 | void Hammer::set (float Fs_, float m_, float K_, float p_, float Z_, float alpha_, int escapeDelay_) 19 | { 20 | escapeDelay = escapeDelay_; 21 | dt = 1.0f / (Fs_ * S); 22 | dti = 1.0f / dt; 23 | p = p_; 24 | K = K_; 25 | mi = 1.0f / m_; 26 | alpha = alpha_; 27 | Z2i = 1.0f / (2.0f * Z_); 28 | } 29 | 30 | Hammer::~Hammer() 31 | { 32 | } 33 | 34 | void Hammer::strike (float v_) 35 | { 36 | x = 0; 37 | v = v_; 38 | bEscaped = false; 39 | escapeCount = 0; 40 | } 41 | 42 | float Hammer::load (float vin0, float vin1) 43 | { 44 | //static FILE *fp = fopen("hammer","w"); 45 | if (bEscaped) 46 | return 0.0; 47 | 48 | float vin = vin0; 49 | float dvin = (vin1 - vin0) / S; 50 | //printf("hammer %g %g\n",vin0, vin1); 51 | for(int j=0;j0)?pow((float)x,(float)p):0; 55 | float dupdt = (up-upprev)*dti; 56 | float v1; 57 | float x1; 58 | for(int k=0;k0)?pow((float)x1,(float)p):0; 69 | float dupdtnew = (upnew - upprev) * 0.5f * dti; 70 | float change = dupdtnew - dupdt; 71 | dupdt = dupdt + (float)0.5*change; 72 | } 73 | vin += dvin; 74 | v = v1; 75 | x = x1; 76 | upprev = up; 77 | } 78 | 79 | if (F == 0) 80 | { 81 | escapeCount++; 82 | if(escapeCount > escapeDelay) 83 | { 84 | bEscaped = true; 85 | } 86 | } 87 | else 88 | { 89 | escapeCount = 0; 90 | } 91 | //fprintf(fp,"%g\n",F); 92 | return F; 93 | } 94 | 95 | float Hammer::getX() 96 | { 97 | return x; 98 | } 99 | 100 | bool Hammer::isEscaped() 101 | { 102 | return bEscaped; 103 | } 104 | -------------------------------------------------------------------------------- /plugin/Source/hammer.h: -------------------------------------------------------------------------------- 1 | #ifndef HAMMER_H 2 | #define HAMMER_H 3 | 4 | class Hammer 5 | { 6 | public: 7 | Hammer(); 8 | void set (float Fs, float m, float K, float p, float Z, float alpha, int escapeDelay); 9 | ~Hammer(); 10 | 11 | float getX(); 12 | void strike(float v); 13 | float load(float vin, float vin1); 14 | bool isEscaped(); 15 | 16 | protected: 17 | float dt; 18 | float dti; 19 | float x; 20 | float v; 21 | float a; 22 | 23 | bool bEscaped; 24 | int escapeDelay; 25 | int escapeCount; 26 | float mi; 27 | float K; 28 | float p; 29 | float F; 30 | float upprev; 31 | float alpha; 32 | float Z2i; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /plugin/Source/qiano.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "reverb.h" 9 | #include "utils.h" 10 | #include "qiano.h" 11 | #include 12 | 13 | #define HSTRING 1 14 | 15 | // The dwgs uses velocity waves dy/dt, which reflect with -1 16 | // These wave directly interacts with the hammer 17 | // which has incoming string impedance Z = sqrt(T mu) 18 | // velocity waves are converted to slope waves as 19 | // s = dy/dx = dy/dt / sqrt(T/mu) 20 | // Total velocity = sTop + sBottom 21 | // Total slope = sTop - sBottom 22 | 23 | //XXX delay clear opt 24 | 25 | float TUNE[3][3] = { 26 | {1.0f, 0.0f, 0.0f }, 27 | {0.9997f, 1.0003f, 0.0f}, 28 | { 1.0001f, 1.0003f, 0.9996f } 29 | }; 30 | 31 | 32 | Param params[NumParams] = 33 | { 34 | { pYoungsModulus, "E", "GPa"}, 35 | { pStringDensity, "rho", "kg / m^3" }, 36 | { pHammerMass, "m", "kg" }, 37 | { pStringTension, "T", "kg m / s^2" }, 38 | { pStringLength, "L", "m" }, 39 | { pStringRadius, "r", "m" }, 40 | { pHammerCompliance, "p", "" }, 41 | { pHammerSpringConstant, "K", "kg / s^2" }, 42 | { pHammerHysteresis, "alpha", "" }, 43 | { pBridgeImpedance, "Zb", "" }, 44 | { pBridgeHorizontalImpedance, "ZbH", "" }, 45 | { pVerticalHorizontalImpedance, "Zvh", "" }, 46 | { pHammerPosition, "pos", "" }, 47 | { pSoundboardSize, "size", "" }, 48 | { pStringDecay, "c1", "" }, 49 | { pStringLopass, "c3", "" }, 50 | { pDampedStringDecay, "d1", "" }, 51 | { pDampedStringLopass, "d3", "" }, 52 | { pSoundboardDecay, "s1", "" }, 53 | { pSoundboardLopass, "s3", "" }, 54 | { pLongitudinalGamma, "gammaL", "" }, 55 | { pLongitudinalGammaQuadratic, "gammaL2", "" }, 56 | { pLongitudinalGammaDamped, "gammaLDamped", "" }, 57 | { pLongitudinalGammaQuadraticDamped, "gammaL2Damped", "" }, 58 | { pLongitudinalMix, "lmix", "" }, 59 | { pLongitudinalTransverseMix, "ltmix", "" }, 60 | { pVolume, "volume", "" }, 61 | { pMaxVelocity, "maxv", "m/s" }, 62 | { pStringDetuning, "detune", "%" }, 63 | { pBridgeMass, "mb", "kg" }, 64 | { pBridgeSpring, "kb", "kg/s^2" }, 65 | { pDwgs4, "dwgs4", "" }, 66 | { pDownsample, "downsample", "" }, 67 | { pLongModes, "longmodes", "" } 68 | }; 69 | 70 | int getParameterIndex (const char *key) 71 | { 72 | for (int i = 0; i < NumParams; i++) 73 | if (! strcmp (key,params[i].name)) 74 | return i; 75 | 76 | return -1; 77 | } 78 | 79 | double PianoNote::freqTable[NUM_NOTES]; 80 | 81 | void PianoNote::fillFrequencyTable() 82 | { 83 | double NOTE_UP_SCALAR = pow (2.0, 1.0 / 12.0); 84 | double A = 6.875; // A 85 | A *= NOTE_UP_SCALAR; // A# 86 | A *= NOTE_UP_SCALAR; // B 87 | A *= NOTE_UP_SCALAR; // C, frequency of midi note 0 88 | 89 | for (int i = 0; (i < NUM_NOTES); i++) // 128 midi notes 90 | { 91 | freqTable[i] = A; 92 | A *= NOTE_UP_SCALAR; 93 | } 94 | } 95 | 96 | float PianoNote::goUp() 97 | { 98 | while (upSampleDelayNeeded) 99 | { 100 | goUpDelayed(); 101 | upSampleDelayNeeded--; 102 | } 103 | 104 | return goUpDelayed(); 105 | } 106 | 107 | float PianoNote::goUpDelayed() 108 | { 109 | if (tUp == 0) 110 | { 111 | if (downsample == 2) 112 | { 113 | alignas(32) float in[8]; 114 | for (int i = 0; i < 8; i += 2) 115 | { 116 | in[i] = goDown(); 117 | in[i + 1] = 0; 118 | } 119 | vec8 out8 = upSampleFilter.filter8 (simde_mm256_load_ps(in)); 120 | simde_mm256_store_ps (outUp, out8); 121 | } 122 | else 123 | { 124 | // no downsample here 125 | for (int i = 0; i < 8; i++) 126 | { 127 | outUp[i] = goDown(); 128 | } 129 | } 130 | } 131 | 132 | float out = outUp[tUp]; 133 | tUp = (tUp + 1) & 7; 134 | return out; 135 | } 136 | 137 | float PianoNote::goDown() 138 | { 139 | while(downSampleDelayNeeded) 140 | { 141 | goDownDelayed(); 142 | downSampleDelayNeeded--; 143 | } 144 | return goDownDelayed(); 145 | } 146 | 147 | float PianoNote::goDownDelayed() 148 | { 149 | if (tDown == 0) 150 | { 151 | alignas(32) float in[8]; 152 | if(piano->USE_DWGS4 && hammer.isEscaped()) 153 | { 154 | auto vec = go4(); 155 | simde_mm_store_ps(in, vec); 156 | vec = go4(); 157 | simde_mm_store_ps(in + 4, vec); 158 | } 159 | else 160 | { 161 | for(int i = 0; i < 8; i++) 162 | { 163 | in[i] = go(); 164 | } 165 | } 166 | vec8 out8 = downSampleFilter.filter8 (simde_mm256_load_ps(in)); 167 | simde_mm256_store_ps (outDown, out8); 168 | } 169 | 170 | float out = outDown[tDown]; 171 | tDown = (tDown + upsample) & 7; 172 | 173 | return out; 174 | } 175 | 176 | 177 | /* The note's string outputs sum to give a load at the bridge 178 | The bridge is treated seperately for each note, 179 | the sum of all incoming waves at the bridge becomes the 180 | load for the soundobard. 181 | The magnitude of the tarnsverse oscillations is set by K 182 | The output of the soundboard reflects back into the bridge junction, 183 | and the soundboard output is also the output of the qiano 184 | */ 185 | 186 | float PianoNote::go() 187 | { 188 | float output; 189 | auto& v = piano->vals; 190 | 191 | float longForce = 0; 192 | 193 | while(tLong <= 0) 194 | { 195 | float vstringT0 = 0.0; 196 | float vstringT1 = 0.0; 197 | 198 | for(int k = 0; k < nstrings; k++) 199 | { 200 | vstringT0 += stringT[k].input_velocity(); 201 | vstringT1 += stringT[k].next_input_velocity(); 202 | } 203 | float vin0 = vstringT0 * nstringsi; 204 | float vin1 = vstringT1 * nstringsi; 205 | float hload = hammer.load(vin0, vin1) * Z2i; 206 | 207 | float sbloadT = 0.0; 208 | float sbloadHT = 0.0; 209 | 210 | for(int k=0;k= 0) 239 | { 240 | longForce = stringT[0].tran2long(longDelay); 241 | } 242 | tLong++; 243 | } 244 | tLong = 0; 245 | 246 | //longForce = 0; 247 | 248 | output = (tranForces[tTranRead] + tranForcesH[tTranRead]) * tranBridgeForce + /*longHP.filter*/(longTranForces[tTranRead] * longTranBridgeForce * v[pLongitudinalTransverseMix] + longForce * longBridgeForce * v[pLongitudinalMix]); 249 | 250 | //cout << output << "\n"; 251 | float delayed = outputdelay.goDelay(output); 252 | energy = energy + output*output - delayed*delayed; 253 | if(energy>maxEnergy) 254 | maxEnergy = energy; 255 | 256 | tTranRead = (tTranRead + 1) % TranBufferSize; 257 | 258 | return output; 259 | } 260 | 261 | vec4 PianoNote::go4() 262 | { 263 | auto& v = piano->vals; 264 | 265 | if(!bInit4) { 266 | for(int k=0;kinit_string4(); 268 | stringT[k].init_string4(); 269 | #ifdef HSTRING 270 | // stringHT[k]->init_string4(); 271 | stringHT[k].init_string4(); 272 | #endif 273 | } 274 | bInit4 = true; 275 | } 276 | 277 | vec4 sbloadT = {0}; 278 | vec4 sbloadHT = {0}; 279 | 280 | for(int k=0;kmaxEnergy) 322 | maxEnergy = energy; 323 | 324 | tTranRead = (tTranRead + 4)%TranBufferSize; 325 | 326 | return output; 327 | } 328 | 329 | void Piano::init (float Fs_, int blockSize_) 330 | { 331 | Fs = Fs_; 332 | blockSize = blockSize_; 333 | PianoNote::fillFrequencyTable(); 334 | 335 | for (int k = PIANO_MIN_NOTE; k <= PIANO_MAX_NOTE; k++) 336 | { 337 | noteArray[k] = std::make_unique(k, int (Fs), this); 338 | } 339 | 340 | input.resize(blockSize); 341 | 342 | #if FDN_REVERB 343 | soundboard = std::make_unique(Fs); 344 | soundboard->set (vals[pSoundboardSize], vals[pSoundboardDecay], vals[pSoundboardLopass]); 345 | #else 346 | soundboard = std::make_unique>(blockSize); 347 | #endif 348 | 349 | if (auxThread) 350 | { 351 | auxThread->signalThreadShouldExit(); 352 | } 353 | auxThreadData.size = 0; 354 | auxThreadData.waiter.release(); 355 | if (auxThread) 356 | { 357 | auxThread->waitForThreadToExit(1000); 358 | } 359 | auxThreadData.buffer.resize(blockSize); 360 | auxThreadData.complete = false; 361 | 362 | auxThread = std::make_unique(*this); 363 | auxThread->startRealtimeThread(juce::Thread::RealtimeOptions{}); 364 | } 365 | 366 | Piano::Piano() 367 | { 368 | for (int k = PIANO_MIN_NOTE; k <= PIANO_MAX_NOTE; k++) 369 | noteArray[k] = nullptr; 370 | 371 | // input = nullptr; 372 | soundboard = nullptr; 373 | } 374 | 375 | PianoNote::PianoNote (int note_, int Fs_, Piano* piano_) 376 | { 377 | Fs = Fs_; 378 | note = note_; 379 | f = float (freqTable[note]); 380 | piano = piano_; 381 | 382 | if (note<31) 383 | nstrings = 1; 384 | else if (note<41) 385 | nstrings = 2; 386 | else 387 | nstrings = 3; 388 | 389 | //nstrings = 1; 390 | nstringsi = 1.0f / (float)nstrings; 391 | 392 | outputdelay.setDelay(Fs); 393 | 394 | longDelay = 8; 395 | tUp = 0; 396 | tDown = 0; 397 | tTran = 0; 398 | tLong = -longDelay; 399 | tTranRead = 0; 400 | bInit4 = false; 401 | bActive = false; 402 | } 403 | 404 | bool PianoNote::isDone() 405 | { 406 | //return false; 407 | float e = maxEnergy * 1e-8f; 408 | return energy < e; 409 | return (energy < 1e-8 * maxEnergy); 410 | } 411 | 412 | void PianoNote::triggerOn (float velocity, float* tune) 413 | { 414 | triggerOn_ = true; 415 | activeSampleAfterOff = 0; 416 | //logf("note = %d velocity = %g\n",note,velocity); 417 | auto& v = piano->vals; 418 | float f0 = 27.5; //A0 note 21 419 | float L = 0.14f + 1.4f / (1 + exp (-3.4f + 1.4f * log (f / f0))); 420 | L = 0.04f + 2.0f / (1.0f + exp (-3.2f + 1.4f * log (f / f0))); 421 | L *= v[pStringLength]; 422 | //L = .115; 423 | float p = 2.0f + 1.0f * log (f / f0) / log (4192 / f0); 424 | p *= v[pHammerCompliance]; 425 | 426 | 427 | //float m = .06 * (1.0 - 0.9*pow((float)log(f/f0)/log(4192/f0),(float)0.1)); 428 | //m=.018 * (1.0 - .7*pow(log(f/f0)/log(4192/f0),0.8)); 429 | float m = 0.02f * pow (0.5f - log (f / 4192), 0.3f); 430 | m = 0.013f - 0.005f * log (f / f0) / log (4192 / f0); 431 | m *= v[pHammerMass]; 432 | //m = .0092; 433 | 434 | //float K,p; 435 | float K = 40/pow((float).7e-3,(float)p); 436 | float alpha = 0.1e-4f * log (f / f0) / log (4192 / f0); 437 | //K *= 1.; 438 | 439 | int N = note - 20; 440 | //float eps = 0.9894f + 8.8e-5f * N; 441 | //p = 3.7 + .015 * N; 442 | //p = 3.7; 443 | //K = 15.5e3 / pow(1e-3,p) * exp(.059*N) * (1 - eps); 444 | //K = 183.0 / pow(1e-3,p) * exp(.045*N); 445 | //float tau = 1e-6 * (2.72 - 0.02 * N + 9e-5 * N * N); 446 | //alpha = tau / (1 - eps); 447 | //alpha = 1e-6 * (148 + 1.83 * N - 5.5e-2 * N*N + 8.5e-4 * N*N*N); 448 | K *= v[pHammerSpringConstant]; 449 | alpha *= v[pHammerHysteresis]; 450 | 451 | 452 | float r = 0.008f * pow ((float)(3.0f + 1.5f * log (f / f0)), (float)-1.4f); 453 | r *= v[pStringRadius]; 454 | 455 | float S = float (PI) * r * r; 456 | mu = S*v[pStringDensity]; 457 | 458 | T = (2*L*f)*(2*L*f)*mu; 459 | //T = v[pStringTension]; 460 | //L = sqrt(T/mu) / (2*f); 461 | 462 | float rcore = (r < 0.0006f) ? r : 0.0006f; 463 | 464 | float Score = float (PI) * rcore * rcore; 465 | float mucore = Score * v[pStringDensity]; 466 | Z = sqrt(T*mu); 467 | float E = v[pYoungsModulus] * 1e9f; 468 | float B = float (PI*PI*PI) * E * (rcore*rcore*rcore*rcore)/(4.0f*L*L*T); 469 | //B *= 5; 470 | //B = 1e-6; 471 | 472 | //cout << B << "\n"; 473 | //cout << B << " " << r << " " << L << "\n"; 474 | //float vLong = sqrt(E*S/mu); 475 | float vLong = sqrt(E*S/mu); 476 | vTran = sqrt(T/mu); 477 | float longFreq1 = vLong / (2 * L); 478 | //float Zlong = sqrt(E*S*mu); 479 | float Zlong = sqrt(E*Score*mucore); 480 | 481 | Z2i = 1.0f / (2 * Z); 482 | alphasb = (2 * Z) / (Z * nstrings + v[pBridgeImpedance]); 483 | 484 | float Zb = v[pBridgeImpedance]; 485 | float hp = v[pHammerPosition]; 486 | hp = hp / (1 + 0.01f * square (log (f / f0))); 487 | //hp = .0081/L; 488 | 489 | 490 | float mBridge = v[pBridgeMass]; 491 | float kBridge = v[pBridgeSpring]; 492 | 493 | 494 | //logf("f = %g, r = %g mm, L = %g, T = %g, hpos = %g, hm = %g, Z = %g, K = %g, B = %g, Zb = %g, alpha = %g, p = %g, vTran=%g, vLong=%g, mBridge=%G, kBridge=%g\n",f,1000*r,L,T,hp,m,Z,K,B,Zb,alpha,p,vTran,vLong,mBridge,kBridge); 495 | 496 | float ZbH = v[pBridgeHorizontalImpedance]; 497 | float Zhv = v[pVerticalHorizontalImpedance]; 498 | float khv = kBridge * 0.0f; 499 | 500 | float ES = E*S; 501 | float gammaL = v[pLongitudinalGamma]; 502 | float gammaL2 = v[pLongitudinalGammaQuadratic]; 503 | 504 | 505 | if (piano->USE_DWGS4 && bInit4) 506 | { 507 | for (int k=0;k 1) 548 | { 549 | upsample /= 2; 550 | downsample = 1; 551 | } 552 | else 553 | { 554 | downsample = 2; 555 | } 556 | } 557 | else 558 | { 559 | downsample = 1; 560 | } 561 | this->downsample = downsample; 562 | 563 | float resample = (float)upsample / (float)downsample; 564 | bridge.create(resample*Fs, 565 | mBridge,kBridge,Zb, 566 | mBridge,kBridge,ZbH, 567 | Zhv,khv, 568 | Z*nstrings, 569 | Z); 570 | 571 | int maxDel2 = 1; 572 | for(int k=0;kvals; 631 | float gammaLDamped = v[pLongitudinalGammaDamped]; 632 | float gammaL2Damped = v[pLongitudinalGammaQuadraticDamped]; 633 | 634 | for (int k = 0; k < nstrings; k++) 635 | { 636 | stringT[k].damper (v[pDampedStringDecay],v[pDampedStringLopass],gammaLDamped,gammaL2Damped,128); 637 | stringHT[k].damper (v[pDampedStringDecay],v[pDampedStringLopass],gammaLDamped,gammaL2Damped,128); 638 | } 639 | } 640 | 641 | void PianoNote::deActivate() 642 | { 643 | bActive = false; 644 | } 645 | 646 | bool PianoNote::isActive() 647 | { 648 | return bActive; 649 | } 650 | 651 | PianoNote::~PianoNote() 652 | { 653 | } 654 | 655 | Piano::~Piano() 656 | { 657 | auxThread->signalThreadShouldExit(); 658 | auxThreadData.waiter.release(); 659 | auxThread->waitForThreadToExit(1000); 660 | } 661 | 662 | 663 | template<> 664 | void Piano::process (std::span block) 665 | { 666 | // the block is always zero because juce::AudioBuffer::clear called outside 667 | threadVoiceIndex_ = numActiveVoices; 668 | 669 | auxThreadData.size = block.size(); 670 | auxThreadData.complete = false; 671 | auxThreadData.waiter.release(); 672 | 673 | int idx = threadVoiceIndex_.fetch_sub(1); 674 | while (idx > 0) 675 | { 676 | PianoNote* note = voiceList[idx - 1]; 677 | for (size_t i = 0; i < block.size(); ++i) 678 | { 679 | block[i] += note->goUp(); 680 | } 681 | idx = threadVoiceIndex_.fetch_sub(1); 682 | } 683 | 684 | while (!auxThreadData.complete) {} 685 | for (size_t i = 0; i < block.size(); ++i) 686 | { 687 | block[i] += auxThreadData.buffer[i]; 688 | } 689 | 690 | for (size_t i = 0; i < block.size(); ++i) 691 | { 692 | #ifdef FDN_REVERB 693 | block[i] = vals[pVolume] * soundboard->reverb(block[i]); 694 | #else 695 | block[i] *= vals[pVolume]; 696 | #endif 697 | } 698 | 699 | #if !FDN_REVERB 700 | soundboard->fft_conv(block.data(), out, block.size()); 701 | #endif 702 | } 703 | 704 | template<> 705 | void Piano::process (std::span block) 706 | { 707 | // the block is always zero because juce::AudioBuffer::clear called outside 708 | for (size_t i = 0; i < numActiveVoices; ++i) 709 | { 710 | PianoNote* note = voiceList[i]; 711 | for (size_t i = 0; i < block.size(); ++i) 712 | { 713 | block[i] += note->goUp(); 714 | } 715 | } 716 | 717 | for (size_t i = 0; i < block.size(); ++i) 718 | { 719 | #ifdef FDN_REVERB 720 | block[i] = vals[pVolume] * soundboard->reverb(block[i]); 721 | #else 722 | block[i] *= vals[pVolume]; 723 | #endif 724 | } 725 | 726 | #if !FDN_REVERB 727 | soundboard->fft_conv(block.data(), out, block.size()); 728 | #endif 729 | } 730 | 731 | void Piano::process (std::span block, juce::MidiBuffer& midi) 732 | { 733 | int delta = 0; 734 | float maxSamples = vals[pMaxTime] * Fs; 735 | for (size_t i = 0; i < numActiveVoices;) 736 | { 737 | PianoNote* v = voiceList[i]; 738 | ++i; 739 | if (!v->triggerOn_) 740 | { 741 | // force stop when a note continue too long 742 | // the long force ends with a DC offset 743 | // cause the energy condition can not stop the note 744 | if (v->activeSampleAfterOff > maxSamples) 745 | { 746 | v->deActivate(); 747 | --i; 748 | std::swap(voiceList[i], voiceList[--numActiveVoices]); 749 | } 750 | v->activeSampleAfterOff += block.size(); 751 | } 752 | if (v->isDone()) 753 | { 754 | v->deActivate(); 755 | --i; 756 | std::swap(voiceList[i], voiceList[--numActiveVoices]); 757 | } 758 | } 759 | 760 | for (auto meta : midi) 761 | { 762 | auto m = meta.getMessage(); 763 | 764 | if (! m.isNoteOnOrOff()) 765 | continue; 766 | 767 | size_t nextDelta = static_cast(m.getTimeStamp()); 768 | nextDelta = std::min (nextDelta, block.size()); 769 | if (wasMultiThread) { 770 | process (block.subspan(delta, nextDelta - delta)); 771 | } 772 | else { 773 | process (block.subspan(delta, nextDelta - delta)); 774 | } 775 | 776 | if (m.getNoteNumber() >= PIANO_MIN_NOTE && m.getNoteNumber() <= PIANO_MAX_NOTE) 777 | { 778 | PianoNote* v = noteArray[m.getNoteNumber()].get(); 779 | if (m.isNoteOn()) 780 | { 781 | if (! (noteArray[m.getNoteNumber()]->isActive())) 782 | { 783 | voiceList[numActiveVoices++] = v; 784 | } 785 | 786 | float velocity = vals[pMaxVelocity] * pow ((float)(m.getVelocity() / 127.0f), 2.0f); 787 | v->triggerOn (velocity, nullptr); 788 | } 789 | else 790 | { 791 | v->triggerOff(); 792 | } 793 | } 794 | 795 | delta = nextDelta; 796 | } 797 | 798 | if (wasMultiThread) { 799 | process (block.subspan(delta)); 800 | } 801 | else { 802 | process (block.subspan(delta)); 803 | } 804 | } 805 | 806 | void Piano::triggerOn (int note, float velocity, float* tune) 807 | { 808 | PianoNote* v = noteArray[note].get(); 809 | v->triggerOn (velocity,tune); 810 | } 811 | 812 | //----------------------------------------------------------------------------------------- 813 | void Piano::setParameter (int32_t index, float value) 814 | { 815 | Value &p = vals[index]; 816 | p.f = value; 817 | 818 | switch (index) 819 | { 820 | case pYoungsModulus: 821 | p.v = 200 * exp (4.0f * (value - 0.5f)); 822 | break; 823 | case pStringDensity: 824 | p.v = 7850 * exp (4.0f * (value - 0.5f)); 825 | break; 826 | case pHammerMass: 827 | p.v = exp (4.0f * (value - 0.5f)); 828 | break; 829 | case pStringTension: 830 | p.v = 800.0f * exp (3.0f * (value - 0.5f)); 831 | break; 832 | case pStringLength: 833 | p.v = exp (2.0f * (value - 0.25f)); 834 | break; 835 | case pStringRadius: 836 | p.v = exp (2.0f * (value - 0.25f)); 837 | break; 838 | case pHammerCompliance: 839 | p.v = 2.0f * value; 840 | break; 841 | case pHammerSpringConstant: 842 | p.v = (2.0f * value); 843 | break; 844 | case pHammerHysteresis: 845 | p.v = exp (4.0f * (value - 0.5f)); 846 | break; 847 | case pBridgeImpedance: 848 | p.v = 8000.0f * exp (12.0f * (value - 0.5f)); 849 | break; 850 | case pBridgeHorizontalImpedance: 851 | p.v = 60000.0f * exp (12.0f * (value - 0.5f)); 852 | break; 853 | case pVerticalHorizontalImpedance: 854 | p.v = 400.0f * exp (12.0f * (value - 0.5f)); 855 | break; 856 | case pHammerPosition: 857 | p.v = 0.05f + value * 0.15f; 858 | break; 859 | case pSoundboardSize: 860 | if (p.v != value) 861 | { 862 | p.v = value; 863 | #if FDN_REVERB 864 | soundboard->set (vals[pSoundboardSize], vals[pSoundboardDecay], vals[pSoundboardLopass]); 865 | #endif 866 | } 867 | break; 868 | case pStringDecay: 869 | p.v = 0.25f * exp (6.0f * (value - 0.25f)); 870 | break; 871 | case pStringLopass: 872 | p.v = 5.85f * exp (6.0f * (value - 0.5f)); 873 | break; 874 | case pDampedStringDecay: 875 | p.v = 8.0f * exp (6.0f * (value - 0.5f)); 876 | break; 877 | case pDampedStringLopass: 878 | p.v = 25.0f * exp (6.0f * (value - 0.5f)); 879 | break; 880 | case pSoundboardDecay: 881 | { 882 | auto n = 20.0f * exp (4.0f * (value - 0.5f)); 883 | if (std::abs (n - p.v) > 0.0001f) 884 | { 885 | p.v = n; 886 | #if FDN_REVERB 887 | soundboard->set (vals[pSoundboardSize], vals[pSoundboardDecay], vals[pSoundboardLopass]); 888 | #endif 889 | } 890 | break; 891 | } 892 | case pSoundboardLopass: 893 | { 894 | auto n = 20.0f * exp (4.0f * (value - 0.5f)); 895 | if (std::abs (n - p.v) > 0.0001f) 896 | { 897 | p.v = n; 898 | #if FDN_REVERB 899 | soundboard->set (vals[pSoundboardSize], vals[pSoundboardDecay], vals[pSoundboardLopass]); 900 | #endif 901 | } 902 | break; 903 | } 904 | case pLongitudinalGamma: 905 | p.v = 1e-2f * exp (10.0f * (value - 0.5f)); 906 | break; 907 | case pLongitudinalGammaQuadratic: 908 | p.v = 1.0e-2f * exp (8.0f * (value - 0.5f)); 909 | break; 910 | case pLongitudinalGammaDamped: 911 | p.v = 5e-2f * exp (10.0f * (value - 0.5f)); 912 | break; 913 | case pLongitudinalGammaQuadraticDamped: 914 | p.v = 3.0e-2f * exp (8.0f * (value - 0.5f)); 915 | break; 916 | case pLongitudinalMix: 917 | p.v = (value == 0.0f) ? 0.0f : 1e0f * exp (16.0f * (value - 0.5f)); 918 | break; 919 | case pLongitudinalTransverseMix: 920 | p.v = (value == 0.0f) ? 0.0f : 1e0f * exp (16.0f * (value - 0.5f)); 921 | break; 922 | case pVolume: 923 | p.v = 5e-3f * exp (8.0f * (value - 0.5f)); 924 | break; 925 | case pMaxVelocity: 926 | p.v = 10 * exp (8.0f * (value - 0.5f)); 927 | break; 928 | case pStringDetuning: 929 | p.v = 1.0f * exp (10.0f * (value - 0.5f)); 930 | break; 931 | case pBridgeMass: 932 | p.v = 10.0f * exp (10.0f * (value - 0.5f)); 933 | break; 934 | case pBridgeSpring: 935 | p.v = 1e5f * exp (20.0f * (value - 0.5f)); 936 | break; 937 | case pDwgs4: 938 | p.v = lrintf (value); 939 | USE_DWGS4 = int (lrintf (value)); 940 | break; 941 | case pDownsample: 942 | p.v = 1 + lrintf(value); 943 | break; 944 | case pLongModes: 945 | p.v = 1 + lrintf(value); 946 | break; 947 | case pMaxTime: 948 | // seconds 949 | p.v = std::lerp(3.0f, 10.0f, value); 950 | break; 951 | case pMultiThread: 952 | { 953 | bool multiThread = value > 0.5f; 954 | if (wasMultiThread != multiThread) { 955 | if (wasMultiThread) { 956 | auxThreadData.size = 0; 957 | if (auxThread) { 958 | auxThread->signalThreadShouldExit(); 959 | } 960 | auxThreadData.waiter.release(); 961 | auxThread->waitForThreadToExit(1000); 962 | } 963 | else { 964 | auxThread = std::make_unique(*this); 965 | auxThread->startRealtimeThread(juce::Thread::RealtimeOptions{}); 966 | } 967 | wasMultiThread = multiThread; 968 | } 969 | } 970 | break; 971 | } 972 | } 973 | 974 | //----------------------------------------------------------------------------------------- 975 | float Piano::getParameter (int32_t index) 976 | { 977 | return vals[index].f; 978 | } 979 | -------------------------------------------------------------------------------- /plugin/Source/qiano.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PIANO_H 3 | #define PIANO_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define PIANO_MIN_NOTE 21 11 | #define PIANO_MAX_NOTE 108 12 | 13 | #undef FDN_REVERB 14 | #define FDN_REVERB 1 15 | 16 | const int NUM_NOTES = 128; // 128 midi notes 17 | 18 | #include "filter.h" 19 | #include "dwgs.h" 20 | #include "reverb.h" 21 | #include "hammer.h" 22 | 23 | enum { 24 | MaxDecimation = 16, 25 | MaxLongDelay = 16, 26 | TranBufferSize = 32, 27 | }; 28 | 29 | class Param 30 | { 31 | public: 32 | int tag; 33 | char name[64]; 34 | char label[64]; 35 | }; 36 | 37 | class Value 38 | { 39 | public: 40 | operator float() const { 41 | return v; 42 | } 43 | float f{}; 44 | float v{}; 45 | }; 46 | 47 | enum parameters { 48 | pYoungsModulus = 0, 49 | pStringDensity, 50 | pHammerMass, 51 | pStringTension, 52 | pStringLength, 53 | pStringRadius, 54 | pHammerCompliance, 55 | pHammerSpringConstant, 56 | pHammerHysteresis, 57 | pBridgeImpedance, 58 | pBridgeHorizontalImpedance, 59 | pVerticalHorizontalImpedance, 60 | pHammerPosition, 61 | pSoundboardSize, 62 | pStringDecay, 63 | pStringLopass, 64 | pDampedStringDecay, 65 | pDampedStringLopass, 66 | pSoundboardDecay, 67 | pSoundboardLopass, 68 | pLongitudinalGamma, 69 | pLongitudinalGammaQuadratic, 70 | pLongitudinalGammaDamped, 71 | pLongitudinalGammaQuadraticDamped, 72 | pLongitudinalMix, 73 | pLongitudinalTransverseMix, 74 | pVolume, 75 | pMaxVelocity, 76 | pStringDetuning, 77 | pBridgeMass, 78 | pBridgeSpring, 79 | pDwgs4, 80 | pDownsample, 81 | pLongModes, 82 | pMaxTime, 83 | pMultiThread, 84 | NumParams 85 | }; 86 | 87 | int getParameterIndex(const char *key); 88 | 89 | class Piano; 90 | 91 | class PianoNote 92 | { 93 | public: 94 | PianoNote (int note, int Fs, Piano *piano); 95 | ~PianoNote(); 96 | float go(); 97 | vec4 go4(); 98 | float goUp(); 99 | float goUpDelayed(); 100 | float goDown(); 101 | float goDownDelayed(); 102 | void triggerOn (float velocity, float* tune); 103 | void triggerOff(); 104 | bool isActive(); 105 | void deActivate(); 106 | 107 | int note; 108 | int activeSampleAfterOff{}; 109 | bool triggerOn_{}; 110 | Piano* piano = nullptr; 111 | 112 | bool isDone(); 113 | float maxEnergy; 114 | float energy; 115 | Delay<65536> outputdelay; 116 | static void fillFrequencyTable(); 117 | static double freqTable[NUM_NOTES]; 118 | 119 | protected: 120 | MSD2Filter bridge; 121 | 122 | bool bActive; 123 | int Fs; 124 | BiquadHP longHP; 125 | float alphasb; 126 | float Z2i; 127 | float nstringsi; 128 | float tranBridgeForce; 129 | float longBridgeForce; 130 | float longTranBridgeForce; 131 | float Z; 132 | float Zhv; 133 | float vTran; 134 | 135 | int downsample; 136 | float longForces[MaxDecimation + 1]; 137 | float longForcesK[MaxDecimation + 1]; 138 | 139 | alignas(32) float tranForces[TranBufferSize]; 140 | alignas(32) float tranForcesH[TranBufferSize]; 141 | alignas(32) float longTranForces[TranBufferSize]; 142 | 143 | float T; 144 | float mu; 145 | float f; 146 | int nstrings; 147 | 148 | dwgs stringT[3]; 149 | dwgs stringHT[3]; 150 | Hammer hammer; 151 | 152 | alignas(32) float outUp[8]; 153 | alignas(32) float outDown[8]; 154 | int tUp; 155 | int tDown; 156 | int upSampleDelayNeeded; 157 | int downSampleDelayNeeded; 158 | 159 | bool bInit4; 160 | int tTran; 161 | int tLong; 162 | int tTranRead; 163 | int longDelay; 164 | int upsample; 165 | 166 | ResampleFIR downSampleFilter; 167 | ResampleFIR upSampleFilter; 168 | }; 169 | 170 | class Piano 171 | { 172 | public: 173 | Piano(); 174 | ~Piano(); 175 | 176 | template 177 | void process (std::span block); 178 | void process (std::span block, juce::MidiBuffer&); 179 | void init (float Fs, int blockSize); 180 | void triggerOn (int note, float velocity, float *tune); 181 | 182 | //vst crap 183 | void setParameter (int32_t index, float value); 184 | float getParameter (int32_t index); 185 | 186 | int USE_DWGS4 = 1; 187 | 188 | protected: 189 | friend class PianoNote; 190 | std::array vals; 191 | std::array voiceList{}; 192 | size_t numActiveVoices = 0; 193 | std::array, NUM_NOTES> noteArray; 194 | int blockSize; 195 | float Fs; 196 | std::vector input; 197 | // multithread 198 | bool wasMultiThread = true; 199 | std::atomic threadVoiceIndex_; 200 | 201 | struct ThreadData { 202 | ThreadData() : waiter(0) {} 203 | std::vector buffer; 204 | std::binary_semaphore waiter; 205 | std::atomic complete{ false }; 206 | int size{}; 207 | }; 208 | std::unique_ptr auxThread; 209 | ThreadData auxThreadData; 210 | 211 | class RenderingThread : public juce::Thread { 212 | public: 213 | RenderingThread(Piano& piano) 214 | : juce::Thread("piano render") 215 | , parentPiano(piano) {} 216 | 217 | void run() override { 218 | while (!threadShouldExit()) 219 | { 220 | parentPiano.auxThreadData.waiter.acquire(); 221 | std::fill_n(parentPiano.auxThreadData.buffer.begin(), parentPiano.auxThreadData.size, 0.0f); 222 | int idx = parentPiano.threadVoiceIndex_.fetch_sub(1); 223 | while (idx > 0) 224 | { 225 | PianoNote* note = parentPiano.voiceList[idx - 1]; 226 | for (size_t i = 0; i < parentPiano.auxThreadData.size; ++i) 227 | { 228 | parentPiano.auxThreadData.buffer[i] += note->goUp(); 229 | } 230 | idx = parentPiano.threadVoiceIndex_.fetch_sub(1); 231 | } 232 | parentPiano.auxThreadData.complete = true; 233 | } 234 | } 235 | private: 236 | Piano& parentPiano; 237 | }; 238 | 239 | #if FDN_REVERB 240 | std::unique_ptr soundboard; 241 | #else 242 | std::unique_ptr> soundboard; 243 | #endif 244 | }; 245 | 246 | void qianoNote(int note, int N, float velocity, double *x, double *tune); 247 | extern "C" void qianoNote(int note, int N, float velocity, float *x, float *tune); 248 | 249 | #endif 250 | -------------------------------------------------------------------------------- /plugin/Source/reverb.h: -------------------------------------------------------------------------------- 1 | #ifndef REVERB_H 2 | #define REVERB_H 3 | 4 | #include "utils.h" 5 | #include "filter.h" 6 | #include "FFTConvolver.h" 7 | #include "TwoStageFFTConvolver.h" 8 | using namespace fftconvolver; 9 | 10 | #include 11 | using namespace std; 12 | 13 | enum { revSize = 65536 }; 14 | 15 | template 16 | class ConvolveReverb { 17 | 18 | enum { 19 | delaySize = 2*size, 20 | resSize = size, 21 | tailBlockSize = 512 22 | }; 23 | 24 | int headBlockSize; 25 | 26 | public: 27 | alignas(32) static float res[resSize]; 28 | int k; 29 | Delay d; 30 | //TwoStageFFTConvolver fftConvolver; 31 | FFTConvolver fftConvolver; 32 | 33 | 34 | ConvolveReverb(int blockSize) : k(0) { 35 | headBlockSize = min(blockSize, (int)tailBlockSize); 36 | //fftConvolver.init(headBlockSize, tailBlockSize, res, size); 37 | fftConvolver.init(headBlockSize, res, size); 38 | } 39 | 40 | void fft_conv(float *in, float *out, int N) { 41 | //fprintf(stderr,"%d %d\n",headBlockSize,N); 42 | fftConvolver.process(in,out,N); 43 | } 44 | 45 | }; 46 | 47 | 48 | enum { 49 | ReverbTaps = 12, 50 | NumLengths = 23 51 | }; 52 | 53 | 54 | class Reverb { 55 | public: 56 | Reverb(float Fs); 57 | void set(float size, float c1, float c3); 58 | float reverb(float in); 59 | float probe(); 60 | 61 | protected: 62 | int getLength(int k); 63 | static int allLengths[NumLengths]; 64 | int lengths[ReverbTaps]{}; 65 | float Fs{}; 66 | Delay<1024> d[ReverbTaps]; 67 | float o[ReverbTaps]{}; 68 | float b[ReverbTaps]{}; 69 | float c[ReverbTaps]{}; 70 | 71 | vec4 o4[ReverbTaps]; 72 | vec4 b4[ReverbTaps]; 73 | vec4 c4[ReverbTaps]; 74 | 75 | float scale{}; 76 | 77 | Loss decay[ReverbTaps]; 78 | float out{}; 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /plugin/Source/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | 4 | float dot(int N, float *A, float *B) { 5 | float dot = 0; 6 | for(int i=0; i> 2; 98 | vec4 *zend = z4 + N4 + 1; 99 | vec4 v; 100 | 101 | y += N - 4; 102 | while (z4 < zend) 103 | { 104 | vec4 w = simde_mm_loadu_ps(y); 105 | // v = simde_mm_sub_ps (simde_mm_shuffle_ps (w, w, SIMDE_MM_SHUFFLE(0, 1, 2, 3)), *x4); 106 | // *z4 = simde_mm_mul_ps(v,v); 107 | v = simde_mm_sub_ps(simde_mm_shuffle_ps(w, w, SIMDE_MM_SHUFFLE(0, 1, 2, 3)), *x4); 108 | z4vec = simde_mm_mul_ps(v, v); 109 | x4++; 110 | y -= 4; 111 | z4++; 112 | } 113 | } 114 | 115 | // 0 0 0 x ... 116 | void diff4 (float *x, float *y, int N) 117 | { 118 | vec4 v0; 119 | vec4 v1; 120 | vec4 *x4 = (vec4*) x; 121 | vec4 *y4 = (vec4*) y; 122 | v0 = *x4; 123 | x4++; 124 | y4++; 125 | int N4 = N >> 2; 126 | vec4 *yend = y4 + N4 ; 127 | while (y4 < yend) 128 | { 129 | *y4 = simde_mm_sub_ps (simde_mm_shuffle_ps (v0, *x4, SIMDE_MM_SHUFFLE (1,0,3,2)), v0); 130 | v0 = *x4; 131 | x4++; 132 | y4++; 133 | } 134 | y[3] = 2 * (x[1] - x[0]); 135 | y[N] = (x[N-2] - x[N-4]); 136 | y[N+1] = (x[N-1] - x[N-3]); 137 | y[N+2] = 2 * (x[N-1] - x[N-2]); 138 | } 139 | 140 | std::ostream& operator<<(std::ostream& os, const vec4 &v) 141 | { 142 | union { 143 | vec4 tempv; 144 | float tempf[4]; 145 | }; 146 | tempv = v; 147 | for(int i=0; i<4; i++) { 148 | os << tempf[i] << " "; 149 | } 150 | return os; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /plugin/Source/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #ifndef __has_extension 5 | #define __has_extension(x) 0 6 | #endif 7 | #define vImage_Utilities_h 8 | #define vImage_CVUtilities_h 9 | 10 | #include 11 | 12 | #include "../../modules/simde/x86/avx.h" 13 | #include "../../modules/simde/x86/sse3.h" 14 | #include "../../modules/simde/x86/svml.h" 15 | 16 | #define vec4 simde__m128 17 | #define vec8 simde__m256 18 | 19 | #ifdef _WIN32 20 | #define posix_memalign(p, a, s) (((*(p)) = _aligned_malloc((s), (a))), *(p) ? 0 :errno) 21 | #define free_memalign(x) _aligned_free(x) 22 | #else 23 | #define free_memalign(x) free(x) 24 | #endif 25 | 26 | #define HALFPI 1.5707963267948966192313216916398 27 | #define PI 3.1415926535897932384626433832795 28 | #define TWOPI 6.283185307179586476925286766559 29 | 30 | std::ostream& operator<<(std::ostream& os, const vec4 &v); 31 | 32 | float dot (int N, float *A, float *B); 33 | float sum8 (simde__m256 x); 34 | float sse_dot (int N, float *A, float *B); 35 | float sum4 (vec4 x); 36 | void ms4 (float *x, float *y, float *z, int N); 37 | void diff4 (float *x, float *y, int N); 38 | 39 | static inline float square(float x) 40 | { 41 | return x * x; 42 | } 43 | 44 | static inline float cube(float x) 45 | { 46 | return x * x * x; 47 | } 48 | 49 | #endif 50 | --------------------------------------------------------------------------------