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 @@
+