├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── images ├── Chords.png ├── Comb.png ├── ExpSeg.png ├── Expander.png ├── Filter.png ├── FormulaOne.png ├── GateSequencer.png ├── LineSeg.png ├── Osc.png ├── PDFilterSweep.png ├── PulseWave.png ├── RndH.png ├── Sequencer.png ├── StereoDelay.png ├── Wavefolder.png ├── Waveshaping2.png └── waveshaping1.png ├── plugin.json ├── presets └── FormulaOne │ ├── Chords.vcvm │ ├── Comb.vcvm │ ├── GateSequencer.vcvm │ ├── LineSeg.vcvm │ ├── PDFilterSweep.vcvm │ ├── PhaseOscillator.vcvm │ ├── RndH.vcvm │ ├── Sequencer.vcvm │ ├── SimpleOscillator.vcvm │ ├── StereoDelay.vcvm │ └── TRCFilter.vcvm ├── res ├── FormulaOne.svg ├── FormulaOneEdit.svg ├── FreeMonoBold.ttf ├── SmallPort.svg ├── TrimpotWhite.svg └── TrimpotWhite9mm.svg └── src ├── FormulaOne.cpp ├── FormulaOne.hpp ├── FormulaOneEdit.cpp ├── exprtk.hpp ├── functions.hpp ├── plugin.cpp ├── plugin.hpp ├── rnd.cpp ├── rnd.h ├── textfield.cpp └── textfield.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /dist 3 | /plugin.so 4 | /plugin.dylib 5 | /plugin.dll 6 | res/scales.json 7 | res/addsynth.json 8 | res/genrscale.js 9 | .DS_Store 10 | .idea 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If RACK_DIR is not defined when calling the Makefile, default to two directories above 2 | RACK_DIR ?= ../.. 3 | 4 | # FLAGS will be passed to both the C and C++ compiler 5 | FLAGS += 6 | include $(RACK_DIR)/arch.mk 7 | ifdef ARCH_WIN 8 | FLAGS += -Wa,-mbig-obj 9 | endif 10 | CFLAGS += 11 | CXXFLAGS += 12 | 13 | # Careful about linking to shared libraries, since you can't assume much about the user's environment and library search path. 14 | # Static libraries are fine, but they should be added to this plugin's build system. 15 | LDFLAGS += 16 | 17 | # Add .cpp files to the build 18 | SOURCES += $(wildcard src/*.cpp) 19 | 20 | # Add files to the ZIP package when running `make dist` 21 | # The compiled plugin and "plugin.json" are automatically added. 22 | DISTRIBUTABLES += res 23 | DISTRIBUTABLES += $(wildcard LICENSE*) 24 | DISTRIBUTABLES += $(wildcard presets) 25 | 26 | # Include the Rack plugin Makefile framework 27 | include $(RACK_DIR)/plugin.mk 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Formula One 3 | 4 | A script/formula evaluation module for VCVRack. This module can do almost anything, and it is quite fast. 5 | 6 | ![](images/FormulaOne.png?raw=true) 7 | 8 | It is based on the [exprtk expression library](http://www.partow.net/programming/exprtk/index.html) 9 | which has very good [benchmarks](https://github.com/ArashPartow/math-parser-benchmark-project#the-rounds). 10 | 11 | It is not intended for replacing native modules because they still will be faster i.e. consume less CPU 12 | and provide more usability. It is for experimenting,learning and making special things. 13 | 14 | ## Examples and Usecases 15 | 16 | The examples are available as factory presets. 17 | 18 | 19 | 20 | 21 | - [Simple polyphonic oscillator](#simple-polyphonic-oscillator) 22 | - [Step by Step](#step-by-step) 23 | - [External Phase Controlled Oscillator](#external-phase-controlled-oscillator) 24 | - [Wave Folder](#wave-folder) 25 | - [Simple Polyphonic Filter](#simple-polyphonic-filter) 26 | - [Step by Step](#step-by-step-1) 27 | - [Polyphonic Comb Filter (Chorus,Flanger or whatever)](#polyphonic-comb-filter-chorusflanger-or-whatever) 28 | - [Step by Step](#step-by-step-2) 29 | - [Stereo Delay](#stereo-delay) 30 | - [Polyphonic Random and Hold](#polyphonic-random-and-hold) 31 | - [Simple Sequencer](#simple-sequencer) 32 | - [Chord Sequencer](#chord-sequencer) 33 | - [Gate Sequencer](#gate-sequencer) 34 | - [A Line Segment Envelope Generator](#a-line-segment-envelope-generator) 35 | - [Waveshaping](#waveshaping) 36 | - [Wavshaping with exp segemnts](#wavshaping-with-exp-segemnts) 37 | - [CZ-Series PD Filter sweep](#cz-series-pd-filter-sweep) 38 | 39 | 40 | 41 | ### Simple polyphonic oscillator 42 | 43 | ![](images/Osc.png?raw=true) 44 | 45 | ``` 46 | var freq := 261.626 * pow(2,w); 47 | var p:= bufr(chn); 48 | var o:=a*4*( 49 | sin(2*pi*p)+ 50 | sin(6*pi*p)/3+ 51 | sin(10*pi*p)/5+ 52 | sin(14*pi*p)/7 53 | ); 54 | var phs:= p+stim*freq; 55 | phs-=trunc(phs); 56 | bufw(chn,phs); 57 | var out:=o; 58 | ``` 59 | #### Step by Step 60 | `var freq := 261.626 * pow(2,w);` Declare a variable freq and assign 61 | the computed frequency from the input `w` (V/Oct). 62 | 63 | `var p:= bufr(chn);` Read the current phase for `chn` from the buffer 64 | into the variable p. The script is evaluated for every 65 | channel which is detected in the `w` input and provides the 66 | value of the input `w` for the current channel via the global variable `w`. 67 | The current channel number is set in the global variable `chn`. 68 | 69 | `var o:=a*4*(sin(2*pi*p)+sin(6*pi*p)/3+sin(10*pi*p)/5+sin(14*pi*p)/7);` 70 | compute the output sample from the current phase. the global variable `a` holds the current 71 | value of the knob `a`; 72 | 73 | `var phs:= p+stim*freq; phs-=trunc(phs);` compute the next phase using the global variable 74 | `stim` which holds the current sample time (`1/sampleRate`). 75 | 76 | `bufw(chn,phs);` write the new phase into the buffer. 77 | 78 | `var out:=o;` the last expression value in the script is returned to the module and written 79 | into the output `out`. 80 | 81 | ### External Phase Controlled Oscillator 82 | ![](images/PulseWave.png?raw=true) 83 | 84 | ```Tcl 85 | a*5*( 86 | sin(2*pi*t)+ 87 | sin(6*pi*t)/3+ 88 | sin(10*pi*t)/5+ 89 | sin(14*pi*t)/7 90 | ) 91 | ``` 92 | 93 | This example does the same as the one above but uses the special input `t` as phase. 94 | The values of `t` are normalized from -5V/5V to 0V/1V. 95 | 96 | ### Wave Folder 97 | ![](images/Wavefolder.png?raw=true) 98 | 99 | `sin((c*5)*t*2*pi+b*5)*a*5` 100 | 101 | The same principle can be used to make a wave folder. 102 | The knob `c` controls the depth and the knob `b` controls the offset. 103 | 104 | ### Simple Polyphonic Filter 105 | 106 | This example shows how to implement a simple LP filter. The algorithm is directly taken from the VCV Rack 107 | [source](https://github.com/VCVRack/Rack/blob/v2/include/dsp/filter.hpp) 108 | 109 | ![](images/Filter.png?raw=true) 110 | 111 | ```Tcl 112 | var f := clamp(2^(c*8),0.001,0.5); 113 | var fc := 2/f; 114 | var out:= (x + bufr(chn) - buf1r(chn) * (1 - fc)) /(1 + fc); 115 | bufw(chn,x); 116 | buf1w(chn,out); 117 | dcb(chn,out); 118 | ``` 119 | #### Step by Step 120 | 121 | `var f := clamp(2^(c*8),0.001,0.5); var fc := 2/f;` Compute the cutoff frequency from the knob `c`. 122 | 123 | ``` 124 | var out:= (x + bufr(chn) - buf1r(chn) * (1 - fc)) /(1 + fc); 125 | bufw(chn,x); 126 | buf1w(chn,out); 127 | ``` 128 | Compute the next filter sample. The two needed state variables are stored per channel in the 129 | buffer (0) and buffer 1. There are 4 buffers of size 4096 available which can be read 130 | via bufr,buf1r,buf2r,buf3r and written via bufw,buf1w,buf2w,buf3w 131 | 132 | `dcb(chn,out);` output the dc blocked sample. 133 | 134 | ### Polyphonic Comb Filter (Chorus,Flanger or whatever) 135 | 136 | ![](images/Comb.png?raw=true) 137 | ```Tcl 138 | var delay_ms:= 2.1; 139 | var len:= delay_ms/1000*sr; 140 | rblen(chn,len); 141 | var c0 := clamp((c+1)/2+y/10*a,0,0.99); 142 | var x0 := rbget(chn,c0*len); 143 | var nx := x/2 + x0 * d; 144 | rbpush(chn,nx); 145 | var out:=x0+nx; 146 | var x1 := rbget(chn,c0*len/2); 147 | out1:=dcb2(chn,x1+nx); 148 | dcb(chn,out); 149 | ``` 150 | This example shows the use of the provided ring buffers. 151 | 152 | #### Step by Step 153 | 154 | `var delay_ms:= 2.1; var len:= delay_ms/1000*sr;` Define the delay length and compute 155 | the buffer size in samples using the global variabel `sr` (sample rate). 156 | 157 | `rblen(chn,len);` set the ring buffer length for each channel. There are 16 ring buffers available with 158 | maximum length of 48000. 159 | 160 | `var c0 := clamp((c+1)/2+y/10*a,0,0.99);`. Compute the relative read position of the buffer 161 | using the knob value c and the input `y` attenuated with the knob value `a`. 162 | 163 | `var x0 := rbget(chn,c0*len);` read the buffer value at the computed index. 164 | 165 | `var nx := x/2 + x0 * d; rbpush(chn,nx);` mix with the input, 166 | add feedback attenuated with knob `d`and write it into the ring buffer. 167 | 168 | `var out:=x0+nx`; store the output in the variable `out`. 169 | 170 | `var x1 := rbget(chn,c0*len/2);` read a second value from the buffer for a stereo effect. 171 | 172 | `out1:=dcb2(chn,x1+nx);` output the 'right' stereo channel in output `out1` via 173 | setting the global variable `out1`. There are two dc blockers available per channel. 174 | 175 | 176 | `dcb(chn,out)`; output the left channel in `out`. (The value of the last expression is 177 | always written into the output `out`). 178 | 179 | ### Stereo Delay 180 | 181 | ![](images/StereoDelay.png?raw=true) 182 | 183 | ```Tcl 184 | var delayR:= 0.375; 185 | var delayL:= 0.750; 186 | var lenR:= delayR*sr; 187 | var lenL:= delayL*sr; 188 | rblen(0,lenL); 189 | rblen(1,lenR); 190 | 191 | var xL := rbget(0,0); 192 | var xR := rbget(1,0); 193 | rbpush(0,x+a*xL); 194 | rbpush(1,x+a*xR); 195 | out1:=dcb(0,(xR*b)+(x*(1-b))); 196 | dcb(1,xL*b+x*(1-b)); 197 | ``` 198 | 199 | Similar to the last example. The knob `a` controls the feedback and the knob `b` dry/wet. 200 | The position in the call rbget is relative to the last write position + 1. With position 0 it returns the 201 | sample which was written length samples before. `rbget(0,-1)` would return the last written sample. 202 | 203 | 204 | ### Polyphonic Random and Hold 205 | 206 | ![](images/RndH.png?raw=true) 207 | 208 | ```Tcl 209 | if(st(chn,z)>0) { 210 | bufw(chn,rnd()); 211 | } 212 | var out := bufr(chn)*10*a; 213 | ``` 214 | There are 4 Schmitt Triggers available per channel (st,st1,st2,st3). 215 | The buffer is used to hold the value until the next trigger arrives. 216 | NB the polyphony is determined by the input channels in the following way: 217 | If the input t is connected the channels of t is used, otherwise the maximum of the 218 | channels of the inputs w,x,y,z. The image above shows the case with one channel. 219 | 220 | ### Simple Sequencer 221 | 222 | ![](images/Sequencer.png?raw=true) 223 | 224 | ```Tcl 225 | var seq[8] := { 0,3/12,5/12, 226 | 7/12,10/12,7/12, 227 | 5/12,3/12}; 228 | 229 | if(st(0,z)>0) { 230 | v1:=v1+1; 231 | if(v1>=8) v1:=0; 232 | } 233 | if(st1(0,y)>0) v1:=0; 234 | var out := seq[v1]; 235 | ``` 236 | 237 | This example shows the use of one of the 8 global variables v1-v8. 238 | `v1` is used as the current position of the sequence which is advanced on arrival 239 | on a trigger on input `z`. The input `y` is used for resetting the sequence. 240 | 241 | ### Chord Sequencer 242 | 243 | ![](images/Chords.png?raw=true) 244 | 245 | ```Tcl 246 | if(v3==0) { 247 | var n := -1; 248 | for(var i:=0;i<36;i+=1) { 249 | bufw(i,n); 250 | n+=1/12; 251 | }; 252 | v3:=1; 253 | }; 254 | var chord[16]:={9,12,16,19, 9,12,14,18, 255 | 7,11,14,18, 7,11,12,16}; 256 | if(st(0,z)>0) {v1+=1; if(v1>=4) v1:=0;} 257 | var out: = bufr(chord[chn+v1*4]); 258 | 259 | ``` 260 | 261 | All variables and buffers are cleared if the script is compiled due to a change. 262 | Here the variable v3 is used to fill a buffer with note values (V/Oct) only once. 263 | (otherwise it costs much CPU). 264 | 265 | Similar to the RndH example, a trigger on the input `z` advances the sequence. The notes of a single 266 | chord are distributed in 4 channels. 267 | 268 | ### Gate Sequencer 269 | 270 | ![](images/GateSequencer.png?raw=true) 271 | 272 | ```Tcl 273 | var data[16] := { 274 | 1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1}; 275 | var dur := 10; var gate:=0; 276 | if(st(chn,z)>0) { 277 | if(data[v1]>0) { 278 | bufw(chn,dur); 279 | } else { 280 | bufw(chn,0); 281 | } 282 | }; 283 | if(bufr(chn)>0) { 284 | gate:=10; bufw(chn,bufr(chn)-1); 285 | }; 286 | if(st1(0,z)>0) { 287 | v1+=1; 288 | if(v1>=16) v1:=0; 289 | }; 290 | var out:= gate; 291 | ``` 292 | This example shows how to make pulses. The duration is defined in samples. 293 | When a trigger arrives and the current position (v1) has a one, a state variable 294 | is set to the duration and decremented on each run until it is zero. 295 | While greater than zero the output is set to 10V. 296 | 297 | ### A Line Segment Envelope Generator 298 | 299 | ![](images/LineSeg.png?raw=true) 300 | 301 | ```Tcl 302 | var vals[6]:={0,5,3,2,2,0}; 303 | var durs[5]:={0.3,0.2,0.5,0.3,0.2}; 304 | if(st(0,w)>0) { v1:=0; v2:=0; v3:=0; } 305 | var end:=0; 306 | if(v1>=v2+durs[v3]) { // advance 307 | if(v3>=5) end:=1; 308 | else { v2+=durs[v3]; v3+=1; } 309 | }; 310 | var o;var pct; 311 | if(end==1) { 312 | o:=vals[5]; 313 | } else { 314 | pct:= (v1-v2)/durs[v3]; 315 | o:=vals[v3]+(vals[v3+1]-vals[v3])*pct; 316 | }; 317 | v1+=stim; 318 | o; 319 | ``` 320 | Outputs line segments according to the values and duration arrays. It is triggered via the 321 | `w` input. 322 | As this is a common usecase the example can be simplified as so: 323 | ```Tcl 324 | if(st(0,w)>0) { v1:=0; } 325 | var o:=lseg(v1,0,0.3,5,0.2,3,0.5,2,0.3, 326 | 2,0.2,0); 327 | v1+=stim; 328 | o; 329 | ``` 330 | The function `lseg` takes as shown the phase as first parameter then alternating value 331 | and duration. If the last value is missing the first value is used instead. 332 | 333 | Additionally there is a function eseg providing exponential segments 334 | 335 | ![](images/ExpSeg.png?raw=true) 336 | 337 | ```Tcl 338 | var o:=eseg(v1, 0,0.3,-5, 5,0.5,-4, 339 | 2,0.3,0, 2,0.5,-3, 0); 340 | if(st(0,w)>0) v1:=0; 341 | 342 | v1+=stim; 343 | o; 344 | ``` 345 | 346 | The `eseg` function takes three alternating parameters value, duration, bending. 347 | The sign of the bending parameter determines if the curve 348 | is fast decaying/raising (<0) or slow decaying/raising (>0). 349 | 350 | 351 | ### Waveshaping 352 | ![](images/waveshaping1.png?raw=true) 353 | 354 | This example shows the definition and use of a user defined function. 355 | ```Tcl 356 | function sign(f) { 357 | f<0?-1:(f>0?1:0); 358 | } 359 | var p:= scl1(-a,0.01,10); 360 | // waveshaping function 361 | sign(x)*(1-p/(abs(x)+p))*5; 362 | ``` 363 | 364 | Functions must be defined always on top, before the normal code starts. 365 | The return value of a function is always the value of the last expression. 366 | 367 | 368 | There are some convenient functions provided for scaling: 369 | 370 | `scl(input,minInput,maxInput,minOutput,maxOutput)` 371 | 372 | this function scales the input expected in the range minInput,maxInput to the 373 | target range minOutput,maxOutput. 374 | 375 | `scl1` as shown in the example is a shortcut for 376 | 377 | `scl(input,-1,1,minOutput,maxOutput)` 378 | 379 | -- suited for the knob inputs. 380 | 381 | #### Wavshaping with exp segemnts 382 | 383 | ![](images/Waveshaping2.png?raw=true) 384 | 385 | ```Tcl 386 | eseg(t,-1,0.5,a*10,1,0.5,b*10,-1)*5 387 | ``` 388 | This example shows the use of the `eseg` function (see above) for waveshaping. 389 | Play around with knob a and b. 390 | 391 | ### CZ-Series PD Filter sweep 392 | 393 | ![](images/PDFilterSweep.png?raw=true) 394 | 395 | ```Tcl 396 | var freq := 261.626 * pow(2,w); 397 | var p:= bufr(chn); 398 | var phs:= p+stim*freq; 399 | if(phs>=1) phs:=0; 400 | bufw(chn,phs); 401 | var inv:=1-p; 402 | out1:=p; 403 | var freq2 := 261.626 * pow(2,a*5); 404 | var p2:= buf2r(chn); 405 | var o:= sin(2*pi*p2)*inv; 406 | var phs2:= p2+stim*freq2; 407 | phs2-=trunc(phs2); 408 | if(phs==0) phs2:=0; 409 | buf2w(chn,phs2); 410 | 411 | var out:=o*5; 412 | ``` 413 | 414 | This script implements the 415 | [Resonant Filter Simulation](https://en.wikipedia.org/wiki/Phase_distortion_synthesis) 416 | from the Casio CZ Series. The knob `a` controls the resonant frequency. 417 | 418 | -------------------------------------------------------------------------------- /images/Chords.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Chords.png -------------------------------------------------------------------------------- /images/Comb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Comb.png -------------------------------------------------------------------------------- /images/ExpSeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/ExpSeg.png -------------------------------------------------------------------------------- /images/Expander.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Expander.png -------------------------------------------------------------------------------- /images/Filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Filter.png -------------------------------------------------------------------------------- /images/FormulaOne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/FormulaOne.png -------------------------------------------------------------------------------- /images/GateSequencer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/GateSequencer.png -------------------------------------------------------------------------------- /images/LineSeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/LineSeg.png -------------------------------------------------------------------------------- /images/Osc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Osc.png -------------------------------------------------------------------------------- /images/PDFilterSweep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/PDFilterSweep.png -------------------------------------------------------------------------------- /images/PulseWave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/PulseWave.png -------------------------------------------------------------------------------- /images/RndH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/RndH.png -------------------------------------------------------------------------------- /images/Sequencer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Sequencer.png -------------------------------------------------------------------------------- /images/StereoDelay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/StereoDelay.png -------------------------------------------------------------------------------- /images/Wavefolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Wavefolder.png -------------------------------------------------------------------------------- /images/Waveshaping2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/Waveshaping2.png -------------------------------------------------------------------------------- /images/waveshaping1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/images/waveshaping1.png -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "slug": "dbRackFormulaOne", 3 | "name": "dbRackFormulaOne", 4 | "version": "2.0.2", 5 | "license": "GPL-3.0-or-later", 6 | "brand": "docB", 7 | "author": "docB", 8 | "authorEmail": "", 9 | "authorUrl": "https://docb.io", 10 | "pluginUrl": "https://github.com/docb/dbRackFormulaOne", 11 | "manualUrl": "https://github.com/docb/dbRackFormulaOne", 12 | "sourceUrl": "https://github.com/docb/dbRackFormulaOne", 13 | "donateUrl": "https://www.paypal.com/paypalme/docb222", 14 | "changelogUrl": "", 15 | "modules": [ 16 | { 17 | "slug": "FormulaOne", 18 | "name": "Formula One", 19 | "description": "A fast script evaluator", 20 | "tags": ["Polyphonic"] 21 | }, 22 | { 23 | "slug": "FormulaOneEdit", 24 | "name": "FormulaOneEdit", 25 | "description": "Expander for editing scripts", 26 | "tags": ["Expander"] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /presets/FormulaOne/Chords.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67250025272369385, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | } 22 | ], 23 | "data": { 24 | "formula": "if(v3==0) {\n var n := -1;\n for(var i:=0;i<36;i+=1) {\n bufw(i,n);\n n+=1/12;\n };\n v3:=1;\n};\nvar chord[16]:={9,12,16,19, 9,12,14,18,\n 7,11,14,18, 7,11,12,16};\nif(st(0,z)>0) {v1+=1; if(v1>=4) v1:=0;}\nvar out: = bufr(chord[chn+v1*4]);\n\n" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /presets/FormulaOne/Comb.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.27250084280967712, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.69000023603439331, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.075000613927841187, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.49749964475631714, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var delay_ms:= 2.1;\nvar len:= delay_ms/1000*sr;\nrblen(chn,len);\nvar c0 := clamp((c+1)/2+y/10*a,0,0.99);\nvar x0 := rbget(chn,c0*len); \nvar nx := x/2 + x0 * d;\nrbpush(chn,nx);\nvar out:=x0+nx;\nvar x1 := rbget(chn,c0*len/2);\nout1:=dcb2(chn,x1+nx);\ndcb(chn,out);\n\n\n" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /presets/FormulaOne/GateSequencer.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67250025272369385, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var data[16] := { \n1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,1};\nvar dur := 10; var gate:=0;\nif(st(chn,z)>0) {\n if(data[v1]>0) {\n bufw(chn,dur);\n } else {\n bufw(chn,0);\n } \n};\nif(bufr(chn)>0) { \n gate:=10; bufw(chn,bufr(chn)-1);\n};\nif(st1(0,z)>0) {\n v1+=1;\n if(v1>=16) v1:=0;\n};\nvar out:= gate;\n" 29 | } 30 | } -------------------------------------------------------------------------------- /presets/FormulaOne/LineSeg.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67250025272369385, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var vals[6]:={0,5,3,2,2,0};\nvar durs[5]:={0.3,0.2,0.5,0.3,0.2};\nif(st(0,w)>0) { v1:=0; v2:=0; v3:=0; }\nvar end:=0;\nif(v1>=v2+durs[v3]) {\n if(v3>=5) end:=1;\n else { v2+=durs[v3]; v3+=1; }\n};\nvar o;var pct;\nif(end==1) {\n o:=vals[5]; \n} else {\n pct:= (v1-v2)/durs[v3];\n o:=vals[v3]+(vals[v3+1]-vals[v3])*pct; \n};\nv1+=stim;\no;\n" 29 | } 30 | } -------------------------------------------------------------------------------- /presets/FormulaOne/PDFilterSweep.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.12999996542930603, 8 | "id": 0 9 | }, 10 | { 11 | "value": -1.0, 12 | "id": 1 13 | }, 14 | { 15 | "value": -1.0, 16 | "id": 2 17 | }, 18 | { 19 | "value": -1.0, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var freq := 261.626 * pow(2,w);\nvar p:= bufr(chn);\nvar phs:= p+stim*freq;\nif(phs>=1) phs:=0;\nbufw(chn,phs);\nvar inv:=1-p;\nout1:=p;\nvar freq2 := 261.626 * pow(2,a*5);\nvar p2:= buf2r(chn);\nvar o:= sin(2*pi*p2)*inv;\nvar phs2:= p2+stim*freq2;\nphs2-=trunc(phs2);\nif(phs==0) phs2:=0;\nbuf2w(chn,phs2);\n\nvar out:=dcb(chn,o*5); \n" 29 | } 30 | } -------------------------------------------------------------------------------- /presets/FormulaOne/PhaseOscillator.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67000055313110352, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.65000027418136597, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.075000613927841187, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.49749964475631714, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "a*5*(\n sin(2*pi*t)+\n sin(6*pi*t)/3+\n sin(10*pi*t)/5+\n sin(14*pi*t)/7\n)" 29 | } 30 | } -------------------------------------------------------------------------------- /presets/FormulaOne/RndH.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.20000040531158447, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | } 22 | ], 23 | "data": { 24 | "formula": "if(st(chn,z)>0) {\n bufw(chn,rnd());\n}\nvar out := bufr(chn)*10*a;\n" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /presets/FormulaOne/Sequencer.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67250025272369385, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | } 22 | ], 23 | "data": { 24 | "formula": "var seq[8] := { 0,3/12,5/12,\n 7/12,10/12,7/12,\n 5/12,3/12};\n\nif(st(0,z)>0) { \n v1:=v1+1;\n if(v1>=8) v1:=0;\n} \nif(st1(0,y)>0) v1:=0;\nvar out := seq[v1];\n" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /presets/FormulaOne/SimpleOscillator.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.70500016212463379, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.069999665021896362, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var freq := 261.626 * pow(2,w);\nvar p:= bufr(chn);\nvar o:=a*4*(\nsin(2*pi*p)+\nsin(6*pi*p)/3+\nsin(10*pi*p)/5+\nsin(14*pi*p)/7\n);\nvar phs:= p+stim*freq;\nphs-=trunc(phs);\nbufw(chn,phs);\nvar out:=o; \n" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /presets/FormulaOne/StereoDelay.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67000055313110352, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.65000027418136597, 12 | "id": 1 13 | }, 14 | { 15 | "value": 0.075000613927841187, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.49749964475631714, 20 | "id": 3 21 | }, 22 | { 23 | "value": 0.0, 24 | "id": 4 25 | } 26 | ], 27 | "data": { 28 | "formula": "var delayR:= 0.375;\nvar delayL:= 0.750;\nvar lenR:= delayR*sr;\nvar lenL:= delayL*sr;\nrblen(0,lenL);\nrblen(1,lenR);\n\nvar xL := rbget(0,0); \nvar xR := rbget(1,0);\nrbpush(0,x+a*xL);\nrbpush(1,x+a*xR);\nout1:=dcb(0,(xR*b)+(x*(1-b)));\ndcb(1,xL*b+x*(1-b));\n\n\n\n" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /presets/FormulaOne/TRCFilter.vcvm: -------------------------------------------------------------------------------- 1 | { 2 | "plugin": "dbRackFormulaOne", 3 | "model": "FormulaOne", 4 | "version": "2.0.0", 5 | "params": [ 6 | { 7 | "value": 0.67250025272369385, 8 | "id": 0 9 | }, 10 | { 11 | "value": 0.62250030040740967, 12 | "id": 1 13 | }, 14 | { 15 | "value": -0.57749992609024048, 16 | "id": 2 17 | }, 18 | { 19 | "value": 0.0, 20 | "id": 3 21 | } 22 | ], 23 | "data": { 24 | "formula": "var f := clamp(2^(c*8),0.001,0.5);\nvar fc := 2/f;\nvar out:= (x + bufr(chn) - buf1r(chn) * (1 - fc)) /(1 + fc);\nbufw(chn,x); \nbuf1w(chn,out);\n// dcblock\ndcb(chn,out);" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /res/FreeMonoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docb/dbRackFormulaOne/bd8afc4813e49af5032b5f44303fac309f859fdb/res/FreeMonoBold.ttf -------------------------------------------------------------------------------- /res/SmallPort.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 23 | 29 | 30 | 32 | 36 | 37 | 39 | 43 | 50 | 51 | 52 | 59 | 64 | 65 | 67 | 73 | 74 | 76 | 80 | 81 | 83 | 89 | 90 | 92 | 96 | 97 | 99 | 103 | 110 | 111 | 112 | 119 | 124 | 125 | 127 | 133 | 134 | 136 | 140 | 141 | 142 | 165 | 167 | 168 | 170 | image/svg+xml 171 | 173 | 174 | 176 | 177 | 178 | 179 | 184 | 189 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /res/TrimpotWhite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /res/TrimpotWhite9mm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 25 | 29 | 30 | 31 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 70 | 75 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/FormulaOne.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | #include "FormulaOne.hpp" 5 | 6 | 7 | void FormulaOne::process(const ProcessArgs &args) { 8 | if(compiled) { 9 | sr=args.sampleRate; 10 | smpTime=args.sampleTime; 11 | a=params[A_PARAM].getValue(); 12 | b=params[B_PARAM].getValue(); 13 | c=params[C_PARAM].getValue(); 14 | d=params[D_PARAM].getValue(); 15 | e=params[E_PARAM].getValue(); 16 | int channels=1; 17 | if(inputs[P_INPUT].isConnected()) { 18 | channels=inputs[P_INPUT].getChannels(); 19 | } else { 20 | switch(polyMode) { 21 | case X: 22 | channels=inputs[X_INPUT].getChannels(); 23 | break; 24 | case Y: 25 | channels=inputs[Y_INPUT].getChannels(); 26 | break; 27 | case Z: 28 | channels=inputs[Z_INPUT].getChannels(); 29 | break; 30 | case W: 31 | channels=inputs[W_INPUT].getChannels(); 32 | break; 33 | default: 34 | channels=inputs[W_INPUT].getChannels(); 35 | channels=std::max(channels,inputs[X_INPUT].getChannels()); 36 | channels=std::max(channels,inputs[Y_INPUT].getChannels()); 37 | channels=std::max(channels,inputs[Z_INPUT].getChannels()); 38 | break; 39 | } 40 | if(channels==0) 41 | channels=1; 42 | } 43 | for(int c=0;c=1.0f) 66 | blinkPhase-=1.0f; 67 | if(compiled) { 68 | lights[OK_LIGHT].value=0; 69 | lights[ERROR_LIGHT].value=1; 70 | } else { 71 | lights[OK_LIGHT].value=(blinkPhase<0.5f)?1.0f:0.0f; 72 | lights[ERROR_LIGHT].value=0; 73 | } 74 | } 75 | 76 | struct FormulaTextField : MTextField { 77 | FormulaOne *module; 78 | 79 | FormulaTextField() : MTextField() { 80 | //bgColor=nvgRGB(0x30,0x30,0x30); 81 | } 82 | 83 | void setModule(FormulaOne *module) { 84 | this->module=module; 85 | } 86 | 87 | void onChange(const event::Change &e) override { 88 | if(module) { 89 | module->formula=getText(); 90 | module->compile(); 91 | module->extDirty=true; 92 | } 93 | } 94 | 95 | void draw(const DrawArgs &args) override { 96 | if(module&&(module->dirty)) { 97 | int cursorSave=cursor; 98 | setText(module->formula); 99 | cursor=selection=cursorSave; 100 | if(cursor<0 || cursor>(int)text.size()) selection=cursor=0; 101 | module->dirty=false; 102 | module->extDirty=true; 103 | } 104 | MTextField::draw(args); 105 | } 106 | }; 107 | 108 | struct FormulaOneWidget : ModuleWidget { 109 | float ms=mm2px(Vec(5.08f,0)).x; 110 | std::vector polyModeLabels={"Max","x","y","z","w"}; 111 | FormulaOneWidget(FormulaOne *module) { 112 | ScrollWidget *scrollWidget; 113 | setModule(module); 114 | setPanel(createPanel(asset::plugin(pluginInstance,"res/FormulaOne.svg"))); 115 | 116 | addChild(createWidget(Vec(RACK_GRID_WIDTH,0))); 117 | addChild(createWidget(Vec(box.size.x-2*RACK_GRID_WIDTH,0))); 118 | addChild(createWidget(Vec(RACK_GRID_WIDTH,RACK_GRID_HEIGHT-RACK_GRID_WIDTH))); 119 | addChild(createWidget(Vec(box.size.x-2*RACK_GRID_WIDTH,RACK_GRID_HEIGHT-RACK_GRID_WIDTH))); 120 | scrollWidget=new TextScrollWidget(); 121 | scrollWidget->box.size=mm2px(Vec(85,68)); 122 | scrollWidget->box.pos=mm2px(Vec(3.5f,MHEIGHT-54-68)); 123 | INFO("%f",scrollWidget->box.size.y); 124 | addChild(scrollWidget); 125 | auto textField=createWidget(Vec(0,0)); 126 | textField->setModule(module); 127 | textField->box.size=mm2px(Vec(200,160)); 128 | textField->multiline=true; 129 | textField->scroll=scrollWidget; 130 | scrollWidget->container->addChild(textField); 131 | addChild(createLight>(mm2px(Vec(45,MHEIGHT-29)),module,FormulaOne::ERROR_LIGHT)); 132 | addParam(createParam(mm2px(Vec(8.75,MHEIGHT-34.5-8.985f)),module,FormulaOne::A_PARAM)); 133 | addParam(createParam(mm2px(Vec(21.5,MHEIGHT-34.5-8.985f)),module,FormulaOne::B_PARAM)); 134 | addParam(createParam(mm2px(Vec(34.5,MHEIGHT-34.5-8.985f)),module,FormulaOne::C_PARAM)); 135 | addParam(createParam(mm2px(Vec(47.5,MHEIGHT-34.5-8.985f)),module,FormulaOne::D_PARAM)); 136 | addParam(createParam(mm2px(Vec(60.5,MHEIGHT-34.5-8.985f)),module,FormulaOne::E_PARAM)); 137 | addInput(createInput(mm2px(Vec(10,MHEIGHT-10-6.27f)),module,FormulaOne::P_INPUT)); 138 | addInput(createInput(mm2px(Vec(23,MHEIGHT-10-6.27f)),module,FormulaOne::W_INPUT)); 139 | addInput(createInput(mm2px(Vec(36,MHEIGHT-10-6.27f)),module,FormulaOne::X_INPUT)); 140 | addInput(createInput(mm2px(Vec(49,MHEIGHT-10-6.27f)),module,FormulaOne::Y_INPUT)); 141 | addInput(createInput(mm2px(Vec(62,MHEIGHT-10-6.27f)),module,FormulaOne::Z_INPUT)); 142 | 143 | addOutput(createOutput(mm2px(Vec(79.212,MHEIGHT-10-6.27f)),module,FormulaOne::V_OUTPUT)); 144 | addOutput(createOutput(mm2px(Vec(79.212,MHEIGHT-24-6.27f)),module,FormulaOne::V1_OUTPUT)); 145 | addOutput(createOutput(mm2px(Vec(79.212,MHEIGHT-38-6.27f)),module,FormulaOne::V2_OUTPUT)); 146 | } 147 | void appendContextMenu(Menu *menu) override { 148 | FormulaOne *module=dynamic_cast(this->module); 149 | assert(module); 150 | menu->addChild(new MenuSeparator); 151 | auto inSelect=new LabelIntSelectItem(&module->polyMode,polyModeLabels); 152 | inSelect->text="Channels from"; 153 | inSelect->rightText=polyModeLabels[module->polyMode]+" "+RIGHT_ARROW; 154 | menu->addChild(inSelect); 155 | 156 | 157 | } 158 | }; 159 | 160 | 161 | Model *modelFormulaOne=createModel("FormulaOne"); -------------------------------------------------------------------------------- /src/FormulaOne.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by docb on 2/20/22. 3 | // 4 | 5 | #ifndef DBCAEMODULES_FASTFORMULA_HPP 6 | #define DBCAEMODULES_FASTFORMULA_HPP 7 | 8 | #include "exprtk.hpp" 9 | #include "rnd.h" 10 | #include "textfield.hpp" 11 | #include 12 | #include "functions.hpp" 13 | #include 14 | 15 | typedef exprtk::symbol_table symbol_table_t; 16 | typedef exprtk::expression expression_t; 17 | typedef exprtk::parser parser_t; 18 | typedef exprtk::parser_error::type err_t; 19 | typedef exprtk::lexer::parser_helper prsrhlpr_t; 20 | typedef exprtk::function_compositor compositor_t; 21 | typedef typename compositor_t::function function_t; 22 | 23 | template 24 | struct RB { 25 | T data[S]={}; 26 | size_t pos=0; 27 | size_t len=128; 28 | 29 | RB() { 30 | clear(); 31 | } 32 | 33 | void setLen(int l) { 34 | if(l>1&&l=len) 42 | pos=0; 43 | } 44 | 45 | T get(int relPos) { 46 | int idx=pos+relPos; 47 | while(idx<0) 48 | idx+=len; 49 | return data[idx%len]; 50 | } 51 | 52 | void clear() { 53 | pos=0; 54 | len=1024; 55 | for(size_t k=0;k 61 | struct RBuffers { 62 | RB buffers[K]; 63 | 64 | void setLen(int nr,int l) { 65 | if(nr<0||nr>=K) 66 | return; 67 | buffers[nr].setLen(l); 68 | } 69 | 70 | void push(int nr,T t) { 71 | if(nr<0||nr>=K) 72 | return; 73 | buffers[nr].push(t); 74 | } 75 | 76 | T get(int nr,int relPos) { 77 | if(nr<0||nr>=K) 78 | return T(0); 79 | return buffers[nr].get(relPos); 80 | } 81 | 82 | void clear() { 83 | for(size_t j=0;j 91 | struct Buffer { 92 | T data[S]={}; 93 | 94 | Buffer() { 95 | clear(); 96 | } 97 | 98 | void write(int idx,T t) { 99 | if(idx<0||idx>=S) 100 | return; 101 | data[idx]=t; 102 | } 103 | 104 | T read(int idx) { 105 | if(idx<0||idx>=S) 106 | return T(0); 107 | return data[idx]; 108 | } 109 | 110 | void clear() { 111 | for(size_t k=0;k 117 | struct Random : public exprtk::ifunction { 118 | using exprtk::ifunction::operator(); 119 | RND rnd; 120 | 121 | Random() : exprtk::ifunction(0) { 122 | } 123 | 124 | inline T operator()() override { 125 | return T(rnd.nextDouble()); 126 | } 127 | }; 128 | 129 | template 130 | struct BetaRnd : public exprtk::ifunction { 131 | using exprtk::ifunction::operator(); 132 | RND rnd; 133 | 134 | BetaRnd() : exprtk::ifunction(2) { 135 | } 136 | 137 | inline T operator()(const T &a, const T &b) override { 138 | return T(rnd.nextBeta(a,b)); 139 | } 140 | }; 141 | 142 | template 143 | struct Weibull : public exprtk::ifunction { 144 | using exprtk::ifunction::operator(); 145 | RND rnd; 146 | 147 | Weibull() : exprtk::ifunction(1) { 148 | } 149 | 150 | inline T operator()(const T &a) override { 151 | return T(rnd.nextWeibull(a)); 152 | } 153 | }; 154 | 155 | template 156 | struct Cauchy : public exprtk::ifunction { 157 | using exprtk::ifunction::operator(); 158 | RND rnd; 159 | 160 | Cauchy() : exprtk::ifunction(1) { 161 | } 162 | 163 | inline T operator()(const T &a) override { 164 | return T(rnd.nextCauchy(a)); 165 | } 166 | }; 167 | 168 | template 169 | struct Coin : public exprtk::ifunction { 170 | using exprtk::ifunction::operator(); 171 | RND rnd; 172 | 173 | Coin() : exprtk::ifunction(1) { 174 | } 175 | 176 | inline T operator()(const T &a) override { 177 | return T(rnd.nextCoin(a)?T(0):T(1)); 178 | } 179 | }; 180 | 181 | template 182 | struct STrigger : public exprtk::ifunction { 183 | using exprtk::ifunction::operator(); 184 | dsp::SchmittTrigger st[16]; 185 | 186 | STrigger() : exprtk::ifunction(2) { 187 | } 188 | 189 | inline T operator()(const T &nr,const T &v) override { 190 | int n=int(nr); 191 | if(n>=0&&n<16) 192 | return st[n].process(v)?1.f:0.f; 193 | return T(0); 194 | } 195 | }; 196 | 197 | template 198 | struct DCBlock : public exprtk::ifunction { 199 | using exprtk::ifunction::operator(); 200 | DCBlocker dcb[16]; 201 | 202 | DCBlock() : exprtk::ifunction(2) { 203 | } 204 | 205 | inline T operator()(const T &nr,const T &v) override { 206 | int n=int(nr); 207 | if(n>=0&&n<16) 208 | return dcb[n].process(v); 209 | return T(0); 210 | } 211 | 212 | void reset() { 213 | for(int k=0;k<16;k++) { 214 | dcb[k].reset(); 215 | } 216 | } 217 | }; 218 | 219 | template 220 | struct RBSetLength : public exprtk::ifunction { 221 | using exprtk::ifunction::operator(); 222 | RBuffers *buffers=nullptr; 223 | 224 | RBSetLength() : exprtk::ifunction(2) { 225 | } 226 | 227 | inline T operator()(const T &nr,const T &v) override { 228 | if(buffers) 229 | buffers->setLen(int(nr),int(v)); 230 | return v; 231 | } 232 | }; 233 | 234 | template 235 | struct RBPush : public exprtk::ifunction { 236 | using exprtk::ifunction::operator(); 237 | RBuffers *buffers=nullptr; 238 | 239 | RBPush() : exprtk::ifunction(2) { 240 | } 241 | 242 | inline T operator()(const T &nr,const T &v) override { 243 | if(buffers) { 244 | buffers->push(int(nr),v); 245 | return v; 246 | } 247 | return T(0); 248 | } 249 | }; 250 | 251 | template 252 | struct RBGet : public exprtk::ifunction { 253 | using exprtk::ifunction::operator(); 254 | RBuffers *buffers=nullptr; 255 | 256 | RBGet() : exprtk::ifunction(2) { 257 | } 258 | 259 | inline T operator()(const T &nr,const T &v) override { 260 | if(buffers) 261 | return buffers->get(int(nr),int(v)); 262 | return T(0); 263 | } 264 | }; 265 | 266 | template 267 | struct BufferWrite : public exprtk::ifunction { 268 | using exprtk::ifunction::operator(); 269 | Buffer *buffer=nullptr; 270 | 271 | BufferWrite() : exprtk::ifunction(2) { 272 | } 273 | 274 | inline T operator()(const T &idx,const T &v) override { 275 | if(buffer) { 276 | buffer->write(int(idx),v); 277 | } 278 | return v; 279 | } 280 | }; 281 | 282 | template 283 | struct BufferRead : public exprtk::ifunction { 284 | using exprtk::ifunction::operator(); 285 | Buffer *buffer=nullptr; 286 | 287 | BufferRead() : exprtk::ifunction(1) { 288 | } 289 | 290 | inline T operator()(const T &pos) override { 291 | return buffer?buffer->read((int)pos):0.f; 292 | } 293 | }; 294 | 295 | template 296 | struct Poly : public exprtk::ivararg_function { 297 | inline T operator()(const std::vector &arglist) override { 298 | T result=T(0); 299 | if(!arglist.empty()) { 300 | T in=arglist[0]; 301 | std::vector params; 302 | for(int i=1;i 311 | struct Chebyshev : public exprtk::ivararg_function { 312 | inline T operator()(const std::vector &arglist) override { 313 | T result=T(0); 314 | if(!arglist.empty()) { 315 | T in=arglist[0]; 316 | std::vector params; 317 | for(int i=1;i 326 | struct LinSeg : public exprtk::ivararg_function { 327 | inline T operator()(const std::vector &arglist) override { 328 | T result=T(0); 329 | if(!arglist.empty()) { 330 | T in=arglist[0]; 331 | std::vector params; 332 | for(int i=1;i 341 | struct ExpSeg : public exprtk::ivararg_function { 342 | inline T operator()(const std::vector &arglist) override { 343 | T result=T(0); 344 | if(!arglist.empty()) { 345 | T in=arglist[0]; 346 | std::vector params; 347 | for(int i=1;i 356 | struct Spline : public exprtk::ivararg_function { 357 | inline T operator()(const std::vector &arglist) override { 358 | T result=T(0); 359 | if(!arglist.empty()) { 360 | T in=arglist[0]; 361 | std::vector params; 362 | for(size_t i=1;i 371 | struct Saw : public exprtk::ifunction { 372 | using exprtk::ifunction::operator(); 373 | 374 | Saw() : exprtk::ifunction(1) { 375 | } 376 | 377 | inline T operator()(const T &v) override { 378 | return saw(v); 379 | } 380 | 381 | }; 382 | 383 | template 384 | struct Tri : public exprtk::ifunction { 385 | using exprtk::ifunction::operator(); 386 | 387 | Tri() : exprtk::ifunction(1) { 388 | } 389 | 390 | inline T operator()(const T &v) override { 391 | return tri(v); 392 | } 393 | 394 | }; 395 | 396 | template 397 | struct InX : public exprtk::ifunction { 398 | using exprtk::ifunction::operator(); 399 | M *m=nullptr; 400 | explicit InX(M *_m) : exprtk::ifunction(1),m(_m) { 401 | } 402 | 403 | inline T operator()(const T &v) override { 404 | if(v>=0 && v<16) { 405 | return m->inputs[M::X_INPUT].getVoltage((int)v); 406 | } 407 | return 0; 408 | } 409 | }; 410 | 411 | template 412 | struct InY : public exprtk::ifunction { 413 | using exprtk::ifunction::operator(); 414 | M *m=nullptr; 415 | explicit InY(M *_m) : exprtk::ifunction(1),m(_m) { 416 | } 417 | 418 | inline T operator()(const T &v) override { 419 | if(v>=0 && v<16) { 420 | return m->inputs[M::Y_INPUT].getVoltage((int)v); 421 | } 422 | return 0; 423 | } 424 | }; 425 | 426 | template 427 | struct InZ : public exprtk::ifunction { 428 | using exprtk::ifunction::operator(); 429 | M *m=nullptr; 430 | explicit InZ(M *_m) : exprtk::ifunction(1),m(_m) { 431 | } 432 | 433 | inline T operator()(const T &v) override { 434 | if(v>=0 && v<16) { 435 | return m->inputs[M::Z_INPUT].getVoltage((int)v); 436 | } 437 | return 0; 438 | } 439 | }; 440 | 441 | template 442 | struct InW : public exprtk::ifunction { 443 | using exprtk::ifunction::operator(); 444 | M *m=nullptr; 445 | explicit InW(M *_m) : exprtk::ifunction(1),m(_m) { 446 | } 447 | 448 | inline T operator()(const T &v) override { 449 | if(v>=0 && v<16) { 450 | return m->inputs[M::W_INPUT].getVoltage((int)v); 451 | } 452 | return 0; 453 | } 454 | }; 455 | 456 | template 457 | struct Scale : public exprtk::ifunction { 458 | using exprtk::ifunction::operator(); 459 | 460 | Scale() : exprtk::ifunction(5) { 461 | } 462 | 463 | inline T operator()(const T &v,const T &min,const T &max,const T &newmin,const T &newmax) override { 464 | return scale(v,min,max,newmin,newmax); 465 | } 466 | 467 | }; 468 | 469 | template 470 | struct Scale1 : public exprtk::ifunction { 471 | using exprtk::ifunction::operator(); 472 | 473 | Scale1() : exprtk::ifunction(3) { 474 | } 475 | 476 | inline T operator()(const T &v,const T &min,const T &max) override { 477 | return scale(v,-1.f,1.f,min,max); 478 | } 479 | }; 480 | 481 | struct function_definition { 482 | std::string name; 483 | std::string body; 484 | std::vector var_list; 485 | 486 | void clear() { 487 | name.clear(); 488 | body.clear(); 489 | var_list.clear(); 490 | } 491 | }; 492 | 493 | enum func_parse_result { 494 | e_parse_unknown=0,e_parse_success=1,e_parse_partial=2,e_parse_lexfail=4,e_parse_notfunc=8 495 | }; 496 | 497 | struct parse_function_definition_impl : public exprtk::lexer::parser_helper { 498 | func_parse_result process(std::string &func_def,function_definition &fd) { 499 | if(!init(func_def)) 500 | return e_parse_lexfail; 501 | if(!token_is(token_t::e_symbol,"function")) 502 | return e_parse_notfunc; 503 | if(!token_is(token_t::e_symbol,prsrhlpr_t::e_hold)) 504 | return e_parse_partial; 505 | 506 | fd.name=current_token().value; 507 | 508 | next_token(); 509 | 510 | if(!token_is(token_t::e_lbracket)) 511 | return e_parse_partial; 512 | 513 | if(!token_is(token_t::e_rbracket)) { 514 | std::vector var_list; 515 | 516 | for(;;) { 517 | // (x,y,z,....w) 518 | if(!token_is(token_t::e_symbol,prsrhlpr_t::e_hold)) 519 | return e_parse_partial; 520 | 521 | var_list.push_back(current_token().value); 522 | 523 | next_token(); 524 | 525 | if(token_is(token_t::e_rbracket)) 526 | break; 527 | 528 | if(!token_is(token_t::e_comma)) 529 | return e_parse_partial; 530 | } 531 | 532 | var_list.swap(fd.var_list); 533 | } 534 | 535 | std::size_t body_begin=current_token().position; 536 | std::size_t body_end=current_token().position; 537 | 538 | int bracket_stack=0; 539 | 540 | if(!token_is(token_t::e_lcrlbracket,prsrhlpr_t::e_hold)) 541 | return e_parse_partial; 542 | 543 | for(;;) { 544 | body_end=current_token().position; 545 | 546 | if(token_is(token_t::e_lcrlbracket)) 547 | bracket_stack++; 548 | else if(token_is(token_t::e_rcrlbracket)) { 549 | if(0==--bracket_stack) 550 | break; 551 | } else { 552 | if(lexer().finished()) 553 | return e_parse_partial; 554 | 555 | next_token(); 556 | } 557 | } 558 | 559 | std::size_t size=body_end-body_begin+1; 560 | 561 | fd.body=func_def.substr(body_begin,size); 562 | 563 | const std::size_t index=body_begin+size; 564 | 565 | if(index rBuffers; 629 | RBPush bufPush; 630 | RBGet bufGet; 631 | RBSetLength bufSetLength; 632 | 633 | Buffer buf[4]; 634 | BufferWrite bufWrite[4]; 635 | BufferRead bufRead[4]; 636 | 637 | Random random; 638 | STrigger strigger[4]; 639 | DCBlock dcb; 640 | DCBlock dcb2; 641 | 642 | int polyMode=MAX; 643 | 644 | Poly poly; 645 | Chebyshev chebyshev; 646 | LinSeg linseg; 647 | ExpSeg expseg; 648 | Spline spline; 649 | Saw saw; 650 | Tri tri; 651 | Scale scale; 652 | Scale1 scale1; 653 | Cauchy cauchy; 654 | Coin coin; 655 | BetaRnd beta; 656 | Weibull weibull; 657 | InX inX{this}; 658 | InY inY{this}; 659 | InZ inZ{this}; 660 | InW inW{this}; 661 | 662 | FormulaOne() { 663 | config(PARAMS_LEN,INPUTS_LEN,OUTPUTS_LEN,LIGHTS_LEN); 664 | configInput(X_INPUT,"x"); 665 | configInput(Y_INPUT,"y"); 666 | configInput(Z_INPUT,"z"); 667 | configInput(W_INPUT,"w"); 668 | configInput(P_INPUT,"t - Phase ([-5,5] -> [0,1])"); 669 | configOutput(V_OUTPUT,"CV"); 670 | configOutput(V1_OUTPUT,"CV 1"); 671 | configOutput(V2_OUTPUT,"CV 2"); 672 | configParam(A_PARAM,-1,1,0,"a"); 673 | configParam(B_PARAM,-1,1,0,"b"); 674 | configParam(C_PARAM,-1,1,0,"c"); 675 | configParam(D_PARAM,-1,1,0,"d"); 676 | configParam(E_PARAM,-1,1,0,"e"); 677 | symbol_table.add_variable("y",y); 678 | symbol_table.add_variable("z",z); 679 | symbol_table.add_variable("x",x); 680 | symbol_table.add_variable("w",w); 681 | symbol_table.add_variable("t",t); 682 | symbol_table.add_variable("a",a); 683 | symbol_table.add_variable("b",b); 684 | symbol_table.add_variable("c",c); 685 | symbol_table.add_variable("d",d); 686 | symbol_table.add_variable("e",e); 687 | symbol_table.add_variable("v1",v1); 688 | symbol_table.add_variable("v2",v2); 689 | symbol_table.add_variable("v3",v3); 690 | symbol_table.add_variable("v4",v4); 691 | symbol_table.add_variable("v5",v5); 692 | symbol_table.add_variable("v6",v6); 693 | symbol_table.add_variable("v7",v7); 694 | symbol_table.add_variable("v8",v8); 695 | symbol_table.add_variable("out1",out1); 696 | symbol_table.add_variable("out2",out2); 697 | symbol_table.add_variable("chn",channel); 698 | symbol_table.add_variable("sr",sr); 699 | symbol_table.add_variable("stim",smpTime); 700 | for(int k=0;k<4;k++) { 701 | bufWrite[k].buffer=&buf[k]; 702 | bufRead[k].buffer=&buf[k]; 703 | } 704 | bufGet.buffers=&rBuffers; 705 | bufPush.buffers=&rBuffers; 706 | bufSetLength.buffers=&rBuffers; 707 | symbol_table.add_function("rblen",bufSetLength); 708 | symbol_table.add_function("rbget",bufGet); 709 | symbol_table.add_function("rbpush",bufPush); 710 | symbol_table.add_function("bufr",bufRead[0]); 711 | symbol_table.add_function("bufw",bufWrite[0]); 712 | symbol_table.add_function("buf1r",bufRead[1]); 713 | symbol_table.add_function("buf1w",bufWrite[1]); 714 | symbol_table.add_function("buf2r",bufRead[2]); 715 | symbol_table.add_function("buf2w",bufWrite[2]); 716 | symbol_table.add_function("buf3r",bufRead[3]); 717 | symbol_table.add_function("buf3w",bufWrite[3]); 718 | 719 | symbol_table.add_function("st",strigger[0]); 720 | symbol_table.add_function("st1",strigger[1]); 721 | symbol_table.add_function("st2",strigger[2]); 722 | symbol_table.add_function("st3",strigger[3]); 723 | symbol_table.add_function("rnd",random); 724 | symbol_table.add_function("dcb",dcb); 725 | symbol_table.add_function("dcb2",dcb2); 726 | symbol_table.add_function("poly",poly); 727 | symbol_table.add_function("chb",chebyshev); 728 | symbol_table.add_function("lseg",linseg); 729 | symbol_table.add_function("eseg",expseg); 730 | symbol_table.add_function("spl",spline); 731 | symbol_table.add_function("saw",saw); 732 | symbol_table.add_function("tri",tri); 733 | symbol_table.add_function("scl",scale); 734 | symbol_table.add_function("scl1",scale1); 735 | symbol_table.add_function("getX",inX); 736 | symbol_table.add_function("getY",inY); 737 | symbol_table.add_function("getZ",inZ); 738 | symbol_table.add_function("getW",inW); 739 | symbol_table.add_constants(); 740 | compositor.add_auxiliary_symtab(symbol_table); 741 | expression.register_symbol_table(compositor.symbol_table()); 742 | expression.register_symbol_table(symbol_table); 743 | } 744 | 745 | void clear() { 746 | for(int k=0;k<4;k++) { 747 | buf[k].clear(); 748 | } 749 | v1=0; 750 | v2=0; 751 | v3=0; 752 | v4=0; 753 | v5=0; 754 | v6=0; 755 | v7=0; 756 | v8=0; 757 | dcb.reset(); 758 | } 759 | 760 | func_parse_result parse_function_definition(std::string &func_def,function_definition &cf) { 761 | parse_function_definition_impl parser; 762 | return parser.process(func_def,cf); 763 | } 764 | 765 | bool resolveFunctions(std::string &form) { 766 | std::string::size_type pos=form.find("function",0); 767 | if(pos==std::string::npos) 768 | return false; 769 | form=form.substr(pos); 770 | function_definition fd; 771 | 772 | func_parse_result fp_result=parse_function_definition(form,fd); 773 | 774 | if(e_parse_success==fp_result) { 775 | std::string vars; 776 | 777 | for(std::size_t i=0;i(100e-6)); 818 | std::string form=formula; 819 | resolveFunctions(form); 820 | //INFO("code after parsing functions %s",form.c_str()); 821 | compiled=parser.compile(form,expression); 822 | if(!compiled) { 823 | INFO("Error: %s\n",parser.error().c_str()); 824 | 825 | for(std::size_t i=0;ievent->setSelectedWidget(container->children.front()); 865 | } 866 | } 867 | 868 | }; 869 | #endif //DBCAEMODULES_FASTFORMULA_HPP 870 | -------------------------------------------------------------------------------- /src/FormulaOneEdit.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "textfield.hpp" 3 | #include "FormulaOne.hpp" 4 | extern Model *modelFormulaOne; 5 | struct FormulaOneEdit : Module { 6 | enum ParamId { 7 | PARAMS_LEN 8 | }; 9 | enum InputId { 10 | INPUTS_LEN 11 | }; 12 | enum OutputId { 13 | OUTPUTS_LEN 14 | }; 15 | enum LightId { 16 | LIGHTS_LEN 17 | }; 18 | FormulaOne *formel1=nullptr; 19 | 20 | FormulaOneEdit() { 21 | config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); 22 | } 23 | 24 | void process(const ProcessArgs& args) override { 25 | if(leftExpander.module) { 26 | if(leftExpander.module->model==modelFormulaOne) { 27 | if(formel1==nullptr) { 28 | formel1=reinterpret_cast(leftExpander.module); 29 | formel1->extDirty=true; 30 | } 31 | } else { 32 | formel1=nullptr; 33 | } 34 | } else { 35 | formel1=nullptr; 36 | } 37 | } 38 | }; 39 | struct TestTextField : MTextField { 40 | 41 | TestTextField() : MTextField() { 42 | } 43 | 44 | void onChange(const event::Change &e) override { 45 | } 46 | 47 | void draw(const DrawArgs& args) override { 48 | MTextField::draw(args); 49 | } 50 | }; 51 | 52 | struct ExtFormulaTextField : MTextField { 53 | FormulaOneEdit *module; 54 | 55 | ExtFormulaTextField() : MTextField() { 56 | //bgColor=nvgRGB(0x30,0x30,0x30); 57 | } 58 | 59 | void setModule(FormulaOneEdit *module) { 60 | this->module=module; 61 | } 62 | 63 | void onChange(const event::Change &e) override { 64 | if(module&&module->formel1) { 65 | module->formel1->formula=getText(); 66 | module->formel1->compile(); 67 | module->formel1->dirty=true; 68 | } 69 | } 70 | 71 | void draw(const DrawArgs &args) override { 72 | if(module&&module->formel1&&module->formel1->extDirty) { 73 | int cursorSave=cursor; 74 | setText(module->formel1->formula.substr(0)); 75 | cursor=selection=cursorSave; 76 | module->formel1->extDirty=false; 77 | } else { 78 | 79 | } 80 | MTextField::draw(args); 81 | } 82 | }; 83 | 84 | struct FormulaOneEditWidget : ModuleWidget { 85 | ScrollWidget *scrollWidget; 86 | FormulaOneEditWidget(FormulaOneEdit* module) { 87 | setModule(module); 88 | setPanel(createPanel(asset::plugin(pluginInstance, "res/FormulaOneEdit.svg"))); 89 | 90 | addChild(createWidget(Vec(RACK_GRID_WIDTH, 0))); 91 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0))); 92 | addChild(createWidget(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 93 | addChild(createWidget(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH))); 94 | scrollWidget = new TextScrollWidget(); 95 | scrollWidget->box.size=mm2px(Vec(176,115)); 96 | scrollWidget->box.pos = mm2px(Vec(3.5f,MHEIGHT-7-115)); 97 | INFO("%f",scrollWidget->box.size.y); 98 | addChild(scrollWidget); 99 | auto textField=createWidget(Vec(0,0)); 100 | textField->box.size=mm2px(Vec(200,400)); 101 | textField->multiline=true; 102 | textField->setModule(module); 103 | textField->scroll = scrollWidget; 104 | 105 | scrollWidget->container->addChild(textField); 106 | } 107 | }; 108 | 109 | 110 | Model* modelFormulaOneEdit = createModel("FormulaOneEdit"); -------------------------------------------------------------------------------- /src/functions.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by docb on 2/24/22. 3 | // 4 | 5 | #ifndef DBRACKFORMULAONE_FUNCTIONS_HPP 6 | #define DBRACKFORMULAONE_FUNCTIONS_HPP 7 | 8 | template 9 | T chebyshev(T in,std::vector coeff) { 10 | int len=coeff.size(); 11 | if(len<2) 12 | return 0; 13 | T v1[len*2]; 14 | T v2[len*2]; 15 | v1[0]=v1[1]=T(1); 16 | 17 | for(int i=2;ii) { 31 | for(int j=1;j= 0 ; --i) { 39 | sum *= in; 40 | sum += v2[i]; 41 | } 42 | return sum; 43 | } 44 | template 45 | T linseg(T in,const std::vector ¶ms) { 46 | int len = params.size(); 47 | if(len==0) return T(0); 48 | if(len==1) return params[0]; 49 | float s=0; 50 | float pre=0; 51 | int j=1; 52 | for(;j=len) return params[len%2==1?len-1:0]; 58 | float pct=params[j]==0?1:(in-pre)/params[j]; 59 | return params[j-1]+pct*(params[(j+1) 62 | T expseg(T in,const std::vector ¶ms) { 63 | int len = params.size(); 64 | if(len<4 || (len-1)%3!=0) return T(0); 65 | float s=0; 66 | float pre=0; 67 | int j=1; 68 | for(;j=len) return params[len-1]; 74 | float pct=params[j]==0?1:(in-pre)/params[j]; 75 | float f=params[j+1]==0?pct:(1 - std::exp( pct*params[j+1])) / (1 - std::exp(params[j+1])); 76 | return params[j-1]+f*(params[j+2]-params[j-1]); 77 | } 78 | 79 | template 80 | T spline(T phs,const std::vector &pts) { 81 | int len = pts.size(); 82 | if(phs>=1.f) phs=0.99999f; 83 | if(phs<0.f) phs = 0.f; 84 | int idx0 = int(phs*float(len))%len; 85 | int idx1 = (idx0+1)%len; 86 | int idx2 = (idx0+2)%len; 87 | int idx3 = (idx0+3)%len; 88 | float a0=pts[idx3]-pts[idx2]-pts[idx0]+pts[idx1]; 89 | float a1=pts[idx0]-pts[idx1]-a0; 90 | float a2=pts[idx2]-pts[idx0]; 91 | float a3=pts[idx1]; 92 | float start = float(idx0)/float(len); 93 | float stepPhs = (phs - start)*len; 94 | return ((a0*stepPhs+a1)*stepPhs+a2)*stepPhs+a3; 95 | } 96 | 97 | template 98 | T poly(T in,const std::vector &coeff) { 99 | if(coeff.size()==0) 100 | return 0; 101 | int last=coeff.size()-1; 102 | T sum=coeff[last]; 103 | for(int i=last;i>=0;--i) { 104 | sum*=in; 105 | sum+=coeff[i]; 106 | } 107 | return sum; 108 | } 109 | template 110 | T scale(T v,T min, T max, T newmin, T newmax) { 111 | T pct = (v-min)/(max-min); 112 | return newmin+pct*(newmax-newmin); 113 | } 114 | 115 | template 116 | T saw(T v) { 117 | return v-std::floor(v+0.5); 118 | } 119 | 120 | template 121 | T tri(T v) { 122 | return 2*std::fabs(2*saw(v))-1; 123 | } 124 | 125 | template 126 | T pls(T v) { 127 | T t=tri(v); 128 | if(t>0) 129 | return 1; 130 | if(t<0) 131 | return -1; 132 | return 0; 133 | } 134 | 135 | #endif //DBRACKFORMULAONE_FUNCTIONS_HPP 136 | -------------------------------------------------------------------------------- /src/plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | 3 | 4 | Plugin* pluginInstance; 5 | 6 | extern Model* modelFormulaOne; 7 | extern Model* modelFormulaOneEdit; 8 | 9 | void init(Plugin* p) { 10 | pluginInstance = p; 11 | 12 | // Add modules here 13 | p->addModel(modelFormulaOne); 14 | p->addModel(modelFormulaOneEdit); 15 | 16 | // Any other plugin initialization may go here. 17 | // As an alternative, consider lazy-loading assets and lookup tables when your module is created to reduce startup times of Rack. 18 | } 19 | -------------------------------------------------------------------------------- /src/plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | using namespace rack; 6 | 7 | // Declare the Plugin, defined in plugin.cpp 8 | extern Plugin* pluginInstance; 9 | 10 | // Declare each Model, defined in each module source file 11 | // extern Model* modelMyModule; 12 | #define TWOPIF 6.2831853f 13 | #define MHEIGHT 128.5f 14 | 15 | template 16 | struct DCBlocker { 17 | T x = 0.f; 18 | T y = 0.f; 19 | T process(T v) { 20 | y = v - x + y * 0.99f; 21 | x = v; 22 | return y; 23 | } 24 | void reset() { 25 | x = 0.f; 26 | y = 0.f; 27 | } 28 | }; 29 | struct TrimbotWhite : SvgKnob { 30 | TrimbotWhite() { 31 | minAngle=-0.8*M_PI; 32 | maxAngle=0.8*M_PI; 33 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance,"res/TrimpotWhite.svg"))); 34 | } 35 | }; 36 | 37 | struct TrimbotWhite9 : SvgKnob { 38 | TrimbotWhite9() { 39 | minAngle=-0.8*M_PI; 40 | maxAngle=0.8*M_PI; 41 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance,"res/TrimpotWhite9mm.svg"))); 42 | } 43 | }; 44 | 45 | struct SmallPort : app::SvgPort { 46 | SmallPort() { 47 | setSvg(APP->window->loadSvg(asset::plugin(pluginInstance,"res/SmallPort.svg"))); 48 | } 49 | }; 50 | 51 | struct LabelIntSelectItem : MenuItem { 52 | int *value; 53 | std::vector labels; 54 | 55 | LabelIntSelectItem(int *val,std::vector _labels) : value(val),labels(std::move(_labels)) { 56 | } 57 | 58 | Menu *createChildMenu() override { 59 | Menu *menu=new Menu; 60 | for(unsigned k=0;kaddChild(createCheckMenuItem(labels[k],"",[=]() { 62 | return *value==int(k); 63 | },[=]() { 64 | *value=k; 65 | })); 66 | } 67 | return menu; 68 | } 69 | }; -------------------------------------------------------------------------------- /src/rnd.cpp: -------------------------------------------------------------------------------- 1 | #include "rnd.h" 2 | #include 3 | static std::atomic counter {0}; 4 | 5 | void RND::reset(unsigned long long rseed) { 6 | if(rseed==0) { 7 | state=seed=(unsigned long long)(time(nullptr)+(counter++))*1234567; 8 | } else { 9 | state=seed=rseed*1234567; 10 | } 11 | } -------------------------------------------------------------------------------- /src/rnd.h: -------------------------------------------------------------------------------- 1 | #ifndef RND_H 2 | #define RND_H 3 | 4 | #include "cmath" 5 | #include 6 | 7 | class RND { 8 | public: 9 | 10 | explicit RND(unsigned long long rseed=0) { 11 | reset(rseed); 12 | } 13 | 14 | void reset() { 15 | state=seed; 16 | } 17 | 18 | void reset(unsigned long long rseed); 19 | 20 | unsigned long next() { 21 | state=(a*state+c)%m; 22 | unsigned long ret=state>>16; 23 | return ret; 24 | } 25 | 26 | double nextDouble() { 27 | return (double)next()/(double)(m>>16); 28 | } 29 | 30 | bool nextCoin(float thresh = 0.5f) { 31 | return nextDouble() > thresh; 32 | } 33 | 34 | int nextRange(int from, int to) { 35 | if(from==to) return from; 36 | if(from=1) 56 | break; 57 | } 58 | return r1/r2; 59 | } 60 | 61 | double nextWeibull(double dens) { 62 | if(dens<=1) return nextDouble(); 63 | return pow(-(log(1.f-(nextDouble() * .63))), (dens)); 64 | } 65 | 66 | double nextTri(int count) { 67 | if(count<2) return nextDouble(); 68 | double z=0; 69 | for(int j=0;jr) 92 | r=z; 93 | } 94 | return r; 95 | } 96 | double nextCauchy(double _a) { 97 | if (_a > 1.0) 98 | _a = 1.0; 99 | else if (_a < 0.0001) 100 | _a = 0.0001; 101 | double rnd = nextDouble(); 102 | return (1/_a)*tan(atan(10*_a)*(rnd))*0.1; 103 | } 104 | double nextExp(double _a) { 105 | if (_a > 1.0) 106 | _a = 1.0; 107 | else if (_a < 0.0001) 108 | _a = 0.0001; 109 | double _c=log(1.0-(0.999*_a)); 110 | double r=nextDouble()*0.999*_a; 111 | return (log(1.0-r)/_c); 112 | } 113 | private: 114 | unsigned long long state; 115 | unsigned long long seed; 116 | unsigned long long a=25214903917; 117 | unsigned long long c=11; 118 | unsigned long long m=0x0001000000000000ULL; 119 | }; 120 | 121 | #endif // RND_H 122 | -------------------------------------------------------------------------------- /src/textfield.cpp: -------------------------------------------------------------------------------- 1 | #include "plugin.hpp" 2 | #include "textfield.hpp" 3 | #include 4 | #include 5 | 6 | struct MTextFieldCopyItem : ui::MenuItem { 7 | WeakPtr textField; 8 | 9 | void onAction(const ActionEvent &e) override { 10 | if(!textField) 11 | return; 12 | textField->copyClipboard(); 13 | APP->event->setSelectedWidget(textField); 14 | } 15 | }; 16 | 17 | 18 | struct MTextFieldCutItem : ui::MenuItem { 19 | WeakPtr textField; 20 | 21 | void onAction(const ActionEvent &e) override { 22 | if(!textField) 23 | return; 24 | textField->cutClipboard(); 25 | APP->event->setSelectedWidget(textField); 26 | } 27 | }; 28 | 29 | 30 | struct MTextFieldPasteItem : ui::MenuItem { 31 | WeakPtr textField; 32 | 33 | void onAction(const ActionEvent &e) override { 34 | if(!textField) 35 | return; 36 | textField->pasteClipboard(); 37 | APP->event->setSelectedWidget(textField); 38 | } 39 | }; 40 | 41 | 42 | struct MTextFieldSelectAllItem : ui::MenuItem { 43 | WeakPtr textField; 44 | 45 | void onAction(const ActionEvent &e) override { 46 | if(!textField) 47 | return; 48 | textField->selectAll(); 49 | APP->event->setSelectedWidget(textField); 50 | } 51 | }; 52 | 53 | 54 | MTextField::MTextField() { 55 | fontPath=asset::plugin(pluginInstance,"res/FreeMonoBold.ttf"); 56 | } 57 | 58 | int MTextField::_bndIconLabelTextPosition(NVGcontext *ctx,float x,float y,float w,float h,float fontsize,const char *label,int px,int py) { 59 | std::shared_ptr font=APP->window->loadFont(fontPath); 60 | float bounds[4]; 61 | float pleft=BND_TEXT_RADIUS; 62 | if(!label) 63 | return -1; 64 | 65 | if(font&&font->handle>=0) { 66 | 67 | x+=pleft; 68 | y+=BND_WIDGET_HEIGHT-BND_TEXT_PAD_DOWN; 69 | 70 | nvgFontFaceId(ctx,font->handle); 71 | nvgFontSize(ctx,fontsize); 72 | nvgTextAlign(ctx,NVG_ALIGN_LEFT|NVG_ALIGN_BASELINE); 73 | 74 | w-=BND_TEXT_RADIUS+pleft; 75 | 76 | float asc,desc,lh; 77 | 78 | int nrows=nvgTextBreakLines(ctx,label,NULL,w,rows,_BND_MAX_ROWS); 79 | if(nrows==0) 80 | return 0; 81 | nvgTextBoxBounds(ctx,x,y,w,label,NULL,bounds); 82 | nvgTextMetrics(ctx,&asc,&desc,&lh); 83 | 84 | // calculate vertical position 85 | int row=_clamp((int)((float)(py-bounds[1])/lh),0,nrows-1); 86 | // search horizontal position 87 | int nglyphs=nvgTextGlyphPositions(ctx,x,y,rows[row].start,rows[row].end+1,glyphs,_BND_MAX_GLYPHS); 88 | int col,p=0; 89 | for(col=0;col0&&col font=APP->window->loadFont(fontPath); 119 | float pleft=BND_TEXT_RADIUS; 120 | int cbegin=std::min(cursor,selection); 121 | int cend=std::max(cursor,selection); 122 | float x=0; float y=0; float w=box.size.x; 123 | NVGcontext *ctx = APP->window->vg; 124 | if(font&&font->handle>=0) { 125 | 126 | x+=pleft; 127 | y+=BND_WIDGET_HEIGHT-BND_TEXT_PAD_DOWN; 128 | 129 | nvgFontFaceId(ctx,font->handle); 130 | nvgFontSize(ctx,fontSize); 131 | nvgTextAlign(ctx,NVG_ALIGN_LEFT|NVG_ALIGN_BASELINE); 132 | 133 | w-=BND_TEXT_RADIUS+pleft; 134 | 135 | if(cend>=cbegin) { 136 | int c0r,c1r; 137 | float c0x,c0y,c1x,c1y; 138 | float desc,lh; 139 | static NVGtextRow rows[_BND_MAX_ROWS]; 140 | int nrows=nvgTextBreakLines(ctx,text.c_str(),text.c_str()+cend+1,w,rows,_BND_MAX_ROWS); 141 | nvgTextMetrics(ctx,NULL,&desc,&lh); 142 | 143 | _bndCaretPosition(ctx,x,y,desc,lh,text.c_str()+cbegin,rows,nrows,&c0r,&c0x,&c0y); 144 | px=c0x; 145 | py=c0y; 146 | } 147 | } 148 | } 149 | void MTextField::_bndIconLabelCaret(NVGcontext *ctx,float x,float y,float w,float h,NVGcolor color,float fontsize,const char *label,NVGcolor caretcolor,int cbegin,int cend) { 150 | std::shared_ptr font=APP->window->loadFont(fontPath); 151 | float pleft=BND_TEXT_RADIUS; 152 | if(!label) 153 | return; 154 | 155 | if(font&&font->handle>=0) { 156 | 157 | x+=pleft; 158 | y+=BND_WIDGET_HEIGHT-BND_TEXT_PAD_DOWN; 159 | 160 | nvgFontFaceId(ctx,font->handle); 161 | nvgFontSize(ctx,fontsize); 162 | nvgTextAlign(ctx,NVG_ALIGN_LEFT|NVG_ALIGN_BASELINE); 163 | 164 | w-=BND_TEXT_RADIUS+pleft; 165 | 166 | if(cend>=cbegin) { 167 | int c0r,c1r; 168 | float c0x,c0y,c1x,c1y; 169 | float desc,lh; 170 | static NVGtextRow rows[_BND_MAX_ROWS]; 171 | int nrows=nvgTextBreakLines(ctx,label,label+cend+1,w,rows,_BND_MAX_ROWS); 172 | nvgTextMetrics(ctx,NULL,&desc,&lh); 173 | 174 | _bndCaretPosition(ctx,x,y,desc,lh,label+cbegin,rows,nrows,&c0r,&c0x,&c0y); 175 | _bndCaretPosition(ctx,x,y,desc,lh,label+cend,rows,nrows,&c1r,&c1x,&c1y); 176 | 177 | nvgBeginPath(ctx); 178 | if(cbegin==cend) { 179 | nvgFillColor(ctx,nvgRGBf(0.337,0.502,0.761)); 180 | nvgRect(ctx,c0x-1,c0y,2,lh+1); 181 | } else { 182 | nvgFillColor(ctx,caretcolor); 183 | if(c0r==c1r) { 184 | nvgRect(ctx,c0x-1,c0y,c1x-c0x+1,lh+1); 185 | } else { 186 | int blk=c1r-c0r-1; 187 | nvgRect(ctx,c0x-1,c0y,x+w-c0x+1,lh+1); 188 | nvgRect(ctx,x,c1y,c1x-x+1,lh+1); 189 | if(blk) 190 | nvgRect(ctx,x,c0y+lh,w,blk*lh+1); 191 | } 192 | } 193 | nvgFill(ctx); 194 | } 195 | 196 | nvgBeginPath(ctx); 197 | nvgFillColor(ctx,color); 198 | nvgTextBox(ctx,x,y,w,label,NULL); 199 | } 200 | } 201 | 202 | 203 | void MTextField::draw(const DrawArgs &args) { 204 | nvgScissor(args.vg,RECT_ARGS(args.clipBox)); 205 | 206 | int begin=std::min(cursor,selection); 207 | int end=std::max(cursor,selection); 208 | 209 | _bndIconLabelCaret(args.vg,0.0,0.0,box.size.x,box.size.y,nvgRGB(128,255,128),fontSize,text.c_str(),nvgRGBA(128,128,128,128),begin,end); 210 | 211 | // Draw placeholder text 212 | if(text.empty()) { 213 | _bndIconLabelCaret(args.vg,0.0,0.0,box.size.x,box.size.y,bndGetTheme()->textFieldTheme.itemColor,fontSize,placeholder.c_str(),bndGetTheme()->textFieldTheme.itemColor,0,-1); 214 | } 215 | 216 | nvgResetScissor(args.vg); 217 | } 218 | 219 | void MTextField::onDragHover(const DragHoverEvent &e) { 220 | OpaqueWidget::onDragHover(e); 221 | 222 | if(e.origin==this) { 223 | int pos=getTextPosition(e.pos); 224 | cursor=pos; 225 | } 226 | } 227 | 228 | void MTextField::onButton(const ButtonEvent &e) { 229 | OpaqueWidget::onButton(e); 230 | 231 | if(e.action==GLFW_PRESS&&e.button==GLFW_MOUSE_BUTTON_LEFT) { 232 | cursor=selection=getTextPosition(e.pos); 233 | } 234 | 235 | if(e.action==GLFW_PRESS&&e.button==GLFW_MOUSE_BUTTON_RIGHT) { 236 | createContextMenu(); 237 | e.consume(this); 238 | } 239 | } 240 | 241 | void MTextField::onSelectText(const SelectTextEvent &e) { 242 | if(e.codepoint<128) { 243 | std::string newText(1,(char)e.codepoint); 244 | insertText(newText); 245 | } 246 | e.consume(this); 247 | } 248 | 249 | void MTextField::split() { 250 | char prev=0; 251 | int rk=0; 252 | int start=0; 253 | for(int k=0;k0) { 266 | textRows[rk].begin=text.size(); 267 | textRows[rk++].end=text.size(); 268 | } 269 | currentMax=rk; 270 | } 271 | 272 | void MTextField::onSelectKey(const SelectKeyEvent &e) { 273 | if(e.action==GLFW_PRESS||e.action==GLFW_REPEAT) { 274 | // Backspace 275 | if(e.key==GLFW_KEY_BACKSPACE&&(e.mods&RACK_MOD_MASK)==0) { 276 | if(cursor==selection) { 277 | cursor=std::max(cursor-1,0); 278 | } 279 | insertText(""); 280 | e.consume(this); 281 | } 282 | // Ctrl+Backspace 283 | if(e.key==GLFW_KEY_BACKSPACE&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 284 | if(cursor==selection) { 285 | cursorToPrevWord(); 286 | } 287 | insertText(""); 288 | e.consume(this); 289 | } 290 | // Delete 291 | if(e.key==GLFW_KEY_DELETE&&(e.mods&RACK_MOD_MASK)==0) { 292 | if(cursor==selection) { 293 | cursor=std::min(cursor+1,(int)text.size()); 294 | } 295 | insertText(""); 296 | e.consume(this); 297 | } 298 | // Ctrl+Delete 299 | if(e.key==GLFW_KEY_DELETE&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 300 | if(cursor==selection) { 301 | cursorToNextWord(); 302 | } 303 | insertText(""); 304 | e.consume(this); 305 | } 306 | // Left 307 | if(e.key==GLFW_KEY_LEFT) { 308 | if((e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 309 | cursorToPrevWord(); 310 | } else { 311 | cursor=std::max(cursor-1,0); 312 | } 313 | if(!(e.mods&GLFW_MOD_SHIFT)) { 314 | selection=cursor; 315 | } 316 | e.consume(this); 317 | } 318 | // Right 319 | if(e.key==GLFW_KEY_RIGHT) { 320 | if((e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 321 | cursorToNextWord(); 322 | } else { 323 | cursor=std::min(cursor+1,(int)text.size()); 324 | } 325 | if(!(e.mods&GLFW_MOD_SHIFT)) { 326 | selection=cursor; 327 | } 328 | e.consume(this); 329 | } 330 | 331 | if(e.key==GLFW_KEY_UP) { 332 | if(cursor>0) { 333 | //INFO("cursor=%d",cursor); 334 | split(); 335 | for(int k=0;k=textRows[k].begin&&cursor<=textRows[k].end) { 340 | //INFO("row=%d",k); 341 | if(k>0) { 342 | int pos=std::min(cursor-textRows[k].begin,textRows[k-1].end-textRows[k-1].begin); 343 | cursor=textRows[k-1].begin+pos; 344 | } 345 | break; 346 | } 347 | } 348 | 349 | if(!(e.mods&GLFW_MOD_SHIFT)) { 350 | selection=cursor; 351 | } 352 | } 353 | e.consume(this); 354 | } 355 | 356 | if(e.key==GLFW_KEY_DOWN) { 357 | if(cursor=textRows[k].begin&&cursor<=textRows[k].end) { 364 | //INFO("row=%d",k); 365 | if(k=textRows[k].begin&&cursor<=textRows[k].end) { 390 | selection=cursor=textRows[k].begin; 391 | } 392 | } 393 | } else if((e.mods&RACK_MOD_MASK)==GLFW_MOD_SHIFT) { 394 | split(); 395 | for(int k=0;k=textRows[k].begin&&cursor<=textRows[k].end) { 397 | cursor=textRows[k].begin; 398 | } 399 | } 400 | } 401 | e.consume(this); 402 | } 403 | 404 | if(e.key==GLFW_KEY_END) { 405 | if((e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 406 | selection=text.size(); 407 | cursor=text.size(); 408 | } else if((e.mods&RACK_MOD_MASK)==(GLFW_MOD_SHIFT|RACK_MOD_CTRL)) { 409 | cursor=text.size(); 410 | } else if((e.mods&RACK_MOD_MASK)==0) { 411 | split(); 412 | for(int k=0;k=textRows[k].begin&&cursor<=textRows[k].end) { 414 | selection=cursor=textRows[k].end; 415 | } 416 | } 417 | } else if((e.mods&RACK_MOD_MASK)==GLFW_MOD_SHIFT) { 418 | split(); 419 | for(int k=0;k=textRows[k].begin&&cursor<=textRows[k].end) { 421 | cursor=textRows[k].end; 422 | } 423 | } 424 | } 425 | e.consume(this); 426 | } 427 | 428 | 429 | /* 430 | // Home 431 | if(e.key==GLFW_KEY_HOME&&(e.mods&RACK_MOD_MASK)==0) { 432 | selection=cursor=0; 433 | e.consume(this); 434 | } 435 | // Shift+Home 436 | if(e.key==GLFW_KEY_HOME&&(e.mods&RACK_MOD_MASK)==GLFW_MOD_SHIFT) { 437 | cursor=0; 438 | e.consume(this); 439 | } 440 | // End 441 | if(e.key==GLFW_KEY_END&&(e.mods&RACK_MOD_MASK)==0) { 442 | selection=cursor=text.size(); 443 | e.consume(this); 444 | } 445 | // Shift+End 446 | if(e.key==GLFW_KEY_END&&(e.mods&RACK_MOD_MASK)==GLFW_MOD_SHIFT) { 447 | cursor=text.size(); 448 | e.consume(this); 449 | } 450 | */ 451 | // Ctrl+V 452 | if(e.keyName=="v"&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 453 | pasteClipboard(false); 454 | e.consume(this); 455 | } 456 | // Ctrl+X 457 | if(e.keyName=="x"&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 458 | cutClipboard(false); 459 | e.consume(this); 460 | } 461 | // Ctrl+C 462 | if(e.keyName=="c"&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 463 | copyClipboard(false); 464 | e.consume(this); 465 | } 466 | // Ctrl+A 467 | if(e.keyName=="a"&&(e.mods&RACK_MOD_MASK)==RACK_MOD_CTRL) { 468 | selectAll(); 469 | e.consume(this); 470 | } 471 | // Enter 472 | if((e.key==GLFW_KEY_ENTER||e.key==GLFW_KEY_KP_ENTER)&&(e.mods&RACK_MOD_MASK)==0) { 473 | if(multiline) { 474 | insertText("\n"); 475 | } else { 476 | ActionEvent eAction; 477 | onAction(eAction); 478 | } 479 | e.consume(this); 480 | } 481 | // Tab 482 | if(e.key==GLFW_KEY_TAB&&(e.mods&RACK_MOD_MASK)==0) { 483 | if(nextField) 484 | APP->event->setSelectedWidget(nextField); 485 | e.consume(this); 486 | } 487 | // Shift-Tab 488 | if(e.key==GLFW_KEY_TAB&&(e.mods&RACK_MOD_MASK)==GLFW_MOD_SHIFT) { 489 | if(prevField) 490 | APP->event->setSelectedWidget(prevField); 491 | e.consume(this); 492 | } 493 | // Consume all printable keys 494 | if(e.keyName!="") { 495 | e.consume(this); 496 | } 497 | float px,py; 498 | getCursorPosition(px,py); 499 | //INFO("%f %f %f %f %f",px,py,scroll->offset.x,scroll->offset.y,scroll->box.size.y); 500 | while(py>scroll->box.size.y-24+scroll->offset.y) scroll->offset.y+=10; 501 | while(pyoffset.y) scroll->offset.y-=10; 502 | if(scroll->offset.y<0) scroll->offset.y = 0; 503 | 504 | while(px>scroll->box.size.x-24+scroll->offset.x) scroll->offset.x+=10; 505 | while(pxoffset.x) scroll->offset.x-=10; 506 | if(scroll->offset.x<0) scroll->offset.x = 0; 507 | 508 | assert(0<=cursor); 509 | assert(cursor<=(int)text.size()); 510 | assert(0<=selection); 511 | assert(selection<=(int)text.size()); 512 | } 513 | } 514 | 515 | int MTextField::getTextPosition(math::Vec mousePos) { 516 | return _bndIconLabelTextPosition(APP->window->vg,0.0,0.0,box.size.x,box.size.y,fontSize,text.c_str(),mousePos.x,mousePos.y); 517 | } 518 | 519 | std::string MTextField::getText() { 520 | return text; 521 | } 522 | 523 | void MTextField::setText(std::string text) { 524 | if(this->text!=text) { 525 | this->text=text; 526 | // ChangeEvent 527 | ChangeEvent eChange; 528 | onChange(eChange); 529 | } 530 | selection=cursor=text.size(); 531 | } 532 | 533 | void MTextField::selectAll() { 534 | cursor=text.size(); 535 | selection=0; 536 | } 537 | 538 | std::string MTextField::getSelectedText() { 539 | int begin=std::min(cursor,selection); 540 | int len=std::abs(selection-cursor); 541 | return text.substr(begin,len); 542 | } 543 | 544 | void MTextField::insertText(std::string text) { 545 | bool changed=false; 546 | if(cursor!=selection) { 547 | // Delete selected text 548 | int begin=std::min(cursor,selection); 549 | int len=std::abs(selection-cursor); 550 | this->text.erase(begin,len); 551 | cursor=selection=begin; 552 | changed=true; 553 | } 554 | if(!text.empty()) { 555 | this->text.insert(cursor,text); 556 | cursor+=text.size(); 557 | selection=cursor; 558 | changed=true; 559 | } 560 | if(changed) { 561 | ChangeEvent eChange; 562 | onChange(eChange); 563 | } 564 | } 565 | 566 | void MTextField::copyClipboard(bool menu) { 567 | if(cursor==selection) 568 | return; 569 | glfwSetClipboardString(APP->window->win,getSelectedText().c_str()); 570 | } 571 | 572 | void MTextField::cutClipboard(bool menu) { 573 | copyClipboard(); 574 | insertText(""); 575 | } 576 | 577 | void MTextField::pasteClipboard(bool menu) { 578 | const char *newText=glfwGetClipboardString(APP->window->win); 579 | if(!newText) 580 | return; 581 | std::string strText(newText); 582 | strText.erase(std::remove(strText.begin(), strText.end(), 0x0d), strText.end()); 583 | insertText(strText); 584 | } 585 | 586 | void MTextField::cursorToPrevWord() { 587 | size_t pos=text.rfind(' ',std::max(cursor-2,0)); 588 | if(pos==std::string::npos) 589 | cursor=0; 590 | else 591 | cursor=std::min((int)pos+1,(int)text.size()); 592 | } 593 | 594 | void MTextField::cursorToNextWord() { 595 | size_t pos=text.find(' ',std::min(cursor+1,(int)text.size())); 596 | if(pos==std::string::npos) 597 | pos=text.size(); 598 | cursor=pos; 599 | } 600 | 601 | void MTextField::createContextMenu() { 602 | menu=createMenu(); 603 | 604 | MTextFieldCutItem *cutItem=new MTextFieldCutItem; 605 | cutItem->text="Cut"; 606 | cutItem->rightText=RACK_MOD_CTRL_NAME 607 | "+X"; 608 | cutItem->textField=this; 609 | menu->addChild(cutItem); 610 | 611 | MTextFieldCopyItem *copyItem=new MTextFieldCopyItem; 612 | copyItem->text="Copy"; 613 | copyItem->rightText=RACK_MOD_CTRL_NAME 614 | "+C"; 615 | copyItem->textField=this; 616 | menu->addChild(copyItem); 617 | 618 | MTextFieldPasteItem *pasteItem=new MTextFieldPasteItem; 619 | pasteItem->text="Paste"; 620 | pasteItem->rightText=RACK_MOD_CTRL_NAME 621 | "+V"; 622 | pasteItem->textField=this; 623 | menu->addChild(pasteItem); 624 | 625 | MTextFieldSelectAllItem *selectAllItem=new MTextFieldSelectAllItem; 626 | selectAllItem->text="Select all"; 627 | selectAllItem->rightText=RACK_MOD_CTRL_NAME 628 | "+A"; 629 | selectAllItem->textField=this; 630 | menu->addChild(selectAllItem); 631 | } 632 | -------------------------------------------------------------------------------- /src/textfield.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // From Rack ui but modified to have more possibilities to customize. 4 | // Uses also some functions of the blender utilities directly to provide more lines in the text. 5 | // Adds the feature to move up and down using the arrow keys. 6 | 7 | //#include "plugin.hpp" 8 | #define _BND_MAX_ROWS 100 9 | #define _BND_MAX_GLYPHS 10240 10 | struct TextRow { 11 | int begin; 12 | int end; 13 | }; 14 | struct MTextField : OpaqueWidget { 15 | ScrollWidget *scroll; 16 | std::string text; 17 | std::string placeholder; 18 | /** Masks text with "*". */ 19 | bool password=false; 20 | bool multiline=false; 21 | int fontSize = 10; 22 | /** The index of the text cursor */ 23 | int cursor=0; 24 | /** The index of the other end of the selection. 25 | If nothing is selected, this is equal to `cursor`. 26 | */ 27 | int selection=0; 28 | 29 | int _bndIconLabelTextPosition(NVGcontext *ctx,float x,float y,float w,float h,float fontsize,const char *label,int px,int py); 30 | 31 | void _bndCaretPosition(NVGcontext *ctx,float x,float y,float desc,float lineHeight,const char *caret,NVGtextRow *rows,int nrows,int *cr,float *cx,float *cy); 32 | 33 | void _bndIconLabelCaret(NVGcontext *ctx,float x,float y,float w,float h,NVGcolor color,float fontsize,const char *label,NVGcolor caretcolor,int cbegin,int cend); 34 | 35 | /** For Tab and Shift-Tab focusing. 36 | */ 37 | widget::Widget *prevField=NULL; 38 | widget::Widget *nextField=NULL; 39 | ui::Menu *menu; 40 | std::basic_string fontPath; 41 | NVGtextRow rows[_BND_MAX_ROWS]; 42 | TextRow textRows[_BND_MAX_ROWS]; 43 | NVGglyphPosition glyphs[_BND_MAX_GLYPHS]; 44 | float _clamp(float v,float mn,float mx) { 45 | return (v>mx)?mx:(v