├── .editorconfig ├── .gitignore ├── .gitmodules ├── .prettierrc ├── .vscode └── settings.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── external └── CMakeLists.txt ├── media ├── logo.svg └── plugin_screenshot.png ├── public ├── CMakeLists.txt ├── fonts │ └── Saira-Medium.ttf ├── icons │ ├── bell.svg │ ├── fullscreen.svg │ ├── highpass.svg │ ├── lowpass.svg │ ├── powerswitch.svg │ └── settings.svg └── logo.svg ├── scripts └── setup.iss └── src ├── CMakeLists.txt ├── app ├── app.hpp ├── editor.cpp └── editor.hpp ├── main.cpp ├── pages ├── home │ ├── equalizer │ │ ├── equalizer.cpp │ │ ├── equalizer.hpp │ │ └── ui │ │ │ ├── analyzer.cpp │ │ │ ├── analyzer.hpp │ │ │ ├── constants.hpp │ │ │ ├── equalizer_response.cpp │ │ │ ├── equalizer_response.hpp │ │ │ ├── filter_button.cpp │ │ │ ├── filter_button.hpp │ │ │ ├── filter_panel.cpp │ │ │ ├── filter_panel.hpp │ │ │ ├── filter_response.cpp │ │ │ ├── filter_response.hpp │ │ │ ├── grid.cpp │ │ │ ├── grid.hpp │ │ │ └── ui.hpp │ ├── gain │ │ ├── gain.cpp │ │ ├── gain.hpp │ │ └── ui │ │ │ ├── bar.cpp │ │ │ ├── bar.hpp │ │ │ ├── constants.hpp │ │ │ ├── level_meter.cpp │ │ │ ├── level_meter.hpp │ │ │ ├── scale.cpp │ │ │ └── scale.hpp │ ├── home.cpp │ └── home.hpp └── pages.hpp ├── shared ├── constants.hpp ├── dsp │ ├── dsp.hpp │ ├── fft.cpp │ ├── fft.hpp │ ├── window.cpp │ └── window.hpp ├── math │ ├── mapping.cpp │ ├── mapping.hpp │ ├── math.hpp │ ├── smoothing.cpp │ └── smoothing.hpp ├── parameters │ ├── equalizer.hpp │ ├── filter.hpp │ ├── gain.hpp │ └── parameters.hpp ├── proccessors │ ├── analyzer.cpp │ ├── analyzer.hpp │ ├── base_processor.hpp │ ├── cascade_processor.hpp │ ├── equalizer.cpp │ ├── equalizer.hpp │ ├── filter.cpp │ ├── filter.hpp │ ├── gain.cpp │ ├── gain.hpp │ ├── plugin.cpp │ ├── plugin.hpp │ └── processors.hpp ├── shared.hpp ├── types.hpp └── ui │ ├── colours.cpp │ ├── colours.hpp │ ├── icon_button.cpp │ ├── icon_button.hpp │ ├── label_rotary_slider.cpp │ ├── label_rotary_slider.hpp │ ├── layout_component.hpp │ ├── look_and_feel.cpp │ ├── look_and_feel.hpp │ ├── paths.cpp │ ├── paths.hpp │ └── ui.hpp └── widgets ├── layout ├── header │ ├── header.cpp │ ├── header.hpp │ └── ui │ │ ├── logo.cpp │ │ ├── logo.hpp │ │ ├── right_buttons_stack.cpp │ │ ├── right_buttons_stack.hpp │ │ └── ui.hpp └── layout.hpp └── widgets.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = false 12 | insert_final_newline = false -------------------------------------------------------------------------------- /.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 | # Build files 35 | /build 36 | 37 | TODO.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/juce"] 2 | path = external/juce 3 | url = https://github.com/juce-framework/JUCE.git 4 | [submodule "external/friz"] 5 | path = external/friz 6 | url = https://github.com/bgporter/animator 7 | [submodule "external/spline"] 8 | path = external/spline 9 | url = https://github.com/ttk592/spline.git 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "semi": true, 6 | "useTabs": false, 7 | "tabWidth": 2 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", 3 | "files.associations": { 4 | "*.mm": "cpp", 5 | "algorithm": "cpp", 6 | "array": "cpp", 7 | "atomic": "cpp", 8 | "bit": "cpp", 9 | "cctype": "cpp", 10 | "charconv": "cpp", 11 | "chrono": "cpp", 12 | "clocale": "cpp", 13 | "cmath": "cpp", 14 | "codecvt": "cpp", 15 | "compare": "cpp", 16 | "complex": "cpp", 17 | "concepts": "cpp", 18 | "condition_variable": "cpp", 19 | "coroutine": "cpp", 20 | "cstdarg": "cpp", 21 | "cstddef": "cpp", 22 | "cstdint": "cpp", 23 | "cstdio": "cpp", 24 | "cstdlib": "cpp", 25 | "cstring": "cpp", 26 | "ctime": "cpp", 27 | "cwchar": "cpp", 28 | "cwctype": "cpp", 29 | "deque": "cpp", 30 | "exception": "cpp", 31 | "forward_list": "cpp", 32 | "list": "cpp", 33 | "map": "cpp", 34 | "resumable": "cpp", 35 | "set": "cpp", 36 | "string": "cpp", 37 | "unordered_map": "cpp", 38 | "unordered_set": "cpp", 39 | "vector": "cpp", 40 | "format": "cpp", 41 | "fstream": "cpp", 42 | "functional": "cpp", 43 | "future": "cpp", 44 | "initializer_list": "cpp", 45 | "iomanip": "cpp", 46 | "ios": "cpp", 47 | "iosfwd": "cpp", 48 | "iostream": "cpp", 49 | "istream": "cpp", 50 | "iterator": "cpp", 51 | "limits": "cpp", 52 | "locale": "cpp", 53 | "memory": "cpp", 54 | "mutex": "cpp", 55 | "new": "cpp", 56 | "numeric": "cpp", 57 | "optional": "cpp", 58 | "ostream": "cpp", 59 | "queue": "cpp", 60 | "ratio": "cpp", 61 | "shared_mutex": "cpp", 62 | "sstream": "cpp", 63 | "stdexcept": "cpp", 64 | "stop_token": "cpp", 65 | "streambuf": "cpp", 66 | "system_error": "cpp", 67 | "thread": "cpp", 68 | "tuple": "cpp", 69 | "type_traits": "cpp", 70 | "typeindex": "cpp", 71 | "typeinfo": "cpp", 72 | "utility": "cpp", 73 | "xfacet": "cpp", 74 | "xhash": "cpp", 75 | "xiosbase": "cpp", 76 | "xlocale": "cpp", 77 | "xlocbuf": "cpp", 78 | "xlocinfo": "cpp", 79 | "xlocmes": "cpp", 80 | "xlocmon": "cpp", 81 | "xlocnum": "cpp", 82 | "xloctime": "cpp", 83 | "xmemory": "cpp", 84 | "xstddef": "cpp", 85 | "xstring": "cpp", 86 | "xtr1common": "cpp", 87 | "xtree": "cpp", 88 | "xutility": "cpp", 89 | "ranges": "cpp", 90 | "span": "cpp", 91 | "bitset": "cpp", 92 | "valarray": "cpp", 93 | "numbers": "cpp", 94 | "regex": "cpp", 95 | "cassert": "cpp" 96 | }, 97 | "cmake.configureOnOpen": true 98 | } 99 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | 3 | set(PROJECT_NAME "equalize_it") 4 | project(${PROJECT_NAME} VERSION 0.0.1) 5 | 6 | set(COMPANY_NAME "SmEgDm") 7 | set(PLUGIN_MANUFACTURER_CODE "Smeg") 8 | set(PRODUCT_NAME "Equalize It") 9 | set(PLUGIN_CODE "Eqit") 10 | set(FORMATS Standalone VST3) 11 | set(COPY_PLUGIN_AFTER_BUILD FALSE) 12 | 13 | set(CMAKE_CXX_STANDARD 20) 14 | 15 | add_subdirectory(external) 16 | add_subdirectory(public) 17 | add_subdirectory(src) 18 | 19 | if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") 20 | add_compile_options(-fdiagnostics-color=always) 21 | elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") 22 | add_compile_options(-fcolor-diagnostics) 23 | endif() 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | Logo 6 | 7 |

Equalize It

8 | 9 |

10 | 12-band parametric EQ 11 |

12 |
13 | 14 |

Table of Contents

15 |
    16 |
  1. 17 | About The Project 18 | 21 |
  2. 22 |
  3. 23 | Getting Started 24 | 28 |
  4. 29 |
  5. Usage
  6. 30 |
  7. License
  8. 31 |
32 | 33 | ## About The Project 34 | 35 | Plugin Screenshot 36 | 37 | The project is VST-plugin for equalization. The user interface includes a spectrum analyzer, a filter control panel, frequency response curves, and level meters. 38 | 39 | There are 3 types of IIR-filters available: 40 | 41 | - low pass; 42 | - high pass; 43 | - peak. 44 | 45 | The releases have an installer for Windows, but if you want to test the plugin for other operating systems, try building it. 46 | 47 | ### Built With 48 | 49 | - [JUCE](https://github.com/juce-framework/JUCE) 50 | - [CMake](https://cmake.org/) 51 | - [C++ spline library](https://github.com/ttk592/spline) 52 | - [Friz](https://github.com/bgporter/animator) 53 | 54 | ## How to build 55 | 56 | ### Prerequisites 57 | 58 | - [Install CMake](https://cmake.org/download/) version 3.21 or higher 59 | - (Optional) [Install Clang](https://releases.llvm.org/download.html) compiler for C++ 60 | - (Optional) [Install Ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages) generator 61 | 62 | ### Installation 63 | 64 | 1. Clone the repo with submodules 65 | 66 | ```sh 67 | git clone https://github.com/SmEgDm/equalize_it.git --recursive 68 | ``` 69 | 70 | 2. Set the formats you need in `CMakeLists.txt` (all formats can be found [here](https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md)) 71 | ```cmake 72 | set(FORMATS [ ...]) 73 | ``` 74 | 75 | ### Build 76 | 77 | 1. Configure the project 78 | 79 | ```sh 80 | cmake -B build G 81 | ``` 82 | 83 | 2. Run build 84 | 85 | ```sh 86 | cmake --build build --config Release 87 | ``` 88 | 89 | ## Usage 90 | 91 | ### Add/Remove filters 92 | 93 | The filter is _added_ by double-clicking on an empty area of ​​the spectrum analyzer. The filter can be _removed_ by pressing the right mouse button on the filter button (point on the spectrum analyzer). 94 | 95 | ### Filter panel 96 | 97 | To _open_ the filter panel, click the filter button, and to _hide_ it, click on an empty area. The panel is _draggable_. 98 | 99 | ## License 100 | 101 | Distributed under the GPL-3.0 License. See `LICENSE` for more information. 102 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # JUCE 2 | add_subdirectory(juce) 3 | 4 | set_property(GLOBAL PROPERTY USE_FOLDERS YES PARENT_SCOPE) 5 | 6 | option(JUCE_ENABLE_MODULE_SOURCE_GROUPS 7 | "Show all module sources in IDE projects" ON) 8 | 9 | set(JUCE_DEPENDENCIES 10 | juce::juce_core 11 | juce::juce_audio_basics 12 | juce::juce_audio_devices 13 | juce::juce_audio_processors 14 | juce::juce_audio_utils 15 | juce::juce_audio_formats 16 | juce::juce_graphics 17 | juce::juce_gui_basics 18 | juce::juce_data_structures 19 | juce::juce_dsp 20 | juce::juce_events 21 | juce::juce_gui_extra 22 | PARENT_SCOPE) 23 | 24 | # Friz 25 | juce_add_module("${CMAKE_CURRENT_LIST_DIR}/friz/Source/friz") 26 | 27 | # Spline 28 | set(SPLINE_PATH 29 | "${CMAKE_CURRENT_LIST_DIR}/spline/src" 30 | PARENT_SCOPE) 31 | -------------------------------------------------------------------------------- /media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/plugin_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoracle/equalize_it/cb36aeb6f7ff68cd5fa1e051a33820999f657c00/media/plugin_screenshot.png -------------------------------------------------------------------------------- /public/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file( 2 | GLOB_RECURSE PUBLIC_FILES CONFIGURE_DEPENDS 3 | "${CMAKE_CURRENT_LIST_DIR}/fonts/*.ttf" 4 | "${CMAKE_CURRENT_LIST_DIR}/icons/*.svg" "${CMAKE_CURRENT_LIST_DIR}/logo.svg") 5 | 6 | juce_add_binary_data(public_binaries SOURCES ${PUBLIC_FILES}) 7 | -------------------------------------------------------------------------------- /public/fonts/Saira-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/egoracle/equalize_it/cb36aeb6f7ff68cd5fa1e051a33820999f657c00/public/fonts/Saira-Medium.ttf -------------------------------------------------------------------------------- /public/icons/bell.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/highpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/lowpass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/powerswitch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /public/icons/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /scripts/setup.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "EqualizeIt" 5 | #define MyAppVersion "1.0.0" 6 | #define MyAppPublisher "SmEgDm" 7 | #define MyAppURL "https://github.com/SmEgDm/equalize_it" 8 | 9 | [Setup] 10 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 11 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 12 | AppId={{079CCD95-6629-4EF4-96BA-3B1E93762A32} 13 | AppName={#MyAppName} 14 | AppVersion={#MyAppVersion} 15 | ;AppVerName={#MyAppName} {#MyAppVersion} 16 | AppPublisher={#MyAppPublisher} 17 | AppPublisherURL={#MyAppURL} 18 | AppSupportURL={#MyAppURL} 19 | AppUpdatesURL={#MyAppURL} 20 | CreateAppDir=no 21 | LicenseFile=..\LICENSE 22 | ; Uncomment the following line to run in non administrative install mode (install for current user only.) 23 | ;PrivilegesRequired=lowest 24 | OutputDir=. 25 | OutputBaseFilename=EqualizeItSetup-1.0.0 26 | Compression=lzma 27 | SolidCompression=yes 28 | WizardStyle=modern 29 | 30 | [Languages] 31 | Name: "english"; MessagesFile: "compiler:Default.isl" 32 | Name: "armenian"; MessagesFile: "compiler:Languages\Armenian.isl" 33 | Name: "brazilianportuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" 34 | Name: "bulgarian"; MessagesFile: "compiler:Languages\Bulgarian.isl" 35 | Name: "catalan"; MessagesFile: "compiler:Languages\Catalan.isl" 36 | Name: "corsican"; MessagesFile: "compiler:Languages\Corsican.isl" 37 | Name: "czech"; MessagesFile: "compiler:Languages\Czech.isl" 38 | Name: "danish"; MessagesFile: "compiler:Languages\Danish.isl" 39 | Name: "dutch"; MessagesFile: "compiler:Languages\Dutch.isl" 40 | Name: "finnish"; MessagesFile: "compiler:Languages\Finnish.isl" 41 | Name: "french"; MessagesFile: "compiler:Languages\French.isl" 42 | Name: "german"; MessagesFile: "compiler:Languages\German.isl" 43 | Name: "hebrew"; MessagesFile: "compiler:Languages\Hebrew.isl" 44 | Name: "hungarian"; MessagesFile: "compiler:Languages\Hungarian.isl" 45 | Name: "icelandic"; MessagesFile: "compiler:Languages\Icelandic.isl" 46 | Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl" 47 | Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl" 48 | Name: "norwegian"; MessagesFile: "compiler:Languages\Norwegian.isl" 49 | Name: "polish"; MessagesFile: "compiler:Languages\Polish.isl" 50 | Name: "portuguese"; MessagesFile: "compiler:Languages\Portuguese.isl" 51 | Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" 52 | Name: "slovak"; MessagesFile: "compiler:Languages\Slovak.isl" 53 | Name: "slovenian"; MessagesFile: "compiler:Languages\Slovenian.isl" 54 | Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl" 55 | Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl" 56 | Name: "ukrainian"; MessagesFile: "compiler:Languages\Ukrainian.isl" 57 | 58 | [Files] 59 | Source: "..\build\src\equalize_it_artefacts\Debug\VST3\Equalize It.vst3\Contents\x86_64-win\Equalize It.vst3"; DestDir: "{commoncf64}\VST3"; Flags: ignoreversion 60 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 61 | 62 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | juce_add_plugin( 2 | ${PROJECT_NAME} 3 | COPY_PLUGIN_AFTER_BUILD 4 | ${COPY_PLUGIN_AFTER_BUILD} 5 | PLUGIN_MANUFACTURER_CODE 6 | ${PLUGIN_MANUFACTURER_CODE} 7 | PLUGIN_CODE 8 | ${PLUGIN_CODE} 9 | FORMATS 10 | ${FORMATS} 11 | COMPANY_NAME 12 | ${COMPANY_NAME} 13 | PRODUCT_NAME 14 | ${PRODUCT_NAME}) 15 | 16 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 17 | 18 | file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/*.cpp" 19 | "${CMAKE_CURRENT_LIST_DIR}/*.hpp") 20 | target_sources(${PROJECT_NAME} PRIVATE ${SRC_FILES}) 21 | 22 | set_target_properties(${PROJECT_NAME} PROPERTIES FOLDER "") 23 | 24 | source_group( 25 | TREE ${CMAKE_CURRENT_LIST_DIR} 26 | PREFIX "" 27 | FILES ${SRC_FILES}) 28 | 29 | target_compile_definitions( 30 | ${PROJECT_NAME} 31 | PUBLIC JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0 JUCE_VST3_CAN_REPLACE_VST2=0 32 | PROJECT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}) 33 | 34 | target_link_libraries( 35 | ${PROJECT_NAME} 36 | PRIVATE ${JUCE_DEPENDENCIES} public_binaries friz 37 | PUBLIC juce::juce_recommended_config_flags juce::juce_recommended_lto_flags 38 | juce::juce_recommended_warning_flags) 39 | 40 | target_include_directories( 41 | ${PROJECT_NAME} 42 | PUBLIC "${CMAKE_CURRENT_LIST_DIR}/app" "${CMAKE_CURRENT_LIST_DIR}/shared" 43 | "${CMAKE_CURRENT_LIST_DIR}/widgets" "${CMAKE_CURRENT_LIST_DIR}/pages" 44 | ${SPLINE_PATH}) 45 | -------------------------------------------------------------------------------- /src/app/app.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "editor.hpp" -------------------------------------------------------------------------------- /src/app/editor.cpp: -------------------------------------------------------------------------------- 1 | #include "editor.hpp" 2 | 3 | PluginEditor::PluginEditor(PluginProcessor &p) 4 | : juce::AudioProcessorEditor(&p), homePage(p) { 5 | juce::LookAndFeel_V4::setDefaultLookAndFeel(&lookAndFeel); 6 | 7 | const auto desktopArea = 8 | juce::Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea; 9 | setSize(defaultWidth, defaultHeight); 10 | setResizable(true, true); 11 | setResizeLimits(minWidth, minHeight, desktopArea.getWidth(), 12 | desktopArea.getHeight()); 13 | 14 | addAndMakeVisible(homePage); 15 | 16 | resized(); 17 | } 18 | 19 | PluginEditor::~PluginEditor() { 20 | juce::LookAndFeel_V4::setDefaultLookAndFeel(nullptr); 21 | } 22 | 23 | void PluginEditor::resized() { homePage.setBounds(getLocalBounds()); } -------------------------------------------------------------------------------- /src/app/editor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pages.hpp" 4 | #include "shared.hpp" 5 | 6 | class PluginEditor : public juce::AudioProcessorEditor { 7 | public: 8 | enum { 9 | minWidth = 720, 10 | minHeight = 480, 11 | defaultWidth = 1280, 12 | defaultHeight = 720 13 | }; 14 | 15 | public: 16 | explicit PluginEditor(PluginProcessor &); 17 | ~PluginEditor() override; 18 | 19 | void resized() override; 20 | 21 | private: 22 | PluginLookAndFeel lookAndFeel; 23 | 24 | HomePage homePage; 25 | 26 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginEditor) 27 | }; 28 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "app.hpp" 2 | 3 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { 4 | std::function 5 | createEditorCallback = [](PluginProcessor *processor) { 6 | return new PluginEditor(*processor); 7 | }; 8 | 9 | return new PluginProcessor(createEditorCallback); 10 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/equalizer.cpp: -------------------------------------------------------------------------------- 1 | #include "equalizer.hpp" 2 | 3 | #include 4 | 5 | Equalizer::Equalizer(PluginProcessor &p) 6 | : pluginProcessor(p), uiState(p.getUiState()), analyzer(p), freqResponse(p), 7 | filterPanel(p, [&]() { update(); }) { 8 | addMouseListener(this, true); 9 | 10 | addAndMakeVisible(grid); 11 | addAndMakeVisible(analyzer); 12 | 13 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 14 | ++id) { 15 | filterFreqResponses.push_back( 16 | std::make_unique(pluginProcessor, id)); 17 | addChildComponent(filterFreqResponses.back().get()); 18 | } 19 | 20 | addAndMakeVisible(freqResponse); 21 | 22 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 23 | ++id) { 24 | filterButtons.push_back(std::make_unique( 25 | id, pluginProcessor, [&]() { update(); })); 26 | addChildComponent(filterButtons.back().get()); 27 | } 28 | 29 | addChildComponent(filterPanel); 30 | 31 | update(); 32 | 33 | resized(); 34 | } 35 | 36 | void Equalizer::resized() { 37 | layout.templateRows = {Track(Fr(1))}; 38 | layout.templateColumns = {Track(Fr(1))}; 39 | layout.items = {juce::GridItem(grid)}; 40 | 41 | LayoutComponent::resized(); 42 | 43 | const auto bounds = getLocalBounds(); 44 | 45 | analyzer.setBounds(bounds); 46 | for (auto &filterFreqResponse : filterFreqResponses) { 47 | filterFreqResponse->setBounds(bounds); 48 | } 49 | freqResponse.setBounds(bounds); 50 | filterPanel.setBounds(bounds.withSizeKeepingCentre(300, 170).translated( 51 | 0.0f, static_cast(getHeight()) * 0.5f - 85.0f - 30.0f)); 52 | for (auto &filterButton : filterButtons) { 53 | filterButton->timerCallback(); 54 | } 55 | } 56 | 57 | void Equalizer::mouseDown(const juce::MouseEvent &event) { 58 | LayoutComponent::mouseDown(event); 59 | 60 | if (event.eventComponent == &freqResponse) { 61 | uiState.selectedFilterID = -1; 62 | update(); 63 | } 64 | } 65 | 66 | void Equalizer::mouseDoubleClick(const juce::MouseEvent &event) { 67 | LayoutComponent::mouseDoubleClick(event); 68 | 69 | if (event.eventComponent != &freqResponse || !uiState.addFilter()) { 70 | return; 71 | } 72 | 73 | const float xMin = 0.0f; 74 | const float xMax = static_cast(event.eventComponent->getWidth()); 75 | const float yMax = static_cast(event.eventComponent->getHeight()); 76 | 77 | const auto xToFreq = math::invLogMapping(xMin, xMax, constants::GRID_MIN_FREQ, 78 | constants::GRID_MAX_FREQ); 79 | const auto yToDb = math::segmentMapping(yMax, 0.0f, constants::GRID_MIN_DB, 80 | constants::GRID_MAX_DB); 81 | const auto dBToNorm = math::segmentMapping(-12, 12, 0, 1); 82 | 83 | const float x = static_cast(event.getMouseDownX()); 84 | const float y = static_cast(event.getMouseDownY()); 85 | 86 | FilterParameters filterParameters(uiState.selectedFilterID, 87 | pluginProcessor.getAPVTS()); 88 | const auto freqToNorm = filterParameters.frequency->getNormalisableRange(); 89 | 90 | filterParameters.isActive->beginChangeGesture(); 91 | filterParameters.isActive->setValueNotifyingHost(1.0f); 92 | filterParameters.isActive->endChangeGesture(); 93 | 94 | filterParameters.frequency->beginChangeGesture(); 95 | filterParameters.frequency->setValueNotifyingHost( 96 | freqToNorm.convertTo0to1(xToFreq(x))); 97 | filterParameters.frequency->endChangeGesture(); 98 | 99 | filterParameters.gain->beginChangeGesture(); 100 | filterParameters.gain->setValueNotifyingHost(dBToNorm(yToDb(y))); 101 | filterParameters.gain->endChangeGesture(); 102 | 103 | filterButtons[uiState.selectedFilterID - 1]->setCentrePosition( 104 | event.getMouseDownPosition()); 105 | filterButtons[uiState.selectedFilterID - 1]->setVisible(true); 106 | 107 | update(); 108 | } 109 | 110 | void Equalizer::update() { 111 | filterPanel.update(); 112 | for (const auto &[filterID, isUsed] : uiState.usedFilterIDs) { 113 | filterFreqResponses[filterID - 1]->setVisible(isUsed); 114 | filterButtons[filterID - 1]->setVisible(isUsed); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/pages/home/equalizer/equalizer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | #include "ui/ui.hpp" 5 | 6 | #include 7 | #include 8 | 9 | class Equalizer : public LayoutComponent { 10 | public: 11 | Equalizer(PluginProcessor &); 12 | 13 | void resized() override; 14 | 15 | void mouseDown(const juce::MouseEvent &event) override; 16 | void mouseDoubleClick(const juce::MouseEvent &event) override; 17 | 18 | void update(); 19 | 20 | private: 21 | PluginProcessor &pluginProcessor; 22 | PluginProcessor::UiState &uiState; 23 | 24 | GridComponent grid; 25 | AnalyzerComponent analyzer; 26 | std::vector> filterFreqResponses; 27 | EqualizerFrequencyResponse freqResponse; 28 | FilterPanel filterPanel; 29 | std::vector> filterButtons; 30 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/analyzer.cpp: -------------------------------------------------------------------------------- 1 | #define _USE_MATH_DEFINES 2 | 3 | #include "analyzer.hpp" 4 | #include "constants.hpp" 5 | 6 | #include 7 | #include 8 | 9 | AnalyzerComponent::AnalyzerComponent(PluginProcessor &p) : pluginProcessor(p) { 10 | startTimerHz(24); 11 | } 12 | 13 | void AnalyzerComponent::paint(juce::Graphics &g) { 14 | auto equalizerProcessor = pluginProcessor.getEqualizerProcessor(); 15 | auto analyzerProcessor = pluginProcessor.getAnalyzerProcessor(); 16 | 17 | if (!equalizerProcessor || !analyzerProcessor) { 18 | return; 19 | } 20 | 21 | const float xMin = 0.0f; 22 | const float xMax = static_cast(getWidth()); 23 | const float yMin = 0.0f; 24 | const float yMax = static_cast(getHeight()); 25 | 26 | const float sampleRate = 27 | static_cast(analyzerProcessor->getSampleRate()); 28 | const float halfSampleRate = sampleRate * 0.5f; 29 | const int fftSize = analyzerProcessor->getFFTSize(); 30 | 31 | const auto xToFreq = math::invLogMapping(xMin, xMax, constants::GRID_MIN_FREQ, 32 | constants::GRID_MAX_FREQ); 33 | const auto dBToY = math::segmentMapping( 34 | constants::MINUS_INFINITY_DB, constants::ANALYZER_MAX_DB, yMax, 0.0f); 35 | 36 | std::function preSpectrum, postSpectrum; 37 | { 38 | const auto amplitudes = analyzerProcessor->getAmplitudes(); 39 | const auto binToFreq = dsp::binToFrequencyMapping(fftSize, sampleRate); 40 | 41 | std::vector freqs, dBs; 42 | for (std::size_t i = 1; i < amplitudes.size(); ++i) { 43 | const float freq = binToFreq(static_cast(i)); 44 | freqs.push_back(freq); 45 | dBs.push_back(amplitudes[i].getCurrentValue()); 46 | } 47 | 48 | tk::spline preSpectrumSpline(freqs, dBs); 49 | preSpectrum = [preSpectrumSpline](float freq) { 50 | return preSpectrumSpline(freq); 51 | }; 52 | 53 | const auto freqResponse = equalizerProcessor->getFrequencyResponse(); 54 | postSpectrum = [freqResponse, &preSpectrum](float freq) { 55 | float preDB = preSpectrum(freq); 56 | if (preDB <= constants::MINUS_INFINITY_DB) { 57 | return preDB; 58 | } 59 | return preDB + freqResponse(freq); 60 | }; 61 | } 62 | 63 | const auto spectrumToXY = [&](std::function &spectrum) { 64 | return [&](float x) { 65 | const float freq = xToFreq(x); 66 | return freq > halfSampleRate ? yMax : dBToY(spectrum(freq)); 67 | }; 68 | }; 69 | 70 | { 71 | const auto preSpectrumXY = spectrumToXY(preSpectrum); 72 | 73 | juce::Path preSpectrumPath; 74 | preSpectrumPath.preallocateSpace(3 * (static_cast(xMax) + 3)); 75 | 76 | preSpectrumPath.startNewSubPath(xMin, preSpectrumXY(xMin)); 77 | for (float x = xMin + 1.0f; x < xMax; ++x) { 78 | preSpectrumPath.lineTo(x, preSpectrumXY(x)); 79 | } 80 | preSpectrumPath.lineTo(xMax, yMax); 81 | preSpectrumPath.lineTo(xMin, yMax); 82 | preSpectrumPath.closeSubPath(); 83 | 84 | juce::ColourGradient gradient( 85 | juce::Colours::transparentWhite, juce::Point(0.0f, yMax), 86 | juce::Colour(0xff87bfff), juce::Point(0.0f, yMin), false); 87 | 88 | g.setGradientFill(gradient); 89 | g.fillPath(preSpectrumPath); 90 | } 91 | 92 | { 93 | const auto postSpectrumXY = spectrumToXY(postSpectrum); 94 | 95 | juce::Path postSpectrumPath; 96 | postSpectrumPath.preallocateSpace(3 * static_cast(xMax)); 97 | 98 | postSpectrumPath.startNewSubPath(xMin, postSpectrumXY(xMin)); 99 | for (float x = xMin + 1.0f; x < xMax; ++x) { 100 | postSpectrumPath.lineTo(x, postSpectrumXY(x)); 101 | } 102 | 103 | g.setColour(juce::Colour(0xff87bfff)); 104 | g.strokePath( 105 | postSpectrumPath, 106 | juce::PathStrokeType(2.f, juce::PathStrokeType::JointStyle::curved, 107 | juce::PathStrokeType::EndCapStyle::rounded)); 108 | } 109 | } 110 | 111 | void AnalyzerComponent::timerCallback() { 112 | auto analyzerProcessor = pluginProcessor.getAnalyzerProcessor(); 113 | if (analyzerProcessor) { 114 | analyzerProcessor->updateAmplitudes(); 115 | } 116 | 117 | repaint(); 118 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/analyzer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class AnalyzerComponent : public LayoutComponent, private juce::Timer { 9 | public: 10 | AnalyzerComponent(PluginProcessor &); 11 | 12 | void paint(juce::Graphics &) override; 13 | 14 | void timerCallback() override; 15 | 16 | private: 17 | PluginProcessor &pluginProcessor; 18 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace constants { 4 | 5 | const float GRID_MIN_FREQ = 5.0f; 6 | const float GRID_MAX_FREQ = 22500.0f; 7 | 8 | const float GRID_MIN_DB = -15.0f; 9 | const float GRID_MAX_DB = 13.0f; 10 | const float GRID_DB_STEP = 3.0f; 11 | 12 | const float ANALYZER_MAX_DB = 10.0f; 13 | 14 | } // namespace constants 15 | -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/equalizer_response.cpp: -------------------------------------------------------------------------------- 1 | #include "equalizer_response.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | EqualizerFrequencyResponse::EqualizerFrequencyResponse(PluginProcessor &p) 6 | : pluginProcessor(p), frequencyResponse([](float) { return 0.0f; }) { 7 | startTimerHz(24); 8 | } 9 | 10 | void EqualizerFrequencyResponse::paint(juce::Graphics &g) { 11 | const auto equalizerProcessor = pluginProcessor.getEqualizerProcessor(); 12 | 13 | if (!equalizerProcessor) { 14 | return; 15 | } 16 | 17 | const float xMin = 0.0f; 18 | float xMax = static_cast(getWidth()); 19 | const float yMax = static_cast(getHeight()); 20 | 21 | const float halfSampleRate = 22 | static_cast(equalizerProcessor->getSampleRate() * 0.5); 23 | 24 | const auto xToFreq = math::invLogMapping(xMin, xMax, constants::GRID_MIN_FREQ, 25 | constants::GRID_MAX_FREQ); 26 | const auto dBToY = math::segmentMapping(constants::GRID_MIN_DB, 27 | constants::GRID_MAX_DB, yMax, 0.0f); 28 | 29 | juce::Path freqRespPath; 30 | freqRespPath.preallocateSpace(3 * static_cast(xMax)); 31 | 32 | freqRespPath.startNewSubPath(xMin, dBToY(frequencyResponse(xMin))); 33 | for (float x = xMin + 1; x < xMax; ++x) { 34 | const float freq = xToFreq(x); 35 | if (freq > halfSampleRate) { 36 | break; 37 | } 38 | freqRespPath.lineTo(x, dBToY(frequencyResponse(freq))); 39 | } 40 | 41 | g.setColour(juce::Colour(0xffff5050)); 42 | g.strokePath(freqRespPath, juce::PathStrokeType( 43 | 2.f, juce::PathStrokeType::JointStyle::curved, 44 | juce::PathStrokeType::EndCapStyle::rounded)); 45 | } 46 | 47 | void EqualizerFrequencyResponse::timerCallback() { 48 | const auto equalizerProcessor = pluginProcessor.getEqualizerProcessor(); 49 | 50 | if (equalizerProcessor) { 51 | frequencyResponse = equalizerProcessor->getFrequencyResponse(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/equalizer_response.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | 7 | class EqualizerFrequencyResponse : public juce::Component, private juce::Timer { 8 | public: 9 | EqualizerFrequencyResponse(PluginProcessor &); 10 | 11 | void paint(juce::Graphics &) override; 12 | 13 | void timerCallback() override; 14 | 15 | private: 16 | PluginProcessor &pluginProcessor; 17 | std::function frequencyResponse; 18 | 19 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EqualizerFrequencyResponse) 20 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_button.cpp: -------------------------------------------------------------------------------- 1 | #include "filter_button.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | FilterButton::FilterButton(int id, PluginProcessor &p, 6 | std::function uec) 7 | : ShapeButton(juce::String::formatted("filter_button_%d", id), 8 | colours::getFilterColour(id), 9 | colours::getFilterColour(id).darker(0.25), 10 | colours::getFilterColour(id)), 11 | filterID(id), pluginProcessor(p), updateEqualizerCallback(uec), 12 | filterParameters(FilterParameters(id, p.getAPVTS())), 13 | uiState(p.getUiState()) { 14 | startTimerHz(24); 15 | 16 | setShape(paths::createCircle(), true, true, false); 17 | setOutline(colours::getFilterColour(id).brighter(1.25), 3.0f); 18 | 19 | setSize(buttonSize, buttonSize); 20 | 21 | constrainer.setMinimumOnscreenAmounts(buttonSize, buttonSize, buttonSize, 22 | buttonSize); 23 | } 24 | 25 | void FilterButton::mouseDown(const juce::MouseEvent &event) { 26 | ShapeButton::mouseDown(event); 27 | 28 | if (event.mods.isRightButtonDown()) { 29 | uiState.removeFilter(filterID); 30 | 31 | filterParameters.isActive->beginChangeGesture(); 32 | filterParameters.isActive->setValueNotifyingHost(0.0f); 33 | filterParameters.isActive->endChangeGesture(); 34 | 35 | setVisible(false); 36 | updateEqualizerCallback(); 37 | 38 | return; 39 | } 40 | 41 | uiState.selectedFilterID = filterID; 42 | updateEqualizerCallback(); 43 | 44 | return; 45 | 46 | dragger.startDraggingComponent(this, event); 47 | } 48 | 49 | void FilterButton::mouseDrag(const juce::MouseEvent &event) { 50 | ShapeButton::mouseDrag(event); 51 | 52 | return; 53 | 54 | dragger.dragComponent(this, event, &constrainer); 55 | } 56 | 57 | void FilterButton::timerCallback() { 58 | const auto parent = getParentComponent(); 59 | 60 | const float xMin = 0.0f; 61 | const float xMax = static_cast(parent->getWidth()); 62 | const float yMax = static_cast(parent->getHeight()); 63 | 64 | const auto freqToX = math::logMapping(constants::GRID_MIN_FREQ, 65 | constants::GRID_MAX_FREQ, xMin, xMax); 66 | const auto dBToY = math::segmentMapping(constants::GRID_MIN_DB, 67 | constants::GRID_MAX_DB, yMax, 0.0f); 68 | 69 | const int x = static_cast(freqToX(filterParameters.getFrequencyValue())); 70 | const int y = static_cast(dBToY(filterParameters.gain->get())); 71 | setCentrePosition(x, y); 72 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_button.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class FilterButton : public juce::ShapeButton, private juce::Timer { 9 | public: 10 | FilterButton(int, PluginProcessor &, std::function); 11 | 12 | void mouseDown(const juce::MouseEvent &event) override; 13 | void mouseDrag(const juce::MouseEvent &event) override; 14 | 15 | void timerCallback() override; 16 | 17 | private: 18 | const int buttonSize = 20; 19 | 20 | int filterID; 21 | PluginProcessor &pluginProcessor; 22 | std::function updateEqualizerCallback; 23 | 24 | FilterParameters filterParameters; 25 | PluginProcessor::UiState &uiState; 26 | 27 | juce::ComponentDragger dragger; 28 | juce::ComponentBoundsConstrainer constrainer; 29 | 30 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FilterButton) 31 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_panel.cpp: -------------------------------------------------------------------------------- 1 | #include "filter_panel.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | FilterPanel::Wrapper::Wrapper(int id, PluginProcessor &p) 9 | : filterID(id), pluginProcessor(p), qualitySlider("Q"), 10 | frequencySlider("F"), gainSlider("G"), 11 | lowpassButton("lowpass", BinaryData::lowpass_svg), 12 | peakButton("peak", BinaryData::bell_svg), 13 | highpassButton("highpass", BinaryData::highpass_svg) { 14 | qualitySlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 15 | qualitySlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 15); 16 | qualitySlider.setRange(0.0, 1.0, 0.01); 17 | qualitySliderAttachment = std::make_unique( 18 | pluginProcessor.getAPVTS(), FilterParameters::getQualityID(id), 19 | qualitySlider); 20 | addAndMakeVisible(qualitySlider); 21 | 22 | frequencySlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 23 | frequencySlider.setTextBoxStyle(juce::Slider::NoTextBox, false, 80, 20); 24 | frequencySlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 15); 25 | frequencySlider.setRange(0.0, 1.0, 0.01); 26 | frequencySlider.setTextValueSuffix(" Hz"); 27 | frequencySliderAttachment = std::make_unique( 28 | pluginProcessor.getAPVTS(), FilterParameters::getFrequencyID(id), 29 | frequencySlider); 30 | addAndMakeVisible(frequencySlider); 31 | 32 | gainSlider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 33 | gainSlider.setTextBoxStyle(juce::Slider::NoTextBox, false, 80, 20); 34 | gainSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 15); 35 | gainSlider.setRange(0.0, 1.0, 0.01); 36 | gainSlider.setTextValueSuffix(" dB"); 37 | gainSliderAttachment = std::make_unique( 38 | pluginProcessor.getAPVTS(), FilterParameters::getGainID(id), gainSlider); 39 | addAndMakeVisible(gainSlider); 40 | 41 | lowpassButton.onClick = [&]() { 42 | FilterParameters params(filterID, pluginProcessor.getAPVTS()); 43 | params.filterTypeChoice->beginChangeGesture(); 44 | params.filterTypeChoice->setValueNotifyingHost(0.0f); 45 | params.filterTypeChoice->endChangeGesture(); 46 | }; 47 | addAndMakeVisible(lowpassButton); 48 | 49 | peakButton.onClick = [&]() { 50 | FilterParameters params(filterID, pluginProcessor.getAPVTS()); 51 | params.filterTypeChoice->beginChangeGesture(); 52 | params.filterTypeChoice->setValueNotifyingHost(0.5f); 53 | params.filterTypeChoice->endChangeGesture(); 54 | }; 55 | addAndMakeVisible(peakButton); 56 | 57 | highpassButton.onClick = [&]() { 58 | FilterParameters params(filterID, pluginProcessor.getAPVTS()); 59 | params.filterTypeChoice->beginChangeGesture(); 60 | params.filterTypeChoice->setValueNotifyingHost(1.0f); 61 | params.filterTypeChoice->endChangeGesture(); 62 | }; 63 | addAndMakeVisible(highpassButton); 64 | 65 | resized(); 66 | } 67 | 68 | void FilterPanel::Wrapper::resized() { 69 | layout.templateRows = {Track(Px(40)), Track(Fr(1))}; 70 | layout.templateColumns = {Track(Fr(1)), Track(Fr(1)), Track(Fr(1))}; 71 | 72 | layout.items.add(juce::GridItem(lowpassButton).withArea(1, 1, 1, 1)); 73 | layout.items.add(juce::GridItem(peakButton).withArea(1, 2, 1, 2)); 74 | layout.items.add(juce::GridItem(highpassButton).withArea(1, 3, 1, 3)); 75 | layout.items.add(juce::GridItem(qualitySlider).withArea(2, 1, 2, 1)); 76 | layout.items.add(juce::GridItem(frequencySlider).withArea(2, 2, 2, 2)); 77 | layout.items.add(juce::GridItem(gainSlider).withArea(2, 3, 2, 3)); 78 | 79 | LayoutComponent::resized(); 80 | } 81 | 82 | FilterPanel::FilterPanel(PluginProcessor &p, std::function uec) 83 | : pluginProcessor(p), uiState(p.getUiState()), updateEqualizerCallback(uec), 84 | prevFilterButton("prev_filter", juce::Colour(0xff233248).darker(0.3f), 85 | colours::primaryColour, colours::responseCurveColour), 86 | nextFilterButton("next_filter", juce::Colour(0xff233248).darker(0.3f), 87 | colours::primaryColour, colours::responseCurveColour) { 88 | prevFilterButton.setShape(paths::createRoundedTriangle(180.0f), true, false, 89 | false); 90 | prevFilterButton.onClick = [&]() { 91 | uiState.selectPrevFilter(); 92 | updateEqualizerCallback(); 93 | }; 94 | addAndMakeVisible(prevFilterButton); 95 | 96 | nextFilterButton.setShape(paths::createRoundedTriangle(0.0f), true, false, 97 | false); 98 | nextFilterButton.onClick = [&]() { 99 | uiState.selectNextFilter(); 100 | updateEqualizerCallback(); 101 | }; 102 | addAndMakeVisible(nextFilterButton); 103 | 104 | filterLabel.setFont(juce::Font(24.0f)); 105 | filterLabel.setJustificationType(juce::Justification::centred); 106 | addAndMakeVisible(filterLabel); 107 | 108 | for (int i = constants::FILTER_MIN_ID; i <= constants::FILTER_MAX_ID; ++i) { 109 | wrappers.push_back(std::make_unique(i, p)); 110 | addChildComponent(*wrappers.back()); 111 | } 112 | 113 | resized(); 114 | } 115 | 116 | void FilterPanel::paint(juce::Graphics &g) { 117 | juce::Path background; 118 | background.addRoundedRectangle(getLocalBounds(), cornerSize); 119 | g.setColour(juce::Colours::white); 120 | g.fillPath(background); 121 | 122 | g.setColour(juce::Colour(0xff233248).darker(0.3f)); 123 | g.drawRoundedRectangle( 124 | getLocalBounds().toFloat().reduced(0.5f * lineThickness), cornerSize, 125 | lineThickness); 126 | } 127 | 128 | void FilterPanel::resized() { 129 | layout.templateRows = {Track(Px(paddingTop + lineThickness)), 130 | Track(Px(headerHeight)), Track(Fr(1)), 131 | Track(Px(paddingBottom + lineThickness))}; 132 | layout.templateColumns = { 133 | Track(Px(paddingLeft + lineThickness)), 134 | Track(Px(buttonSize)), 135 | Track(Fr(1)), 136 | Track(Px(labelSize)), 137 | Track(Fr(1)), 138 | Track(Px(buttonSize)), 139 | Track(Px(paddingRight + lineThickness)), 140 | }; 141 | 142 | layout.items.add(juce::GridItem(prevFilterButton) 143 | .withArea(2, 2, 2, 2) 144 | .withSize(buttonSize, buttonSize) 145 | .withJustifySelf(juce::GridItem::JustifySelf::center) 146 | .withAlignSelf(juce::GridItem::AlignSelf::center)); 147 | layout.items.add(juce::GridItem(filterLabel).withArea(2, 4, 2, 4)); 148 | layout.items.add(juce::GridItem(nextFilterButton) 149 | .withArea(2, 6, 2, 6) 150 | .withSize(buttonSize, buttonSize) 151 | .withJustifySelf(juce::GridItem::JustifySelf::center) 152 | .withAlignSelf(juce::GridItem::AlignSelf::center)); 153 | for (auto &wrapper : wrappers) { 154 | layout.items.add(juce::GridItem(*wrapper) 155 | .withArea(3, 3, 3, 6) 156 | .withJustifySelf(juce::GridItem::JustifySelf::end) 157 | .withAlignSelf(juce::GridItem::AlignSelf::end)); 158 | } 159 | 160 | constrainer.setMinimumOnscreenAmounts(getHeight(), getWidth(), getHeight(), 161 | getWidth()); 162 | 163 | LayoutComponent::resized(); 164 | } 165 | 166 | void FilterPanel::mouseDown(const juce::MouseEvent &event) { 167 | LayoutComponent::mouseDown(event); 168 | dragger.startDraggingComponent(this, event); 169 | } 170 | 171 | void FilterPanel::mouseDrag(const juce::MouseEvent &event) { 172 | LayoutComponent::mouseDrag(event); 173 | dragger.dragComponent(this, event, &constrainer); 174 | } 175 | 176 | void FilterPanel::update() { 177 | if (!uiState.hasSelectedFilter()) { 178 | setVisible(false); 179 | return; 180 | } 181 | 182 | filterLabel.setText( 183 | juce::String::formatted("Filter %d", uiState.selectedFilterID), 184 | juce::NotificationType::dontSendNotification); 185 | filterLabel.setColour(juce::Label::textColourId, 186 | colours::getFilterColour(uiState.selectedFilterID)); 187 | 188 | for (const auto &[id, isUsed] : uiState.usedFilterIDs) { 189 | bool shouldBeVisible = (id == uiState.selectedFilterID); 190 | wrappers[static_cast(id) - 1]->setVisible(shouldBeVisible); 191 | } 192 | 193 | setVisible(true); 194 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_panel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "shared.hpp" 6 | 7 | class FilterPanel : public LayoutComponent { 8 | public: 9 | class Wrapper : public LayoutComponent { 10 | public: 11 | Wrapper(int, PluginProcessor &); 12 | 13 | void resized() override; 14 | 15 | private: 16 | int filterID; 17 | PluginProcessor &pluginProcessor; 18 | 19 | LabelRotarySlider qualitySlider; 20 | std::unique_ptr qualitySliderAttachment; 21 | 22 | LabelRotarySlider frequencySlider; 23 | std::unique_ptr frequencySliderAttachment; 24 | 25 | LabelRotarySlider gainSlider; 26 | std::unique_ptr gainSliderAttachment; 27 | 28 | IconButton lowpassButton; 29 | IconButton peakButton; 30 | IconButton highpassButton; 31 | }; 32 | 33 | FilterPanel(PluginProcessor &, std::function); 34 | 35 | void paint(juce::Graphics &g) override; 36 | void resized() override; 37 | 38 | void mouseDown(const juce::MouseEvent &event) override; 39 | void mouseDrag(const juce::MouseEvent &event) override; 40 | 41 | void update(); 42 | 43 | private: 44 | PluginProcessor &pluginProcessor; 45 | std::function updateEqualizerCallback; 46 | 47 | PluginProcessor::UiState &uiState; 48 | 49 | const float cornerSize = 10.0f; 50 | const float lineThickness = 3.0f; 51 | 52 | const float paddingTop = 10.0f; 53 | const float paddingBottom = 10.0f; 54 | const float paddingLeft = 10.0f; 55 | const float paddingRight = 10.0f; 56 | 57 | const float headerHeight = 30.0f; 58 | const float buttonSize = 15.0f; 59 | const float labelSize = 100.0f; 60 | 61 | juce::ShapeButton prevFilterButton; 62 | juce::ShapeButton nextFilterButton; 63 | juce::Label filterLabel; 64 | std::vector> wrappers; 65 | 66 | juce::ComponentDragger dragger; 67 | juce::ComponentBoundsConstrainer constrainer; 68 | 69 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FilterPanel) 70 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_response.cpp: -------------------------------------------------------------------------------- 1 | #include "filter_response.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | FilterFrequencyResponse::FilterFrequencyResponse(PluginProcessor &p, int id) 6 | : pluginProcessor(p), filterID(id), colour(colours::getFilterColour(id)), 7 | parameters(id, p.getAPVTS()), 8 | frequencyResponse([](float) { return 0.0f; }) { 9 | startTimerHz(24); 10 | } 11 | 12 | void FilterFrequencyResponse::paint(juce::Graphics &g) { 13 | if (pluginProcessor.getSampleRate() == 0) { 14 | return; 15 | } 16 | 17 | const float xMin = 0.0f; 18 | float xMax = static_cast(getWidth()); 19 | const float yMax = static_cast(getHeight()); 20 | 21 | const float halfSampleRate = 22 | static_cast(pluginProcessor.getSampleRate() * 0.5); 23 | 24 | const auto xToFreq = math::invLogMapping(xMin, xMax, constants::GRID_MIN_FREQ, 25 | constants::GRID_MAX_FREQ); 26 | const auto dBToY = math::segmentMapping(constants::GRID_MIN_DB, 27 | constants::GRID_MAX_DB, yMax, 0.0f); 28 | 29 | juce::Path freqRespPath; 30 | freqRespPath.preallocateSpace(3 * (static_cast(xMax) + 3)); 31 | 32 | float xLast = xMin; 33 | float yLast = dBToY(frequencyResponse(xToFreq(xMin))); 34 | const float yStart = yLast; 35 | freqRespPath.startNewSubPath(xLast, yLast); 36 | for (float x = xMin + 1; x < xMax; ++x) { 37 | const float freq = xToFreq(x); 38 | if (freq > halfSampleRate) { 39 | break; 40 | } 41 | xLast = x; 42 | yLast = dBToY(frequencyResponse(freq)); 43 | freqRespPath.lineTo(xLast, yLast); 44 | } 45 | 46 | g.setColour(colour); 47 | g.strokePath(freqRespPath, juce::PathStrokeType( 48 | 1.f, juce::PathStrokeType::JointStyle::curved, 49 | juce::PathStrokeType::EndCapStyle::rounded)); 50 | 51 | if (parameters.getIsActiveValue()) { 52 | freqRespPath.lineTo(xLast, juce::jmax(yStart, yLast)); 53 | freqRespPath.lineTo(xMin, juce::jmax(yStart, yLast)); 54 | freqRespPath.closeSubPath(); 55 | 56 | if (pluginProcessor.getUiState().selectedFilterID == filterID) { 57 | g.setColour(colour.withAlpha(0.1f)); 58 | } else { 59 | g.setColour(colour.withAlpha(0.01f)); 60 | } 61 | g.fillPath(freqRespPath); 62 | } 63 | } 64 | 65 | void FilterFrequencyResponse::timerCallback() { 66 | const auto equalizerProcessor = pluginProcessor.getEqualizerProcessor(); 67 | if (!equalizerProcessor) { 68 | return; 69 | } 70 | 71 | const auto filterProcessor = equalizerProcessor->getFilter(filterID); 72 | if (!filterProcessor) { 73 | return; 74 | } 75 | 76 | frequencyResponse = filterProcessor->getFrequencyResponse(); 77 | } 78 | 79 | std::function &FilterFrequencyResponse::getFrequencyResponse() { 80 | return frequencyResponse; 81 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/filter_response.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | 7 | class FilterFrequencyResponse : public juce::Component, private juce::Timer { 8 | public: 9 | FilterFrequencyResponse(PluginProcessor &, int); 10 | 11 | void paint(juce::Graphics &) override; 12 | void resized() override {} 13 | 14 | void timerCallback() override; 15 | 16 | std::function &getFrequencyResponse(); 17 | 18 | private: 19 | PluginProcessor &pluginProcessor; 20 | int filterID; 21 | 22 | juce::Colour colour; 23 | FilterParameters parameters; 24 | std::function frequencyResponse; 25 | 26 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FilterFrequencyResponse) 27 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/grid.cpp: -------------------------------------------------------------------------------- 1 | #include "grid.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | void GridComponent::paint(juce::Graphics &g) { 6 | g.setColour(juce::Colours::black); 7 | g.setFont(juce::Font(getFontSize(getWidth()))); 8 | 9 | const float circleWidth = 5.0f; 10 | const float halfCircleWidth = circleWidth * 0.5f; 11 | 12 | float w = getWidth(); 13 | float h = getHeight(); 14 | 15 | float labelWidth = 30.0f; 16 | 17 | auto freqMap = math::logMapping(constants::GRID_MIN_FREQ, 18 | constants::GRID_MAX_FREQ, 0.0f, w); 19 | auto dBMap = math::segmentMapping(constants::GRID_MIN_DB, 20 | constants::GRID_MAX_DB, -h, 0.0f); 21 | 22 | for (const auto &[freq, label] : labelFrequencies) { 23 | float x = freqMap(freq); 24 | 25 | g.setColour(juce::Colour(0xffe0e0e0)); 26 | for (int dB = constants::GRID_MIN_DB + constants::GRID_DB_STEP; 27 | dB < constants::GRID_MAX_DB; dB += constants::GRID_DB_STEP) { 28 | float y = -dBMap(float(dB)); 29 | 30 | g.fillEllipse(x - halfCircleWidth, y - halfCircleWidth, circleWidth, 31 | circleWidth); 32 | } 33 | 34 | g.setColour(juce::Colours::black); 35 | g.drawText(label, int(x - labelWidth * 0.5f), int(h - labelWidth), 36 | int(labelWidth), int(labelWidth), juce::Justification::centred); 37 | } 38 | 39 | for (int dB = constants::GRID_MIN_DB + constants::GRID_DB_STEP; 40 | dB < constants::GRID_MAX_DB; dB += constants::GRID_DB_STEP) { 41 | float y = -dBMap(float(dB)); 42 | 43 | g.drawText(juce::String(dB), int(labelWidth), int(y - labelWidth * 0.5f), 44 | int(labelWidth), int(labelWidth), juce::Justification::centred); 45 | } 46 | } -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | 7 | class GridComponent : public LayoutComponent { 8 | public: 9 | GridComponent() 10 | : fontLinInterpCoef((maxFontSize - minFontSize) / (maxWidth - minWidth)) { 11 | } 12 | 13 | void paint(juce::Graphics &) override; 14 | 15 | private: 16 | float getFontSize(float width) { 17 | return minFontSize + fontLinInterpCoef * (width - minWidth); 18 | } 19 | 20 | const float minFontSize = 14.0f; 21 | const float maxFontSize = 20.0f; 22 | const float minWidth = 720.0f; 23 | const float maxWidth = 1920.0f; 24 | float fontLinInterpCoef; 25 | 26 | const std::map labelFrequencies{ 27 | {20.0f, "20"}, {50.0f, "50"}, {100.0f, "100"}, {200.0f, "200"}, 28 | {500.0f, "500"}, {1000.0f, "1k"}, {2000.0f, "2k"}, {5000.0f, "5k"}, 29 | {10000.0f, "10k"}, {20000.0f, "20k"}}; 30 | }; -------------------------------------------------------------------------------- /src/pages/home/equalizer/ui/ui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "analyzer.hpp" 4 | #include "constants.hpp" 5 | #include "equalizer_response.hpp" 6 | #include "filter_button.hpp" 7 | #include "filter_panel.hpp" 8 | #include "filter_response.hpp" 9 | #include "grid.hpp" -------------------------------------------------------------------------------- /src/pages/home/gain/gain.cpp: -------------------------------------------------------------------------------- 1 | #include "gain.hpp" 2 | 3 | Gain::Gain(PluginProcessor &pluginProcessor) : levelMeter(pluginProcessor) { 4 | addAndMakeVisible(levelMeter); 5 | 6 | slider.setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 7 | slider.setTextBoxStyle(juce::Slider::NoTextBox, false, 80, 20); 8 | slider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 50, 15); 9 | slider.setRange(0.0, 1.0, 0.01); 10 | slider.setTextValueSuffix(" dB"); 11 | addAndMakeVisible(slider); 12 | 13 | sliderAttachment = std::make_unique( 14 | pluginProcessor.getAPVTS(), GainParameters::getWetID(), slider); 15 | 16 | resized(); 17 | } 18 | 19 | void Gain::resized() { 20 | layout.templateRows = {Track(Fr(1)), Track(Px(75))}; 21 | layout.templateColumns = {Track(Fr(1))}; 22 | layout.items = {juce::GridItem(levelMeter), juce::GridItem(slider)}; 23 | 24 | LayoutComponent::resized(); 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/home/gain/gain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | #include "ui/level_meter.hpp" 5 | 6 | class Gain : public LayoutComponent { 7 | public: 8 | Gain(PluginProcessor &); 9 | 10 | void resized() override; 11 | 12 | private: 13 | LabelRotarySlider slider; 14 | std::unique_ptr sliderAttachment; 15 | 16 | LevelMeterComponent levelMeter; 17 | }; -------------------------------------------------------------------------------- /src/pages/home/gain/ui/bar.cpp: -------------------------------------------------------------------------------- 1 | #include "bar.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | void BarComponent::paint(juce::Graphics &g) { 6 | auto bounds = getLocalBounds().toFloat(); 7 | 8 | auto dBMap = math::segmentMapping( 9 | constants::GAIN_MIN_DB, constants::GAIN_MAX_DB, 0.0f, float(getHeight())); 10 | const auto scaledY = dBMap(level); 11 | 12 | g.setGradientFill(juce::ColourGradient( 13 | juce::Colour(0xff87bfff), bounds.getBottomLeft(), 14 | juce::Colour(0xff2683ee), bounds.getTopLeft(), false)); 15 | g.fillRoundedRectangle(bounds.removeFromBottom(scaledY), 5.0f); 16 | } 17 | 18 | void BarComponent::timerCallback() { 19 | auto gainProcessor = pluginProcessor.getGainProcessor(); 20 | 21 | if (gainProcessor) { 22 | level = gainProcessor->getRmsValue(channel); 23 | } 24 | 25 | repaint(); 26 | } -------------------------------------------------------------------------------- /src/pages/home/gain/ui/bar.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | 7 | class BarComponent : public juce::Component, private juce::Timer { 8 | public: 9 | BarComponent(PluginProcessor &pluginProcessor, int channel) 10 | : pluginProcessor(pluginProcessor), channel(channel), 11 | level(constants::MINUS_INFINITY_DB) { 12 | startTimerHz(30); 13 | } 14 | 15 | void paint(juce::Graphics &) override; 16 | 17 | void timerCallback() override; 18 | 19 | private: 20 | PluginProcessor &pluginProcessor; 21 | int channel; 22 | 23 | float level; 24 | }; -------------------------------------------------------------------------------- /src/pages/home/gain/ui/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace constants { 4 | 5 | const float GAIN_MIN_DB = -84.0f; 6 | const float GAIN_MAX_DB = 3.0f; 7 | 8 | } // namespace constants 9 | -------------------------------------------------------------------------------- /src/pages/home/gain/ui/level_meter.cpp: -------------------------------------------------------------------------------- 1 | #include "level_meter.hpp" 2 | 3 | LevelMeterComponent::LevelMeterComponent(PluginProcessor &pluginProcessor) 4 | : leftBar(pluginProcessor, 0), rightBar(pluginProcessor, 1) { 5 | addAndMakeVisible(leftBar); 6 | addAndMakeVisible(scale); 7 | addAndMakeVisible(rightBar); 8 | 9 | resized(); 10 | } 11 | 12 | void LevelMeterComponent::resized() { 13 | layout.templateRows = {Track(Fr(1))}; 14 | layout.templateColumns = {Track(Px(10)), Track(Fr(1)), Track(Px(10))}; 15 | layout.items = {juce::GridItem(leftBar), juce::GridItem(scale), 16 | juce::GridItem(rightBar)}; 17 | 18 | LayoutComponent::resized(); 19 | } -------------------------------------------------------------------------------- /src/pages/home/gain/ui/level_meter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include "bar.hpp" 6 | #include "scale.hpp" 7 | 8 | class LevelMeterComponent : public LayoutComponent { 9 | public: 10 | LevelMeterComponent(PluginProcessor &); 11 | 12 | void resized() override; 13 | 14 | private: 15 | BarComponent leftBar; 16 | ScaleComponent scale; 17 | BarComponent rightBar; 18 | }; -------------------------------------------------------------------------------- /src/pages/home/gain/ui/scale.cpp: -------------------------------------------------------------------------------- 1 | #include "scale.hpp" 2 | 3 | #include "constants.hpp" 4 | 5 | void ScaleComponent::paint(juce::Graphics &g) { 6 | const float lineThickness = 2.0f; 7 | 8 | float xMax = float(getWidth()); 9 | float xCenter = xMax * 0.5f; 10 | float yMax = float(getHeight()); 11 | 12 | juce::Line verticalLine(juce::Point(xCenter, 0), 13 | juce::Point(xCenter, yMax)); 14 | g.setColour(juce::Colours::black); 15 | g.drawLine(verticalLine, lineThickness); 16 | 17 | float fontSize = getFontSize(yMax); 18 | g.setFont(juce::Font(fontSize)); 19 | 20 | float labelWidth = fontSize; 21 | float halfLabelWidth = float(labelWidth) * 0.5f; 22 | 23 | float labelHeight = 15; 24 | float halfLabelHeight = float(labelHeight) * 0.5f; 25 | 26 | auto dBMap = math::segmentMapping(constants::GAIN_MIN_DB, 27 | constants::GAIN_MAX_DB, -yMax, 0.0f); 28 | 29 | for (size_t i = 0; i < labels.size() - 1; ++i) { 30 | float y = -dBMap(float(labels[i])); 31 | 32 | juce::Line horizontalLine( 33 | juce::Point(xCenter - labelWidth * 0.75f, y), 34 | juce::Point(xCenter + labelWidth * 0.75f, y)); 35 | g.setColour(juce::Colours::black); 36 | g.drawLine(horizontalLine, lineThickness); 37 | 38 | juce::Rectangle textRect( 39 | xCenter - halfLabelWidth, y - halfLabelHeight, labelWidth, labelHeight); 40 | 41 | g.setColour(juce::Colours::white); 42 | g.fillRect(textRect); 43 | 44 | g.setColour(juce::Colours::black); 45 | g.drawText(juce::String(-labels[i]), textRect, 46 | juce::Justification::centred); 47 | } 48 | } -------------------------------------------------------------------------------- /src/pages/home/gain/ui/scale.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include 6 | 7 | class ScaleComponent : public LayoutComponent { 8 | public: 9 | ScaleComponent() 10 | : fontLinInterpCoef((maxFontSize - minFontSize) / 11 | (maxHeight - minHeight)) {} 12 | 13 | void paint(juce::Graphics &g) override; 14 | 15 | private: 16 | float getFontSize(float height) { 17 | return minFontSize + fontLinInterpCoef * (height - minHeight); 18 | } 19 | 20 | std::vector labels{0, -3, -6, -9, -12, -18, -24, 21 | -30, -36, -48, -60, -72, -84}; 22 | 23 | const float minFontSize = 14.0f; 24 | const float maxFontSize = 20.0f; 25 | const float minHeight = 480.0f; 26 | const float maxHeight = 1080.0f; 27 | float fontLinInterpCoef; 28 | }; -------------------------------------------------------------------------------- /src/pages/home/home.cpp: -------------------------------------------------------------------------------- 1 | #include "home.hpp" 2 | 3 | HomePage::HomePage(PluginProcessor &pluginProcessor) : body(pluginProcessor) { 4 | addAndMakeVisible(body); 5 | addAndMakeVisible(header); 6 | 7 | resized(); 8 | } 9 | 10 | void HomePage::paint(juce::Graphics &g) { g.fillAll(juce::Colours::white); } 11 | 12 | void HomePage::resized() { 13 | layout.templateRows = {Track(Px(headerHeight)), Track(Fr(1))}; 14 | layout.templateColumns = {Track(Fr(1))}; 15 | layout.items = {juce::GridItem(header), juce::GridItem(body)}; 16 | 17 | LayoutComponent::resized(); 18 | } 19 | 20 | HomePage::Body::Body(PluginProcessor &pluginProcessor) 21 | : equalizer(pluginProcessor), gain(pluginProcessor) { 22 | addAndMakeVisible(equalizer); 23 | addAndMakeVisible(gain); 24 | 25 | resized(); 26 | } 27 | 28 | void HomePage::Body::resized() { 29 | layout.templateRows = {Track(Fr(1))}; 30 | layout.templateColumns = {Track(Px(15)), Track(Fr(1)), Track(Px(55)), 31 | Track(Px(15))}; 32 | layout.items = {juce::GridItem(), juce::GridItem(equalizer), 33 | juce::GridItem(gain), juce::GridItem()}; 34 | 35 | LayoutComponent::resized(); 36 | } -------------------------------------------------------------------------------- /src/pages/home/home.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | #include "widgets.hpp" 5 | 6 | #include "equalizer/equalizer.hpp" 7 | #include "gain/gain.hpp" 8 | 9 | class HomePage : public LayoutComponent { 10 | public: 11 | enum { headerHeight = 40 }; 12 | 13 | class Body : public LayoutComponent { 14 | public: 15 | Body(PluginProcessor &); 16 | 17 | void resized() override; 18 | 19 | private: 20 | Equalizer equalizer; 21 | Gain gain; 22 | }; 23 | 24 | public: 25 | HomePage(PluginProcessor &); 26 | 27 | void paint(juce::Graphics &g) override; 28 | void resized() override; 29 | 30 | private: 31 | Header header; 32 | Body body; 33 | }; -------------------------------------------------------------------------------- /src/pages/pages.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "home/home.hpp" -------------------------------------------------------------------------------- /src/shared/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace constants { 4 | 5 | constexpr float MINUS_INFINITY_DB = -100.0f; 6 | 7 | constexpr int FILTER_MIN_ID = 1; 8 | constexpr int FILTER_MAX_ID = 12; 9 | constexpr int FILTERS_COUNT = 12; 10 | 11 | } // namespace constants 12 | -------------------------------------------------------------------------------- /src/shared/dsp/dsp.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fft.hpp" 4 | #include "window.hpp" -------------------------------------------------------------------------------- /src/shared/dsp/fft.cpp: -------------------------------------------------------------------------------- 1 | #include "fft.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace dsp { 7 | 8 | void fft(ComplexArray &phasors) { 9 | const int n = phasors.size(); 10 | if (n == 1) { 11 | return; 12 | } 13 | 14 | ComplexArray odd = phasors[std::slice(1, n / 2, 2)]; 15 | fft(odd); 16 | 17 | ComplexArray even = phasors[std::slice(0, n / 2, 2)]; 18 | fft(even); 19 | 20 | for (int k = 0; k < n / 2; ++k) { 21 | const std::complex t = 22 | std::polar(1.0, -2 * std::numbers::pi * k / n) * odd[k]; 23 | 24 | phasors[k] = even[k] + t; 25 | phasors[k + n / 2] = even[k] - t; 26 | } 27 | } 28 | 29 | std::function binToFrequencyMapping(int fftSize, float sampleRate) { 30 | return [=](int index) { return float(index) * sampleRate / float(fftSize); }; 31 | } 32 | 33 | } // namespace dsp 34 | -------------------------------------------------------------------------------- /src/shared/dsp/fft.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace dsp { 8 | 9 | using ComplexArray = std::valarray>; 10 | 11 | void fft(ComplexArray &); 12 | 13 | std::function binToFrequencyMapping(int, float); 14 | 15 | } // namespace dsp 16 | -------------------------------------------------------------------------------- /src/shared/dsp/window.cpp: -------------------------------------------------------------------------------- 1 | #include "window.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace dsp { 7 | 8 | namespace { 9 | 10 | class HannWindow : public Window { 11 | public: 12 | WindowKind getKind() const override { return WindowKind::Hann; } 13 | 14 | void calculateCoefficients(int width) override { 15 | const float a0 = 0.5f; 16 | const float a1 = 0.5f; 17 | 18 | for (int i = 0; i < width; ++i) { 19 | const float k = 2 * std::numbers::pi * i; 20 | 21 | float real = a0 - a1 * std::cos(k / (width - 1)); 22 | coefficients[i] = std::complex(real, 0.0f); 23 | } 24 | } 25 | }; 26 | 27 | class HammingWindow : public Window { 28 | public: 29 | WindowKind getKind() const override { return WindowKind::Hamming; } 30 | 31 | void calculateCoefficients(int width) override { 32 | const float a0 = 0.5f; 33 | const float a1 = 0.5f; 34 | 35 | for (int i = 0; i < width; ++i) { 36 | const float k = 2 * std::numbers::pi * i; 37 | 38 | float real = a0 - a1 * std::cos(k / (width - 1)); 39 | coefficients[i] = std::complex(real, 0.0f); 40 | } 41 | } 42 | }; 43 | 44 | class NuttallWindow : public Window { 45 | public: 46 | WindowKind getKind() const override { return WindowKind::Nuttall; } 47 | 48 | void calculateCoefficients(int width) override { 49 | const float a0 = 0.355768f; 50 | const float a1 = 0.487396f; 51 | const float a2 = 0.144232f; 52 | const float a3 = 0.012604f; 53 | 54 | for (int i = 0; i < width; ++i) { 55 | const float k = 2 * std::numbers::pi * i; 56 | 57 | float real = a0 - a1 * std::cos(k / width) + 58 | a2 * std::cos(2 * k / width) - a3 * std::cos(3 * k / width); 59 | coefficients[i] = std::complex(real, 0.0f); 60 | } 61 | } 62 | }; 63 | 64 | } // namespace 65 | 66 | Window *Window::createWindow(WindowKind kind) { 67 | switch (kind) { 68 | case WindowKind::Hann: 69 | return new HannWindow(); 70 | case WindowKind::Hamming: 71 | return new HammingWindow(); 72 | case WindowKind::Nuttall: 73 | return new NuttallWindow(); 74 | default: 75 | return nullptr; 76 | } 77 | } 78 | 79 | } // namespace dsp 80 | -------------------------------------------------------------------------------- /src/shared/dsp/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace dsp { 7 | 8 | enum class WindowKind { Hann, Hamming, Nuttall }; 9 | 10 | class Window { 11 | public: 12 | void updateCoefficients(int width) { 13 | coefficients.resize(width); 14 | calculateCoefficients(width); 15 | } 16 | 17 | const std::valarray> &getCoefficients() const noexcept { 18 | return coefficients; 19 | } 20 | 21 | virtual WindowKind getKind() const = 0; 22 | 23 | static Window *createWindow(WindowKind); 24 | 25 | protected: 26 | virtual void calculateCoefficients(int) = 0; 27 | 28 | std::valarray> coefficients; 29 | }; 30 | 31 | } // namespace dsp 32 | -------------------------------------------------------------------------------- /src/shared/math/mapping.cpp: -------------------------------------------------------------------------------- 1 | #include "mapping.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace math { 7 | 8 | std::function segmentMapping(float fromMin, float fromMax, 9 | float toMin, float toMax) { 10 | return [=](float x) { 11 | return toMin + (toMax - toMin) * (x - fromMin) / (fromMax - fromMin); 12 | }; 13 | } 14 | 15 | std::function logMapping(float fromMin, float fromMax, 16 | float toMin, float toMax) { 17 | assert(fromMin > 0); 18 | assert(fromMax > 0); 19 | 20 | auto rawMapping = 21 | segmentMapping(std::log10(fromMin), std::log10(fromMax), toMin, toMax); 22 | 23 | return [=](float x) { return rawMapping(std::log10(x)); }; 24 | } 25 | 26 | std::function invLogMapping(float fromMin, float fromMax, 27 | float toMin, float toMax) { 28 | auto rawMapping = 29 | segmentMapping(fromMin, fromMax, std::log10(toMin), std::log10(toMax)); 30 | 31 | return [=](float x) { return std::pow(10.0f, rawMapping(x)); }; 32 | } 33 | 34 | } // namespace math 35 | -------------------------------------------------------------------------------- /src/shared/math/mapping.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace math { 6 | 7 | std::function segmentMapping(float, float, float, float); 8 | 9 | std::function logMapping(float, float, float, float); 10 | 11 | std::function invLogMapping(float, float, float, float); 12 | 13 | } // namespace math 14 | -------------------------------------------------------------------------------- /src/shared/math/math.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mapping.hpp" 4 | #include "smoothing.hpp" -------------------------------------------------------------------------------- /src/shared/math/smoothing.cpp: -------------------------------------------------------------------------------- 1 | #include "smoothing.hpp" 2 | 3 | namespace math { 4 | 5 | std::valarray> 6 | octaveSmoothing(int n, std::valarray> &sequence) { 7 | int N = int(sequence.size()); 8 | 9 | std::valarray> smoothed(N); 10 | 11 | float aCoef = pow(2, -0.5f / n); 12 | float bCoef = 1 / aCoef; 13 | 14 | int prevA = 0; 15 | int prevB = 0; 16 | 17 | smoothed[0] = sequence[0] * std::conj(sequence[0]); 18 | 19 | for (int k = 1; k < N; ++k) { 20 | int a = int(k * aCoef); 21 | int b = std::min(N - 1, int(k * bCoef)); 22 | 23 | std::complex aSum(0.0f, 0.0f); 24 | for (int i = prevA; i <= a - 1; ++i) { 25 | aSum += sequence[i] * std::conj(sequence[i]); 26 | } 27 | 28 | std::complex bSum(0.0f, 0.0f); 29 | for (int i = prevB + 1; i <= b; ++i) { 30 | bSum += sequence[i] * std::conj(sequence[i]); 31 | } 32 | 33 | smoothed[k] = float(prevB - prevA + 1) * smoothed[k - 1] + bSum - aSum; 34 | smoothed[k] /= b - a + 1; 35 | 36 | prevA = a; 37 | prevB = b; 38 | } 39 | 40 | for (int k = 0; k < N; ++k) { 41 | smoothed[k] = std::complex(std::sqrt(smoothed[k].real()), 0.0f); 42 | } 43 | 44 | return smoothed; 45 | } 46 | 47 | } // namespace math 48 | -------------------------------------------------------------------------------- /src/shared/math/smoothing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace math { 7 | 8 | std::valarray> 9 | octaveSmoothing(int, std::valarray> &); 10 | 11 | } // namespace math 12 | -------------------------------------------------------------------------------- /src/shared/parameters/equalizer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../constants.hpp" 6 | #include "../types.hpp" 7 | #include "filter.hpp" 8 | 9 | struct EqualizerParameters { 10 | juce::AudioParameterBool *isActive; 11 | static constexpr bool defaultIsActiveValue = true; 12 | 13 | EqualizerParameters(types::APVTS &apvts) { 14 | isActive = dynamic_cast( 15 | apvts.getParameter(EqualizerParameters::getIsActiveID())); 16 | } 17 | 18 | static void addToLayout(types::APVTS::ParameterLayout &layout) { 19 | layout.add(std::make_unique( 20 | getIsActiveID(), getIsActiveName(), defaultIsActiveValue)); 21 | 22 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 23 | ++id) { 24 | FilterParameters::addToLayout(id, layout); 25 | } 26 | } 27 | 28 | static inline juce::String getIsActiveID() noexcept { 29 | return "equalizer_active"; 30 | } 31 | static inline juce::String getIsActiveName() noexcept { 32 | return "Equalizer Active"; 33 | } 34 | bool getIsActiveValue() const { return isActive->get(); } 35 | }; -------------------------------------------------------------------------------- /src/shared/parameters/filter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../types.hpp" 6 | 7 | struct FilterParameters { 8 | juce::AudioParameterChoice *filterTypeChoice; 9 | static const types::FilterType defaultFilterType = types::FilterType::Peak; 10 | 11 | juce::AudioParameterBool *isActive; 12 | static constexpr bool defaultIsActiveValue = false; 13 | 14 | juce::AudioParameterFloat *quality; 15 | static constexpr float minQualityValue = 0.1f; 16 | static constexpr float maxQualityValue = 10.0f; 17 | static constexpr float defaultQualityValue = 1.0f; 18 | 19 | juce::AudioParameterFloat *frequency; 20 | static constexpr float minFrequencyValue = 20.0f; 21 | static constexpr float maxFrequencyValue = 20000.0f; 22 | static constexpr float defaultFrequencyValue = 1000.0f; 23 | 24 | juce::AudioParameterFloat *gain; 25 | static constexpr float minGainValue = -12.0f; 26 | static constexpr float maxGainValue = 12.0f; 27 | static constexpr float defaultGainValue = 0.0f; 28 | 29 | FilterParameters(int filterID, types::APVTS &apvts) { 30 | filterTypeChoice = dynamic_cast( 31 | apvts.getParameter(FilterParameters::getFilterTypeChoiceID(filterID))); 32 | isActive = dynamic_cast( 33 | apvts.getParameter(FilterParameters::getIsActiveID(filterID))); 34 | frequency = dynamic_cast( 35 | apvts.getParameter(FilterParameters::getFrequencyID(filterID))); 36 | quality = dynamic_cast( 37 | apvts.getParameter(FilterParameters::getQualityID(filterID))); 38 | gain = dynamic_cast( 39 | apvts.getParameter(FilterParameters::getGainID(filterID))); 40 | } 41 | 42 | static void addToLayout(int filterID, types::APVTS::ParameterLayout &layout) { 43 | juce::StringArray choices{"Low Pass", "Peak", "High Pass"}; 44 | layout.add(std::make_unique( 45 | getFilterTypeChoiceID(filterID), getFilterTypeChoiceName(filterID), 46 | choices, static_cast(defaultFilterType))); 47 | 48 | layout.add(std::make_unique( 49 | getIsActiveID(filterID), getIsActiveName(filterID), 50 | defaultIsActiveValue)); 51 | 52 | layout.add(std::make_unique( 53 | getQualityID(filterID), getQualityName(filterID), minQualityValue, 54 | maxQualityValue, defaultQualityValue)); 55 | 56 | layout.add(std::make_unique( 57 | getFrequencyID(filterID), getFrequencyName(filterID), 58 | juce::NormalisableRange(minFrequencyValue, maxFrequencyValue, 59 | 0.01f, 0.2f), 60 | defaultFrequencyValue)); 61 | 62 | layout.add(std::make_unique( 63 | getGainID(filterID), getGainName(filterID), minGainValue, maxGainValue, 64 | defaultGainValue)); 65 | } 66 | 67 | static juce::String getFilterTypeChoiceID(int filterID) noexcept { 68 | return juce::String::formatted("filter_%d_type", filterID); 69 | } 70 | static juce::String getFilterTypeChoiceName(int filterID) noexcept { 71 | return juce::String::formatted("Filter %d Type", filterID); 72 | } 73 | types::FilterType getFilterType() const { 74 | return static_cast(filterTypeChoice->getIndex()); 75 | } 76 | 77 | static juce::String getIsActiveID(int filterID) noexcept { 78 | return juce::String::formatted("filter_%d_active", filterID); 79 | } 80 | static juce::String getIsActiveName(int filterID) noexcept { 81 | return juce::String::formatted("Filter %d Actrive", filterID); 82 | } 83 | bool getIsActiveValue() const { return isActive->get(); } 84 | 85 | static juce::String getQualityID(int filterID) noexcept { 86 | return juce::String::formatted("filter_%d_quality", filterID); 87 | } 88 | static juce::String getQualityName(int filterID) noexcept { 89 | return juce::String::formatted("Filter %d Quality", filterID); 90 | } 91 | float getQualityValue() const { return quality->get(); } 92 | 93 | static juce::String getFrequencyID(int filterID) noexcept { 94 | return juce::String::formatted("filter_%d_frequency", filterID); 95 | } 96 | static juce::String getFrequencyName(int filterID) noexcept { 97 | return juce::String::formatted("Filter %d Frequency", filterID); 98 | } 99 | float getFrequencyValue() const { return frequency->get(); } 100 | 101 | static juce::String getGainID(int filterID) noexcept { 102 | return juce::String::formatted("filter_%d_gain", filterID); 103 | } 104 | static juce::String getGainName(int filterID) noexcept { 105 | return juce::String::formatted("Filter %d Gain", filterID); 106 | } 107 | float getGainValue() const { 108 | return juce::Decibels::decibelsToGain(gain->get()); 109 | } 110 | }; -------------------------------------------------------------------------------- /src/shared/parameters/gain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../types.hpp" 6 | 7 | struct GainParameters { 8 | juce::AudioParameterFloat *wet; 9 | static constexpr float minWetValue = -24.0f; 10 | static constexpr float maxWetValue = 24.0f; 11 | static constexpr float defaultWetValue = 0.0f; 12 | 13 | GainParameters(types::APVTS &apvts) { 14 | wet = dynamic_cast( 15 | apvts.getParameter(GainParameters::getWetID())); 16 | } 17 | 18 | static void addToLayout(types::APVTS::ParameterLayout &layout) { 19 | layout.add(std::make_unique( 20 | getWetID(), getWetName(), minWetValue, maxWetValue, defaultWetValue)); 21 | } 22 | 23 | static inline juce::String getWetID() noexcept { return "gain_wet"; } 24 | static inline juce::String getWetName() noexcept { return "Gain Wet"; } 25 | 26 | float getWetDbValue() const { 27 | return juce::Decibels::decibelsToGain(wet->get()); 28 | } 29 | }; -------------------------------------------------------------------------------- /src/shared/parameters/parameters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "equalizer.hpp" 4 | #include "filter.hpp" 5 | #include "gain.hpp" -------------------------------------------------------------------------------- /src/shared/proccessors/analyzer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "analyzer.hpp" 3 | #include "../constants.hpp" 4 | #include "../math/math.hpp" 5 | 6 | AnalyzerProcessor::AnalyzerProcessor() { update(); } 7 | 8 | void AnalyzerProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { 9 | BaseProcessor::prepareToPlay(sampleRate, samplesPerBlock); 10 | 11 | for (auto &litude : amplitudes) { 12 | amplitude.reset(sampleRate, rampLengthAmplitudes); 13 | } 14 | } 15 | 16 | void AnalyzerProcessor::processBlock(juce::AudioSampleBuffer &audioBuffer, 17 | juce::MidiBuffer &midiBuffer) { 18 | BaseProcessor::processBlock(audioBuffer, midiBuffer); 19 | 20 | for (auto &litude : amplitudes) { 21 | amplitude.skip(audioBuffer.getNumSamples()); 22 | } 23 | 24 | if (audioBuffer.getNumChannels() > 0) { 25 | const float *channelDataLeft = audioBuffer.getReadPointer(0); 26 | const float *channelDataRight = audioBuffer.getReadPointer(1); 27 | 28 | for (auto i = 0; i < audioBuffer.getNumSamples(); ++i) { 29 | auto monoSample = (channelDataLeft[i] + channelDataRight[i]) / 2; 30 | 31 | pushNextSampleToAudioBlock(monoSample); 32 | } 33 | } 34 | } 35 | 36 | void AnalyzerProcessor::changeFFTOrder(int newOrder) { 37 | jassert(newOrder >= 10 && newOrder <= 13); 38 | 39 | fftOrder = newOrder; 40 | fftSize = 1 << newOrder; 41 | 42 | update(); 43 | } 44 | 45 | void AnalyzerProcessor::changeWindowKind(dsp::WindowKind kind) { 46 | windowKind = kind; 47 | 48 | update(); 49 | } 50 | 51 | void AnalyzerProcessor::update() { 52 | audioBlock.resize(fftSize); 53 | audioBlockIndex = 0; 54 | nextBlockReady = false; 55 | 56 | phasors.resize(fftSize * 2); 57 | if (window == nullptr || window->getKind() != windowKind) { 58 | window = 59 | std::unique_ptr(dsp::Window::createWindow(windowKind)); 60 | } 61 | window->updateCoefficients(fftSize); 62 | 63 | amplitudes.resize(fftSize); 64 | for (auto &litude : amplitudes) { 65 | amplitude.setCurrentAndTargetValue(constants::MINUS_INFINITY_DB); 66 | } 67 | 68 | double sampleRate = getSampleRate(); 69 | 70 | if (sampleRate != 0) { 71 | for (auto &litude : amplitudes) { 72 | amplitude.reset(sampleRate, rampLengthAmplitudes); 73 | } 74 | } 75 | } 76 | 77 | void AnalyzerProcessor::pushNextSampleToAudioBlock(float sample) { 78 | if (audioBlockIndex == fftSize) { 79 | if (!nextBlockReady) { 80 | moveAudioBlockSamples(); 81 | nextBlockReady = true; 82 | } 83 | 84 | audioBlockIndex = 0; 85 | } 86 | 87 | audioBlock[audioBlockIndex] = sample; 88 | audioBlockIndex++; 89 | } 90 | 91 | void AnalyzerProcessor::moveAudioBlockSamples() { 92 | for (int i = 0; i < audioBlock.size(); ++i) { 93 | phasors[i] = std::complex(audioBlock[i], 0.0f); 94 | } 95 | for (int i = audioBlock.size(); i < phasors.size(); ++i) { 96 | phasors[i] = std::complex(0.0f, 0.0f); 97 | } 98 | } 99 | 100 | void AnalyzerProcessor::updateAmplitudes() { 101 | if (!nextBlockReady) { 102 | return; 103 | } 104 | 105 | phasors = window->getCoefficients() * phasors; 106 | dsp::fft(phasors); 107 | 108 | phasors = math::octaveSmoothing(24, phasors); 109 | 110 | for (int i = 0; i < amplitudes.size(); ++i) { 111 | float amplitude = 112 | juce::Decibels::gainToDecibels(std::abs(phasors[i]) / fftSize); 113 | 114 | if (amplitude < amplitudes[i].getCurrentValue()) { 115 | amplitudes[i].setTargetValue(amplitude); 116 | } else { 117 | amplitudes[i].setCurrentAndTargetValue(amplitude); 118 | } 119 | } 120 | 121 | nextBlockReady = false; 122 | } 123 | -------------------------------------------------------------------------------- /src/shared/proccessors/analyzer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../dsp/dsp.hpp" 9 | #include "base_processor.hpp" 10 | 11 | class AnalyzerProcessor : public BaseProcessor { 12 | public: 13 | AnalyzerProcessor(); 14 | 15 | void prepareToPlay(double, int) override; 16 | void processBlock(juce::AudioSampleBuffer &, juce::MidiBuffer &) override; 17 | 18 | void changeFFTOrder(int); 19 | void changeWindowKind(dsp::WindowKind); 20 | 21 | int getFFTSize() const { return fftSize; } 22 | 23 | void updateAmplitudes(); 24 | const std::valarray> &getAmplitudes() const { 25 | return amplitudes; 26 | } 27 | 28 | private: 29 | void update(); 30 | 31 | void pushNextSampleToAudioBlock(float); 32 | void moveAudioBlockSamples(); 33 | 34 | int fftOrder = 12; 35 | int fftSize = 1 << 12; 36 | 37 | int audioBlockIndex = 0; 38 | std::vector audioBlock; 39 | bool nextBlockReady = false; 40 | 41 | std::valarray> phasors; 42 | dsp::WindowKind windowKind = dsp::WindowKind::Nuttall; 43 | std::unique_ptr window; 44 | 45 | const float rampLengthAmplitudes = 0.5f; 46 | std::valarray> amplitudes; 47 | }; -------------------------------------------------------------------------------- /src/shared/proccessors/base_processor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class BaseProcessor : public juce::AudioProcessor { 8 | public: 9 | BaseProcessor() 10 | : AudioProcessor( 11 | BusesProperties() 12 | .withInput("Input", juce::AudioChannelSet::stereo(), true) 13 | .withOutput("Output", juce::AudioChannelSet::stereo(), true)) {} 14 | 15 | virtual bool isBusesLayoutSupported(const BusesLayout &layouts) const { 16 | if (layouts.getMainInputChannelSet() == juce::AudioChannelSet::disabled() || 17 | layouts.getMainOutputChannelSet() == juce::AudioChannelSet::disabled()) 18 | return false; 19 | 20 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() && 21 | layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 22 | return false; 23 | 24 | return layouts.getMainInputChannelSet() == 25 | layouts.getMainOutputChannelSet(); 26 | } 27 | 28 | virtual void prepareToPlay(double, int) {} 29 | virtual void releaseResources() {} 30 | virtual void processBlock(juce::AudioSampleBuffer &audioBuffer, 31 | juce::MidiBuffer &) { 32 | for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); 33 | ++i) { 34 | audioBuffer.clear(i, 0, audioBuffer.getNumSamples()); 35 | } 36 | } 37 | 38 | virtual juce::AudioProcessorEditor *createEditor() { return nullptr; } 39 | virtual bool hasEditor() const { return false; } 40 | 41 | virtual const juce::String getName() const { return {}; } 42 | bool acceptsMidi() const { return false; } 43 | bool producesMidi() const { return false; } 44 | virtual double getTailLengthSeconds() const { return 0.0; } 45 | 46 | virtual int getNumPrograms() { return 0; } 47 | virtual int getCurrentProgram() { return 0; } 48 | virtual void setCurrentProgram(int) {} 49 | virtual const juce::String getProgramName(int) { return {}; } 50 | virtual void changeProgramName(int, const juce::String &) {} 51 | 52 | virtual void getStateInformation(juce::MemoryBlock &) {} 53 | virtual void setStateInformation(const void *, int) {} 54 | 55 | virtual std::function getFrequencyResponse() { 56 | return [](float) { return 0.0f; }; 57 | } 58 | 59 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BaseProcessor) 60 | }; -------------------------------------------------------------------------------- /src/shared/proccessors/cascade_processor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "base_processor.hpp" 4 | 5 | class CascadeProcessor : public BaseProcessor { 6 | public: 7 | using AudioGraphIOProcessor = 8 | juce::AudioProcessorGraph::AudioGraphIOProcessor; 9 | using Node = juce::AudioProcessorGraph::Node; 10 | 11 | CascadeProcessor() : audioGraph(new juce::AudioProcessorGraph()) {} 12 | 13 | virtual void prepareToPlay(double sampleRate, int samplesPerBlock) { 14 | BaseProcessor::prepareToPlay(sampleRate, samplesPerBlock); 15 | 16 | audioGraph->setPlayConfigDetails(getMainBusNumInputChannels(), 17 | getMainBusNumOutputChannels(), sampleRate, 18 | samplesPerBlock); 19 | audioGraph->prepareToPlay(sampleRate, samplesPerBlock); 20 | 21 | initializeGraph(); 22 | } 23 | 24 | virtual void releaseResources() { audioGraph->releaseResources(); } 25 | 26 | virtual void processBlock(juce::AudioSampleBuffer &audioBuffer, 27 | juce::MidiBuffer &midiBuffer) { 28 | BaseProcessor::processBlock(audioBuffer, midiBuffer); 29 | 30 | updateGraph(); 31 | 32 | audioGraph->processBlock(audioBuffer, midiBuffer); 33 | } 34 | 35 | protected: 36 | virtual void initializeEffectNodes() {} 37 | virtual void updateGraph() {} 38 | virtual void connectAudioNodes() { 39 | for (int channel = 0; channel < 2; ++channel) { 40 | audioGraph->addConnection({{audioInputNode->nodeID, channel}, 41 | {audioOutputNode->nodeID, channel}}); 42 | } 43 | } 44 | 45 | std::unique_ptr audioGraph; 46 | 47 | Node::Ptr audioInputNode; 48 | Node::Ptr audioOutputNode; 49 | 50 | private: 51 | void initializeGraph() { 52 | audioGraph->clear(); 53 | 54 | audioInputNode = 55 | audioGraph->addNode(std::make_unique( 56 | AudioGraphIOProcessor::audioInputNode)); 57 | audioOutputNode = 58 | audioGraph->addNode(std::make_unique( 59 | AudioGraphIOProcessor::audioOutputNode)); 60 | 61 | initializeEffectNodes(); 62 | connectAudioNodes(); 63 | } 64 | }; -------------------------------------------------------------------------------- /src/shared/proccessors/equalizer.cpp: -------------------------------------------------------------------------------- 1 | #include "equalizer.hpp" 2 | 3 | EqualizerProcessor::EqualizerProcessor(types::APVTS &apvts) 4 | : treeState(apvts), params(apvts) {} 5 | 6 | void EqualizerProcessor::processBlock(juce::AudioSampleBuffer &audioBuffer, 7 | juce::MidiBuffer &midiBuffer) { 8 | if (params.getIsActiveValue()) { 9 | CascadeProcessor::processBlock(audioBuffer, midiBuffer); 10 | } else { 11 | BaseProcessor::processBlock(audioBuffer, midiBuffer); 12 | } 13 | } 14 | 15 | FilterProcessor *EqualizerProcessor::getFilter(int filterID) { 16 | jassert(filterID >= constants::FILTER_MIN_ID && 17 | filterID <= constants::FILTER_MAX_ID); 18 | 19 | return dynamic_cast( 20 | filterNodes[filterID - 1]->getProcessor()); 21 | } 22 | 23 | void EqualizerProcessor::initializeEffectNodes() { 24 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 25 | ++id) { 26 | filterNodes[id - 1] = 27 | audioGraph->addNode(std::make_unique(id, treeState)); 28 | } 29 | } 30 | 31 | void EqualizerProcessor::connectAudioNodes() { 32 | for (int channel = 0; channel < 2; ++channel) { 33 | audioGraph->addConnection( 34 | {{audioInputNode->nodeID, channel}, {filterNodes[0]->nodeID, channel}}); 35 | for (int i = 0; i < constants::FILTERS_COUNT - 1; ++i) { 36 | audioGraph->addConnection({{filterNodes[i]->nodeID, channel}, 37 | {filterNodes[i + 1]->nodeID, channel}}); 38 | } 39 | audioGraph->addConnection( 40 | {{filterNodes[constants::FILTERS_COUNT - 1]->nodeID, channel}, 41 | {audioOutputNode->nodeID, channel}}); 42 | } 43 | } 44 | 45 | std::function EqualizerProcessor::getFrequencyResponse() { 46 | std::vector> filterResponses; 47 | 48 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 49 | ++id) { 50 | filterResponses.push_back(getFilter(id)->getFrequencyResponse()); 51 | } 52 | 53 | return [&, filterResponses](float freq) { 54 | float response = 0; 55 | for (auto &filterResponse : filterResponses) { 56 | response += filterResponse(freq); 57 | } 58 | return response; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/shared/proccessors/equalizer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../constants.hpp" 4 | #include "../parameters/equalizer.hpp" 5 | #include "../types.hpp" 6 | #include "cascade_processor.hpp" 7 | #include "filter.hpp" 8 | 9 | class EqualizerProcessor : public CascadeProcessor { 10 | public: 11 | EqualizerProcessor(types::APVTS &); 12 | 13 | void processBlock(juce::AudioSampleBuffer &, juce::MidiBuffer &) override; 14 | 15 | FilterProcessor *getFilter(int); 16 | 17 | std::function getFrequencyResponse() override; 18 | 19 | protected: 20 | void initializeEffectNodes() override; 21 | void connectAudioNodes() override; 22 | 23 | private: 24 | types::APVTS &treeState; 25 | EqualizerParameters params; 26 | Node::Ptr filterNodes[constants::FILTERS_COUNT]; 27 | }; -------------------------------------------------------------------------------- /src/shared/proccessors/filter.cpp: -------------------------------------------------------------------------------- 1 | #include "filter.hpp" 2 | 3 | FilterProcessor::FilterProcessor(int filterID, types::APVTS &apvts) 4 | : id(filterID), params(filterID, apvts), 5 | filter(std::make_unique()) {} 6 | 7 | void FilterProcessor::prepareToPlay(double sampleRate, int samplesPerBlock) { 8 | BaseProcessor::prepareToPlay(sampleRate, samplesPerBlock); 9 | 10 | juce::dsp::ProcessSpec spec; 11 | 12 | spec.sampleRate = sampleRate; 13 | spec.maximumBlockSize = juce::uint32(samplesPerBlock); 14 | spec.numChannels = juce::uint32(getTotalNumOutputChannels()); 15 | 16 | filter->reset(); 17 | updateFilter(); 18 | filter->prepare(spec); 19 | } 20 | 21 | void FilterProcessor::processBlock(juce::AudioSampleBuffer &audioBuffer, 22 | juce::MidiBuffer &midiBuffer) { 23 | BaseProcessor::processBlock(audioBuffer, midiBuffer); 24 | 25 | if (params.getIsActiveValue()) { 26 | juce::dsp::AudioBlock audioBlock(audioBuffer); 27 | updateFilter(); 28 | filter->process(juce::dsp::ProcessContextReplacing(audioBlock)); 29 | } 30 | } 31 | 32 | void FilterProcessor::updateFilter() { 33 | auto iirCoefs = getIIRCoefficients(); 34 | jassert(iirCoefs != nullptr); 35 | 36 | *filter->state = *iirCoefs; 37 | } 38 | 39 | FilterProcessor::IIRCoefficientsPtr FilterProcessor::getIIRCoefficients() { 40 | float sr = float(getSampleRate()); 41 | float freq = params.getFrequencyValue(); 42 | float quality = params.getQualityValue(); 43 | float gainFactor = params.getGainValue(); 44 | 45 | IIRCoefficientsPtr iirCoefs; 46 | switch (params.getFilterType()) { 47 | case types::FilterType::LowPass: 48 | iirCoefs = IIRCoefficients::makeLowPass(sr, freq, quality); 49 | break; 50 | case types::FilterType::Peak: 51 | iirCoefs = IIRCoefficients::makePeakFilter(sr, freq, quality, gainFactor); 52 | break; 53 | case types::FilterType::HighPass: 54 | iirCoefs = IIRCoefficients::makeHighPass(sr, freq, quality); 55 | break; 56 | default: 57 | break; 58 | } 59 | 60 | jassert(iirCoefs != nullptr); 61 | 62 | return iirCoefs; 63 | } 64 | 65 | std::function FilterProcessor::getFrequencyResponse() { 66 | if (!params.getIsActiveValue()) { 67 | return BaseProcessor::getFrequencyResponse(); 68 | } 69 | 70 | const auto iirCoefs = getIIRCoefficients(); 71 | const double sr = getSampleRate(); 72 | 73 | return [&, iirCoefs, sr](float freq) { 74 | double mag = 75 | iirCoefs->getMagnitudeForFrequency(static_cast(freq), sr); 76 | return juce::Decibels::gainToDecibels(static_cast(mag)); 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /src/shared/proccessors/filter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../parameters/filter.hpp" 6 | #include "../types.hpp" 7 | #include "base_processor.hpp" 8 | 9 | class FilterProcessor : public BaseProcessor { 10 | public: 11 | using IIRFilter = juce::dsp::IIR::Filter; 12 | using IIRCoefficients = juce::dsp::IIR::Coefficients; 13 | using MultiChannelFilter = 14 | juce::dsp::ProcessorDuplicator; 15 | using IIRCoefficientsPtr = juce::ReferenceCountedObjectPtr; 16 | 17 | FilterProcessor(int, types::APVTS &); 18 | 19 | void prepareToPlay(double, int) override; 20 | void processBlock(juce::AudioSampleBuffer &, juce::MidiBuffer &) override; 21 | 22 | std::function getFrequencyResponse() override; 23 | 24 | private: 25 | void updateFilter(); 26 | IIRCoefficientsPtr getIIRCoefficients(); 27 | 28 | int id; 29 | FilterParameters params; 30 | std::unique_ptr filter; 31 | }; 32 | -------------------------------------------------------------------------------- /src/shared/proccessors/gain.cpp: -------------------------------------------------------------------------------- 1 | #include "gain.hpp" 2 | 3 | #include "../constants.hpp" 4 | 5 | GainProcessor::GainProcessor(types::APVTS &apvts) : params(apvts) {} 6 | 7 | void GainProcessor::prepareToPlay(double sampleRate, int) { 8 | previousWetValueDb = params.getWetDbValue(); 9 | 10 | rmsValueLeft.reset(sampleRate, 0.5); 11 | rmsValueRight.reset(sampleRate, 0.5); 12 | 13 | rmsValueLeft.setCurrentAndTargetValue(constants::MINUS_INFINITY_DB); 14 | rmsValueLeft.setCurrentAndTargetValue(constants::MINUS_INFINITY_DB); 15 | } 16 | 17 | void GainProcessor::processBlock(juce::AudioSampleBuffer &audioBuffer, 18 | juce::MidiBuffer &) { 19 | applyGain(audioBuffer); 20 | updateRmsValue(audioBuffer, 0); 21 | updateRmsValue(audioBuffer, 1); 22 | } 23 | 24 | float GainProcessor::getRmsValue(int channel) { 25 | jassert(channel == 0 || channel == 1); 26 | 27 | if (channel == 0) { 28 | return rmsValueLeft.getCurrentValue(); 29 | } 30 | 31 | return rmsValueRight.getCurrentValue(); 32 | } 33 | 34 | void GainProcessor::updateRmsValue(juce::AudioSampleBuffer &audioBuffer, 35 | int channel) { 36 | jassert(channel == 0 || channel == 1); 37 | 38 | auto &rmsValue = channel == 0 ? rmsValueLeft : rmsValueRight; 39 | 40 | rmsValue.skip(audioBuffer.getNumSamples()); 41 | 42 | const auto value = juce::Decibels::gainToDecibels( 43 | audioBuffer.getRMSLevel(channel, 0, audioBuffer.getNumSamples())); 44 | if (value < rmsValue.getCurrentValue()) { 45 | rmsValue.setTargetValue(value); 46 | } else { 47 | rmsValue.setCurrentAndTargetValue(value); 48 | } 49 | } 50 | 51 | void GainProcessor::applyGain(juce::AudioSampleBuffer &audioBuffer) { 52 | float currentWetValueDb = params.getWetDbValue(); 53 | 54 | if (currentWetValueDb == previousWetValueDb) { 55 | audioBuffer.applyGain(currentWetValueDb); 56 | } else { 57 | audioBuffer.applyGainRamp(0, audioBuffer.getNumSamples(), 58 | previousWetValueDb, currentWetValueDb); 59 | previousWetValueDb = currentWetValueDb; 60 | } 61 | } -------------------------------------------------------------------------------- /src/shared/proccessors/gain.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../parameters/gain.hpp" 4 | #include "../types.hpp" 5 | #include "base_processor.hpp" 6 | 7 | class GainProcessor : public BaseProcessor { 8 | public: 9 | GainProcessor(types::APVTS &); 10 | 11 | void prepareToPlay(double, int) override; 12 | void processBlock(juce::AudioSampleBuffer &, juce::MidiBuffer &) override; 13 | 14 | float getRmsValue(int); 15 | 16 | private: 17 | void updateRmsValue(juce::AudioSampleBuffer &, int); 18 | void applyGain(juce::AudioSampleBuffer &); 19 | 20 | GainParameters params; 21 | float previousWetValueDb; 22 | 23 | juce::LinearSmoothedValue rmsValueLeft, rmsValueRight; 24 | }; 25 | -------------------------------------------------------------------------------- /src/shared/proccessors/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | #include "../constants.hpp" 4 | 5 | // UiState 6 | 7 | PluginProcessor::UiState::UiState() : selectedFilterID(-1) { 8 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 9 | ++id) { 10 | usedFilterIDs.insert(std::pair(id, false)); 11 | } 12 | } 13 | 14 | bool PluginProcessor::UiState::hasSelectedFilter() { 15 | return selectedFilterID != -1; 16 | } 17 | 18 | bool PluginProcessor::UiState::hasUsedFilters() { 19 | for (const auto &[filterID, isUsed] : usedFilterIDs) { 20 | if (isUsed) { 21 | return true; 22 | } 23 | } 24 | 25 | return false; 26 | } 27 | 28 | PluginProcessor::UiState::UiState(int sfi, juce::Array &filterIDs) 29 | : selectedFilterID(sfi) { 30 | for (int id = constants::FILTER_MIN_ID; id <= constants::FILTER_MAX_ID; 31 | ++id) { 32 | usedFilterIDs.insert(std::pair(id, false)); 33 | } 34 | for (const auto &filterID : filterIDs) { 35 | usedFilterIDs[filterID] = true; 36 | } 37 | } 38 | 39 | void PluginProcessor::UiState::selectPrevFilter() { 40 | jassert(hasSelectedFilter()); 41 | 42 | auto it = usedFilterIDs.find(selectedFilterID); 43 | if (it == usedFilterIDs.begin()) { 44 | return; 45 | } 46 | 47 | do { 48 | --it; 49 | if (it->second) { 50 | selectedFilterID = it->first; 51 | break; 52 | } 53 | } while (it != usedFilterIDs.begin()); 54 | } 55 | 56 | void PluginProcessor::UiState::selectNextFilter() { 57 | jassert(hasSelectedFilter()); 58 | 59 | for (auto it = std::next(usedFilterIDs.find(selectedFilterID)); 60 | it != usedFilterIDs.end(); ++it) { 61 | if (it->second) { 62 | selectedFilterID = it->first; 63 | break; 64 | } 65 | } 66 | } 67 | 68 | bool PluginProcessor::UiState::addFilter() { 69 | for (const auto &[id, isUsed] : usedFilterIDs) { 70 | if (!isUsed) { 71 | selectedFilterID = id; 72 | usedFilterIDs[id] = true; 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | void PluginProcessor::UiState::removeFilter(int filterID) { 81 | usedFilterIDs[filterID] = false; 82 | 83 | selectedFilterID = -1; 84 | for (const auto &[id, isUsed] : usedFilterIDs) { 85 | if (isUsed) { 86 | selectedFilterID = id; 87 | } 88 | } 89 | } 90 | 91 | juce::var PluginProcessor::UiState::toVar() const { 92 | juce::var filterIDs; 93 | for (const auto &[id, isUsed] : usedFilterIDs) { 94 | if (isUsed) { 95 | filterIDs.append(id); 96 | } 97 | } 98 | 99 | juce::var var; 100 | 101 | var.getDynamicObject()->setProperty("selectedFilterID", selectedFilterID); 102 | var.getDynamicObject()->setProperty("filterIDs", filterIDs); 103 | 104 | return var; 105 | } 106 | 107 | PluginProcessor::UiState 108 | PluginProcessor::UiState::fromVar(const juce::var &var) { 109 | if (var.isObject()) { 110 | int selectedFilterID = var.getProperty("selectedFilterID", -1); 111 | 112 | juce::Array filterIDs; 113 | auto varFilterIDs = var.getProperty("filterIDs", juce::var()); 114 | auto refFilterIDs = varFilterIDs.getArray(); 115 | for (const auto &refFilterID : *refFilterIDs) { 116 | filterIDs.add(refFilterID); 117 | } 118 | 119 | return UiState(selectedFilterID, filterIDs); 120 | } 121 | 122 | return UiState(); 123 | } 124 | 125 | // PluginProcessor 126 | 127 | PluginProcessor::PluginProcessor( 128 | std::function &callback) 129 | : createEditorCallback(callback), 130 | apvts(*this, nullptr, "ParametersState", createParameterLayout()) {} 131 | 132 | juce::AudioProcessorEditor *PluginProcessor::createEditor() { 133 | return createEditorCallback(this); 134 | } 135 | 136 | bool PluginProcessor::hasEditor() const { return true; } 137 | 138 | const juce::String PluginProcessor::getName() const { return JucePlugin_Name; } 139 | 140 | int PluginProcessor::getNumPrograms() { return 1; } 141 | 142 | void PluginProcessor::getStateInformation(juce::MemoryBlock &destData) { 143 | return; 144 | juce::ValueTree uiStateTree("UiState"); 145 | uiStateTree.setProperty("UiStateObject", uiState.toVar(), nullptr); 146 | 147 | juce::ValueTree pluginStateTree("PluginState"); 148 | pluginStateTree.appendChild(apvts.copyState(), nullptr); 149 | pluginStateTree.appendChild(uiStateTree, nullptr); 150 | 151 | std::unique_ptr xml(pluginStateTree.createXml()); 152 | copyXmlToBinary(*xml, destData); 153 | } 154 | 155 | void PluginProcessor::setStateInformation(const void *data, int sizeInBytes) { 156 | return; 157 | std::unique_ptr xmlState( 158 | getXmlFromBinary(data, sizeInBytes)); 159 | 160 | if (!xmlState.get() || !xmlState->hasTagName("PluginState")) { 161 | return; 162 | } 163 | 164 | juce::ValueTree pluginStateTree(juce::ValueTree::fromXml(*xmlState)); 165 | apvts.replaceState(pluginStateTree.getChildWithName(apvts.state.getType())); 166 | uiState = UiState::fromVar( 167 | pluginStateTree.getChildWithName("UiState").getProperty("UiStateObject")); 168 | } 169 | 170 | AnalyzerProcessor *PluginProcessor::getAnalyzerProcessor() { 171 | if (analyzerNode) { 172 | return dynamic_cast( 173 | analyzerNode.get()->getProcessor()); 174 | } 175 | 176 | return nullptr; 177 | } 178 | 179 | GainProcessor *PluginProcessor::getGainProcessor() { 180 | if (gainNode) { 181 | return dynamic_cast(gainNode.get()->getProcessor()); 182 | } 183 | return nullptr; 184 | } 185 | 186 | EqualizerProcessor *PluginProcessor::getEqualizerProcessor() { 187 | if (equalizerNode) { 188 | return dynamic_cast( 189 | equalizerNode.get()->getProcessor()); 190 | } 191 | 192 | return nullptr; 193 | } 194 | 195 | types::APVTS &PluginProcessor::getAPVTS() { return apvts; } 196 | PluginProcessor::UiState &PluginProcessor::getUiState() { return uiState; } 197 | 198 | void PluginProcessor::initializeEffectNodes() { 199 | analyzerNode = audioGraph->addNode(std::make_unique()); 200 | gainNode = audioGraph->addNode(std::make_unique(apvts)); 201 | equalizerNode = 202 | audioGraph->addNode(std::make_unique(apvts)); 203 | } 204 | 205 | void PluginProcessor::connectAudioNodes() { 206 | for (int channel = 0; channel < 2; ++channel) { 207 | audioGraph->addConnection( 208 | {{audioInputNode->nodeID, channel}, {analyzerNode->nodeID, channel}}); 209 | audioGraph->addConnection( 210 | {{analyzerNode->nodeID, channel}, {equalizerNode->nodeID, channel}}); 211 | audioGraph->addConnection( 212 | {{equalizerNode->nodeID, channel}, {gainNode->nodeID, channel}}); 213 | audioGraph->addConnection( 214 | {{gainNode->nodeID, channel}, {audioOutputNode->nodeID, channel}}); 215 | } 216 | } 217 | 218 | types::APVTS::ParameterLayout PluginProcessor::createParameterLayout() { 219 | types::APVTS::ParameterLayout layout; 220 | 221 | GainParameters::addToLayout(layout); 222 | EqualizerParameters::addToLayout(layout); 223 | 224 | return layout; 225 | } 226 | -------------------------------------------------------------------------------- /src/shared/proccessors/plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../types.hpp" 7 | #include "analyzer.hpp" 8 | #include "cascade_processor.hpp" 9 | #include "equalizer.hpp" 10 | #include "gain.hpp" 11 | 12 | class PluginProcessor : public CascadeProcessor { 13 | public: 14 | struct UiState { 15 | int selectedFilterID; 16 | std::map usedFilterIDs; 17 | 18 | UiState(); 19 | UiState(int, juce::Array &); 20 | 21 | bool hasSelectedFilter(); 22 | bool hasUsedFilters(); 23 | 24 | void selectPrevFilter(); 25 | void selectNextFilter(); 26 | 27 | bool addFilter(); 28 | void removeFilter(int); 29 | 30 | juce::var toVar() const; 31 | static UiState fromVar(const juce::var &); 32 | }; 33 | 34 | PluginProcessor( 35 | std::function &); 36 | 37 | juce::AudioProcessorEditor *createEditor() override; 38 | bool hasEditor() const override; 39 | 40 | const juce::String getName() const override; 41 | int getNumPrograms() override; 42 | 43 | void getStateInformation(juce::MemoryBlock &) override; 44 | void setStateInformation(const void *, int) override; 45 | 46 | AnalyzerProcessor *getAnalyzerProcessor(); 47 | GainProcessor *getGainProcessor(); 48 | EqualizerProcessor *getEqualizerProcessor(); 49 | 50 | types::APVTS &getAPVTS(); 51 | UiState &getUiState(); 52 | 53 | protected: 54 | void initializeEffectNodes() override; 55 | void connectAudioNodes() override; 56 | 57 | private: 58 | types::APVTS::ParameterLayout createParameterLayout(); 59 | 60 | std::function 61 | createEditorCallback; 62 | 63 | types::APVTS apvts; 64 | UiState uiState; 65 | 66 | Node::Ptr analyzerNode; 67 | Node::Ptr gainNode; 68 | Node::Ptr equalizerNode; 69 | }; 70 | -------------------------------------------------------------------------------- /src/shared/proccessors/processors.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "gain.hpp" 4 | #include "plugin.hpp" -------------------------------------------------------------------------------- /src/shared/shared.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "constants.hpp" 4 | #include "dsp/dsp.hpp" 5 | #include "math/math.hpp" 6 | #include "proccessors/processors.hpp" 7 | #include "types.hpp" 8 | #include "ui/ui.hpp" -------------------------------------------------------------------------------- /src/shared/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace types { 6 | 7 | using APVTS = juce::AudioProcessorValueTreeState; 8 | 9 | enum class FilterType { LowPass, Peak, HighPass }; 10 | 11 | } // namespace types 12 | -------------------------------------------------------------------------------- /src/shared/ui/colours.cpp: -------------------------------------------------------------------------------- 1 | #include "colours.hpp" 2 | 3 | #include "../constants.hpp" 4 | 5 | namespace colours { 6 | 7 | juce::Colour getFilterColour(int filterID) { 8 | jassert(filterID >= 1 && filterID <= 12); 9 | 10 | switch (filterID) { 11 | case 1: 12 | return juce::Colour(0xff279d87); 13 | case 2: 14 | return juce::Colour(0xff237fb3); 15 | case 3: 16 | return juce::Colour(0xfffab935); 17 | case 4: 18 | return juce::Colour(0xffa1976a); 19 | case 5: 20 | return juce::Colour(0xff7e3e6f); 21 | case 6: 22 | return juce::Colour(0xffc79d52); 23 | case 7: 24 | return juce::Colour(0xff7e618a); 25 | case 8: 26 | return juce::Colour(0xffee72f3); 27 | case 9: 28 | return juce::Colour(0xff89dfe8); 29 | case 10: 30 | return juce::Colour(0xff5423ac); 31 | case 11: 32 | return juce::Colour(0xff97d385); 33 | case 12: 34 | return juce::Colour(0xff687cdc); 35 | } 36 | } 37 | 38 | } // namespace colours 39 | -------------------------------------------------------------------------------- /src/shared/ui/colours.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace colours { 6 | 7 | const juce::Colour primaryColour(0xff87bfff); 8 | const juce::Colour responseCurveColour(0xffff5050); 9 | 10 | juce::Colour getFilterColour(int); 11 | 12 | } // namespace colours 13 | -------------------------------------------------------------------------------- /src/shared/ui/icon_button.cpp: -------------------------------------------------------------------------------- 1 | #include "icon_button.hpp" 2 | 3 | IconButton::LookAndFeel::LookAndFeel() 4 | : juce::LookAndFeel_V4(), 5 | backgroundColour(juce::Colours::transparentWhite) {} 6 | 7 | void IconButton::LookAndFeel::drawButtonBackground(juce::Graphics &g, 8 | juce::Button &button, 9 | const juce::Colour &, bool, 10 | bool) { 11 | g.setColour(backgroundColour); 12 | g.fillRect(0, 0, button.getWidth(), button.getHeight()); 13 | } 14 | 15 | void IconButton::LookAndFeel::setBackgroundColour(const juce::Colour &colour) { 16 | backgroundColour = colour; 17 | } 18 | 19 | IconButton::AnimatorProperties::AnimatorProperties(IconButton *but) 20 | : button(but) { 21 | juce::Colour toBackgroundColour(toBackgoundColorInt); 22 | r = toBackgroundColour.getRed(); 23 | g = toBackgroundColour.getGreen(); 24 | b = toBackgroundColour.getBlue(); 25 | } 26 | 27 | void IconButton::AnimatorProperties::addFromEffect(friz::Animator &anim) { 28 | if (anim.getAnimation(fromAnimationId) != nullptr) { 29 | return; 30 | } 31 | 32 | auto fromEffect = friz::makeAnimation( 33 | fromAnimationId, {fromAlpha}, {toAlpha}, animationDuration, 34 | parametricCurveType); 35 | fromEffect->updateFn = [this](int, const auto &vals) { 36 | button->lookAndFeel.setBackgroundColour(juce::Colour(r, g, b, vals[0])); 37 | button->repaint(); 38 | }; 39 | 40 | anim.addAnimation(std::move(fromEffect)); 41 | } 42 | 43 | void IconButton::AnimatorProperties::addToEffect(friz::Animator &anim) { 44 | if (anim.getAnimation(toAnimationId) != nullptr) { 45 | return; 46 | } 47 | 48 | auto toEffect = friz::makeAnimation( 49 | fromAnimationId, {toAlpha}, {fromAlpha}, animationDuration, 50 | parametricCurveType); 51 | toEffect->updateFn = [this](int, const auto &vals) { 52 | button->lookAndFeel.setBackgroundColour(juce::Colour(r, g, b, vals[0])); 53 | button->repaint(); 54 | }; 55 | 56 | anim.addAnimation(std::move(toEffect)); 57 | } 58 | 59 | IconButton::IconButton(const juce::String &name, const char *svgIcon) 60 | : juce::DrawableButton( 61 | name, juce::DrawableButton::ButtonStyle::ImageOnButtonBackground), 62 | animatorProps(this), animator{ 63 | std::make_unique( 64 | this)} { 65 | auto drawable = 66 | juce::Drawable::createFromSVG(*juce::XmlDocument::parse(svgIcon)); 67 | setImages(drawable.get()); 68 | 69 | setLookAndFeel(&lookAndFeel); 70 | setMouseCursor(juce::MouseCursor::PointingHandCursor); 71 | } 72 | 73 | IconButton::~IconButton() { setLookAndFeel(nullptr); } 74 | 75 | void IconButton::mouseEnter(const juce::MouseEvent &) { 76 | animatorProps.addFromEffect(animator); 77 | } 78 | 79 | void IconButton::mouseExit(const juce::MouseEvent &) { 80 | animatorProps.addToEffect(animator); 81 | } -------------------------------------------------------------------------------- /src/shared/ui/icon_button.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "layout_component.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class IconButton : public juce::DrawableButton { 9 | public: 10 | class LookAndFeel : public juce::LookAndFeel_V4 { 11 | public: 12 | LookAndFeel(); 13 | 14 | void drawButtonBackground(juce::Graphics &, juce::Button &, 15 | const juce::Colour &, bool, bool) override; 16 | 17 | void setBackgroundColour(const juce::Colour &); 18 | 19 | private: 20 | juce::Colour backgroundColour; 21 | }; 22 | 23 | class AnimatorProperties { 24 | public: 25 | const juce::uint32 toBackgoundColorInt = 0xffe3e4e6; 26 | static constexpr float animationDuration = 200.0f; 27 | static constexpr float fromAlpha = 0.0f; 28 | static constexpr float toAlpha = 0.3f; 29 | const static friz::Parametric::CurveType parametricCurveType = 30 | friz::Parametric::kEaseInCubic; 31 | 32 | public: 33 | AnimatorProperties(IconButton *); 34 | 35 | void addFromEffect(friz::Animator &); 36 | void addToEffect(friz::Animator &); 37 | 38 | private: 39 | IconButton *button; 40 | 41 | juce::uint8 r, g, b; 42 | 43 | int fromAnimationId = 0; 44 | int toAnimationId = 1; 45 | }; 46 | 47 | public: 48 | IconButton(const juce::String &, const char *); 49 | ~IconButton() override; 50 | 51 | void mouseEnter(const juce::MouseEvent &) override; 52 | void mouseExit(const juce::MouseEvent &) override; 53 | 54 | private: 55 | LookAndFeel lookAndFeel; 56 | 57 | AnimatorProperties animatorProps; 58 | friz::Animator animator; 59 | }; -------------------------------------------------------------------------------- /src/shared/ui/label_rotary_slider.cpp: -------------------------------------------------------------------------------- 1 | #include "label_rotary_slider.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include "colours.hpp" 7 | #include "paths.hpp" 8 | 9 | LabelRotarySlider::LabelRotarySlider(juce::String l) : label(l) { 10 | setLookAndFeel(&lookAndFeel); 11 | setSliderStyle(juce::Slider::SliderStyle::RotaryVerticalDrag); 12 | } 13 | 14 | LabelRotarySlider::LabelRotarySlider() : LabelRotarySlider("") {} 15 | 16 | LabelRotarySlider::~LabelRotarySlider() { setLookAndFeel(nullptr); } 17 | 18 | void LabelRotarySlider::LookAndFeel::drawRotarySlider( 19 | juce::Graphics &g, int x, int y, int width, int height, float sliderPos, 20 | float rotaryStartAngle, float rotaryEndAngle, juce::Slider &slider) { 21 | const float pi = static_cast(std::numbers::pi); 22 | const float radius = 23 | static_cast(juce::jmin(width, height)) * 0.5f * 0.75f; 24 | const float centreX = static_cast(x + width) * 0.5f; 25 | const float centreY = static_cast(y + height) * 0.5f; 26 | const float rx = centreX - radius; 27 | const float ry = centreY - radius; 28 | const float rw = radius * 2.0f; 29 | const float angle = 30 | rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); 31 | const auto bounds = slider.getLocalBounds(); 32 | 33 | const auto makeTransform = [&](float scaleFactor) { 34 | return juce::AffineTransform::scale(scaleFactor) 35 | .rotated(pi * 0.5f) 36 | .translated(0.0f, -radius) 37 | .rotated(angle) 38 | .translated(centreX, centreY); 39 | }; 40 | 41 | juce::Path filledArc; 42 | filledArc.addPieSegment(rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, 43 | 0.75f); 44 | 45 | juce::Path backTriangle(paths::createRoundedTriangle(0.0f)); 46 | backTriangle.applyTransform(makeTransform(12.0f)); 47 | 48 | juce::Path triangle(paths::createRoundedTriangle(0.0f)); 49 | triangle.applyTransform(makeTransform(8.0f)); 50 | 51 | g.setColour(juce::Colour(0xff233248).darker(0.3f)); 52 | g.fillPath(filledArc); 53 | 54 | g.setFont(25.0f); 55 | g.drawText( 56 | dynamic_cast(slider).getLabel(), 57 | bounds.translated(-bounds.getCentreX(), -bounds.getCentreY()) 58 | .translated(static_cast(centreX), static_cast(centreY)), 59 | juce::Justification::centred); 60 | 61 | g.setColour(juce::Colours::white); 62 | g.fillPath(backTriangle); 63 | 64 | g.setColour(juce::Colour(0xff2683ee).brighter(0.333f)); 65 | g.fillPath(triangle); 66 | } 67 | 68 | juce::Label * 69 | LabelRotarySlider::LookAndFeel::createSliderTextBox(juce::Slider &slider) { 70 | juce::Label *textBox = juce::LookAndFeel_V4::createSliderTextBox(slider); 71 | textBox->setColour(juce::Label::backgroundColourId, 72 | juce::Colours::transparentWhite); 73 | textBox->setColour(juce::Label::backgroundWhenEditingColourId, 74 | juce::Colours::transparentWhite); 75 | textBox->setColour(juce::Label::textWhenEditingColourId, 76 | juce::Colours::black); 77 | textBox->setColour(juce::Label::outlineColourId, 78 | juce::Colours::transparentWhite); 79 | textBox->setColour(juce::Label::outlineWhenEditingColourId, 80 | juce::Colours::transparentWhite); 81 | textBox->setColour(juce::Label::textColourId, juce::Colours::black); 82 | 83 | return textBox; 84 | } 85 | 86 | juce::String &LabelRotarySlider::getLabel() noexcept { return label; } -------------------------------------------------------------------------------- /src/shared/ui/label_rotary_slider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class LabelRotarySlider : public juce::Slider { 6 | public: 7 | class LookAndFeel : public juce::LookAndFeel_V4 { 8 | public: 9 | void drawRotarySlider(juce::Graphics &, int, int, int, int, float, float, 10 | float, juce::Slider &) override; 11 | juce::Label *createSliderTextBox(juce::Slider &) override; 12 | }; 13 | 14 | LabelRotarySlider(juce::String); 15 | LabelRotarySlider(); 16 | 17 | ~LabelRotarySlider(); 18 | 19 | juce::String &getLabel() noexcept; 20 | 21 | private: 22 | LookAndFeel lookAndFeel; 23 | juce::String label; 24 | 25 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LabelRotarySlider) 26 | }; -------------------------------------------------------------------------------- /src/shared/ui/layout_component.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class LayoutComponent : public juce::Component { 7 | public: 8 | using Track = juce::Grid::TrackInfo; 9 | using Fr = juce::Grid::Fr; 10 | using Px = juce::Grid::Px; 11 | 12 | public: 13 | LayoutComponent() {} 14 | virtual ~LayoutComponent() {} 15 | 16 | virtual void paint(juce::Graphics &) {} 17 | virtual void resized() { layout.performLayout(getLocalBounds()); } 18 | 19 | protected: 20 | juce::Grid layout; 21 | 22 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LayoutComponent) 23 | }; 24 | -------------------------------------------------------------------------------- /src/shared/ui/look_and_feel.cpp: -------------------------------------------------------------------------------- 1 | #include "look_and_feel.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "colours.hpp" 8 | 9 | PluginLookAndFeel::PluginLookAndFeel() { 10 | setDefaultSansSerifTypeface(getCustomFont()); 11 | } 12 | 13 | juce::Typeface::Ptr PluginLookAndFeel::getTypefaceForFont(const juce::Font &) { 14 | return getCustomFont(); 15 | } 16 | 17 | const juce::Typeface::Ptr PluginLookAndFeel::getCustomFont() { 18 | return juce::Typeface::createSystemTypefaceFor( 19 | BinaryData::SairaMedium_ttf, BinaryData::SairaMedium_ttfSize); 20 | } -------------------------------------------------------------------------------- /src/shared/ui/look_and_feel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class PluginLookAndFeel : public juce::LookAndFeel_V4 { 6 | public: 7 | PluginLookAndFeel(); 8 | 9 | private: 10 | juce::Typeface::Ptr getTypefaceForFont(const juce::Font &) override; 11 | const juce::Typeface::Ptr getCustomFont(); 12 | }; -------------------------------------------------------------------------------- /src/shared/ui/paths.cpp: -------------------------------------------------------------------------------- 1 | #include "paths.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace paths { 7 | 8 | juce::Path createRoundedTriangle(float angle) { 9 | const float pi = static_cast(std::numbers::pi); 10 | const float firstAngle = 2.0f * pi / 3.0f; 11 | const float secondAngle = 2.0f * firstAngle; 12 | 13 | juce::Path triangle; 14 | triangle.startNewSubPath(1.0f, 0.0f); 15 | triangle.lineTo(std::cos(firstAngle), std::sin(firstAngle)); 16 | triangle.lineTo(std::cos(secondAngle), std::sin(secondAngle)); 17 | triangle.closeSubPath(); 18 | 19 | triangle.applyTransform(juce::AffineTransform::rotation(angle * pi / 180.0f)); 20 | 21 | return triangle.createPathWithRoundedCorners(0.333f); 22 | } 23 | 24 | juce::Path createCircle() { 25 | juce::Path circle; 26 | 27 | circle.addEllipse(0.0f, 0.0f, 1.0f, 1.0f); 28 | 29 | return circle; 30 | } 31 | 32 | } // namespace paths 33 | -------------------------------------------------------------------------------- /src/shared/ui/paths.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace paths { 6 | 7 | juce::Path createRoundedTriangle(float); 8 | juce::Path createCircle(); 9 | 10 | } // namespace paths -------------------------------------------------------------------------------- /src/shared/ui/ui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "colours.hpp" 4 | #include "icon_button.hpp" 5 | #include "label_rotary_slider.hpp" 6 | #include "layout_component.hpp" 7 | #include "look_and_feel.hpp" 8 | #include "paths.hpp" -------------------------------------------------------------------------------- /src/widgets/layout/header/header.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "header.hpp" 4 | 5 | Header::Header() : LayoutComponent() { 6 | addAndMakeVisible(logo); 7 | // addAndMakeVisible(rightButtonsStack); 8 | 9 | resized(); 10 | } 11 | 12 | void Header::paint(juce::Graphics &g) { 13 | // float width = float(getWidth()); 14 | // float centerX = width / 2; 15 | 16 | // float quarterLogoWidth = logoWidth / 4; 17 | 18 | // float height = float(getHeight()) - 1; 19 | // float bodyHeight = height * 0.75f; 20 | 21 | // juce::Path p; 22 | 23 | // // Painting separator 24 | // p.startNewSubPath(0.0f, bodyHeight); 25 | // p.lineTo(centerX - logoWidth, bodyHeight); 26 | // p.lineTo(centerX - quarterLogoWidth, height); 27 | // p.lineTo(centerX + quarterLogoWidth, height); 28 | // p.lineTo(centerX + logoWidth, bodyHeight); 29 | // p.lineTo(width, bodyHeight); 30 | 31 | // g.setColour(juce::Colour(0x60344661)); 32 | // g.strokePath(p, juce::PathStrokeType(1)); 33 | 34 | // // Painting background 35 | // p.lineTo(width, 0.0f); 36 | // p.lineTo(0.0f, 0.0f); 37 | // p.lineTo(0.0f, bodyHeight); 38 | 39 | // juce::FillType fill; 40 | // fill.setColour(juce::Colours::white); 41 | 42 | // g.setFillType(juce::FillType(juce::Colours::white)); 43 | // g.fillPath(p); 44 | } 45 | 46 | void Header::resized() { 47 | float paddingSize = 48 | std::max((float(getWidth()) - containerWidth) / 2, 0); 49 | 50 | auto contentMargin = 51 | juce::GridItem::Margin(0.0f, 0.0f, float(getHeight()) * 0.25f, 0.0f); 52 | 53 | layout.templateRows = {Track(Fr(1))}; 54 | layout.templateColumns = {Track(Px(paddingSize)), Track(Px(125)), 55 | Track(Fr(1)), Track(Px(logoWidth)), 56 | Track(Fr(1)), Track(Px(125)), 57 | Track(Px(paddingSize))}; 58 | layout.alignItems = juce::Grid::AlignItems::center; 59 | layout.justifyContent = juce::Grid::JustifyContent::center; 60 | layout.items = {juce::GridItem(), 61 | juce::GridItem(), 62 | juce::GridItem(), 63 | juce::GridItem(logo), 64 | juce::GridItem(), 65 | juce::GridItem(rightButtonsStack).withMargin(contentMargin), 66 | juce::GridItem()}; 67 | 68 | LayoutComponent::resized(); 69 | } -------------------------------------------------------------------------------- /src/widgets/layout/header/header.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | #include "ui/ui.hpp" 6 | 7 | class Header : public LayoutComponent { 8 | public: 9 | static inline const float containerWidth = 1200.0f; 10 | static inline const float logoWidth = 140.0f; 11 | 12 | public: 13 | Header(); 14 | 15 | void paint(juce::Graphics &) override; 16 | void resized() override; 17 | 18 | private: 19 | Logo logo; 20 | RightButtonsStack rightButtonsStack; 21 | }; -------------------------------------------------------------------------------- /src/widgets/layout/header/ui/logo.cpp: -------------------------------------------------------------------------------- 1 | #include "logo.hpp" 2 | 3 | #include 4 | 5 | Logo::Logo() 6 | : drawable(juce::Drawable::createFromSVG( 7 | *juce::XmlDocument::parse(BinaryData::logo_svg))) {} 8 | 9 | void Logo::paint(juce::Graphics &g) { 10 | drawable->setTransformToFit( 11 | juce::Rectangle(0, 0, getWidth(), getHeight()).toFloat(), 12 | juce::RectanglePlacement::centred); 13 | 14 | drawable->draw(g, 1.0f); 15 | } 16 | -------------------------------------------------------------------------------- /src/widgets/layout/header/ui/logo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | class Logo : public LayoutComponent { 6 | public: 7 | Logo(); 8 | 9 | void paint(juce::Graphics &g) override; 10 | 11 | private: 12 | std::unique_ptr drawable; 13 | }; -------------------------------------------------------------------------------- /src/widgets/layout/header/ui/right_buttons_stack.cpp: -------------------------------------------------------------------------------- 1 | #include "right_buttons_stack.hpp" 2 | 3 | #include 4 | 5 | RightButtonsStack::FullScreenButton::FullScreenButton() 6 | : IconButton("fullscreen", BinaryData::fullscreen_svg) {} 7 | 8 | RightButtonsStack::SettingsButton::SettingsButton() 9 | : IconButton("settings", BinaryData::settings_svg) {} 10 | 11 | RightButtonsStack::RightButtonsStack() : LayoutComponent() { 12 | addAndMakeVisible(fullScreenButton); 13 | addAndMakeVisible(settingsButton); 14 | 15 | resized(); 16 | } 17 | 18 | void RightButtonsStack::resized() { 19 | float buttomMarginPx = float(getHeight() - buttonSize) / 2; 20 | const auto buttonMargin = 21 | juce::GridItem::Margin(buttomMarginPx, 0, buttomMarginPx, 0); 22 | 23 | layout.templateRows = {Track(Fr(1))}; 24 | layout.templateColumns = {Track(Px(buttonSize)), Track(Px(buttonSize)), 25 | Track(Px(buttonSize))}; 26 | layout.alignItems = juce::Grid::AlignItems::center; 27 | layout.justifyContent = juce::Grid::JustifyContent::end; 28 | layout.columnGap = Px(30); 29 | layout.items = {juce::GridItem(settingsButton).withMargin(buttonMargin), 30 | juce::GridItem(fullScreenButton).withMargin(buttonMargin)}; 31 | 32 | LayoutComponent::resized(); 33 | } 34 | -------------------------------------------------------------------------------- /src/widgets/layout/header/ui/right_buttons_stack.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "shared.hpp" 4 | 5 | class RightButtonsStack : public LayoutComponent { 6 | public: 7 | enum { buttonSize = 20 }; 8 | 9 | public: 10 | class FullScreenButton : public IconButton { 11 | public: 12 | FullScreenButton(); 13 | }; 14 | 15 | class SettingsButton : public IconButton { 16 | public: 17 | SettingsButton(); 18 | }; 19 | 20 | public: 21 | RightButtonsStack(); 22 | 23 | void resized() override; 24 | 25 | private: 26 | FullScreenButton fullScreenButton; 27 | SettingsButton settingsButton; 28 | }; -------------------------------------------------------------------------------- /src/widgets/layout/header/ui/ui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "logo.hpp" 4 | #include "right_buttons_stack.hpp" -------------------------------------------------------------------------------- /src/widgets/layout/layout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "header/header.hpp" -------------------------------------------------------------------------------- /src/widgets/widgets.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "layout/layout.hpp" --------------------------------------------------------------------------------