diff --git a/JuceLibraryCode/AppConfig.h b/JuceLibraryCode/AppConfig.h index c729d3d..754a863 100644 --- a/JuceLibraryCode/AppConfig.h +++ b/JuceLibraryCode/AppConfig.h @@ -390,13 +390,13 @@ #define JucePlugin_EditorRequiresKeyboardFocus 0 #endif #ifndef JucePlugin_Version - #define JucePlugin_Version 1.0.1 + #define JucePlugin_Version 1.1.0 #endif #ifndef JucePlugin_VersionCode - #define JucePlugin_VersionCode 0x10001 + #define JucePlugin_VersionCode 0x10100 #endif #ifndef JucePlugin_VersionString - #define JucePlugin_VersionString "1.0.1" + #define JucePlugin_VersionString "1.1.0" #endif #ifndef JucePlugin_VSTUniqueID #define JucePlugin_VSTUniqueID JucePlugin_PluginCode diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h index ab8e813..275ce61 100644 --- a/JuceLibraryCode/JuceHeader.h +++ b/JuceLibraryCode/JuceHeader.h @@ -50,7 +50,7 @@ namespace ProjectInfo { const char* const projectName = "eBeamer"; const char* const companyName = "Polimi ISPL - Eventide"; - const char* const versionString = "1.0.1"; - const int versionNumber = 0x10001; + const char* const versionString = "1.1.0"; + const int versionNumber = 0x10100; } #endif diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index ca42e99..82f3979 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ A JUCE-based VST3 plug-in and standalone application to support the [eStick](htt - Just want to try out the VST or the app? Go the the [release](https://github.com/listensmart/ebeamer/releases) page. - If you're not familiar with JUCE, [this](https://juce.com/learn) is a good place to start. - If you're developing on Windows, download and extract the [ASIO ASK](https://www.steinberg.net/en/company/developers.html) in the *ASIO/* folder. +- Don't have the eSticks yet? Start developing with the [eStick simulator](https://github.com/luca-bondi/estick-simulator). ## Contributing - Any contribution to the project is highly appreciated! Get in touch to know more. diff --git a/Source/Beamformer.cpp b/Source/Beamformer.cpp index 44e64d6..1d168df 100644 --- a/Source/Beamformer.cpp +++ b/Source/Beamformer.cpp @@ -1,124 +1,161 @@ /* - Beamforming processing class + Beamforming processing class Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #include "Beamformer.h" BeamformerDoa::BeamformerDoa(Beamformer &b, - int numDoas_, + int numDoaHor_, + int numDoaVer_, float sampleRate_, int numActiveInputChannels, int firLen, - std::shared_ptr fft_) : beamformer(b) { - - numDoas = numDoas_; + std::shared_ptr fft_) : Thread("DOA"), beamformer(b) { + + numDoaHor = numDoaHor_; + numDoaVer = numDoaVer_; fft = fft_; sampleRate = sampleRate_; - + /** Initialize levels and FIR */ - doaLevels.resize(numDoas, -100); - doaFirFFT.resize(numDoas); - + doaLevels.resize(numDoaVer,numDoaHor); + doaLevels.setConstant(-100); + doaFirFFT.resize(numDoaHor*numDoaVer); + /** Allocate inputBuffer */ inputBuffer = AudioBufferFFT(numActiveInputChannels, fft); - + /** Allocate convolution buffer */ convolutionBuffer = AudioBufferFFT(1, fft); - + /** Allocate DOA beam */ doaBeam.setSize(1, fft->getSize()); - + /** Compute FIR for DOA estimation */ AudioBuffer tmpFir(numActiveInputChannels, firLen); - BeamParameters tmpBeamParams{0, 0}; - for (auto dirIdx = 0; dirIdx < numDoas; dirIdx++) { - tmpBeamParams.doa = -1 + (2. / (numDoas - 1) * dirIdx); - b.getFir(tmpFir, tmpBeamParams, 1); - doaFirFFT[dirIdx] = AudioBufferFFT(numActiveInputChannels, fft); - doaFirFFT[dirIdx].setTimeSeries(tmpFir); - doaFirFFT[dirIdx].prepareForConvolution(); + BeamParameters tmpBeamParams{0,0,0}; + for (auto vDirIdx = 0; vDirIdx < numDoaVer; vDirIdx++) { + if (numDoaVer > 1){ + tmpBeamParams.doaY = -1 + (2. / (numDoaVer - 1) * vDirIdx); + } + for (auto hDirIdx = 0; hDirIdx < numDoaHor; hDirIdx++) { + tmpBeamParams.doaX = -1 + (2. / (numDoaHor - 1) * hDirIdx); + b.getFir(tmpFir, tmpBeamParams, 1); + auto dirIdx = vDirIdx * numDoaHor + hDirIdx; + doaFirFFT[dirIdx] = AudioBufferFFT(numActiveInputChannels, fft); + doaFirFFT[dirIdx].setTimeSeries(tmpFir); + doaFirFFT[dirIdx].prepareForConvolution(); + } } } -BeamformerDoa::~BeamformerDoa() { -} - -void BeamformerDoa::timerCallback() { - - beamformer.getDoaInputBuffer(inputBuffer); - - /** Compute DOA levels */ - for (auto dirIdx = 0; dirIdx < numDoas; dirIdx++) { - doaBeam.clear(); - for (auto inCh = 0; inCh < inputBuffer.getNumChannels(); inCh++) { - /** Convolve inputs and DOA FIR */ - convolutionBuffer.convolve(0, inputBuffer, inCh, doaFirFFT[dirIdx], inCh); - convolutionBuffer.addToTimeSeries(0, doaBeam, 0); +void BeamformerDoa::run() { + + while (!threadShouldExit()){ + + const auto startTick = Time::getHighResolutionTicks(); + + beamformer.getDoaInputBuffer(inputBuffer); + + /** Compute DOA levels */ + for (auto vDirIdx = 0; vDirIdx < numDoaVer; vDirIdx++) { + for (auto hDirIdx = 0; hDirIdx < numDoaHor; hDirIdx++) { + auto dirIdx = vDirIdx * numDoaHor + hDirIdx; + doaBeam.clear(); + for (auto inCh = 0; inCh < inputBuffer.getNumChannels(); inCh++) { + /** Convolve inputs and DOA FIR */ + convolutionBuffer.convolve(0, inputBuffer, inCh, doaFirFFT[dirIdx], inCh); + convolutionBuffer.addToTimeSeries(0, doaBeam, 0); + } + + const Range minMax = FloatVectorOperations::findMinAndMax(doaBeam.getReadPointer(0), + doaBeam.getNumSamples()); + const float dirEnergy = jmax(abs(minMax.getStart()), abs(minMax.getEnd())); + const float dirEnergyDb = Decibels::gainToDecibels(dirEnergy); + doaLevels(vDirIdx,hDirIdx) = dirEnergyDb; + } } - - const Range minMax = FloatVectorOperations::findMinAndMax(doaBeam.getReadPointer(0), - doaBeam.getNumSamples()); - const float dirEnergy = jmax(abs(minMax.getStart()), abs(minMax.getEnd())); - const float dirEnergyDb = Decibels::gainToDecibels(dirEnergy); - doaLevels[dirIdx] = dirEnergyDb; + beamformer.setDoaEnergy(doaLevels); + + const auto endTick = Time::getHighResolutionTicks(); + const float elapsedTime = Time::highResolutionTicksToSeconds(endTick-startTick); + const float expectedPeriod = 1.f/ENERGY_UPDATE_FREQ; + if (elapsedTime < expectedPeriod){ + Time::waitForMillisecondCounter(Time::getMillisecondCounter() + (expectedPeriod-elapsedTime)); + } + } +} - beamformer.setDoaEnergy(doaLevels); - +BeamformerDoa::~BeamformerDoa(){ + } // ============================================================================== -Beamformer::Beamformer(int numBeams_, int numDoas_) { - +Beamformer::Beamformer(int numBeams_, MicConfig mic, double sampleRate_, int maximumExpectedSamplesPerBlock_) { + numBeams = numBeams_; - numDoas = numDoas_; - + numDoaVer = isLinearArray(mic) ? 1 : NUM_DOAY; + numDoaHor = NUM_DOAX; + micConfig = mic; + sampleRate = sampleRate_; + maximumExpectedSamplesPerBlock = maximumExpectedSamplesPerBlock_; + + /** Alpha for FIR update */ + alpha = 1 - exp(-(maximumExpectedSamplesPerBlock / sampleRate) / firUpdateTimeConst); + firIR.resize(numBeams); firFFT.resize(numBeams); - -} - -Beamformer::~Beamformer() { - -} - -MicConfig Beamformer::getMicConfig() const { - return micConfig; -} - -void Beamformer::setMicConfig(MicConfig micConfig_) { - micConfig = micConfig_; -} - -void Beamformer::initAlg() { - + + /** Distance between microphones in eSticks*/ + const float micDistX = 0.03; + const float micDistY = 0.03; + /** Determine configuration parameters */ switch (micConfig) { - case LMA_1ESTICK: + case ULA_1ESTICK: numMic = 16; + numRows = 1; + break; + case ULA_2ESTICK: + numMic = 32; + numRows = 1; break; - case LMA_2ESTICK: + case URA_2ESTICK: numMic = 32; + numRows = 2; + break; + case ULA_3ESTICK: + numMic = 48; + numRows = 1; break; - case LMA_3ESTICK: + case URA_3ESTICK: numMic = 48; + numRows = 3; break; - case LMA_4ESTICK: + case ULA_4ESTICK: numMic = 64; + numRows = 1; + break; + case URA_4ESTICK: + numMic = 64; + numRows = 4; + break; + case URA_2x2ESTICK: + numMic = 64; + numRows = 2; break; } - /** Distance between microphones in eSticks*/ - const float micDist = 0.03; - alg = std::make_unique(micDist, numMic, sampleRate, soundspeed); - + alg = std::make_unique(micDistX, micDistY, numMic, numRows, sampleRate, soundspeed); + firLen = alg->getFirLen(); - + /** Create shared FFT object */ fft = std::make_shared(ceil(log2(firLen + maximumExpectedSamplesPerBlock - 1))); - + /** Allocate FIR filters */ for (auto &f : firIR) { f = AudioBuffer(numMic, firLen); @@ -128,21 +165,21 @@ void Beamformer::initAlg() { f = AudioBufferFFT(numMic, fft); f.clear(); } - + /** Allocate input buffers */ inputBuffer = AudioBufferFFT(numMic, fft); - + /** Allocate convolution buffer */ convolutionBuffer = AudioBufferFFT(1, fft); - + /** Allocate beam output buffer */ beamBuffer.setSize(numBeams, convolutionBuffer.getNumSamples() / 2); beamBuffer.clear(); - + /** Allocate DOA input buffer */ doaInputBuffer.setSize(numMic, maximumExpectedSamplesPerBlock); doaInputBuffer.clear(); - + /** Set DOA input Filter */ doaBPFilters.clear(); doaBPFilters.resize(numMic); @@ -150,31 +187,32 @@ void Beamformer::initAlg() { for (auto &f : doaBPFilters) { f.setCoefficients(doaIIRCoeff); } - + /** Prepare and start DOA thread */ - doaThread = std::make_unique(*this, numDoas, sampleRate, numMic, firLen, fft); - doaThread->startTimerHz(doaUpdateFrequency); + doaThread = std::make_unique(*this, numDoaHor, numDoaVer, sampleRate, numMic, firLen, fft); + doaThread->startThread(); + } -void Beamformer::prepareToPlay(double sampleRate_, int maximumExpectedSamplesPerBlock_) { - - sampleRate = sampleRate_; - maximumExpectedSamplesPerBlock = maximumExpectedSamplesPerBlock_; - - /** Alpha for FIR update */ - alpha = 1 - exp(-(maximumExpectedSamplesPerBlock / sampleRate) / firUpdateTimeConst); +Beamformer::~Beamformer() { + doaThread->stopThread(500); +} - initAlg(); +MicConfig Beamformer::getMicConfig() const { + return micConfig; } + void Beamformer::setBeamParameters(int beamIdx, const BeamParameters &beamParams) { + if (alg == nullptr) + return; alg->getFir(firIR[beamIdx], beamParams, alpha); firFFT[beamIdx].setTimeSeries(firIR[beamIdx]); firFFT[beamIdx].prepareForConvolution(); } void Beamformer::processBlock(const AudioBuffer &inBuffer) { - + { GenericScopedLock lock(doaInputBufferLock); for (auto chIdx = 0; chIdx < jmin(numMic, inBuffer.getNumChannels()); chIdx++) { @@ -182,11 +220,11 @@ void Beamformer::processBlock(const AudioBuffer &inBuffer) { doaBPFilters[chIdx].processSamples(doaInputBuffer.getWritePointer(chIdx), inBuffer.getNumSamples()); } } - + /** Compute inputs FFT */ inputBuffer.setTimeSeries(inBuffer); inputBuffer.prepareForConvolution(); - + for (auto beamIdx = 0; beamIdx < numBeams; beamIdx++) { for (auto inCh = 0; inCh < inputBuffer.getNumChannels(); inCh++) { /** Convolve inputs and FIR */ @@ -195,7 +233,7 @@ void Beamformer::processBlock(const AudioBuffer &inBuffer) { convolutionBuffer.addToTimeSeries(0, beamBuffer, beamIdx); } } - + } void Beamformer::getFir(AudioBuffer &fir, const BeamParameters ¶ms, float alpha) const { @@ -212,7 +250,6 @@ void Beamformer::getBeams(AudioBuffer &outBuffer) { jassert(outBuffer.getNumChannels() == numBeams); auto numSplsOut = outBuffer.getNumSamples(); auto numSplsShift = beamBuffer.getNumSamples() - numSplsOut; - AudioBuffer tmp(1, numSplsShift); for (auto beamIdx = 0; beamIdx < numBeams; beamIdx++) { /** Copy beamBuffer to outBuffer */ outBuffer.copyFrom(beamIdx, 0, beamBuffer, beamIdx, 0, numSplsOut); @@ -223,36 +260,12 @@ void Beamformer::getBeams(AudioBuffer &outBuffer) { } } -void Beamformer::setDoaEnergy(const std::vector &energy) { +void Beamformer::setDoaEnergy(const Mtx &energy) { GenericScopedLock lock(doaLock); doaLevels = energy; } -void Beamformer::getDoaEnergy(std::vector &outDoaLevels) const { +void Beamformer::getDoaEnergy(Mtx &outDoaLevels) const { GenericScopedLock lock(doaLock); outDoaLevels = doaLevels; } - -void Beamformer::releaseResources() { - - /** Release FIR filters */ - for (auto &f : firIR) { - f.setSize(0, 0); - } - for (auto &f : firFFT) { - f.setSize(0, 0); - } - - /** Release input buffer */ - inputBuffer.setSize(0, 0); - - /** Release convolution buffer */ - convolutionBuffer.setSize(0, 0); - - /** Release beam buffer */ - beamBuffer.setSize(0, 0); - - /** Stop DOA thread */ - doaThread.reset(); - -} diff --git a/Source/Beamformer.h b/Source/Beamformer.h index ba784e0..865df2c 100644 --- a/Source/Beamformer.h +++ b/Source/Beamformer.h @@ -18,13 +18,14 @@ class Beamformer; -/** Class that computes periodically the Direction of Arrival of sound +/** Thread that computes periodically the Direction of Arrival of sound */ -class BeamformerDoa : public Timer { +class BeamformerDoa : public Thread { public: BeamformerDoa(Beamformer &b, - int numDoas_, + int numDoaHor_, + int numDoaVer_, float sampleRate_, int numActiveInputChannels, int firLen, @@ -32,15 +33,18 @@ class BeamformerDoa : public Timer { ~BeamformerDoa(); - void timerCallback() override; + void run() override; private: /** Reference to the Beamformer */ Beamformer &beamformer; - /** Number of directions of arrival */ - int numDoas; + /** Number of directions of arrival, horizontal axis */ + int numDoaHor; + + /** Number of directions of arrival, vertical axis */ + int numDoaVer; /** Sampling frequency [Hz] */ float sampleRate; @@ -61,7 +65,7 @@ class BeamformerDoa : public Timer { AudioBuffer doaBeam; /** DOA levels [dB] */ - std::vector doaLevels; + Mtx doaLevels; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamformerDoa); @@ -76,27 +80,18 @@ class Beamformer { /** Initialize the Beamformer with a set of static parameters. @param numBeams: number of beams the beamformer has to compute - @param numDoas: number of directions of arrival to compute the energy + @param mic: microphone configuration + @param sampleRate: + @param maximumExpectedSamplesPerBlock: */ - Beamformer(int numBeams, int numDoas); + Beamformer(int numBeams, MicConfig mic, double sampleRate, int maximumExpectedSamplesPerBlock); /** Destructor. */ ~Beamformer(); - - /** Set microphone configuration */ - void setMicConfig(MicConfig micConfig_); - + /** Get microphone configuration */ MicConfig getMicConfig() const; - /** Set the parameters before execution. - - To be called inside AudioProcessor::prepareToPlay. - This method allocates the needed buffers and performs the necessary pre-calculations that are dependent - on sample rate, buffer size and channel configurations. - */ - void prepareToPlay(double sampleRate_, int maximumExpectedSamplesPerBlock_); - /** Process a new block of samples. To be called inside AudioProcessor::processBlock. @@ -121,19 +116,14 @@ class Beamformer { void getFir(AudioBuffer &fir, const BeamParameters ¶ms, float alpha = 1) const; /** Copy the estimated energy contribution from the directions of arrival */ - void getDoaEnergy(std::vector &energy) const; + void getDoaEnergy(Mtx &energy) const; /** Set the estimated energy contribution from the directions of arrival */ - void setDoaEnergy(const std::vector &energy); + void setDoaEnergy(const Mtx &energy); /** Get last doa filtered input buffer */ void getDoaInputBuffer(AudioBufferFFT &dst) const; - /** Release not needed resources. - - To be called inside AudioProcessor::releaseResources. - */ - void releaseResources(); private: @@ -150,12 +140,16 @@ class Beamformer { /** Number of microphones */ int numMic = 16; + + /** Number of rows */ + int numRows = 1; /** Number of beams */ int numBeams; /** Number of directions of arrival */ - int numDoas; + int numDoaHor; + int numDoaVer; /** Beamforming algorithm */ std::unique_ptr alg; @@ -185,7 +179,7 @@ class Beamformer { float alpha = 1; /** Microphones configuration */ - MicConfig micConfig = LMA_1ESTICK; + MicConfig micConfig = ULA_1ESTICK; /** Initialize the beamforming algorithm */ void initAlg(); @@ -197,7 +191,7 @@ class Beamformer { const float doaUpdateFrequency = 10; /** DOA levels [dB] */ - std::vector doaLevels; + Mtx doaLevels; /** DOA Band pass Filters */ std::vector doaBPFilters; diff --git a/Source/BeamformingAlgorithms.cpp b/Source/BeamformingAlgorithms.cpp index 03789cd..adec4b1 100644 --- a/Source/BeamformingAlgorithms.cpp +++ b/Source/BeamformingAlgorithms.cpp @@ -9,15 +9,19 @@ namespace DAS { - FarfieldLMA::FarfieldLMA(float micDist_, int numMic_, float fs_, float soundspeed_) { + FarfieldURA::FarfieldURA(float micDistX_, float micDistY_, + int numMic_, int numRows_, float fs_, float soundspeed_) { - micDist = micDist_; + micDistX = micDistX_; + micDistY = micDistY_; numMic = numMic_; + numRows = numRows_; + numMicPerRow = numMic/numRows; fs = fs_; soundspeed = soundspeed_; commonDelay = 64; - firLen = ceil(numMic * micDist / soundspeed * fs) + 2 * commonDelay; + firLen = ceil(jmax(numMic/numRows * micDistX,numRows * micDistY) / soundspeed * fs) + 2 * commonDelay; fft = std::make_unique(ceil(log2(firLen))); @@ -28,18 +32,28 @@ namespace DAS { } - int FarfieldLMA::getFirLen() const { + int FarfieldURA::getFirLen() const { return firLen; } - void FarfieldLMA::getFir(AudioBuffer &fir, const BeamParameters ¶ms, float alpha) const { + void FarfieldURA::getFir(AudioBuffer &fir, const BeamParameters ¶ms, float alpha) const { /** Angle in radians (0 front, pi/2 source closer to last channel, -pi/2 source closer to first channel */ - const float angleRad = (params.doa + 1) * pi / 2; + const float angleRadX = params.doaX * pi / 2; + /** Angle in radians (0 front, pi/2 source closer to last channel, -pi/2 source closer to first channel */ + const float angleRadY = params.doaY * pi / 2; + /** Delay between adjacent microphones [s] */ + const float deltaX = sin(angleRadX) * micDistX / soundspeed; /** Delay between adjacent microphones [s] */ - const float delta = -cos(angleRad) * micDist / soundspeed; - /** Compute delays for each microphone [s] */ - Vec micDelays = delta * Vec::LinSpaced(numMic, 0, numMic - 1); + const float deltaY = sin(angleRadY) * micDistY / soundspeed; + /** Compute delays for each microphone, X component [s] */ + const Vec micDelaysX = deltaX * Vec::LinSpaced(numMicPerRow, 0, numMicPerRow - 1); + /** Compute delays for each microphone, Y component [s] */ + const Vec micDelaysY = deltaY * Vec::LinSpaced(numRows, 0, numRows - 1); + /** Matrix of delays. Eigen is column-first.*/ + Mtx micDelaysMtx = micDelaysX.replicate(1,numRows) + micDelaysY.transpose().replicate(numMicPerRow,1); + /** Vector of delays */ + Eigen::Map micDelays(micDelaysMtx.data(),micDelaysMtx.size()); /** Compensate for minimum delay and apply common delay */ micDelays.array() += -micDelays.minCoeff() + commonDelay / fs; /** Compute the fractional delays in frequency domain */ @@ -47,11 +61,21 @@ namespace DAS { /** Compute how many microphones are muted at each end */ - const int inactiveMicAtBorder = roundToInt((numMic / 2 - 1) * params.width); - /** Generate the mask of active microphones */ - Vec micGains = Vec::Ones(numMic); - micGains.head(inactiveMicAtBorder).array() = 0; - micGains.tail(inactiveMicAtBorder).array() = 0; + const int inactiveMicAtBorderX = roundToInt((numMicPerRow / 2 - 1) * params.width); + const int inactiveMicAtBorderY = roundToInt((numRows / 2 - 1) * params.width); + /** Generate the mask of active microphones. Eigen is column-first.*/ + Mtx micGainsMtx = Mtx::Ones(numMicPerRow,numRows); + for (auto colIdx = 0; colIdx < numRows; colIdx++){ + if ((colIdx < inactiveMicAtBorderY) || (colIdx>=numRows-inactiveMicAtBorderY)){ + micGainsMtx.col(colIdx).setZero(); + }else{ + micGainsMtx.col(colIdx).head(inactiveMicAtBorderX).array() = 0; + micGainsMtx.col(colIdx).tail(inactiveMicAtBorderX).array() = 0; + } + } + + Eigen::Map micGains(micGainsMtx.data(),micGainsMtx.size()); + /** Normalize the power */ micGains.array() *= referencePower / micGains.sum(); diff --git a/Source/BeamformingAlgorithms.h b/Source/BeamformingAlgorithms.h index e272ba3..8031e88 100644 --- a/Source/BeamformingAlgorithms.h +++ b/Source/BeamformingAlgorithms.h @@ -11,12 +11,22 @@ #include "SignalProcessing.h" #include "BeamformingAlgorithms.h" -/** Beam parameters data structure for a linear 1D array */ +/** Beam parameters data structure for a Uniform Rectangular Array + Convention used: + - Array seen from behind + - Upper-left microphone is number 0 + - eSticks are running along the x axes + - eSticks are stacked along the y axes, top-most eStick has mic form 0 to 15 + */ typedef struct { - /** DIrection of arrival of the beam. - Range: -1 (source closer to first microphone) to +1 (source closer to last microphone) + /** Pointing direction of the beam, x axis. + Range: -1 (source closer to first microphone, left) to +1 (source closer to last microphone, right) */ - float doa; + float doaX; + /** Pointing direction of the beam, y axis. + Range: -1 (source closer to first microphone, top) to +1 (source closer to last microphone, bottom) + */ + float doaY; /** Width of the beam. Range: 0 (the most focused) to 1 (the least focused) */ @@ -44,25 +54,27 @@ class BeamformingAlgorithm { }; - +/** Delay-And-Sum Beamformers*/ namespace DAS { -/** Farfield Linear Microphone Array Delay and Sum beamformer +/** Farfield Uniform Rectangular Array Beamformer - This class is used do setup a LMA and compute the FIR impulse response + This class is used do setup a URA and compute the FIR impulse response */ - class FarfieldLMA : public BeamformingAlgorithm { + class FarfieldURA : public BeamformingAlgorithm { public: - /** initialize the LMA + /** initialize the URA - @param micDist: microphones distance [m] - @param numMic: number of microphone capsules + @param micDistX: microphones distance on the X axes [m] + @param micDistY: microphones distance on the Y axes [m] + @param numMic: total number of microphone capsules + @param numRows: total number of rows @param fs: sampling frequency [Hz] @param soundspeed: sampling frequency [m/s] */ - FarfieldLMA(float micDist, int numMic, float fs, float soundspeed); + FarfieldURA(float micDistX, float micDistY, int numMic, int numRows, float fs, float soundspeed); /** Get the minimum FIR length for the given configuration [samples] */ int getFirLen() const override; @@ -77,11 +89,20 @@ namespace DAS { private: - /** Distance between microphones [m] */ - float micDist; - + /** Distance between microphones, X axes [m] */ + float micDistX; + + /** Distance between microphones, Y axes [m] */ + float micDistY; + /** Number of microphones */ int numMic; + + /** Number of rows */ + int numRows; + + /** Number of mic per row */ + int numMicPerRow; /** Sampling frequency [Hz] */ float fs; diff --git a/Source/CpuLoadComp.cpp b/Source/CpuLoadComp.cpp index f31ec28..df1823b 100644 --- a/Source/CpuLoadComp.cpp +++ b/Source/CpuLoadComp.cpp @@ -11,11 +11,8 @@ //============================================================================== CpuLoadComp::CpuLoadComp() { - text.setFont(textHeight); - text.setText("0 %"); - text.setReadOnly(true); - label.setFont(textHeight); - label.setText("CPU load", NotificationType::dontSendNotification); + text.setText("0 %", NotificationType::dontSendNotification); + label.setText("CPU", NotificationType::dontSendNotification); label.setJustificationType(Justification::left); label.attachToComponent(&text, true); addAndMakeVisible(text); @@ -40,5 +37,5 @@ void CpuLoadComp::resized() { void CpuLoadComp::timerCallback() { if (callback == nullptr) return; - text.setText(String(int(callback->getCpuLoad() * 100)) + "%"); + text.setText(String(int(callback->getCpuLoad() * 100)) + "%", NotificationType::dontSendNotification); } diff --git a/Source/CpuLoadComp.h b/Source/CpuLoadComp.h index d0302e8..4c22426 100644 --- a/Source/CpuLoadComp.h +++ b/Source/CpuLoadComp.h @@ -35,7 +35,7 @@ class CpuLoadComp : public Component, JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CpuLoadComp) /** Load indicator text */ - TextEditor text; + Label text; /** Label for load indicator text */ Label label; @@ -46,7 +46,6 @@ class CpuLoadComp : public Component, Callback *callback = nullptr; // Constants - const float textHeight = 10; - const float labelWidth = 50; + const float labelWidth = 45; }; diff --git a/Source/MeterComp.cpp b/Source/MeterComp.cpp index 25b6656..a1deee4 100644 --- a/Source/MeterComp.cpp +++ b/Source/MeterComp.cpp @@ -3,25 +3,25 @@ Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #include "MeterComp.h" void RoundLed::paint(Graphics &g) { - + Rectangle area = getLocalBounds().toFloat(); auto side = area.getHeight() > area.getWidth() ? area.getWidth() : area.getHeight(); auto ctr = area.getCentre(); area = Rectangle(side, side); area.setCentre(ctr); - + g.setColour(colour); g.fillEllipse(area); } void RoundLed::resized() { - + } @@ -42,24 +42,24 @@ void MultiChannelLedBar::makeLayout() { } void MultiChannelLedBar::paint(Graphics &) { - + } void MultiChannelLedBar::resized() { - + if (callback == nullptr) { return; } callback->getMeterValues(values, meterId); auto num = values.size(); Rectangle area = getLocalBounds(); - + int step = isHorizontal ? floor(area.getWidth() / num) : floor(area.getHeight() / num); int otherDim = isHorizontal ? area.getHeight() : area.getWidth(); otherDim = jmin(otherDim, step - 1); - + const auto areaCtr = area.getCentre(); - + // Re-center the area if (isHorizontal) { area.setWidth((int) (step * num)); @@ -69,40 +69,43 @@ void MultiChannelLedBar::resized() { area.setWidth(otherDim); } area.setCentre(areaCtr); - + for (auto ledIdx = 0; ledIdx < num; ++ledIdx) { Rectangle ledArea = isHorizontal ? area.removeFromLeft(step) : area.removeFromTop(step); leds[ledIdx]->setBounds(ledArea); } + +} +Colour MultiChannelLedBar::dbToColor(float valDb){ + Colour col; + if (valDb > RED_LT) { + col = Colours::red; + } else if (valDb > ORANGE_LT) { + col = Colours::orange; + } else if (valDb > YELLOW_LT) { + col = Colours::yellow; + } else if (valDb > GREEN_LT) { + col = Colours::lightgreen; + } else { + col = Colours::darkgreen; + } + return col; } void MultiChannelLedBar::timerCallback() { - + if (callback == nullptr) return; - + callback->getMeterValues(values, meterId); - - if (values.size() != leds.size()) { + + if (values.size() != leds.size()) makeLayout(); - } - - for (auto ledIdx = 0; ledIdx < leds.size(); ++ledIdx) { - auto value = values.at(ledIdx); - Colour col; - if (value > Decibels::decibelsToGain(RED_LT)) { - col = Colours::red; - } else if (value > Decibels::decibelsToGain(YELLOW_LT)) { - col = Colours::yellow; - } else if (value > Decibels::decibelsToGain(GREEN_LT)) { - col = Colours::lightgreen; - } else { - col = Colours::grey; - } - leds[ledIdx]->colour = col; - } - + + for (auto ledIdx = 0; ledIdx < leds.size(); ++ledIdx) + leds[ledIdx]->colour = dbToColor(Decibels::gainToDecibels(values.at(ledIdx))); + repaint(); } @@ -118,20 +121,20 @@ void MultiChannelLedBar::setCallback(MeterDecay::Callback *cb, int metId) { SingleChannelLedBar::SingleChannelLedBar(size_t numLeds, bool isHorizontal) { jassert(numLeds > 4); - + this->isHorizontal = isHorizontal; - + const float ledStep = 3; //dB - + leds.clear(); th.clear(); for (auto ledIdx = 0; ledIdx < numLeds; ++ledIdx) { leds.push_back(std::make_unique()); - + auto ledThDb = ledIdx == (numLeds - 1) ? RED_LT : -((numLeds - 1 - ledIdx) * ledStep); th.push_back(ledThDb); - leds[ledIdx]->colour = thToColour(ledThDb, false); - + leds[ledIdx]->colour = dbToColour(-100, ledThDb); + addAndMakeVisible(leds[ledIdx].get()); } } @@ -143,11 +146,11 @@ void SingleChannelLedBar::setCallback(MeterDecay::Callback *pr, int metId, int c } void SingleChannelLedBar::paint(Graphics &) { - + } void SingleChannelLedBar::resized() { - + Rectangle area = getLocalBounds().toFloat(); auto num = leds.size(); float step = isHorizontal ? area.getWidth() / num : area.getHeight() / num; @@ -155,35 +158,31 @@ void SingleChannelLedBar::resized() { Rectangle ledArea = isHorizontal ? area.removeFromLeft(step) : area.removeFromBottom(step); leds[ledIdx]->setBounds(ledArea.toNearestInt()); } - + } void SingleChannelLedBar::timerCallback() { if (provider == nullptr) return; - + auto valueDb = Decibels::gainToDecibels(provider->getMeterValue(meterId, channel)); for (auto ledIdx = 0; ledIdx < leds.size(); ++ledIdx) - leds[ledIdx]->colour = thToColour(th[ledIdx], valueDb > th[ledIdx]); - + leds[ledIdx]->colour = dbToColour(valueDb, th[ledIdx]); + repaint(); } -Colour SingleChannelLedBar::thToColour(float th, bool active) { - if (th >= RED_LT) { - if (active) - return Colours::red; - else - return Colours::darkred; - } else if (th >= YELLOW_LT) { - if (active) - return Colours::yellow; - else - return Colours::darkgoldenrod; +Colour SingleChannelLedBar::dbToColour(float valDb, float thDb) { + const bool active = valDb >= thDb; + Colour col; + if (thDb >= RED_LT) { + col = active ? Colours::red : Colours::darkred; + } else if (thDb >= ORANGE_LT) { + col = active ? Colours::orange : Colours::darkorange.darker(); + } else if (thDb >= YELLOW_LT) { + col = active ? Colours::yellow : Colours::darkgoldenrod; } else { - if (active) - return Colours::lightgreen; - else - return Colours::darkgreen; + col = active ? Colours::lightgreen : Colours::darkgreen; } + return col; } diff --git a/Source/MeterComp.h b/Source/MeterComp.h index 577884e..96ad516 100644 --- a/Source/MeterComp.h +++ b/Source/MeterComp.h @@ -3,14 +3,15 @@ Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #pragma once #define RED_LT -0.5f //dB +#define ORANGE_LT -3.0f //dB #define YELLOW_LT -6.0f //dB -#define GREEN_LT -20.0f //dB +#define GREEN_LT -18.0f //dB #include "../JuceLibraryCode/JuceHeader.h" #include "MidiComp.h" @@ -20,17 +21,17 @@ class RoundLed : public Component { public: - + RoundLed() {}; - + Colour colour; - + void paint(Graphics &) override; - + void resized() override; - + private: - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RoundLed) }; @@ -38,67 +39,69 @@ class RoundLed : public Component { //============================================================================== class MultiChannelLedBar : public Component, public Timer { public: - + MultiChannelLedBar() {}; - + ~MultiChannelLedBar() {}; - + void paint(Graphics &) override; - + void resized() override; - + void setCallback(MeterDecay::Callback *cb, int metId); - + void setHorizontal() { isHorizontal = true; }; - + void setVertical() { isHorizontal = false; }; - - + + static Colour dbToColor(float valDb); + + private: - + MeterDecay::Callback *callback = nullptr; int meterId; - + bool isHorizontal = true; - + std::vector> leds; std::vector values; - + void timerCallback() override; - + void makeLayout(); - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChannelLedBar) }; //============================================================================== class SingleChannelLedBar : public Component, public Timer { public: - + SingleChannelLedBar(size_t numLeds = 7, bool isHorizontal = false); - + ~SingleChannelLedBar() {}; - + void setCallback(MeterDecay::Callback *cb, int meterId, int channel); - + void paint(Graphics &) override; - + void resized() override; - - static Colour thToColour(float th, bool active); - + + static Colour dbToColour(float valDb, float thDb); + private: - + MeterDecay::Callback *provider = nullptr; int meterId; int channel; - + bool isHorizontal; - + std::vector th; std::vector> leds; - + void timerCallback() override; - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SingleChannelLedBar) }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 1e3c1e2..d24fd1a 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -1,268 +1,306 @@ /* - eBeamer Plugin Processor GUI + eBeamer Plugin Processor GUI Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #include "PluginProcessor.h" #include "PluginEditor.h" -JucebeamAudioProcessorEditor::JucebeamAudioProcessorEditor(EbeamerAudioProcessor &p, AudioProcessorValueTreeState &v) - : AudioProcessorEditor(&p), processor(p), valueTreeState(v) { - +EBeamerAudioProcessorEditor::EBeamerAudioProcessorEditor(EbeamerAudioProcessor &p, AudioProcessorValueTreeState &v) +: AudioProcessorEditor(&p), processor(p), valueTreeState(v) { + //============================================================================== setSize(GUI_WIDTH, GUI_HEIGHT); - + //============================================================================== scene.setCallback(&p); scene.setBeamColors(beamColours); addAndMakeVisible(scene); - + //============================================================================== steerLabel.setText("STEER", NotificationType::dontSendNotification); steerLabel.setJustificationType(Justification::centred); addAndMakeVisible(steerLabel); - - steeringBeam1Label.setText("1", NotificationType::dontSendNotification); - steeringBeam1Label.setJustificationType(Justification::left); - steeringBeam1Label.attachToComponent(&steeringBeam1Slider, true); - addAndMakeVisible(steeringBeam1Label); - - steeringBeam2Label.setText("2", NotificationType::dontSendNotification); - steeringBeam2Label.setJustificationType(Justification::left); - steeringBeam2Label.attachToComponent(&steeringBeam2Slider, true); - addAndMakeVisible(steeringBeam2Label); - - steeringBeam1Slider.setSliderStyle(Slider::LinearHorizontal); - steeringBeam1Slider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); - steeringBeam1Slider.setColour(Slider::thumbColourId, beamColours[0]); - steeringBeam1Slider.setPopupMenuEnabled(true); - steeringBeam1Slider.setCallback(&processor, "steerBeam1"); - addAndMakeVisible(steeringBeam1Slider); - - steeringBeam2Slider.setSliderStyle(Slider::LinearHorizontal); - steeringBeam2Slider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); - steeringBeam2Slider.setColour(Slider::thumbColourId, beamColours[1]); - steeringBeam2Slider.setPopupMenuEnabled(true); - steeringBeam2Slider.setCallback(&processor, "steerBeam2"); - addAndMakeVisible(steeringBeam2Slider); - - steeringBeam1SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeam1", steeringBeam1Slider)); - steeringBeam2SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeam2", steeringBeam2Slider)); - + + steerBeam1Label.setText("1", NotificationType::dontSendNotification); + steerBeam1Label.setJustificationType(Justification::left); + steerBeam1Label.attachToComponent(&steerBeamX1Slider, true); + addAndMakeVisible(steerBeam1Label); + + steerBeam2Label.setText("2", NotificationType::dontSendNotification); + steerBeam2Label.setJustificationType(Justification::left); + steerBeam2Label.attachToComponent(&steerBeamX2Slider, true); + addAndMakeVisible(steerBeam2Label); + + steerBeamX1Slider.setSliderStyle(Slider::LinearHorizontal); + steerBeamX1Slider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); + steerBeamX1Slider.setColour(Slider::thumbColourId, beamColours[0]); + steerBeamX1Slider.setPopupMenuEnabled(true); + steerBeamX1Slider.setCallback(&processor, "steerBeamX1"); + addAndMakeVisible(steerBeamX1Slider); + + steerBeamX2Slider.setSliderStyle(Slider::LinearHorizontal); + steerBeamX2Slider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); + steerBeamX2Slider.setColour(Slider::thumbColourId, beamColours[1]); + steerBeamX2Slider.setPopupMenuEnabled(true); + steerBeamX2Slider.setCallback(&processor, "steerBeamX2"); + addAndMakeVisible(steerBeamX2Slider); + + steerBeamY1Slider.setSliderStyle(Slider::LinearVertical); + steerBeamY1Slider.setTextBoxStyle(Slider::TextBoxAbove, false, LABEL_WIDTH, LABEL_HEIGHT); + steerBeamY1Slider.setColour(Slider::thumbColourId, beamColours[0]); + steerBeamY1Slider.setPopupMenuEnabled(true); + steerBeamY1Slider.setCallback(&processor, "steerBeamY1"); + addAndMakeVisible(steerBeamY1Slider); + + steerBeamY2Slider.setSliderStyle(Slider::LinearVertical); + steerBeamY2Slider.setTextBoxStyle(Slider::TextBoxAbove, false, LABEL_WIDTH, LABEL_HEIGHT); + steerBeamY2Slider.setColour(Slider::thumbColourId, beamColours[1]); + steerBeamY2Slider.setPopupMenuEnabled(true); + steerBeamY2Slider.setCallback(&processor, "steerBeamY2"); + addAndMakeVisible(steerBeamY2Slider); + + steerBeamX1SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeamX1", steerBeamX1Slider)); + steerBeamX2SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeamX2", steerBeamX2Slider)); + steerBeamY1SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeamY1", steerBeamY1Slider)); + steerBeamY2SliderAttachment.reset(new SliderAttachment(valueTreeState, "steerBeamY2", steerBeamY2Slider)); + //============================================================================== widthLabel.setText("WIDTH", NotificationType::dontSendNotification); widthLabel.setJustificationType(Justification::centred); addAndMakeVisible(widthLabel); - + widthBeam1Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); widthBeam1Knob.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); widthBeam1Knob.setColour(Slider::thumbColourId, beamColours[0]); widthBeam1Knob.setPopupMenuEnabled(true); widthBeam1Knob.setCallback(&processor, "widthBeam1"); addAndMakeVisible(widthBeam1Knob); - + widthBeam2Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); widthBeam2Knob.setTextBoxStyle(Slider::TextBoxLeft, false, LABEL_WIDTH, LABEL_HEIGHT); widthBeam2Knob.setColour(Slider::thumbColourId, beamColours[1]); widthBeam2Knob.setPopupMenuEnabled(true); widthBeam2Knob.setCallback(&processor, "widthBeam2"); addAndMakeVisible(widthBeam2Knob); - + widthBeam1KnobAttachment.reset(new SliderAttachment(valueTreeState, "widthBeam1", widthBeam1Knob)); widthBeam2KnobAttachment.reset(new SliderAttachment(valueTreeState, "widthBeam2", widthBeam2Knob)); - + //============================================================================== panLabel.setText("PAN", NotificationType::dontSendNotification); panLabel.setJustificationType(Justification::centred); addAndMakeVisible(panLabel); - + panBeam1Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); panBeam1Knob.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); panBeam1Knob.setColour(Slider::thumbColourId, beamColours[0]); panBeam1Knob.setPopupMenuEnabled(true); panBeam1Knob.setCallback(&processor, "panBeam1"); addAndMakeVisible(panBeam1Knob); - + panBeam2Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); panBeam2Knob.setTextBoxStyle(Slider::TextBoxLeft, false, LABEL_WIDTH, LABEL_HEIGHT); panBeam2Knob.setColour(Slider::thumbColourId, beamColours[1]); panBeam2Knob.setPopupMenuEnabled(true); panBeam2Knob.setCallback(&processor, "panBeam2"); addAndMakeVisible(panBeam2Knob); - + panBeam1KnobAttachment.reset(new SliderAttachment(valueTreeState, "panBeam1", panBeam1Knob)); panBeam2KnobAttachment.reset(new SliderAttachment(valueTreeState, "panBeam2", panBeam2Knob)); - + //============================================================================== - + levelLabel.setText("LEVEL", NotificationType::dontSendNotification); levelLabel.setJustificationType(Justification::centred); addAndMakeVisible(levelLabel); - + levelBeam1Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); levelBeam1Knob.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); levelBeam1Knob.setColour(Slider::thumbColourId, beamColours[0]); levelBeam1Knob.setPopupMenuEnabled(true); levelBeam1Knob.setCallback(&processor, "levelBeam1"); addAndMakeVisible(levelBeam1Knob); - + levelBeam2Knob.setSliderStyle(Slider::RotaryHorizontalVerticalDrag); levelBeam2Knob.setTextBoxStyle(Slider::TextBoxLeft, false, LABEL_WIDTH, LABEL_HEIGHT); levelBeam2Knob.setColour(Slider::thumbColourId, beamColours[1]); levelBeam2Knob.setPopupMenuEnabled(true); levelBeam2Knob.setCallback(&processor, "levelBeam2"); addAndMakeVisible(levelBeam2Knob); - + levelBeam1KnobAttachment.reset(new SliderAttachment(valueTreeState, "levelBeam1", levelBeam1Knob)); levelBeam2KnobAttachment.reset(new SliderAttachment(valueTreeState, "levelBeam2", levelBeam2Knob)); - + //============================================================================== - + muteLabel.setText("MUTE", NotificationType::dontSendNotification); muteLabel.setJustificationType(Justification::centred); addAndMakeVisible(muteLabel); - + muteBeam1Button.setButtonText("1"); muteBeam1Button.setCallback(&processor, "muteBeam1"); addAndMakeVisible(muteBeam1Button); - + muteBeam2Button.setButtonText("2"); muteBeam2Button.setCallback(&processor, "muteBeam2"); addAndMakeVisible(muteBeam2Button); - + beam1MuteButtonAttachment.reset(new ButtonAttachment(valueTreeState, "muteBeam1", muteBeam1Button)); beam2MuteButtonAttachment.reset(new ButtonAttachment(valueTreeState, "muteBeam2", muteBeam2Button)); - + getLookAndFeel().setColour(MuteButton::buttonOnColourId, Colours::darkred); - + //============================================================================== - + beam1Meter.setCallback(&processor, 1, 0); beam1Meter.startTimerHz(BEAM_METER_UPDATE_FREQ); addAndMakeVisible(beam1Meter); - + beam2Meter.setCallback(&processor, 1, 1); beam2Meter.startTimerHz(BEAM_METER_UPDATE_FREQ); addAndMakeVisible(beam2Meter); - + //============================================================================== - + hpfLabel.setText("HPF", NotificationType::dontSendNotification); hpfLabel.setJustificationType(Justification::left); hpfLabel.attachToComponent(&hpfSlider, true); - + hpfSlider.setSliderStyle(Slider::LinearHorizontal); hpfSlider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); hpfSlider.setPopupMenuEnabled(true); hpfSlider.setCallback(&processor, "hpf"); addAndMakeVisible(hpfSlider); - + hpfSliderAttachment.reset(new SliderAttachment(valueTreeState, "hpf", hpfSlider)); - + //============================================================================== - + inputMeter.setCallback(&processor, 0); inputMeter.startTimerHz(INPUT_METER_UPDATE_FREQ); addAndMakeVisible(inputMeter); - + //============================================================================== - + gainLabel.setText("GAIN", NotificationType::dontSendNotification); gainLabel.setJustificationType(Justification::left); gainLabel.attachToComponent(&gainSlider, true); - + gainSlider.setSliderStyle(Slider::LinearHorizontal); gainSlider.setTextBoxStyle(Slider::TextBoxRight, false, LABEL_WIDTH, LABEL_HEIGHT); gainSlider.setPopupMenuEnabled(true); gainSlider.setCallback(&processor, "gainMic"); addAndMakeVisible(gainSlider); - + gainSliderAttachment.reset(new SliderAttachment(valueTreeState, "gainMic", gainSlider)); - + //===================================================== // Add CPU Load and start its timer cpuLoad.setSource(&processor); cpuLoad.startTimerHz(CPULOAD_UPDATE_FREQ); addAndMakeVisible(cpuLoad); - + //===================================================== // Add front facing toggle - frontToggleLabel.setText("FLIP", NotificationType::dontSendNotification); - frontToggleLabel.setFont(10); + frontToggleLabel.setText("FRONT", NotificationType::dontSendNotification); frontToggleLabel.attachToComponent(&frontToggle, true); frontToggle.setCallback(&processor, "frontFacing"); addAndMakeVisible(frontToggle); - + frontToggleAttachment.reset(new ButtonAttachment(valueTreeState, "frontFacing", frontToggle)); - + //===================================================== // Configuration selection combo - configComboLabel.setText("CONFIG", NotificationType::dontSendNotification); - configComboLabel.setFont(10); + configComboLabel.setText("SETUP", NotificationType::dontSendNotification); configComboLabel.attachToComponent(&configCombo, true); configCombo.addItemList(micConfigLabels, 10); addAndMakeVisible(configCombo); - + configComboLabelAttachment.reset(new ComboBoxAttachment(valueTreeState, "config", configCombo)); - + + /* The editor needs to change its layout when the config changes */ + valueTreeState.addParameterListener("config", this); + valueTreeState.addParameterListener("frontFacing", this); + } -JucebeamAudioProcessorEditor::~JucebeamAudioProcessorEditor() { +EBeamerAudioProcessorEditor::~EBeamerAudioProcessorEditor() { } //============================================================================== -void JucebeamAudioProcessorEditor::paint(Graphics &g) { +void EBeamerAudioProcessorEditor::paint(Graphics &g) { // (Our component is opaque, so we must completely fill the background with a solid colour) g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId)); - + g.setColour(Colours::white); g.setFont(15.0f); - - const auto versionArea = getBounds().removeFromBottom(10); + + auto versionArea = getBounds().removeFromBottom(12); + versionArea.removeFromBottom(2); g.setColour(Colours::lightgrey); g.setFont(12); g.drawText("ISPL and Eventide - eBeamer v" + String(JucePlugin_VersionString), versionArea, Justification::centredBottom, false); - + } -void JucebeamAudioProcessorEditor::resized() { - +void EBeamerAudioProcessorEditor::resized() { + auto area = getLocalBounds(); - area.removeFromLeft(LEFT_RIGHT_MARGIN); - area.removeFromRight(LEFT_RIGHT_MARGIN); area.removeFromTop(TOP_BOTTOM_MARGIN); area.removeFromBottom(TOP_BOTTOM_MARGIN); - + auto sceneArea = area.removeFromTop(SCENE_HEIGHT); - sceneArea.removeFromRight((area.getWidth() - SCENE_WIDTH) / 2); - sceneArea.removeFromLeft((area.getWidth() - SCENE_WIDTH) / 2); - scene.setBounds(sceneArea); - + + if (isLinearArray(static_cast((int)*valueTreeState.getRawParameterValue("config")))){ + steerBeamY1Slider.setVisible(false); + steerBeamY2Slider.setVisible(false); + sceneArea.removeFromRight((area.getWidth() - SCENE_WIDTH) / 2); + sceneArea.removeFromLeft((area.getWidth() - SCENE_WIDTH) / 2); + scene.setBounds(sceneArea); + }else{ + steerBeamY1Slider.setVisible(true); + steerBeamY2Slider.setVisible(true); + sceneArea.removeFromLeft(15); + sceneArea.removeFromRight(15); + steerBeamY1Slider.setBounds(sceneArea.removeFromLeft(50)); + steerBeamY2Slider.setBounds(sceneArea.removeFromRight(50)); + sceneArea.removeFromLeft(5); + sceneArea.removeFromRight(5); + sceneArea.removeFromTop(10); + sceneArea.removeFromBottom(10); + scene.setBounds(sceneArea); + } + + area.removeFromLeft(LEFT_RIGHT_MARGIN); + area.removeFromRight(LEFT_RIGHT_MARGIN); + + area.removeFromTop(STEER_SLIDER_TOP_MARGIN); - steeringBeam1Slider.setBounds(area.removeFromTop(STEER_SLIDER_HEIGHT).withTrimmedLeft(LABEL_BEAM_WIDTH)); - steeringBeam2Slider.setBounds(area.removeFromTop(STEER_SLIDER_HEIGHT).withTrimmedLeft(LABEL_BEAM_WIDTH)); - + steerBeamX1Slider.setBounds(area.removeFromTop(STEER_SLIDER_HEIGHT).withTrimmedLeft(LABEL_BEAM_WIDTH)); + steerBeamX2Slider.setBounds(area.removeFromTop(STEER_SLIDER_HEIGHT).withTrimmedLeft(LABEL_BEAM_WIDTH)); + steerLabel.setBounds(area.removeFromTop(LABEL_HEIGHT)); - + area.removeFromLeft(KNOBS_LEFT_RIGHT_MARGIN); area.removeFromRight(KNOBS_LEFT_RIGHT_MARGIN); - + auto knobsArea = area.removeFromTop(KNOB_HEIGHT + KNOB_TOP_MARGIN); knobsArea.removeFromTop(KNOB_TOP_MARGIN); widthBeam1Knob.setBounds(knobsArea.removeFromLeft(KNOB_WIDTH)); widthBeam2Knob.setBounds(knobsArea.removeFromRight(KNOB_WIDTH)); widthLabel.setBounds(knobsArea); - + knobsArea = area.removeFromTop(KNOB_HEIGHT + KNOB_TOP_MARGIN); knobsArea.removeFromTop(KNOB_TOP_MARGIN); panBeam1Knob.setBounds(knobsArea.removeFromLeft(KNOB_WIDTH)); panBeam2Knob.setBounds(knobsArea.removeFromRight(KNOB_WIDTH)); panLabel.setBounds(knobsArea); - + knobsArea = area.removeFromTop(KNOB_HEIGHT + KNOB_TOP_MARGIN); knobsArea.removeFromTop(KNOB_TOP_MARGIN); levelBeam1Knob.setBounds(knobsArea.removeFromLeft(KNOB_WIDTH)); @@ -278,7 +316,7 @@ void JucebeamAudioProcessorEditor::resized() { meterArea.removeFromRight(BEAM_LEFT_RIGHT_MARGIN); beam2Meter.setBounds(meterArea.removeFromRight(BEAM_LED_WIDTH)); levelLabel.setBounds(knobsArea); - + auto mutesArea = area.removeFromTop(MUTE_HEIGHT + MUTE_TOP_MARGIN); mutesArea.removeFromTop(MUTE_TOP_MARGIN); mutesArea.removeFromLeft(MUTE_LEFT_RIGHT_MARGIN); @@ -286,29 +324,39 @@ void JucebeamAudioProcessorEditor::resized() { muteBeam1Button.setBounds(mutesArea.removeFromLeft(MUTE_WIDTH)); muteBeam2Button.setBounds(mutesArea.removeFromRight(MUTE_WIDTH)); muteLabel.setBounds(mutesArea); - + area.removeFromTop(INPUT_SECTION_TOP_MARGIN); hpfSlider.setBounds(area.removeFromTop(INPUT_HPF_SLIDER_HEIGHT).withTrimmedLeft(INPUT_HPF_LABEL_WIDTH)); - + auto inputLedArea = area.removeFromTop(INPUT_LED_HEIGHT); inputLedArea.removeFromLeft(INPUT_LEFT_RIGHT_MARGIN); inputLedArea.removeFromRight(INPUT_LEFT_RIGHT_MARGIN); inputMeter.setBounds(inputLedArea); - + gainSlider.setBounds(area.removeFromTop(INPUT_GAIN_SLIDER_HEIGHT).withTrimmedLeft(INPUT_GAIN_LABEL_WIDTH)); - + //=============================================================== - /** Prepare area for the performance monitor */ - auto performanceMonitorArea = area.removeFromTop(PREFORMANCE_MONITOR_HEIGHT); - + /** Prepare area for the footer */ + area.removeFromTop(FOOTER_MARGIN); + auto footerArea = area.removeFromTop(FOOTER_HEIGHT); + /** Set area for CPU Load */ - cpuLoad.setBounds(performanceMonitorArea.removeFromLeft(CPULOAD_WIDTH)); - + cpuLoad.setBounds(footerArea.removeFromLeft(CPULOAD_WIDTH)); + /** Set area for front toggle */ - performanceMonitorArea.removeFromLeft(FRONT_TOGGLE_LABEL_WIDTH); - frontToggle.setBounds(performanceMonitorArea.removeFromLeft(FRONT_TOGGLE_WIDTH)); - + frontToggle.setBounds(footerArea.removeFromRight(FRONT_TOGGLE_WIDTH)); + footerArea.removeFromRight(FRONT_TOGGLE_LABEL_WIDTH); + /** Set area for config combo */ - performanceMonitorArea.removeFromLeft(CONFIG_COMBO_LABEL_WIDTH); - configCombo.setBounds(performanceMonitorArea.removeFromLeft(CONFIG_COMBO_WIDTH)); + footerArea.removeFromLeft(CONFIG_COMBO_LABEL_WIDTH); + configCombo.setBounds(footerArea.removeFromLeft(CONFIG_COMBO_WIDTH)); +} + +void EBeamerAudioProcessorEditor::parameterChanged (const String & parameterID, float newValue){ + if (parameterID == "config"){ + resized(); + } + if (parameterID == "frontFacing"){ + scene.resized(); + } } diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 795f2e0..222de22 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -1,9 +1,9 @@ /* - eBeamer Plugin Processor GUI + eBeamer Plugin Processor GUI Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #pragma once @@ -16,88 +16,94 @@ //============================================================================== -class JucebeamAudioProcessorEditor : public AudioProcessorEditor { +class EBeamerAudioProcessorEditor : public AudioProcessorEditor, public AudioProcessorValueTreeState::Listener { public: - + typedef AudioProcessorValueTreeState::SliderAttachment SliderAttachment; typedef AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment; typedef AudioProcessorValueTreeState::ComboBoxAttachment ComboBoxAttachment; - - JucebeamAudioProcessorEditor(EbeamerAudioProcessor &, AudioProcessorValueTreeState &v); - - ~JucebeamAudioProcessorEditor(); - + + EBeamerAudioProcessorEditor(EbeamerAudioProcessor &, AudioProcessorValueTreeState &v); + + ~EBeamerAudioProcessorEditor(); + void paint(Graphics &) override; - + void resized() override; - + private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JucebeamAudioProcessorEditor); - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EBeamerAudioProcessorEditor); + EbeamerAudioProcessor &processor; AudioProcessorValueTreeState &valueTreeState; - + //============================================================================== SceneComp scene; - + //============================================================================== Label steerLabel; - Label steeringBeam1Label, steeringBeam2Label; - SliderCC steeringBeam1Slider, steeringBeam2Slider; - std::unique_ptr steeringBeam1SliderAttachment, steeringBeam2SliderAttachment; - + Label steerBeam1Label, steerBeam2Label; + SliderCC steerBeamX1Slider, steerBeamX2Slider; + SliderCC steerBeamY1Slider, steerBeamY2Slider; + std::unique_ptr steerBeamX1SliderAttachment, steerBeamX2SliderAttachment; + std::unique_ptr steerBeamY1SliderAttachment, steerBeamY2SliderAttachment; + //============================================================================== Label widthLabel; SliderCC widthBeam1Knob, widthBeam2Knob; std::unique_ptr widthBeam1KnobAttachment, widthBeam2KnobAttachment; - + //============================================================================== Label panLabel; PanSlider panBeam1Knob, panBeam2Knob; std::unique_ptr panBeam1KnobAttachment, panBeam2KnobAttachment; - + //============================================================================== Label levelLabel; DecibelSlider levelBeam1Knob, levelBeam2Knob; std::unique_ptr levelBeam1KnobAttachment, levelBeam2KnobAttachment; - + //============================================================================== Label muteLabel; MuteButton muteBeam1Button, muteBeam2Button; std::unique_ptr beam1MuteButtonAttachment, beam2MuteButtonAttachment; - + //============================================================================== MultiChannelLedBar inputMeter; SingleChannelLedBar beam1Meter, beam2Meter; - + //============================================================================== Label hpfLabel; FrequencySlider hpfSlider; std::unique_ptr hpfSliderAttachment; - + //============================================================================== Label gainLabel; DecibelSlider gainSlider; std::unique_ptr gainSliderAttachment; - + //============================================================================== /** CPU load component */ CpuLoadComp cpuLoad; - + //============================================================================== /** Swap side toggle component */ Label frontToggleLabel; ToggleButtonCC frontToggle; std::unique_ptr frontToggleAttachment; - + //============================================================================== /** Configuration selection combo */ - + Label configComboLabel; ComboBox configCombo; std::unique_ptr configComboLabelAttachment; - + //============================================================================== const std::vector beamColours = {Colours::orangered, Colours::royalblue}; - + + //============================================================================== + /** Listener for parameter changes that requre a broad Editor change */ + void parameterChanged (const String & parameterID, float newValue) override; + }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index ee4d11d..e9cc1bb 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -1,9 +1,9 @@ /* - eBeamer Plugin Processor + eBeamer Plugin Processor Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #include "PluginProcessor.h" #include "PluginEditor.h" @@ -11,107 +11,109 @@ //============================================================================== // Helper functions AudioProcessorValueTreeState::ParameterLayout initializeParameters() { - + std::vector> params; - + // Values in dB params.push_back(std::make_unique("config", //tag "Configuration", //name micConfigLabels, //choices 0 //default - )); - + )); + params.push_back(std::make_unique("frontFacing", //tag "Front facing", //name false //default - )); - + )); + params.push_back(std::make_unique("gainMic", //tag "Mic gain", //name 0.0f, //min 40.0f, //max 20.0f //default - )); - + )); + // Values in Hz params.push_back(std::make_unique("hpf", //tag "HPF", 20.0f, //min 500.0f, //max 250.0f //default - )); - + )); + { for (auto beamIdx = 0; beamIdx < NUM_BEAMS; ++beamIdx) { - auto defaultDirection = beamIdx == 0 ? -0.5 : 0.5; - params.push_back(std::make_unique("steerBeam" + String(beamIdx + 1), //tag - "Steering beam" + String(beamIdx + 1), //name + auto defaultDirectionX = beamIdx == 0 ? -0.5 : 0.5; + params.push_back(std::make_unique("steerBeamX" + String(beamIdx + 1), //tag + "Steer " + String(beamIdx + 1) + " hor", //name + -1.0f, //min + 1.0f, //max + defaultDirectionX //default + )); + params.push_back(std::make_unique("steerBeamY" + String(beamIdx + 1), //tag + "Steer " + String(beamIdx + 1) + " ver", //name -1.0f, //min 1.0f, //max - defaultDirection //default - )); + 0 //default + )); params.push_back(std::make_unique("widthBeam" + String(beamIdx + 1), //tag "Width beam" + String(beamIdx + 1), //name 0.0f, //min 1.0f,//max 0.3f//default - )); + )); auto defaultPan = beamIdx == 0 ? -0.5 : 0.5; params.push_back(std::make_unique("panBeam" + String(beamIdx + 1), //tag "Pan beam" + String(beamIdx + 1), //name -1.0f, //min 1.0f, //max defaultPan //default - )); + )); params.push_back(std::make_unique("levelBeam" + String(beamIdx + 1), //tag "Level beam" + String(beamIdx + 1), //name -10.0f, //min 10.0f, //max 0.0f //default - )); - + )); + params.push_back(std::make_unique("muteBeam" + String(beamIdx + 1), //tag "Mute beam" + String(beamIdx + 1), //name false //default - )); - + )); + } } - + return {params.begin(), params.end()}; } //============================================================================== EbeamerAudioProcessor::EbeamerAudioProcessor() - : AudioProcessor(BusesProperties() //The default bus layout accommodates for 4 buses of 16 channels each. - .withInput("eStick#1", AudioChannelSet::ambisonic(3), true) - .withInput("eStick#2", AudioChannelSet::ambisonic(3), true) - .withInput("eStick#3", AudioChannelSet::ambisonic(3), true) - .withInput("eStick#4", AudioChannelSet::ambisonic(3), true) - .withOutput("Output", AudioChannelSet::stereo(), true) -), parameters(*this, nullptr, Identifier("eBeamerParams"), initializeParameters()) { - +: AudioProcessor(BusesProperties() //The default bus layout accommodates for 4 buses of 16 channels each. + .withInput("eStick#1", AudioChannelSet::ambisonic(3), true) + .withInput("eStick#2", AudioChannelSet::ambisonic(3), true) + .withInput("eStick#3", AudioChannelSet::ambisonic(3), true) + .withInput("eStick#4", AudioChannelSet::ambisonic(3), true) + .withOutput("Output", AudioChannelSet::stereo(), true) + ), parameters(*this, nullptr, Identifier("eBeamerParams"), initializeParameters()) { + /** Get parameters pointers */ configParam = parameters.getRawParameterValue("config"); parameters.addParameterListener("config", this); frontFacingParam = parameters.getRawParameterValue("frontFacing"); hpfFreqParam = parameters.getRawParameterValue("hpf"); micGainParam = parameters.getRawParameterValue("gainMic"); - + for (auto beamIdx = 0; beamIdx < NUM_BEAMS; beamIdx++) { - steeringBeamParam[beamIdx] = parameters.getRawParameterValue("steerBeam" + String(beamIdx + 1)); + steerBeamXParam[beamIdx] = parameters.getRawParameterValue("steerBeamX" + String(beamIdx + 1)); + steerBeamYParam[beamIdx] = parameters.getRawParameterValue("steerBeamY" + String(beamIdx + 1)); widthBeamParam[beamIdx] = parameters.getRawParameterValue("widthBeam" + String(beamIdx + 1)); panBeamParam[beamIdx] = parameters.getRawParameterValue("panBeam" + String(beamIdx + 1)); levelBeamParam[beamIdx] = parameters.getRawParameterValue("levelBeam" + String(beamIdx + 1)); muteBeamParam[beamIdx] = parameters.getRawParameterValue("muteBeam" + String(beamIdx + 1)); } - - - /** Initialize the beamformer */ - beamformer = std::make_unique(NUM_BEAMS, NUM_DOAS); - beamformer->setMicConfig(static_cast((int) *configParam)); - + } //============================================================================== @@ -140,35 +142,35 @@ bool EbeamerAudioProcessor::isBusesLayoutSupported(const BusesLayout &layouts) c //============================================================================== void EbeamerAudioProcessor::prepareToPlay(double sampleRate_, int maximumExpectedSamplesPerBlock_) { - + GenericScopedLock lock(processingLock); - + sampleRate = sampleRate_; maximumExpectedSamplesPerBlock = maximumExpectedSamplesPerBlock_; - + /** Number of active input channels */ numActiveInputChannels = getTotalNumInputChannels(); - + /** Number of active output channels */ numActiveOutputChannels = jmin(NUM_BEAMS, getTotalNumOutputChannels()); - + /** Initialize the input gain */ micGain.reset(); micGain.prepare({sampleRate, static_cast(maximumExpectedSamplesPerBlock), numActiveInputChannels}); micGain.setGainDecibels(*micGainParam); micGain.setRampDurationSeconds(gainTimeConst); - + /** Initialize the High Pass Filters */ iirHPFfilters.clear(); iirHPFfilters.resize(numActiveInputChannels); prevHpfFreq = 0; - + /** Initialize the beamformer */ - beamformer->prepareToPlay(sampleRate, maximumExpectedSamplesPerBlock); - + beamformer = std::make_unique(NUM_BEAMS, static_cast((int) *configParam),sampleRate, maximumExpectedSamplesPerBlock); + /** Initialize beams' buffer */ beamBuffer.setSize(NUM_BEAMS, maximumExpectedSamplesPerBlock); - + /** Initialize beam level gains */ for (auto beamIdx = 0; beamIdx < NUM_BEAMS; ++beamIdx) { beamGain[beamIdx].reset(); @@ -176,33 +178,33 @@ void EbeamerAudioProcessor::prepareToPlay(double sampleRate_, int maximumExpecte beamGain[beamIdx].setGainDecibels(*levelBeamParam[beamIdx]); beamGain[beamIdx].setRampDurationSeconds(gainTimeConst); } - + /** initialize meters */ inputMeterDecay = std::make_unique(sampleRate, metersDecay, maximumExpectedSamplesPerBlock, numActiveInputChannels); beamMeterDecay = std::make_unique(sampleRate, metersDecay, maximumExpectedSamplesPerBlock, NUM_BEAMS); - + resourcesAllocated = true; - + /** Time constants */ loadAlpha = 1 - exp(-(maximumExpectedSamplesPerBlock / sampleRate) / loadTimeConst); - + } void EbeamerAudioProcessor::releaseResources() { - + GenericScopedLock lock(processingLock); - + resourcesAllocated = false; - + /** Clear beam buffer */ beamBuffer.setSize(NUM_BEAMS, 0); - + /** Clear the HPF */ iirHPFfilters.clear(); - + /** Clear the Beamformer */ - beamformer->releaseResources(); + beamformer.reset(); } bool EbeamerAudioProcessor::insertCCParamMapping(const MidiCC &cc, const String ¶m) { @@ -223,7 +225,7 @@ void EbeamerAudioProcessor::removeCCParamMapping(const String ¶m) { } void EbeamerAudioProcessor::processCC(const MidiCC &cc, int value) { - + const String paramTag = ccToParamMap[cc]; Value val = parameters.getParameterAsValue(paramTag); auto range = parameters.getParameterRange(paramTag); @@ -235,7 +237,7 @@ void EbeamerAudioProcessor::processCC(const MidiCC &cc, int value) { } else { val.setValue(range.convertFrom0to1(value / 127.)); } - + } void EbeamerAudioProcessor::startCCLearning(const String &p) { @@ -255,14 +257,14 @@ const std::map &EbeamerAudioProcessor::getParamToCCMapping() { } void EbeamerAudioProcessor::processMidi(MidiBuffer &midiMessages) { - + // Loop over CC messages MidiBuffer::Iterator midiIter(midiMessages); MidiMessage midiMess; int samplePosition; while (midiIter.getNextEvent(midiMess, samplePosition)) { if (midiMess.isController()) { - + MidiCC cc = {midiMess.getChannel(), midiMess.getControllerNumber()}; if (ccToParamMap.count(cc) > 0) { /** Process the CC message if mapped */ @@ -274,28 +276,28 @@ void EbeamerAudioProcessor::processMidi(MidiBuffer &midiMessages) { } } } - + /** Clear all messages */ midiMessages.clear(); - + } void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer &midiMessages) { - + const auto startTick = Time::getHighResolutionTicks(); - + GenericScopedLock lock(processingLock); - + processMidi(midiMessages); - + /** If resources are not allocated this is an out-of-order request */ if (!resourcesAllocated) { jassertfalse; return; } - + ScopedNoDenormals noDenormals; - + /**Apply input gain directly on input buffer */ micGain.setGainDecibels(*micGainParam); { @@ -303,10 +305,10 @@ void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer auto context = juce::dsp::ProcessContextReplacing(block); micGain.process(context); } - + // Mic meter inputMeterDecay->push(buffer); - + /** Renew IIR coefficient if cut frequency changed */ if (prevHpfFreq != (bool) *hpfFreqParam) { iirCoeffHPF = IIRCoefficients::makeHighPass(sampleRate, *hpfFreqParam); @@ -315,26 +317,27 @@ void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer iirHPFfilter.setCoefficients(iirCoeffHPF); } } - + /**Apply HPF directly on input buffer */ for (auto inChannel = 0; inChannel < numActiveInputChannels; ++inChannel) { iirHPFfilters[inChannel].processSamples(buffer.getWritePointer(inChannel), buffer.getNumSamples()); } - + /** Set beams parameters */ for (auto beamIdx = 0; beamIdx < NUM_BEAMS; beamIdx++) { - float beamDoa = *steeringBeamParam[beamIdx]; - beamDoa = *frontFacingParam ? -beamDoa : beamDoa; - BeamParameters beamParams = {beamDoa, *widthBeamParam[beamIdx]}; + float beamDoaX = *steerBeamXParam[beamIdx]; + float beamDoaY = -(*steerBeamYParam[beamIdx]); //GUI and Beamforming use opposite vertical conventions + beamDoaX = *frontFacingParam ? -beamDoaX : beamDoaX; + BeamParameters beamParams = {beamDoaX,beamDoaY, *widthBeamParam[beamIdx]}; beamformer->setBeamParameters(beamIdx, beamParams); } - + /** Call the beamformer */ beamformer->processBlock(buffer); - + /** Retrieve beamformer outputs */ beamformer->getBeams(beamBuffer); - + /** Apply beams mute and volume */ for (auto beamIdx = 0; beamIdx < NUM_BEAMS; ++beamIdx) { if ((bool) *muteBeamParam[beamIdx] == false) { @@ -347,13 +350,13 @@ void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer auto contextToUse = dsp::ProcessContextReplacing(block); beamGain[beamIdx].process(contextToUse); } - + /** Measure beam output volume */ beamMeterDecay->push(beamBuffer); - + /** Clear buffer */ buffer.clear(); - + /** Sum beams in output channels */ for (int outChannel = 0; outChannel < numActiveOutputChannels; ++outChannel) { /** Sum the contributes from each beam */ @@ -362,7 +365,7 @@ void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer buffer.addFrom(outChannel, 0, beamBuffer, beamIdx, 0, buffer.getNumSamples(), channelBeamGain); } } - + /** Update load */ { const float elapsedTime = Time::highResolutionTicksToSeconds(Time::getHighResolutionTicks() - startTick); @@ -370,7 +373,7 @@ void EbeamerAudioProcessor::processBlock(AudioBuffer &buffer, MidiBuffer GenericScopedLock lock(loadLock); load = (load * (1 - loadAlpha)) + (curLoad * loadAlpha); } - + } //============================================================================== @@ -406,7 +409,6 @@ void EbeamerAudioProcessor::parameterChanged(const String ¶meterID, float ne //============================================================================== void EbeamerAudioProcessor::setMicConfig(const MicConfig &mc) { - beamformer->setMicConfig(mc); prepareToPlay(sampleRate, maximumExpectedSamplesPerBlock); } @@ -420,12 +422,12 @@ float EbeamerAudioProcessor::getCpuLoad() const { void EbeamerAudioProcessor::getStateInformation(MemoryBlock &destData) { /** Root XML */ std::unique_ptr xml(new XmlElement("eBeamerRoot")); - + /** Parameters state */ auto state = parameters.copyState(); XmlElement *xmlParams = new XmlElement(*state.createXml()); xml->addChildElement(xmlParams); - + /** Save Midi CC - Params Maping */ auto xmlMidi = xml->createNewChildElement("eBeamerMidiMap"); for (auto m : paramToCcMap) { @@ -437,9 +439,9 @@ void EbeamerAudioProcessor::getStateInformation(MemoryBlock &destData) { } void EbeamerAudioProcessor::setStateInformation(const void *data, int sizeInBytes) { - + std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); - + if (xmlState.get() != nullptr) { if (xmlState->hasTagName("eBeamerRoot")) { forEachXmlChildElement (*xmlState, rootElement) { @@ -465,6 +467,10 @@ void EbeamerAudioProcessor::setStateInformation(const void *data, int sizeInByte //============================================================================== +const std::atomic *EbeamerAudioProcessor::getConfigParam() const { + return parameters.getRawParameterValue("config"); +} + const std::atomic *EbeamerAudioProcessor::getFrontFacingParam() const { return parameters.getRawParameterValue("frontFacing"); } @@ -477,12 +483,26 @@ const std::atomic *EbeamerAudioProcessor::getBeamWidth(int idx) const { return parameters.getRawParameterValue("widthBeam" + String(idx + 1)); } -const std::atomic *EbeamerAudioProcessor::getBeamSteer(int idx) const { - return parameters.getRawParameterValue("steerBeam" + String(idx + 1)); +const std::atomic *EbeamerAudioProcessor::getBeamSteerX(int idx) const { + return parameters.getRawParameterValue("steerBeamX" + String(idx + 1)); } -void EbeamerAudioProcessor::getDoaEnergy(std::vector &energy) const { - beamformer->getDoaEnergy(energy); +const std::atomic *EbeamerAudioProcessor::getBeamSteerY(int idx) const { + return parameters.getRawParameterValue("steerBeamY" + String(idx + 1)); +} + +void EbeamerAudioProcessor::setBeamSteerX(int idx, float newVal){ + parameters.getParameterAsValue("steerBeamX"+String(idx+1)).setValue(newVal); +} + +void EbeamerAudioProcessor::setBeamSteerY(int idx, float newVal){ + parameters.getParameterAsValue("steerBeamY"+String(idx+1)).setValue(newVal); +} + +void EbeamerAudioProcessor::getDoaEnergy(Mtx &energy) const { + if (beamformer != nullptr){ + beamformer->getDoaEnergy(energy); + } } //============================================================================== @@ -549,7 +569,7 @@ AudioProcessor *JUCE_CALLTYPE createPluginFilter() { //============================================================================== AudioProcessorEditor *EbeamerAudioProcessor::createEditor() { - return new JucebeamAudioProcessorEditor(*this, parameters); + return new EBeamerAudioProcessorEditor(*this, parameters); } bool EbeamerAudioProcessor::hasEditor() const { diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index fd2a69c..d181204 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -1,9 +1,9 @@ /* - eBeamer Plugin Processor + eBeamer Plugin Processor Authors: Luca Bondi (luca.bondi@polimi.it) -*/ + */ #pragma once @@ -17,106 +17,114 @@ //============================================================================== class EbeamerAudioProcessor : - public AudioProcessor, - public AudioProcessorValueTreeState::Listener, - public MeterDecay::Callback, - public CpuLoadComp::Callback, - public SceneComp::Callback, - public MidiCC::Callback { +public AudioProcessor, +public AudioProcessorValueTreeState::Listener, +public MeterDecay::Callback, +public CpuLoadComp::Callback, +public SceneComp::Callback, +public MidiCC::Callback { public: - + //============================================================================== // JUCE plugin methods - + EbeamerAudioProcessor(); - + ~EbeamerAudioProcessor(); - + const String getName() const override; - + bool acceptsMidi() const override; - + bool producesMidi() const override; - + bool isMidiEffect() const override; - + double getTailLengthSeconds() const override; - + bool isBusesLayoutSupported(const BusesLayout &layouts) const override; - + void prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock) override; - + void processBlock(AudioBuffer &, MidiBuffer &) override; - + void releaseResources() override; - + int getNumPrograms() override; - + int getCurrentProgram() override; - + void setCurrentProgram(int index) override; - + const String getProgramName(int index) override; - + void changeProgramName(int index, const String &newName) override; - + AudioProcessorEditor *createEditor() override; - + bool hasEditor() const override; - + void getStateInformation(MemoryBlock &destData) override; - + void setStateInformation(const void *data, int sizeInBytes) override; - + //============================================================================== // MeterDecay Callback void getMeterValues(std::vector &meter, int meterId) const override; - + float getMeterValue(int meterId, int channel) const override; - + //============================================================================== // CpuLoadComp Callback float getCpuLoad() const override; - + //============================================================================== // MidiCC Callback /** Start learning the specified parameter */ void startCCLearning(const String &p) override; - + /** Stop learning the previous parameter */ void stopCCLearning() override; - + /** Get parameter being learned */ String getCCLearning() const override; - + /** Get a read-only reference to the parameters to CC mapping */ const std::map &getParamToCCMapping() override; - + /** Remove mapping between MidiCC and parameter */ void removeCCParamMapping(const String ¶m) override; - + //============================================================================== //SceneComponent Callback + const std::atomic *getConfigParam() const override; + const std::atomic *getFrontFacingParam() const override; - + const std::atomic *getBeamMute(int idx) const override; - + const std::atomic *getBeamWidth(int idx) const override; - - const std::atomic *getBeamSteer(int idx) const override; - - void getDoaEnergy(std::vector &energy) const override; - + + const std::atomic *getBeamSteerX(int idx) const override; + + const std::atomic *getBeamSteerY(int idx) const override; + + void setBeamSteerX(int idx, float newVal) override; + + void setBeamSteerY(int idx, float newVal) override; + + void getDoaEnergy(Mtx &energy) const override; + private: //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EbeamerAudioProcessor) - + //============================================================================== /** Number of active input channels */ juce::uint32 numActiveInputChannels = 0; /** Number of active output channels */ juce::uint32 numActiveOutputChannels = 0; - + //============================================================================== /** Time Constant for input gain variations */ const float gainTimeConst = 0.1; @@ -124,7 +132,7 @@ class EbeamerAudioProcessor : dsp::Gain micGain; /** Beam gain for each beam */ dsp::Gain beamGain[NUM_BEAMS]; - + //============================================================================== /** Previous HPF cut frequency */ float prevHpfFreq = 0; @@ -132,45 +140,45 @@ class EbeamerAudioProcessor : IIRCoefficients iirCoeffHPF; /** IIR HPF */ std::vector iirHPFfilters; - + //============================================================================== /** The active beamformer */ std::unique_ptr beamformer; - + //============================================================================== // Meters std::unique_ptr inputMeterDecay; std::unique_ptr beamMeterDecay; - + /** Decay of meters [s] */ const float metersDecay = 0.2; - + //============================================================================== // Beams buffers AudioBuffer beamBuffer; - + //============================================================================== /** Lock to prevent releaseResources being called when processBlock is running. AudioPluginHost does it. */ SpinLock processingLock; - + /** Resources for runtime are allocated. This flag is used to compensate for out-of-order calls to prepareToPlay, processBlock and releaseResources */ bool resourcesAllocated = false; - + /** Sample rate [Hz] */ float sampleRate = 48000; - + /** Maximum number of samples per block */ int maximumExpectedSamplesPerBlock = 4096; - + //============================================================================== /** Set a new microphone configuration */ void setMicConfig(const MicConfig &mc); - + //============================================================================== - + /** Measured average load */ float load = 0; /** Load time constant [s] */ @@ -179,15 +187,16 @@ class EbeamerAudioProcessor : float loadAlpha = 1; /** Load lock */ SpinLock loadLock; - + //============================================================================== - + /** Processor parameters tree */ AudioProcessorValueTreeState parameters; - + //============================================================================== // VST parameters - std::atomic *steeringBeamParam[NUM_BEAMS]; + std::atomic *steerBeamXParam[NUM_BEAMS]; + std::atomic *steerBeamYParam[NUM_BEAMS]; std::atomic *widthBeamParam[NUM_BEAMS]; std::atomic *panBeamParam[NUM_BEAMS]; std::atomic *levelBeamParam[NUM_BEAMS]; @@ -196,28 +205,28 @@ class EbeamerAudioProcessor : std::atomic *hpfFreqParam; std::atomic *frontFacingParam; std::atomic *configParam; - + void parameterChanged(const String ¶meterID, float newValue) override; - + //============================================================================== // MIDI management - + std::map ccToParamMap; std::map paramToCcMap; - + /** Process all the received MIDI messages */ void processMidi(MidiBuffer &midiMessages); - + /** Process a MIDI CC message and update parameter as needed */ void processCC(const MidiCC &cc, int value); - + /** Insert mapping between MidiCC and parameter @return: true if insertion successful, false if either cc or param already mapped */ bool insertCCParamMapping(const MidiCC &cc, const String ¶m); - + /** Parameter whose CC is being learned */ String paramCCToLearn = ""; - + }; diff --git a/Source/SceneComp.cpp b/Source/SceneComp.cpp index ce6a699..97205f3 100644 --- a/Source/SceneComp.cpp +++ b/Source/SceneComp.cpp @@ -4,209 +4,271 @@ Authors: Matteo Scerbo (matteo.scerbo@mail.polimi.it) Luca Bondi (luca.bondi@polimi.it) -*/ + */ #include "SceneComp.h" -TileComp::TileComp() { - frontFacingTransf = AffineTransform::rotation(MathConstants::pi, SCENE_WIDTH / 2, SCENE_HEIGHT / 2); -} - void TileComp::paint(Graphics &g) { - Path path; - - path.startNewSubPath(corners[0][0]); - path.lineTo(corners[1][0]); - path.lineTo(corners[1][1]); - path.lineTo(corners[0][1]); - path.closeSubPath(); - - if ((frontFacing != nullptr) && (bool) *frontFacing) { - //TODO: Move this to ComputeVertices in Grid Component - path.applyTransform(frontFacingTransf); - } - + g.setColour(tileColour); g.fillPath(path); - + g.setColour(Colours::black); PathStrokeType strokeType(0.5f); g.strokePath(path, strokeType); } -void TileComp::setFrontFacingParam(const std::atomic *p) { - frontFacing = p; -} void TileComp::setColour(const Colour &col) { tileColour = col; } -void TileComp::setCorners(const juce::Point &p1, - const juce::Point &p2, - const juce::Point &p3, - const juce::Point &p4) { - corners[0][0] = p1; - corners[1][0] = p2; - corners[0][1] = p3; - corners[1][1] = p4; +void TileComp::setPath(const Path& p) { + path = p; } //============================================================================== //============================================================================== GridComp::GridComp() { - for (int i = 0; i < TILE_ROW_COUNT; i++) - for (int j = 0; j < NUM_DOAS; j++) - addAndMakeVisible(tiles[i][j]); - - energy.resize(NUM_DOAS); - energyPreGain.resize(NUM_DOAS, -30); - - // Compute led tresholds const float ledStep = 3; //dB - + th.clear(); - for (auto ledIdx = TILE_ROW_COUNT - 1; ledIdx >= 0; --ledIdx) { - auto ledThDb = ledIdx == (TILE_ROW_COUNT - 1) ? RED_LT : -((TILE_ROW_COUNT - 1 - ledIdx) * ledStep); + for (auto ledIdx = ULA_TILE_ROW_COUNT - 1; ledIdx >= 0; --ledIdx) { + auto ledThDb = ledIdx == (ULA_TILE_ROW_COUNT - 1) ? RED_LT : -((ULA_TILE_ROW_COUNT - 1 - ledIdx) * ledStep); th.push_back(ledThDb); } - - startTimerHz(gridUpdateFrequency); - + } void GridComp::setCallback(const Callback *c) { callback = c; } -void GridComp::setParams(const std::atomic *frontFacing) { - for (int i = 0; i < TILE_ROW_COUNT; i++) { - for (int j = 0; j < NUM_DOAS; j++) { - tiles[i][j].setFrontFacingParam(frontFacing); - } - } +void GridComp::setParams(const std::atomic *config, + const std::atomic *frontFacing) { + frontFacingParam = frontFacing; + configParam = config; } void GridComp::resized() { - computeVertices(); - - for (int i = 0; i < TILE_ROW_COUNT; i++) { - for (int j = 0; j < NUM_DOAS; j++) { - - tiles[i][j].setCorners(vertices[i][j], vertices[i + 1][j], vertices[i][j + 1], vertices[i + 1][j + 1]); - - tiles[i][j].setBounds(getLocalBounds()); - - if (i < TILE_ROW_COUNT / 4) - tiles[i][j].setColour(Colours::red.darker(0.9)); - - if (TILE_ROW_COUNT / 4 <= i && i < TILE_ROW_COUNT / 2) - tiles[i][j].setColour(Colours::yellow.darker(0.9)); - - if (i >= TILE_ROW_COUNT / 2) - tiles[i][j].setColour(Colours::green.darker(0.9)); - + + if (frontFacingParam != nullptr && configParam != nullptr){ + stopTimer(); + GenericScopedLock l(lock); + area = getLocalBounds(); + + makeLayout(); + + AffineTransform transf; + + if ((bool)(*frontFacingParam)){ + if (isLinearArray(static_cast((int)*configParam))){ + transf = AffineTransform::rotation(pi, area.getWidth()/2, area.getHeight()/2); + }else{ + transf = AffineTransform::verticalFlip(area.getHeight()).rotation(pi, area.getWidth()/2, area.getHeight()/2); + } + } + + for (int rowIdx = 0; rowIdx < tiles.size(); rowIdx++) { + for (int colIdx = 0; colIdx < tiles[rowIdx].size(); colIdx++) { + { + Path path; + path.startNewSubPath(vertices[rowIdx][colIdx]); + path.lineTo(vertices[rowIdx + 1][colIdx]); + path.lineTo(vertices[rowIdx + 1][colIdx + 1]); + path.lineTo(vertices[rowIdx][colIdx + 1]); + path.closeSubPath(); + + path.applyTransform(transf); + + tiles[rowIdx][colIdx]->setPath(path); + } + + Colour baseCol; + if (isLinearArray(static_cast((int)*configParam))){ + baseCol = SingleChannelLedBar::dbToColour(-100,th[rowIdx]); + }else{ + baseCol = MultiChannelLedBar::dbToColor(0); + } + tiles[rowIdx][colIdx]->setColour(baseCol); + + tiles[rowIdx][colIdx]->setBounds(area); + + } } + + if (isLinearArray(static_cast((int)*configParam))){ + energyPreGain = Mtx(1, NUM_DOAX); + energy = Mtx(1, NUM_DOAX); + }else{ + energyPreGain = Mtx(NUM_DOAY, NUM_DOAX); + energy = Mtx(NUM_DOAY, NUM_DOAX); + } + energy.setConstant(-100); + energyPreGain.setConstant(-100); + + startTimerHz(gridUpdateFrequency); } + } void GridComp::timerCallback() { - - std::vector newEnergy(NUM_DOAS); + + GenericScopedLock l(lock); + + Mtx newEnergy; callback->getDoaEnergy(newEnergy); - - for (auto dirIdx = 0; dirIdx < energyPreGain.size(); ++dirIdx) { - energyPreGain[dirIdx] = ((1 - inertia) * (newEnergy[dirIdx])) + (inertia * energyPreGain[dirIdx]); - } - + + if (newEnergy.size() != energyPreGain.size()) + return; + + energyPreGain = ((1 - inertia) * (newEnergy)) + (inertia * energyPreGain); + // Very basic automatic gain - auto rangeEnergy = FloatVectorOperations::findMinAndMax(energyPreGain.data(), (int) energyPreGain.size()); - auto maxLevel = rangeEnergy.getEnd() + gain; - - if (maxLevel > 0) { - gain = jmax(gain - maxLevel - 3, minGain); + auto maxLevel = energyPreGain.maxCoeff() + gain; + + if (maxLevel > 3) { + gain = jmax(gain - 3, minGain); } else if (maxLevel < -18) { gain = jmin(gain - maxLevel, maxGain); - } else if (maxLevel > -3) { + } else if (maxLevel > 0) { gain = jmax(gain - 0.5f, minGain); - } else if (maxLevel < -9) { - gain = jmin(gain + 0.5f, maxGain); - } - - for (auto dirIdx = 0; dirIdx < energyPreGain.size(); ++dirIdx) { - energy[dirIdx] = energyPreGain[dirIdx] + gain; + } else if (maxLevel < -6) { + gain = jmin(gain + 0.1f, maxGain); } - - for (int j = 0; j < NUM_DOAS; j++) { - for (int i = 0; i < TILE_ROW_COUNT; i++) { - tiles[i][j].setColour(SingleChannelLedBar::thToColour(th[i], energy[j] > th[i])); + + energy = energyPreGain.array() + gain; + + for (int rowIdx = 0; rowIdx < tiles.size(); rowIdx++) { + for (int colIdx = 0; colIdx < tiles[rowIdx].size(); colIdx++) { + if (configParam != nullptr){ + if (isLinearArray(static_cast((int)*configParam))){ + tiles[rowIdx][colIdx]->setColour(SingleChannelLedBar::dbToColour(energy(0,colIdx),th[rowIdx])); + }else{ + tiles[rowIdx][colIdx]->setColour(MultiChannelLedBar::dbToColor(energy(rowIdx,colIdx))); + } + } + } } - + repaint(); - + } -void GridComp::computeVertices() { - const float w = SCENE_WIDTH; - const float h = SCENE_HEIGHT; - - float angle_diff = MathConstants::pi / NUM_DOAS; - - for (int i = 0; i <= TILE_ROW_COUNT; i++) { - - const float radius = h - h * (exp((float) i / TILE_ROW_COUNT) - 1) / (exp(1) - 1); - - for (int j = 0; j <= NUM_DOAS; j++) { - const float angle = j * angle_diff; - - vertices[i][j].setX(w / 2 - radius * cos(angle)); - vertices[i][j].setY(h - radius * sin(angle)); - +void GridComp::makeLayout() { + + vertices.resize(0); + tiles.resize(0); + + if (isLinearArray(static_cast((int)*configParam))){ + vertices.resize(ULA_TILE_ROW_COUNT+1, std::vector>(NUM_DOAX+1)); + + float angle_diff = MathConstants::pi / NUM_DOAX; + + for (int rowIdx = 0; rowIdx <= ULA_TILE_ROW_COUNT; rowIdx++) { + + const float radius = jmin(area.getHeight(),area.getWidth()/2) * (1 - (exp((float) rowIdx / ULA_TILE_ROW_COUNT) - 1) / (exp(1) - 1)); + + for (int colIdx = 0; colIdx <= NUM_DOAX; colIdx++) { + const float angle = colIdx * angle_diff; + + vertices[rowIdx][colIdx].setX(area.getWidth() / 2 - radius * cos(angle)); + vertices[rowIdx][colIdx].setY(area.getHeight() - radius * sin(angle)); + + } + } + + }else{ + vertices.resize(NUM_DOAY+1, std::vector>(NUM_DOAX+1)); + + const float deltaY = float(area.getHeight()) / NUM_DOAY; + const float deltaX = float(area.getWidth()) / NUM_DOAX; + + for (int rowIdx = 0; rowIdx <= NUM_DOAY; rowIdx++) { + for (int colIdx = 0; colIdx <= NUM_DOAX; colIdx++) { + vertices[rowIdx][colIdx].setY(rowIdx*deltaY); + vertices[rowIdx][colIdx].setX(colIdx*deltaX); + } + } + + } + + tiles.resize(vertices.size()-1); + for (auto &tilesRow : tiles){ + tilesRow.resize(NUM_DOAX); + for (auto &tile :tilesRow){ + tile = std::make_unique(); + addAndMakeVisible(*tile); } } + + + } //============================================================================== //============================================================================== -void BeamComp::setParams(const std::atomic *frontFacing, +void BeamComp::setParams(const std::atomic *config, + const std::atomic *frontFacing, const std::atomic *mute, const std::atomic *width, - const std::atomic *steer) { + const std::atomic *steerX, + const std::atomic *steerY) { muteParam = mute; widthParam = width; - steerParam = steer; + steerXParam = steerX; + steerYParam = steerY; frontFacingParam = frontFacing; + configParam = config; } -void BeamComp::paint(Graphics &g) { - - const float width = (0.1 + 2.9 * (*widthParam)) * SCENE_WIDTH / 10; - const float position = *steerParam; - - Path path; - path.startNewSubPath(0, 0); - path.cubicTo(width, -SCENE_WIDTH / 3, width, -SCENE_WIDTH / 2, 0, -SCENE_WIDTH / 2); - path.cubicTo(-width, -SCENE_WIDTH / 2, -width, -SCENE_WIDTH / 3, 0, 0); - path.closeSubPath(); +void BeamComp::resized(){ + area = getLocalBounds(); +} - path.applyTransform(AffineTransform::rotation((MathConstants::pi / 2) * position)); - path.applyTransform(AffineTransform::translation(SCENE_WIDTH / 2, SCENE_WIDTH / 2)); +void BeamComp::paint(Graphics &g) { - if ((bool) *frontFacingParam) { - path.applyTransform(AffineTransform::verticalFlip(SCENE_HEIGHT)); + path.clear(); + + + if (isLinearArray(static_cast((int)*configParam))){ + const float positionX = *steerXParam; + + const float width = (0.1 + 2.9 * (*widthParam)) * area.getWidth() / 10; + path.startNewSubPath(0, 0); + path.cubicTo(width, -area.getWidth() / 3, width, -area.getWidth() / 2, 0, -area.getWidth() / 2); + path.cubicTo(-width, -area.getWidth() / 2, -width, -area.getWidth() / 3, 0, 0); + path.closeSubPath(); + + path.applyTransform(AffineTransform::rotation((MathConstants::pi / 2) * positionX)); + path.applyTransform(AffineTransform::translation(area.getWidth() / 2, area.getHeight())); + + if ((bool) *frontFacingParam) { + path.applyTransform(AffineTransform::verticalFlip(area.getHeight())); + } + } + else{ + const float positionX = (*steerXParam + 1)*area.getWidth()/2 ; + const float positionY = area.getHeight() - ((*steerYParam + 1)*area.getHeight()/2); + const float width = (0.5 + 2.5 * (*widthParam)) * area.getWidth() / 10; + + path.startNewSubPath(0, 0); + path.addEllipse(positionX-width/2, positionY-width/2, width, width); } + - if (~(bool) *muteParam) { + + if (!(bool) *muteParam) { g.setColour(baseColour.brighter()); g.setOpacity(0.4); g.fillPath(path); } - + g.setColour(baseColour); - g.setOpacity(0.8); + g.setOpacity(0.9); PathStrokeType strokeType(2); g.strokePath(path, strokeType); } @@ -216,17 +278,22 @@ void BeamComp::paint(Graphics &g) { SceneComp::SceneComp() { addAndMakeVisible(grid); - for (int i = 0; i < NUM_BEAMS; i++) - addAndMakeVisible(beams[i]); + for (auto &b:beams){ + addAndMakeVisible(b); + b.addMouseListener(this, true); + } } -void SceneComp::setCallback(const Callback *c) { +void SceneComp::setCallback(Callback *c) { + callback = c; grid.setCallback(c); - grid.setParams(c->getFrontFacingParam()); - + grid.setParams(c->getConfigParam(),c->getFrontFacingParam()); + for (auto idx = 0; idx < NUM_BEAMS; idx++) { - beams[idx].setParams(c->getFrontFacingParam(), c->getBeamMute(idx), c->getBeamWidth(idx), c->getBeamSteer(idx)); + beams[idx].setParams(c->getConfigParam(), c->getFrontFacingParam(), c->getBeamMute(idx), c->getBeamWidth(idx), c->getBeamSteerX(idx), c->getBeamSteerY(idx)); } + + resized(); } void SceneComp::paint(Graphics &g) { @@ -234,9 +301,20 @@ void SceneComp::paint(Graphics &g) { } void SceneComp::resized() { - grid.setBounds(getLocalBounds()); - for (int i = 0; i < NUM_BEAMS; i++) - beams[i].setBounds(getLocalBounds()); + area = getLocalBounds(); + + if (callback != nullptr) + if (!isLinearArray(static_cast((int)*callback->getConfigParam()))) + area.removeFromTop(20); + + if (grid.getBounds() == area){ + grid.resized(); + }else{ + grid.setBounds(area); + } + + for (auto &b: beams) + b.setBounds(area); } void SceneComp::setBeamColors(const std::vector &colours) { @@ -246,5 +324,31 @@ void SceneComp::setBeamColors(const std::vector &colours) { } } +void SceneComp::mouseDown (const MouseEvent& e){ + for (int idx = 0; idx < NUM_BEAMS; idx++){ + if (beams[idx].getPath().contains(e.getMouseDownPosition().toFloat())){ + beamBeingDragged = idx; + dragStartX = *callback->getBeamSteerX(beamBeingDragged); + dragStartY = *callback->getBeamSteerY(beamBeingDragged); + break; + } + } +} + +void SceneComp::mouseDrag (const MouseEvent& e){ + if (beamBeingDragged >= 0){ + const float deltaX = float(e.getDistanceFromDragStartX())/area.getWidth()*2; + const float deltaY = float(-e.getDistanceFromDragStartY())/area.getHeight()*2; + const float newX = jlimit(-1.f,1.f,dragStartX + deltaX); + const float newY = jlimit(-1.f,1.f,dragStartY + deltaY); + callback->setBeamSteerX(beamBeingDragged, newX); + callback->setBeamSteerY(beamBeingDragged, newY); + } +} + +void SceneComp::mouseUp (const MouseEvent& e){ + beamBeingDragged = -1; +} + //============================================================================== //============================================================================== diff --git a/Source/SceneComp.h b/Source/SceneComp.h index 1eb4695..400cff6 100644 --- a/Source/SceneComp.h +++ b/Source/SceneComp.h @@ -4,7 +4,7 @@ Authors: Matteo Scerbo (matteo.scerbo@mail.polimi.it) Luca Bondi (luca.bondi@polimi.it) -*/ + */ #pragma once @@ -15,31 +15,26 @@ class TileComp : public Component { public: - - TileComp(); - + + TileComp() {}; + ~TileComp() {}; - + void paint(Graphics &) override; - + void resized() override {}; - - void setFrontFacingParam(const std::atomic *p); - + void setColour(const Colour &col); - - void setCorners(const juce::Point &, - const juce::Point &, - const juce::Point &, - const juce::Point &); - + + void setPath(const Path &); + private: - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TileComp) - - juce::Point corners[2][2]; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TileComp) + + Path path; Colour tileColour; - + const std::atomic *frontFacing = nullptr; AffineTransform frontFacingTransf; }; @@ -49,42 +44,49 @@ class TileComp : public Component { class GridComp : public Component, public Timer { public: GridComp(); - + ~GridComp() {}; - + void resized() override; - + class Callback { public: virtual ~Callback() = default; - - virtual void getDoaEnergy(std::vector &energy) const = 0; + + virtual void getDoaEnergy(Mtx &energy) const = 0; }; - + void setCallback(const Callback *p); - - void setParams(const std::atomic *frontFacing); - + + void setParams(const std::atomic *config, + const std::atomic *frontFacing); + private: - - TileComp tiles[TILE_ROW_COUNT][NUM_DOAS]; - juce::Point vertices[TILE_ROW_COUNT + 1][NUM_DOAS + 1]; - + + SpinLock lock; + + Rectangle area; + + std::vector>> tiles; + std::vector>> vertices; + const Callback *callback = nullptr; - + const std::atomic *frontFacingParam = nullptr; + const std::atomic *configParam = nullptr; + std::vector th; - - std::vector energy, energyPreGain; + + Mtx energy, energyPreGain; float inertia = 0.85; float gain = 0; const float maxGain = 60, minGain = -20; - + const float gridUpdateFrequency = 10; - - void computeVertices(); - + + void makeLayout(); + void timerCallback() override; - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GridComp) }; @@ -93,31 +95,43 @@ class GridComp : public Component, public Timer { class BeamComp : public Component { public: BeamComp() {}; - + ~BeamComp() {}; - + void paint(Graphics &) override; - - void resized() override {}; - - void setParams(const std::atomic *frontFacing, + + void resized() override; + + void setParams( + const std::atomic *config, + const std::atomic *frontFacing, const std::atomic *mute, const std::atomic *width, - const std::atomic *steer); - + const std::atomic *steerX, + const std::atomic *steerY); + //TODO: Use LookAndFeel void setBaseColor(Colour colour) { baseColour = colour; } - + + const Path& getPath(){ + return path; + }; + private: - + const std::atomic *frontFacingParam = nullptr; const std::atomic *muteParam = nullptr; const std::atomic *widthParam = nullptr; - const std::atomic *steerParam = nullptr; - - + const std::atomic *steerXParam = nullptr; + const std::atomic *steerYParam = nullptr; + const std::atomic *configParam = nullptr; + + Rectangle area; + Colour baseColour = Colours::lightblue; - + + Path path; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BeamComp) }; @@ -126,36 +140,55 @@ class BeamComp : public Component { class SceneComp : public Component { public: SceneComp(); - + ~SceneComp() {}; - + class Callback : public GridComp::Callback { public: virtual ~Callback() = default; - + + virtual const std::atomic *getConfigParam() const = 0; + virtual const std::atomic *getFrontFacingParam() const = 0; - + virtual const std::atomic *getBeamMute(int idx) const = 0; - + virtual const std::atomic *getBeamWidth(int idx) const = 0; - - virtual const std::atomic *getBeamSteer(int idx) const = 0; + + virtual const std::atomic *getBeamSteerX(int idx) const = 0; + + virtual const std::atomic *getBeamSteerY(int idx) const = 0; + + virtual void setBeamSteerX(int idx, float newVal) = 0; + + virtual void setBeamSteerY(int idx, float newVal) = 0; }; - - void setCallback(const Callback *c); - + + void setCallback(Callback *c); + void paint(Graphics &) override; - + void resized() override; - + //TODO: Use LookAndFeel void setBeamColors(const std::vector &colours); - - + + private: - + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SceneComp) - + Callback *callback = nullptr; BeamComp beams[NUM_BEAMS]; GridComp grid; + + Rectangle area; + + void mouseDown (const MouseEvent& e) override; + void mouseDrag (const MouseEvent& e) override; + void mouseUp (const MouseEvent& e) override; + + int beamBeingDragged = -1; + float dragStartX, dragStartY; + + }; diff --git a/Source/SignalProcessing.h b/Source/SignalProcessing.h index 85d38ea..0b0d1b9 100644 --- a/Source/SignalProcessing.h +++ b/Source/SignalProcessing.h @@ -11,6 +11,7 @@ #include "../JuceLibraryCode/JuceHeader.h" typedef Eigen::Matrix Vec; +typedef Eigen::Matrix Mtx; typedef Eigen::Matrix, Eigen::Dynamic, 1> CpxVec; typedef Eigen::Matrix, Eigen::Dynamic, Eigen::Dynamic> CpxMtx; diff --git a/Source/ebeamerDefs.cpp b/Source/ebeamerDefs.cpp new file mode 100644 index 0000000..41f0ee6 --- /dev/null +++ b/Source/ebeamerDefs.cpp @@ -0,0 +1,23 @@ +/* + Project-wise types and definitions + + Authors: + Luca Bondi (luca.bondi@polimi.it) +*/ + +#include "ebeamerDefs.h" + +bool isLinearArray(MicConfig m){ + switch(m){ + case ULA_1ESTICK: + case ULA_2ESTICK: + case ULA_3ESTICK: + case ULA_4ESTICK: + return true; + case URA_2ESTICK: + case URA_3ESTICK: + case URA_4ESTICK: + case URA_2x2ESTICK: + return false; + } +}; diff --git a/Source/ebeamerDefs.h b/Source/ebeamerDefs.h index db892e6..77602c0 100644 --- a/Source/ebeamerDefs.h +++ b/Source/ebeamerDefs.h @@ -8,7 +8,8 @@ #pragma once #define NUM_BEAMS 2 -#define NUM_DOAS 25 +#define NUM_DOAX 25 +#define NUM_DOAY 9 #define GUI_WIDTH 540 #define GUI_HEIGHT 830 @@ -16,7 +17,7 @@ #define SCENE_WIDTH 460 #define SCENE_HEIGHT 230 -#define TILE_ROW_COUNT 7 +#define ULA_TILE_ROW_COUNT 7 #define LABEL_BEAM_WIDTH 25 #define STEER_SLIDER_HEIGHT 40 @@ -45,33 +46,45 @@ #define INPUT_GAIN_SLIDER_HEIGHT 40 #define INPUT_GAIN_LABEL_WIDTH 50 -#define PREFORMANCE_MONITOR_HEIGHT 20 -#define CPULOAD_WIDTH 80 +#define FOOTER_MARGIN 10 +#define FOOTER_HEIGHT 20 +#define CPULOAD_WIDTH 140 #define CPULOAD_UPDATE_FREQ 10 //Hz #define FRONT_TOGGLE_LABEL_WIDTH 40 #define FRONT_TOGGLE_WIDTH 25 #define CONFIG_COMBO_LABEL_WIDTH 65 -#define CONFIG_COMBO_WIDTH 80 +#define CONFIG_COMBO_WIDTH 105 #define INPUT_METER_UPDATE_FREQ 10 //Hz #define BEAM_METER_UPDATE_FREQ 10 //Hz #define ENERGY_UPDATE_FREQ 10 //Hz +#include "../JuceLibraryCode/JuceHeader.h" /** Available eSticks configurations type */ typedef enum { - LMA_1ESTICK, - LMA_2ESTICK, - LMA_3ESTICK, - LMA_4ESTICK, + ULA_1ESTICK, + ULA_2ESTICK, + ULA_3ESTICK, + ULA_4ESTICK, + URA_2ESTICK, + URA_3ESTICK, + URA_4ESTICK, + URA_2x2ESTICK, } MicConfig; /** Available eSticks configurations labels */ const StringArray micConfigLabels({ "Single", - "Hor 2", - "Hor 3", - "Hor 4", + "Horiz 2", + "Horiz 3", + "Horiz 4", + "Stack 2", + "Stack 3", + "Stack 4", + "Stack 2x2", }); + +bool isLinearArray(MicConfig m); diff --git a/docs/gui.png b/docs/gui.png index 0e018cd..26a09b9 100644 Binary files a/docs/gui.png and b/docs/gui.png differ diff --git a/eBeamer.jucer b/eBeamer.jucer index e55302f..df1951b 100644 --- a/eBeamer.jucer +++ b/eBeamer.jucer @@ -5,7 +5,7 @@ pluginVST3Category="Spatial" pluginAAXCategory="512" pluginRTASCategory="512" pluginVSTCategory="kPlugCategSpacializer" pluginAUMainType="'aufx'" pluginName="eBeamer" pluginDesc="eStick beam controller" pluginManufacturer="ISPL Polimi" - pluginManufacturerCode="Ispl" pluginCode="ebea" version="1.0.1" + pluginManufacturerCode="Ispl" pluginCode="ebea" version="1.1.0" bundleIdentifier="it.polimi.deib.ispl.ebeamer" companyWebsite="http://ispl.deib.polimi.it/" aaxIdentifier="it.polimi.deib.ispl.ebeamer" pluginAUExportPrefix="ebeamerAU" pluginCharacteristicsValue="pluginWantsMidiIn" headerPath="..\..\ASIO\common"> @@ -548,6 +548,7 @@ +