commit 66872642c0c38fac65aca1db1b35233999f2fbe3
parent 218c03fd6ec0c06bedbc6cf116ee0e131067e001
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Wed, 17 Apr 2024 23:01:12 +0200
add FST sdk from https://git.iem.at/zmoelnig/FST/-/commits/v0.123.0?ref_type=tags
Diffstat:
34 files changed, 9471 insertions(+), 0 deletions(-)
diff --git a/source/fst/LICENSE b/source/fst/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is 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. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ 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.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ 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 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. Use with the GNU Affero General Public License.
+
+ 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 Affero 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 special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU 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 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 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 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.
+
+ fst
+ Copyright (C) 2019 IOhannes m zmölnig
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ fst Copyright (C) 2019 IOhannes m zmölnig
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ 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 GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/source/fst/README.md b/source/fst/README.md
@@ -0,0 +1,99 @@
+FST - Free Studio Technology: header files for building audio plugins
+=====================================================================
+
+this is a simple header file
+to allow the compilation of audio plugins and audio plugin hosts following
+the industrial standard of Steinberg's VST(2).
+
+Steinberg provides a VST2 SDK, but:
+- it has a proprietary license
+- is no longer available from Steinberg (as they removed the VST2-SDK to push
+ VST3)
+
+FST is the attempt to create a bona-fide reverse-engineered header file,
+that is created without reference to the official VST SDK and without its
+developer(s) having agreed to the VST SDK license agreement.
+
+All reverse-engineering steps are documented in the [REVERSE_ENGINEERING](docs/REVERSE_ENGINEERING.md)
+document.
+
+## USING
+You are free to use FST under our [LICENSE terms](#licensing).
+
+However, Steinberg has trademarked the `VST` name, and explicitly forbids
+anyone from using this name without agreeing to *their* LICENSE terms.
+
+If you plan to release audio plugins based on the FST Interface, you are
+not allowed to call them `VST` or even say that they are `VST Compatible`.
+
+## BONA-FIDA REVERSE-ENGINEERING
+
+Care is being taken to not break any license agreement or copyright terms.
+We also take care not to expose or incriminate any Open Source
+developer/project of unwittingly (or not) violating copyright terms or
+license agreements.
+
+Our self-imposed rules for this purpose are:
+- Do not use any material from Steinberg
+ - Never accept Steinberg's proprietary license agreement
+ - Never obtain the VST(2)-SDK, by whatever means
+- Only use VST-enabled binaries and sources (hosts and plugins) from
+ commercial entities that do not require accepting any license terms.
+- Do not use existing reverse-engineering attempts of the SDK
+
+### Some reasoning
+We do not consider ourselves bound by Steinberg's license if we never accept it
+explicitly or implicitly (e.g. by obtaining a version of the VST-SDKs
+- even if it happens to be bundled with some 3rd party project).
+
+We only use Closed Source binaries from commercial entities under the assumption
+that whowever makes their living from (also) selling plugins, will have a right
+to do so (e.g. they are actually allowed to use the SDKs they are using, most
+likely by coming to an agreement with the SDK vendors).
+Conversely, we do not use binaries from independent Open Source developers
+as it is not our job to track down the legitimacy of their use of SDKs.
+
+We only use binaries that do not require us to actively accept license
+agreements, as they might contain clauses that prevent reverse-engineering.
+
+There are a couple of existing reverse-engineering attempts of the SDK, but we
+haven't found any documentation on how they were created.
+Therefore we cannot rule out that they might have been written with the use of
+the original SDK (by this we do not imply that creating these attempts actually
+infringed any copyright or license terms - only that we cannot be sure about it).
+
+## VERSIONING
+
+The versioning scheme of FST basically follows [semantic versioning](https://semver.org):
+
+Whenever a new opcode, etc. is discovered, the minor version number is
+increased. Thus the minor version number (roughly) reflects the number of
+discovered symbols.
+
+The micro version number is increased whenever a release is made without new
+symbols (e.g. for a compatibility fix for a new compiler).
+
+The major version number will be incremented to `1` once the entire SDK has
+been reverse engineered (as we do not anticipate any API breaking changes).
+
+## LICENSING
+FST is released under the [`GNU General Public License (version 3 or later)`](https://www.gnu.org/licenses/gpl-3.0.en.html).
+
+It is our understanding that if you use the FST-header in your project,
+you have to use a compatible (Open Source) license for your project as well.
+
+There are no plans to release FST under a more permissive license.
+If you want to build closed-source plugins (or plugin hosts), you have to
+use the official Steinberg VST2 SDK, and comply with their licensing terms.
+
+
+## Miss a feature? File a bug!
+So far, FST is not feature complete.
+Opcodes that have not been reversed engineered yet are marked as `deprecated`,
+so you get a compiler warning when using them.
+
+If you find you need a specific opcode that is not supported yet, please file a
+bug at https://git.iem.at/zmoelnig/FST/issues
+
+The more information you can provide, the more likely we will be able to
+implement missing or fix broken things.
diff --git a/source/fst/docs/REVERSE_ENGINEERING.md b/source/fst/docs/REVERSE_ENGINEERING.md
@@ -0,0 +1,4300 @@
+REVERSE ENGINEERING the FST audio plugin API
+============================================
+
+# Part0: used software
+
+our development system used for reverse-engineering is a
+Debian GNU/Linux (amd64) box, running Debian testing/unstable.
+
+
+## general software
+Apart from the general system utilities (like `emacs`, `vi`, `sed`, `grep`,...)
+found on any ordinary Linux system, we also use:
+
+- Compiler: `gcc` (Debian 8.2.0-20) 8.2.0
+- Debugger: `gdb` (Debian 8.2.1-1) 8.2.1
+- Memory Debugger: `valgrind`
+- Hexeditor: `bless`
+- Diff-viewers: `meld`, `vimdiff`, `vbindiff`
+
+
+## VST related
+all VST-related software was accessed on 2019-02-16 unless otherwise indicated.
+
+
+### Frameworks
+
+- JUCE-5.4.1: https://d30pueezughrda.cloudfront.net/juce/juce-5.4.1-linux.zip
+- JUCE-6.0.7: https://github.com/juce-framework/JUCE/releases/download/6.0.7/juce-6.0.7-linux.zip (accessed 2021-02-03)
+- JUCE-7.0.2: https://github.com/juce-framework/JUCE/releases/download/7.0.2/juce-7.0.2-linux.zip (accessed 2022-08-17)
+
+
+### Plugins
+
+- U-he Protoverb: https://u-he.com/products/protoverb/
+- InstaLooper: https://www.audioblast.me/instalooper.html
+- BowEcho: https://ineardisplay.com/plugins/legacy/
+- Danaides: https://ineardisplay.com/plugins/legacy/
+- Digits: http://www.extentofthejam.com/
+- Hypercyclic: http://www.mucoder.net/en/hypercyclic/
+- Tonespace: http://www.mucoder.net/en/tonespace/
+- GVST: https://www.gvst.co.uk/index.htm
+
+- ArpeggiatorTutorial: https://docs.juce.com/master/tutorial_plugin_examples.html (accessed 2022-08-17)
+- NoiseGateTutorial: https://docs.juce.com/master/tutorial_plugin_examples.html (accessed 2022-08-17)
+- SurroundTutorial: https://docs.juce.com/master/tutorial_plugin_examples.html (accessed 2022-08-17)
+- MultiOutSynthTutorial: https://docs.juce.com/master/tutorial_plugin_examples.html (accessed 2022-08-17)
+
+### Hosts
+
+- Reaper: https://www.reaper.fm/download.php (reaper5965_linux_x86_64.tar.xz)
+- MrsWatson: http://teragonaudio.com/MrsWatson.html (MrsWatson-0.9.7)
+
+# Part1: unknown symbols
+
+## starting
+We use JUCE-5.4.1 to compile a VST2-compliant plugin host, but without VST-headers.
+
+- edit `JUCE/extras/AudioPluginHost/AudioPluginHost.jucer` and enable VST, by
+renaming the `JUCE_PLUGINHOST_VST3` variable:
+
+~~~
+JUCE_PLUGINHOST_VST="1"
+~~~
+
+- resave the Project `JUCE/Projucer -resave JUCE/extras/AudioPluginHost/AudioPluginHost.jucer`
+- and try to build it:
+
+~~~bash
+cd JUCE/extras/AudioPluginHost/Builds/LinuxMakefile
+make
+~~~
+
+- JUCE will complain about missing `<pluginterfaces/vst2.x/aeffect.h>` (and later
+`<pluginterfaces/vst2.x/aeffectx.h>`) headers.
+We will just create a single header `fst.h`, and have stub-implementations of `aeffect.h`
+and `aeffectx.h` that simply include the real header.
+
+~~~bash
+mkdir FST
+touch FST/fst.h
+for i in aeffect.h aeffectx.h ; do
+cat > $i <<EOF
+#pragma once
+#include "fst.h"
+EOF
+~~~
+
+- finally, make JUCE use our headers:
+
+~~~bash
+ln -s $(pwd)/FST JUCE/modules/juce_audio_processors/format_types/VST3_SDK/pluginterfaces/vst2.x
+~~~
+
+## getting our first enum names
+
+building the AudioPluginHost gets us a lot of errors (about 501).
+these errors are interesting, as this is what we want to implement in the `fst.h` headers.
+luckily JUCE will attempt to put everything in the `Vst2` namespace, making it easier to find the
+interesting broken things.
+
+So lets start, by extracting all the symbols that should be part of the `Vst2` namespace (but are not):
+
+~~~bash
+make -k -C JUCE/extras/AudioPluginHost/Builds/LinuxMakefile 2>&1 \
+| grep error: \
+| grep "is not a member of .Vst2.$" \
+| sed -e 's|.* error: ||' | sed -e 's|^.||' -e 's|. .*||' \
+| sort -u
+~~~
+
+This gives us the name `ERect` and symbols starting with the following prefixes:
+- `audioMaster*`
+- `eff*`
+- `kPlug*`
+- `kSpeaker*`
+- `kVst*`
+- `Vst*`
+
+As a first draft, let's just add all those symbols as enumerations to our `fst.h` header:
+
+~~~C
+##define FST_UNKNOWN(x) x
+enum {
+ FST_UNKNOWN(audioMasterAutomate),
+ FST_UNKNOWN(audioMasterBeginEdit),
+ // ..
+ FST_UNKNOWN(audioMasterWillReplaceOrAccumulate)
+};
+// ...
+~~~
+
+We wrap all the values into the `FST_UNKNOWN` macro, because we don't know their actual values yet.
+
+We even might go as far as assign some pseudo-random value that is far off to each enum,
+in order to not accidentally trigger a real enum:
+
+~~~
+FST_UNKNOWN_ENUM(x) x = 98765 + __LINE__
+~~~
+
+## some types
+if we now re-compile the AudioPluginHost against our new headers, we get a lot less errors (about 198).
+It's still quite a lot of errors though...
+
+Look at the output of
+
+~~~bash
+make -k -C JUCE/extras/AudioPluginHost/Builds/LinuxMakefile 2>&1 | less
+~~~
+
+There's a number of errors that are `type` related, using these symbols:
+
+- `AEffect`
+- `VstEvent`
+- `VstEvents`
+- `VstPlugCategory`
+- `VstSpeakerArrangement`
+- `VstTimeInfo`
+
+
+### VstEvent
+A quick `rgrep VstEvent` on the JUCE sources leads us to `modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h`, which reveals that
+`VstEvent` is a structure with at least two members `type` and `byteSize`.
+The later is of a type compatible with `size_t` (a `sizeof()` value is assigned to it),
+the former is assigned a value of `kVstMidiType`, and compared to `kVstSysExType`. Most likely these two are in a separate enum `VstEventType` or somesuch.
+
+There's also related types `VstMidiEvent` and `VstMidiSysexEvent` which can be case to `VstEvent`.
+The sysex-type features a `sysexDump` member, that is a dynamically allocated array (most likely of some `BYTE` type).
+
+~~~C
+typedef enum {
+ FST_UNKNOWN_ENUM(kVstMidiType),
+ FST_UNKNOWN_ENUM(kVstSysExType),
+} t_fstEventType;
+
+##define FSTEVENT_COMMON t_fstEventType type; int byteSize
+typedef struct VstEvent_ {
+ FSTEVENT_COMMON;
+} VstEvent;
+
+typedef struct VstMidiEvent_ {
+ FSTEVENT_COMMON;
+ /* FIXXXME: unknown member order */
+ FST_UNKNOWN(int) noteLength;
+ FST_UNKNOWN(int) noteOffset;
+ FST_UNKNOWN(int) detune;
+ FST_UNKNOWN(int) noteOffVelocity;
+ FST_UNKNOWN(int) deltaFrames;
+ char midiData[4];
+} VstMidiEvent;
+typedef struct VstMidiSysexEvent_ {
+ FSTEVENT_COMMON;
+ /* FIXXXME: unknown member order */
+ char*sysexDump;
+ FST_UNKNOWN(int) dumpBytes;
+ FST_UNKNOWN(int) deltaFrames;
+ FST_UNKNOWN(int) flags;
+ FST_UNKNOWN(int) resvd1, resvd2;
+} VstMidiSysexEvent;
+~~~
+
+`VstEvent`, `VstMidiEvent` and `VstSysexEvent` are removed from our previous enum.
+
+
+### VstEvents
+Since `VstEvents` is supposed to be a type, let's naively set it to `int`.
+This reveals, that JUCE really wants a struct with members `numEvents` (`int`)
+and `events` (an array with elements to be cast to `*VstEvent` and the like)
+
+~~~C
+typedef struct VstEvents_ {
+ int numEvents;
+ VstEvent**events;
+} VstEvents;
+~~~
+
+
+### VstSpeakerArrangement
+Since `VstSpeakerArrangement` is supposed to be a type, let's naively set it to `int`.
+This will yield errors about not being able to access members in a non-class type.
+
+~~~bash
+make -C JUCE/extras/AudioPluginHost/Builds/LinuxMakefile 2>&1 \
+| grep VstSpeakerArrangement | grep "error:" \
+| sed -e 's|.* error: request for member ||' -e 's| in .*||' \
+| sort -u
+~~~
+
+Gives us three members, `type`, `speakers`, `numChannels`.
+To get the types of these members, let's set it experimentally to something stupid, like `void*`.
+That reveleals that we need to be able to convert `type` to `int32` (but more likely some enumeration),
+`numChannels` to `int`, and that `speakers` really is an array to `VstSpeakerProperties`.
+
+Somewhere in `modules/juce_audio_processors/format_types/juce_VSTCommon.h`, we can see that
+the `type` is populated by `int channelSetToVstArrangementType()` which really returns values like
+`kSpeakerArrStereo`.
+
+I'd rather assign the `kSpeakerArr*` enumeration a name `t_fstSpeakerArrangementType` and use that for `type`,
+but alas, C++ does not allow use to implicitly cast `int` to `enum`, so we need to use `int` instead.
+
+~~~C
+typedef struct VstSpeakerArrangement_ {
+ int type;
+ int numChannels;
+ VstSpeakerProperties speakers[];
+} VstSpeakerArrangement;
+~~~
+
+#### sidenote: varsized arrays
+The `VstSpeakerArrangement.speakers` could be a pointer (`VstSpeakerProperties*`) or an array (`VstSpeakerProperties[]`).
+Because they are often used almost synonymously, my first attempt used a pointer.
+Much later, i tried compiling and *running* JUCE's AudioPluginHost and it would spectacularily segfault
+when allocating memory for `VstSpeakerArrangement` and assigning values to it.
+It turned out, that using a var-sized array instead of the pointer fixes this issue.
+
+the `type var[]` syntax is C99, for older C-implementations use `type var[0]` instead.
+
+
+### VstSpeakerProperties
+Resolving `VstSpeakerArrangement`, leaves us with a new type `VstSpeakerProperties`.
+We play the above game again, and get:
+
+~~~C
+typedef struct VstSpeakerProperties_ {
+ int type;
+} VstSpeakerProperties;
+~~~
+
+This is highly unlikely: a struct with only a single member would usually be just replaced by the member itself.
+So the `VstSpeakerProperties` will *most likely* have more members, of which we don't know the names nor types.
+
+### VstPlugCategory
+A value of this type is compared to `kPlugCategShell`, so we typedef the enumeration
+with the `kPlug*` names to `VstPlugCategory`.
+
+This also adds the `kPlugSurroundFx` value to this new type, if which I am not sure
+yet.
+
+
+### VstTimeInfo
+Again playing the game by setting `VstTimeInfo` to `int`, gives us members of the struct.
+So far, the types are guessed based on the values they are assigned to (If a floating-point value is assigned,
+we use `double`, for integer types we use `int`):
+
+~~~C
+typedef struct VstTimeInfo_ {
+ FST_UNKNOWN(double) tempo;
+ FST_UNKNOWN(int) timeSigNumerator;
+ FST_UNKNOWN(int) timeSigDenominator;
+ FST_UNKNOWN(double) sampleRate;// = rate;
+ FST_UNKNOWN(int) samplePos;
+ FST_UNKNOWN(int) flags;// = Vst2::kVstNanosValid
+ FST_UNKNOWN(double) nanoSeconds;
+
+ FST_UNKNOWN(double) ppqPos; // (double)position.ppqPosition;
+ FST_UNKNOWN(double) barStartPos; // (double)ppqPositionOfLastBarStart;
+ FST_UNKNOWN(double) cycleStartPos; // (double)ppqLoopStart;
+ FST_UNKNOWN(double) cycleEndPos; // (double)ppqLoopEnd;
+ FST_UNKNOWN(int) smpteFrameRate; //int32
+ FST_UNKNOWN(int) smpteOffset; //int32
+} VstTimeInfo;
+~~~
+
+### AEffect
+rinse & repeat.
+
+we need a few helpers:
+
+- `VSTCALLBACK` this seems to be only a decorator, we use `#define VSTCALLBACK` to ignore it for now
+- `audioMasterCallback` ??? (that's probably a pointer to the dispatcher function)
+
+ effect = module->moduleMain ((Vst2::audioMasterCallback) &audioMaster);
+
+- a pointer-sized-int, for now `typedef long t_fstPtrInt;` will do
+
+
+after that the biggest issue is that the `AEffect` struct contains a few function-pointers, namely
+- `dispatcher`
+- `getParameter` & `setParameter`
+- `process`, `processReplacing` and `processDoubleReplacing`
+
+luckily JUCE maps those functions quite directly, so we get:
+~~~
+t_fstPtrInt dispatcher(AEffect*, int opcode, int index, t_fstPtrInt ivalue, void* const ptr, float fvalue);
+void setParameter(AEffect*, int index, float value);
+float getParameter(AEffect*, int index);
+void process(AEffect*, float**indata, float**outdata, int sampleframes);
+void processReplacing(AEffect*, float**indata, float**outdata, int sampleframes);
+void processReplacingDouble(AEffect*, double**indata, double**outdata, int sampleframes);
+~~~
+
+And we end up with something like the following:
+
+~~~C
+typedef long t_fstPtrInt; /* pointer sized int */
+##define VSTCALLBACK
+
+ /* dispatcher(effect, opcode, index, ivalue, ptr, fvalue) */
+typedef t_fstPtrInt (t_fstEffectDispatcher)(struct AEffect_*, int, int, t_fstPtrInt, void* const, float);
+typedef void (t_fstEffectSetParameter)(struct AEffect_*, int, float);
+typedef float (t_fstEffectGetParameter)(struct AEffect_*, int);
+typedef void (t_fstEffectProcess)(struct AEffect_*, float**indata, float**outdata, int sampleframes);
+typedef void (t_fstEffectProcessInplace)(struct AEffect_*, float**indata, float**outdata, int sampleframes);
+typedef void (t_fstEffectProcessInplaceDbl)(struct AEffect_*, double**indata, double**outdata, int sampleframes);
+
+typedef FST_UNKNOWN(t_fstEffectDispatcher*) audioMasterCallback;
+
+typedef struct AEffect_ {
+ FST_UNKNOWN(int) magic; /* 0x56737450 */
+ FST_UNKNOWN(int) uniqueID;
+ FST_UNKNOWN(int) version;
+
+ FST_UNKNOWN(void*) object; // FIXXXME
+
+ t_fstEffectDispatcher* dispatcher; // (AEffect*, Vst2::effClose, 0, 0, 0, 0);
+ t_fstEffectGetParameter* getParameter; // float(Aeffect*, int)
+ t_fstEffectSetParameter* setParameter; // (Aeffect*, int, float)
+ t_fstEffectProcess* process;
+ t_fstEffectProcessInplace* processReplacing;
+ t_fstEffectProcessInplaceDbl* processDoubleReplacing;
+
+ FST_UNKNOWN(int) numPrograms;
+ FST_UNKNOWN(int) numParams;
+ FST_UNKNOWN(int) numInputs;
+ FST_UNKNOWN(int) numOutputs;
+ FST_UNKNOWN(int) flags;
+ FST_UNKNOWN(int) initialDelay;
+
+ FST_UNKNOWN(t_fstPtrInt) resvd1;
+ FST_UNKNOWN(t_fstPtrInt) resvd2;
+} AEffect;
+~~~
+
+#### the AEffect struct tag
+As of JUCE-7.0.2, the `typedef struct AEffect_ AEffect` fails to compile,
+as it uses `struct AEffect` as an argument (that is: it uses the struct-tag `AEffect`
+rather than the typedef `AEffect`).
+Since I don't know of any way to alias a struct-tag, we really must use `struct AEffect` as in:
+
+~~~C
+typedef struct AEffect {
+/* ... */
+} AEffect;
+~~~
+
+### VstPinProperties
+this is also a type rather than an enum.
+play the typedef game again, and we get:
+
+~~~C
+typedef struct VstPinProperties_ {
+ FST_UNKNOWN(int) arrangementType;
+ char*label;
+ int flags;
+} VstPinProperties;
+~~~
+
+
+### ERect
+the last remaining type we missed is `ERect`.
+
+rinse & repeat and we have:
+
+~~~C
+typedef struct ERect_ {
+ int left;
+ int right;
+ int top;
+ int bottom;
+} ERect;
+~~~
+
+## more symbols
+
+The JUCE wrapper is not entirely symmetric, so only deriving missing
+symbols from AudioPluginHost is not enough.
+We also need to compile a (minimal) Audio Plugin using JUCE,
+to get additional symbols.
+Just creating a new *Audio Plugin* project using Projucer will
+give us some additional symbols.
+
+Apart from an additional `VstPinProperties.shortLabel`,
+we still miss the following types:
+
+~~~
+AEffectDispatcherProc
+AEffectSetParameterProc
+AEffectGetParameterProc
+AEffectProcessProc
+AEffectProcessDoubleProc
+~~~
+
+These function types replace our old `t_fstEffectProcess*` types:
+
+| OLD | NEW |
+|--------------------------------|----------------------------|
+| `t_fstEffectDispatcher` | `AEffectDispatcherProc` |
+| `t_fstEffectGetParameter` | `AEffectGetParameterProc` |
+| `t_fstEffectSetParameter` | `AEffectSetParameterProc` |
+| `t_fstEffectProcess` | `AEffectProcessProc` |
+| `t_fstEffectProcessInplace` | `AEffectProcessProc` |
+| `t_fstEffectProcessInplaceDbl` | `AEffectProcessDoubleProc` |
+
+And we also miss some constants:
+~~~
+kVstMaxLabelLen
+kVstMaxShortLabelLen
+kVstClockValid
+kVstPinIsActive
+kVstSmpte249fps
+kVstSmpteFilm16mm
+kVstSmpteFilm35mm
+kVstVersion
+~~~
+
+The `kVstVersion` is a bit special, as JUCE doesn't use the `Vst2::` namespace on it.
+We have to `#define` it, rather than use an `enum` for it...
+
+## compiling MrsWatson
+Another largish program we can try to compile is teregonaudio's [MrsWatson](http://teragonaudio.com/MrsWatson.html).
+It seems to use some more symbols
+
+| name | notes |
+|-----------------------------------------|-------------------------------------------------|
+| `audioMasterOpenFileSelector` | unused |
+| `audioMasterCloseFileSelector` | unused |
+| `audioMasterEditFile` | deprecated |
+| `audioMasterGetChunkFile` | deprecated |
+| `audioMasterGetInputSpeakerArrangement` | deprecated |
+|-----------------------------------------|-------------------------------------------------|
+| `kVstMaxProgNameLen` | used by `effGetProgramName` |
+| `kVstAutomationUnsupported` | returned by `audioMasterGetAutomationState` |
+| `kVstProcessLevelUnknown` | returned by `audioMasterGetCurrentProcessLevel` |
+| `kVstLangEnglish` | returned by `audioMasterGetLanguage` |
+| `kSpeakerUndefined` | assigned to `VstSpeakerProperties.type` |
+| `kEffectMagic` | `effect->magic` |
+
+
+Additionally *MrsWatson* uses some struct members that JUCE doesn't care about:
+
+| struct | member | type used |
+|------------------------|-------------|-----------|
+| `VstSpeakerProperties` | `azimuth` | float |
+| `VstSpeakerProperties` | `elevation` | float |
+| `VstSpeakerProperties` | `radius` | float |
+| `VstSpeakerProperties` | `reserved` | float |
+| `VstSpeakerProperties` | `name` | char[] |
+|------------------------|-------------|-----------|
+| `VstMidiEvent` | `flags` | int |
+| `VstMidiEvent` | `reserved1` | int |
+| `VstMidiEvent` | `reserved1` | int |
+|------------------------|-------------|-----------|
+| `AEffect` | `user` | void* |
+
+
+And there are also some additional types:
+
+| type | description |
+|-------------|---------------------------------------------------------------------------|
+| `VstInt32` | ordinary int (32bit length?) |
+| `VstIntPtr` | returned by `effect->dispatcher`; used as 4th arg to `effect->dispatcher` |
+
+`VstIntPtr` is what we have already declared as `t_fstPtrInt`.
+
+
+### sidenote
+I only discovered *MrsWatson* after reverse engineering a significant portion for the API.
+You will notice that when reading on the discovery of `VstSpeakerProperties` and `VstMidiEvent`.
+
+## conclusion
+with the changes in place, we can now compile JUCE's AudioPluginProcessor (and *MrsWatson*)
+it's still a long way to make it actually work...
+
+# Part2: how the plugin interfaces with the host
+For now, we have resolved all the names of the API to some more or less random values.
+We also have discovered a few complex types (`struct`s), and while we know the names
+of the struct-members and have an approximate guess of their types, we have no clue about
+the order of the members, and thus the memory layout.
+Since those structs are presumably used to pass data between a plugin host and a plugin,
+getting them right is very important - at least if we don't want crashes.
+
+Until know, we didn't need to know anything about the structure of the API.
+We just blindly provided "reasonable" (dummy) names whenever we encountered an unknown name.
+
+In order to reverse engineer the entire API, we should have a closer look about what we have so far.
+
+- a lot of integer-typed values, presumably grouped in `enum`s
+ - `VstPlugCategory`
+ - a speaker arrangement type
+ - an event type
+- a few "Vst" types
+ - `VstEvent`, `VstMidiEvent`, `VstSysexEvent`, `VstEvents`
+ - `VstSpeakerProperties`, `VstSpeakerArrangement`
+ - `VstTimeInfo`
+ - `VstPinProperties`
+- a generic type
+ - `ERect` (just a rectangle with 4 corners)
+- the somewhat special `AEffect` struct
+ - it is protected with a magic number (that happens to translate to `VstP`)
+ - it contains function pointer
+ - the function pointer members take a pointer to an AEffect as their first argument
+
+we can also make a few assumptions:
+- some of the structs contain a `flags` field, which is presumable an integer type where bits need to be set/unset
+- for simplicity of the API, most of the integer types will be `int32`.
+
+Anyhow, the `AEffect` seems to be a central structure. Most likely it means *Audio Effect*,
+which is a nice name for any kind of plugin.
+So most likely this structure contains an *instance* of a plugin.
+There are some specialized "class methods" (yay: OOP in C!),
+like a DSP block processing `process` and friends, as well as setters and getters for plugin parameters.
+Apart from that there is a generic `dispatcher` function that takes an opcode (an integer)
+that describes the actual functionality.
+That's a nice trick to keep an API small and stable.
+
+Let's have a quick look at how plugins are instantiated (see the `open` method in
+`JUCE/modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp`):
+
+- the plugin file ("dll") is opened (using `dlopen()` or the like)
+- the function with the name `VSTPluginMain` (or as a fallback `main`) is obtained from the dll
+- whenever we want to instantiate a new plugin (`constructEffect`) we call this function,
+ and pass it a callback function as an argument.
+ the plugin may use this callback the query information from the host
+- the `VstPluginMain` function returns a pointer to an instance of `AEffect`
+- we can use the `AEffect.dispatcher` function (which has the same signature as the callback function)
+ to call most of the plugin's methods (including an `open` and `close`).
+ A long list of functions callable via the `dispatcher` can be found in
+ `juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp`
+
+In the `juce_VST_Wrapper.cpp` file, we can also find some more Vst2-symbols of the `eff*` category,
+all of them being used as the opcode argument for the `AEffect.dispatcher`.
+
+~~~
+effEditDraw
+effEditMouse
+effEditSleep
+effEditTop
+effString2Parameter
+effGetVstVersion
+effGetCurrentMidiProgram
+effSetTotalSampleToProcess
+effGetNumMidiInputChannels
+effGetNumMidiOutputChannels
+~~~
+
+We also note that the symbols matching `effFlags*` are never used as opcode specifier.
+Instead they are used as bitmasks for `AEffect.flags`.
+
+While we are at it, maybe the `audioMaster*` symbols are opcodes for the callback we pass
+to the `VstPluginMain` function? Let's check:
+
+~~~bash
+rgrep "audioMaster" JUCE \
+| sed -e 's|audioMasterCallback|CB|g' \
+| egrep "audioMaster[A-Z]"
+~~~
+
+Which seems to confirm our idea.
+Let's also check whether we have missed some `audioMaster*` symbols:
+
+~~~bash
+rgrep "Vst2::audioMaster[A-Z]" JUCE/ \
+| sed -e 's|.*Vst2::\(audioMaster[A-Za-z0-9]*\).*|\1|' \
+| sort -u
+~~~
+
+nope. all of them are already in our `fst.h` file.
+
+
+## Conclusion
+There is only a single symbol that needs to be shared between a plugin and a host:
+the name of the main entry function into the plugin, which can be `VSTPluginMain` or `main`.
+
+The host calls this function, and provides a callback function of type
+
+~~~C
+typedef VstIntPtr (t_fstEffectDispatcher)(AEffect*, int opcode, int, VstIntPtr, void* const, float);
+~~~
+
+The main function returns an `AEffect*` handle, which in turn has a member `dispatcher` of the same
+type `t_fstEffectDispatcher`.
+These two functions do the most work when communicating between host and plugin.
+The opcodes for the plugin start with `eff*` (but not `effFlags*`),
+the opcodes for the host start with `audioMaster*`.
+
+
+
+
+
+# Part2.1: tricks to make our live easier
+
+## fake values
+
+to make our live a bit easier, we want to make sure to be able to differentiate between
+real opcodes (the values we somehow detected from our reverse engineering efforts) and
+fake opcodes (those where we only know the name, but have no clue about their actual value).
+
+The simplest way is to just assign values to the fake opcodes that we are pretty sure are wrong
+and that most likely won't clash with *any* valid opcodes.
+
+For starters, we put the values in some rather high range (e.g. starting at `100000`).
+If the valid opcodes are grouped around 0 (like proper enums), they shouldn't interfere.
+If they use magic numbers instead, they might interfere, but we'll have to deal with that once we are there.
+Usually enumerations just keep incrementing their values automatically (unless overridden by the programmer),
+which makes it somewhat hard to map a given fake value back to an opcode name.
+A nice idea is to encode the line-number into the fake opcode value, using a little helper macro:
+
+~~~C
+#define FST_ENUM(x, y) x = y
+#define FST_ENUM_UNKNOWN(x) x = 100000 + __LINE__
+
+/* ... */
+enum {
+ FST_ENUM(someKnownOpcode, 42),
+ FST_ENUM_UNKNOWN(someUnknownOpcode)
+}
+~~~
+
+In the above example, if we ever encounter an opcode (e.g.) `100241`,
+we just have to open our header-file, go to line `241` (`100241-100000`) and see what fake opcode we actually tried to send.
+
+## dispatcher printout
+
+We want to watch how the host calls a plugin, so we add debugging printout to the dispatcher function of our plugin:
+
+~~~C
+static VstIntPtr dispatcher(AEffect*eff, t_fstInt32 opcode, int index, VstIntPtr ivalue, void* const ptr, float fvalue) {
+ printf("dispatcher(%p, %ld, %d, %ld, %p, %f);",
+ effect, opcode, index, ivalue, ptr, fvalue);
+ /* do the actual work */
+ return 0;
+}
+~~~
+
+This will give use output like:
+
+~~~
+dispatcher(0xEFFECT, 42, 0, 0, 0xPOINTER, 0.000000);
+~~~
+
+Similar on the host side.
+
+## opcode->name mapping
+
+Computers are good at numbers, but humans are usually better with symbols.
+So instead of seeing `opcodes:42` (as defined in the example above), we would rather see `someKnownOpcode`.
+
+We can do that with some little helper function (implemented with a helper macro):
+
+~~~C
+#define FST_UTILS__OPCODESTR(x) \
+ case x: \
+ if(x>100000) \
+ snprintf(output, length, "%d[%s?]", x, #x); \
+ else \
+ snprintf(output, length, "%s[%d]", #x, x); \
+ output[length-1] = 0; \
+ return output
+
+static char*effCode2string(size_t opcode, char*output, size_t length) {
+ switch(opcode) {
+ default: break;
+ FST_UTILS__OPCODESTR(effOpcodeFoo);
+ FST_UTILS__OPCODESTR(effOpcodeBar);
+ /* repeat for all effOpcodes */
+ }
+ snprintf(output, length, "%d", opcode);
+ return output;
+}
+~~~
+
+Now instead of printing the numeric value of an opcode, we can instead print a symbol:
+
+~~~C
+ char opcodestr[512];
+ printf("dispatcher(%p, %s, %d, %ld, %p, %f);",
+ effect, effCode2string(opcode, opcodestr, 512), index, ivalue, ptr, fvalue);
+~~~
+
+which will give us a much more readable:
+
+~~~
+dispatcher(0xEFFECT, someKnownOpcode[42], 0, 0, 0xPOINTER, 0.000000);
+~~~
+
+If we encounter an unknown opcode, we will notice immediately:
+
+~~~
+dispatcher(0xEFFECT, 666, 0, 0, 0xEVIL, 0.000000);
+~~~
+
+
+
+# Part3: AEffect
+So let's try our code against some real plugins/hosts.
+We start with some freely available plugin from a proper commercial plugin vendor,
+that has been compiled for linux.
+There are not many, but [u-he](https://u-he.com/) provides the `Protoverb` as binaries
+for Windows, macOS and Linux (hooray!), free of charge (hooray again!).
+*u-he* sell commercial plugins as well, so i figure they have a proper license from Steinberg
+that allows them to do this. (I'm a bit afraid that many of the FLOSS VST2 plugins simply
+violate Steinberg's terms of use. So I prefer to use plugins from commercial manufacturers for now.
+Just for the record: by no means I'm trying to reverse engineer any of the plugin's trade secrets.)
+
+Another bonus of `Protoverb` is that it is made *without* JUCE.
+While JUCE is certainly a great toolkit, having plugins outside
+that ecosystem will hopefully provide better coverage of edge cases...
+
+Anyhow: we can try to load the `Protoverb` plugin in the JUCE AudioPluginHost that we
+just compiled with our broken `fst.h` header.
+
+Using <kbd>Ctrl</kbd>+<kbd>p</kbd> -> `Options` -> `Scan for new or updated VST plug-ins`,
+we select the folder where we extracted the `Protoverb.64.so` file into and hit <kbd>Scan</kbd>.
+
+Not very surprisingly it fails to load the plugin.
+
+The error is:
+> Creating VST instance: Protoverb.64
+> *** Unhandled VST Callback: 1
+
+This errors is printed in `juce_audio_processors/format_types/juce_VSTPluginFormat.cpp`,
+when dispatching the callback opcodes.
+Adding a little `printf` around the `JUCE_VST_WRAPPER_INVOKE_MAIN` it becomes clear that
+the plugin is calling the callback from within the `VstPluginMain` function.
+And it refuses to instantiate if we cannot handle this request.
+
+Looking at the `audioMaster` opcodes, i only see `audioMasterVersion`
+that looks like if it could be queried at the very beginning.
+JUCE returns a hardcoded value of `2400` for this opcode,
+and it seems like this is the VST-version (the plugin would need to make sure
+that the host is expecting a compatible VST version before it instantiates itself).
+Most likely the `kVstVersion` define uses the same value.
+
+So we now have our first known enum. Yipee!
+
+~~~C
+#define FST_ENUM_EXP(x, y) x = y
+// ...
+ FST_ENUM_EXP(audioMasterVersion, 1),
+~~~
+
+Recompiling, and scanning for the plugin again, leads to...a crash.
+But before that, we get a bit of information (obviously the plugin is quite verbose when
+initializing).
+The interesting parts are
+
+> *** Unhandled VST Callback: 33
+
+and the crash itself, which happens *after* the plugin returned from `VstMainPlugin`.
+
+Starting `gdb` to get a backtrace:
+
+~~~sh
+$ gdb --args JUCE/extras/AudioPluginHost/Builds/LinuxMakefile/build/AudioPluginHost
+[...]
+(gdb) run
+[...]
+Segmentation fault (core dumped)
+(gdb) bt
+#0 0x00007fffed6c9db2 in ?? () from /home/zmoelnig/src/iem/FST/tmp/vst/Protoverb/Protoverb.64.so
+#1 0x00005555556b4d95 in juce::VSTPluginInstance::create (newModule=..., initialSampleRate=44100, initialBlockSize=512)
+ at ../../../../modules/juce_audio_processors/format_types/juce_VSTPluginFormat.cpp:1146
+[...]
+~~~
+
+the relevant line in `juce_VSTPluginFormat.cpp` calls
+
+~~~
+newEffect->dispatcher (newEffect, Vst2::effIdentify, 0, 0, 0, 0);
+~~~
+
+So we are accessing the `dispatcher` member of the `AEffect` struct.
+Since we don't really know the memory layour of `AEffect` yet, the pointer most likely points to garbage.
+
+Anyhow, in order to discover the layout of `AEffect` we need to examine it more closely.
+
+For this we write a super-simplistic `FstHost` application,
+that can load plugins for testing.
+(I originally intended to just use the JUCE framework for this;
+but we will experience lots of crashes, and will have to recompile *often*.
+JUCE is rather big and it turns out that it takes just too long...)
+
+~~~C
+#include <stdio.h>
+#include <dlfcn.h>
+#include "fst.h"
+typedef AEffect* (t_fstMain)(t_fstEffectDispatcher*);
+VstIntPtr dispatcher (AEffect* effect, int opcode, int index, VstIntPtr ivalue, void*ptr, float fvalue) {
+ printf("FstHost::dispatcher(%p, %d, %d, %d, %p, %f);\n", effect, opcode, index, ivalue, ptr, fvalue);
+ return 0;
+}
+t_fstMain* load_plugin(const char* filename) {
+ void*handle = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
+ void*vstfun = 0;
+ if(!handle)return 0;
+ if(!vstfun)vstfun=dlsym(handle, "VSTPluginMain");
+ if(!vstfun)vstfun=dlsym(handle, "main");
+ if(!vstfun)dlclose(handle);
+ printf("loaded %s as %p: %p\n", filename, handle, vstfun);
+ return (t_fstMain*)vstfun;
+}
+int test_plugin(const char*filename) {
+ t_fstMain*vstmain = load_plugin(filename);
+ if(!vstmain)return printf("'%s' was not loaded\n");
+ AEffect*effect = vstmain(&dispatcher);
+ if(!effect)return printf("unable to instantiate plugin from '%s'\n", filename);
+ return 0;
+}
+int main(int argc, const char*argv[]) {
+ for(int i=1; i<argc; i++) test_plugin(argv[i]);
+ return 0;
+}
+~~~
+
+After building with `g++ -IFST FstHost.cpp -o FstHost -ldl -lrt` we can now try to open
+plugins with `./FstHost path/to/plugin.so`.
+
+Running it on a real plugin ("Protoverb.64.so") just returns
+> FstHost::dispatcher((nil), 1, 0, 0, (nil), 0.000000);
+> unable to instantiate plugin from 'tmp/vst/Protoverb/Protoverb.64.so'
+
+So we apparently *must* handle the `audioMasterVersion` opcode in our local dispatcher
+(just like JUCE does); let's extend our local dispatcher to do that:
+
+~~~
+VstIntPtr dispatcher (AEffect* effect, int opcode, int index, VstIntPtr ivalue, void*ptr, float fvalue) {
+ printf("FstHost::dispatcher(%p, %d, %d, %d, %p, %f);\n", effect, opcode, index, ivalue, ptr, fvalue);
+ switch(opcode) {
+ case audioMasterVersion: return 2400;
+ default: break;
+ }
+ return 0;
+}
+~~~
+
+And now we are getting somewhere!
+
+Let's examine the `AEffect` data we get from the VSTPluginMain-function.
+Setting a breakpoint in `gdb` right after the call to `vstmain`, and running
+`print *effect` gives us:
+
+~~~gdb
+$1 = {magic = 1450406992, uniqueID = 0, version = -141927152, object = 0x7ffff78a5d70, dispatcher = 0x7ffff78a5d60,
+ getParameter = 0x7ffff78a5d50, setParameter = 0x500000001, process = 0x200000002, processReplacing = 0x31,
+ processDoubleReplacing = 0x0, numPrograms = 0, numParams = 0, numInputs = 16, numOutputs = 0, flags = 0,
+ initialDelay = 1065353216, resvd1 = 93824992479632, resvd2 = 0}
+~~~
+
+This looks bad enough.
+Not all looks bad though: `magic` has a value of `1450406992`, which really is `0x56737450` in hex,
+which happens to be the magic number `VstP`.
+The rest however is abysmal: negative version numbers, unique IDs that are `0` (how unique can you get),
+a function pointer (to `processReplacing`) that is `0x31` which is definitely invalid.
+
+So let's take a closer look at the actual data.
+For this, we just dump the memory where `AEffect*` points to into a file
+(using `dumpdata(filename, effect, 160);` with the function below, just after calling `VSTPluginMain`)
+and then use some hexeditor on it.
+
+~~~
+#include <string>
+void dumpdata(const char*basename, const void*data, size_t length) {
+ const char*ptr = (const char*)data;
+ std::string filename = std::string(basename);
+ filename+=".bin";
+ FILE*f = fopen(filename.c_str(), "w");
+ for(size_t i=0; i<length; i++) {
+ fprintf(f, "%c", *ptr++);
+ }
+ fclose(f);
+}
+~~~
+
+Inspecting the binary dumps with `bless`, we see that there are quite a lot of
+8- (and some additional 4-) byte sequences that are always zero.
+namely: bytes @04-07 (addresses in hex-notation), @40-57 and @68-6F.
+
+~~~
+00000000 50 74 73 56 00 00 00 00 10 ad 87 f7 ff 7f 00 00 |PtsV............|
+00000010 70 ad 87 f7 ff 7f 00 00 60 ad 87 f7 ff 7f 00 00 |p.......`.......|
+00000020 50 ad 87 f7 ff 7f 00 00 01 00 00 00 05 00 00 00 |P...............|
+00000030 02 00 00 00 02 00 00 00 31 00 00 00 00 00 00 00 |........1.......|
+00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+00000050 10 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3f |...............?|
+00000060 c0 fb 58 55 55 55 00 00 00 00 00 00 00 00 00 00 |..XUUU..........|
+00000070 56 50 68 75 01 00 00 00 80 ad 87 f7 ff 7f 00 00 |VPhu............|
+00000080 90 ad 87 f7 ff 7f 00 00 00 00 00 00 00 00 00 00 |................|
+00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+~~~
+
+If we run this repeatedly on the same plugin file, we can collect multiple dumps
+(don't forget to rename them, else they will be overwritten).
+Comparing those multiple dumps, we can see that many bytes stay the same, but a few
+are changing for each run.
+
+Comparing the changing values with the addresses of heap-allocated memory and functions
+(e.g. the DLL-handle returned by `dlopen()` and the function pointer returned by `dlsym()`)
+it becomes clear, that we have six function pointers
+(e.g. `?? ad 87 f7 ff 7f 00 00` at @8, @10, @18, @20, @78, @80) and
+one heap allocated object (e.g. `c0 fb 58 55 55 55 00 00` at @60).
+On my amd64 system, pointers take 8 byte and aligned at 8 bytes (one halfline in the hexdump).
+
+We don't know which functions are stored in which pointer,
+but it cannot be coincidence, that our `AEffect` struct has exactly 6 function pointers
+(`dispatcher`, `getParameter`, `setParameter`, `process`, `processReplacing`, `processDoubleReplacing`).
+We are also a bit lucky, because we don't really know whether each function ptr must actually be filled
+with a valid function (it might be allowed to set them to `0x0`).
+The `Protoverb` plugin seems to implement all of them!
+
+The heap allocated object (@60) might match the `void*object` member.
+
+Comparing the hexdumps of multiple different plugins, we notice that @70
+there are always 4 printable characters (here `VPhu`), although they are different
+for each plugin.
+Additionally, we notice that the `Protoverb` plugin (being pretty verbose on the stderr)
+said something like:
+> setUniqueID (1969770582)
+
+This number is `0x75685056` in hex. If we compare that to the bytes @70,
+we cannot help but note that we have discovered the location of the `uniqueID` member.
+
+If we start long enough at the remaining byte sequences, we see some other patterns
+as well.
+esp. at positions @28-37, there seem to be four (little endian) 4-byte integer values
+(`1,5,2,2` for the *ProtoVerb*). These could map to `numPrograms`, `numParams`,
+`numInputs` and `numOutputs`.
+These assumption is hardened by looking at other plugins like *Digits*, *tonespace* and *hypercyclic*
+that are synth-only plugins (no input) and indeed have `0` at @34-37.
+I don't know how many plugins really implement *programs*,
+but it doesn't sound absurd if all plugins we inspect have only a single program,
+while most have numerous parameters.
+So for now, let's assume that @28-37 are indeed `numPrograms`, `numParams`, `numInputs`
+and `numOutputs`, each taking 4 bytes.
+
+The remaining non-zero bytesequences are
+- @38-3f
+- @5c-5f: always `00 00 80 3f`, which - weirdly enough - is the `1.f` in single-precision floating point (little endian)
+- @74-77
+
+the bits at position @38-3f are:
+
+| plugin | @38-3f (binary) | decimal |
+|-------------|----------------------------------------------------------------------------|---------|
+| ProtoEffect | `00110001 00000000 00000000 00000000 00000000 00000000 00000000 00000000` | 049 0 |
+| InstaLooper | `00010001 00000000 00000000 00000000 00000000 00000000 00000000 00000000` | 017 0 |
+| Digits | `00110000 00000001 00000000 00000000 00000000 00000000 00000000 00000000` | 304 0 |
+| BowEcho | `00110001 00000010 00000000 00000000 00000000 00000000 00000000 00000000` | 561 0 |
+| Danaides | `00110001 00000010 00000000 00000000 00000000 00000000 00000000 00000000` | 561 0 |
+| hypercyclic | `00110001 00000011 00000000 00000000 00000000 00000000 00000000 00000000` | 817 0 |
+| tonespace | `00110001 00000011 00000000 00000000 00000000 00000000 00000000 00000000` | 817 0 |
+
+whereas the bits at position @74-77 are:
+
+| plugin | @74-77 (binary) | decimal |
+|-------------|---------------------------------------|---------|
+| ProtoEffect | `00000001 00000000 00000000 00000000` | 1 |
+| InstaLooper | `00000001 00000000 00000000 00000000` | 1 |
+| Digits | `00000001 00000000 00000000 00000000` | 1 |
+| BowEcho | `01101110 00000000 00000000 00000000` | 110 |
+| Danaides | `01100110 00000000 00000000 00000000` | 102 |
+| hypercyclic | `11000010 10010110 00000000 00000000` | 38594 |
+| tonespace | `11000011 10111010 00000000 00000000` | 47811 |
+
+
+
+We still have the following fields to find:
+- `flags`
+- `initialDelay`
+- `version`
+- `resvd1`, `resvd2`
+
+The `reserved` fields are most likely set to `0`
+(all fields reserved for future use in structs, usually don't ever get used).
+We know that JUCE (ab?)uses one of them to store a pointer, so they must be 8-bytes (on our 64bit system),
+but we don't have any clue yet about their exact position in memory.
+
+Searching for the `initialDelay` in the JUCE-code, it seems that this value is *in samples*,
+which should make it an `int` value.
+`flags` is certainly `int` (of whatever size, but the more the merrier),
+and `version` most likely as well.
+
+Comparing the values we see at positions @38-3f for various plugins seem to indicate a bitfield
+(rather big changes in the decimal representations, but when looking at the binary representations
+there are not so many changes at all),so for now we assume that this is the `flags` field.
+Whether its 32bit or 64bit we don't really know.
+However, the size of the flags field will most likely be constant across various architectures
+(32bit resp. 64bit), so it probably won't be a "pointer sized int".
+
+The `version` might be encoded in the data at @74-77. This field is (likely)
+a free-form field, where the plugin can put anything (with a possible restriction that
+it should be "better" (newer) plugins should sort after older ones).
+The actual versioning scheme would be left to the plugin authors,
+which might explain the wildly varying version numbers we have
+(with *hyperbolic* and *tonespace* coming from the same author;
+and *BowEcho* and *Danaides* coming from another author;
+but all 4 plugins being based on JUCE (which nudges the user into setting a proper version).
+Using a version number of `1` as *I don't care* is also not something unheard of...).
+
+Collecting all the info we have so far, we end up with something like the following
+(using `_pad` variables to account for unknown 0-memory).
+There's also a `float1` dummy member (that always holds the value `1.f`),
+and the `initialDelay` is set to the seemingly const-value at @50 (`16` in decimal,
+which is a plausible value for a sample delay;
+btw, loading the *Protoverb* plugin into *REAPER*,
+will also print something about a 16samples delay to the stderr).
+
+~~~C
+typedef struct AEffect {
+ t_fstInt32 magic; /* @0 0x56737450, aka 'VstP' */
+ char _pad1[4]; // always 0
+ AEffectDispatcherProc* dispatcher; // ???
+ AEffectProcessProc* process; // ???
+ AEffectGetParameterProc* getParameter; // ???
+ AEffectSetParameterProc* setParameter; // ???
+
+ t_fstInt32 numPrograms;
+ t_fstInt32 numParams;
+ t_fstInt32 numInputs;
+ t_fstInt32 numOutputs;
+
+ VstIntPtr flags; // size unclear
+ VstIntPtr resvd1; //??
+ VstIntPtr resvd2; //??
+ t_fstInt32 initialDelay; //??
+
+ char _pad2[8]; //?
+ float float1; //?
+ void* object; // FIXXXME
+ char _pad3[8]; //??
+
+ t_fstInt32 uniqueID; // @112
+ t_fstInt32 version;
+
+ AEffectProcessProc* processReplacing; //?
+ AEffectProcessDoubleProc* processDoubleReplacing; //?
+
+} AEffect;
+~~~
+
+If we run the same tests on 32bit plugins, we notice that we got the alignment wrong
+in a number of places.
+
+Here's a hexdump of the 1st 96 bytes of a 32bit `AEffect`:
+
+~~~
+00000000 50 74 73 56 50 91 8d f7 30 92 8d f7 f0 91 8d f7 |PtsVP...0.......|
+00000010 c0 91 8d f7 01 00 00 00 05 00 00 00 02 00 00 00 |................|
+00000020 02 00 00 00 31 00 00 00 00 00 00 00 00 00 00 00 |....1...........|
+00000030 10 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3f |...............?|
+00000040 c0 ee 56 56 00 00 00 00 56 50 68 75 01 00 00 00 |..VV....VPhu....|
+00000050 80 92 8d f7 c0 92 8d f7 00 00 00 00 00 00 00 00 |................|
+~~~
+
+As we can see, there is no zero padding after the initial 4-byte `magic`.
+Also the number of zeros around the `object` member seems to be off.
+
+Using `pointer sized int` instead of `int32` helps a bit:
+
+~~~
+typedef struct AEffect {
+ VstIntPtr magic;
+ AEffectDispatcherProc* dispatcher; //??
+ AEffectProcessProc* process; //?
+ AEffectGetParameterProc* getParameter; //??
+ AEffectSetParameterProc* setParameter; //??
+
+ t_fstInt32 numPrograms;
+ t_fstInt32 numParams;
+ t_fstInt32 numInputs;
+ t_fstInt32 numOutputs;
+
+ VstIntPtr flags;
+ VstIntPtr resvd1; //??
+ VstIntPtr resvd2; //??
+ t_fstInt32 initialDelay; //??
+
+ char _pad2[8]; //??
+ float float1; //??
+ void* object;
+ VstIntPtr _pad3; //??
+ t_fstInt32 uniqueID;
+ t_fstInt32 version;
+
+ AEffectProcessProc* processReplacing; //??
+ AEffectProcessDoubleProc* processDoubleReplacing; //??
+} AEffect;
+~~~
+
+
+### conf. REAPER
+Printing the contents `AEffect` for the *Protoverb* plugin in gdb, gives something like:
+
+~~~
+{magic = 1450406992,
+ dispatcher = 0xf78d9150,
+ process = 0xf78d9230,
+ getParameter = 0xf78d91f0,
+ setParameter = 0xf78d91c0,
+ numPrograms = 1,
+ numParams = 5,
+ numInputs = 2,
+ numOutputs = 2,
+ flags = 49,
+ resvd1 = 0,
+ resvd2 = 0,
+ initialDelay = 16,
+ _pad2 = "\000\000\000\000\000\000\000",
+ myfloat = 1,
+ object = 0x5656eec0,
+ _pad3 = 0,
+ uniqueID = 1969770582,
+ version = 1,
+ processReplacing = 0xf78d9280,
+ processDoubleReplacing = 0xf78d92c0
+}
+~~~
+
+Opening the same plugin in *REAPER* we can also learn a few things:
+
+- 2 in, 2 out
+- 1 built-in program named "initialize"
+- 5 parameters
+ - "main: Output" (100.)
+ - "main: Active #FDN" (1.)
+ - "FDN: Feedback" (50.)
+ - "FDN: Dry" (100.)
+ - "FDN: Wet" (30.)
+- `stderr`
+ - setUniqueID (1969770582)
+ - HOST `REAPER` (so the plugin queries the host for the host-name)
+ - Starting REVISION 4105
+ - sending latency to host... 16
+ - GUI: 1200 x 600
+ - GUI: 640 x 575
+ - CONNECTIONS_PER_PARAMETER: 8
+
+
+So at least we have the number of inputs, outputs, programs and parameters right, as well as the uniquID.
+
+
+## flags
+
+We have the following flags to assign:
+- `effFlagsHasEditor`
+- `effFlagsIsSynth`
+- `effFlagsCanDoubleReplacing`
+- `effFlagsCanReplacing`
+- `effFlagsNoSoundInStop`
+- `effFlagsProgramChunks`
+
+While the bitfields at @38 have the following values (displayed as little-endian):
+
+| plugin | flags |
+|-------------|---------------------|
+| ProtoEffect | `00000000 00110001` |
+| InstaLooper | `00000000 00010001` |
+| Digits | `00000001 00110000` |
+| BowEcho | `00000010 00110001` |
+| Danaides | `00000010 00110001` |
+| hypercyclic | `00000011 00110001` |
+| tonespace | `00000011 00110001` |
+
+Things we know from running the plugins through REAPER:
+- all effects except *Digits* have a GUI
+- *Protoverb*, *InstaLooper*, *BowEcho*, *Danaides* are all effects (with input and output)
+- *Digits*, *hypercyclic*, *tonespace* are all instruments (aka synths; no input)
+Things we know from looking at the source code of JUCE-5.4.1
+- all JUCE plugins have `effFlagsHasEditor|effFlagsCanReplacing|effFlagsProgramChunks`
+- we don't know which JUCE version was used to compile our JUCE plugins!
+
+
+Comparing this with our binary flag values, we can conclude:
+
+| flag | value |
+|-------------------|--------|
+| effFlagsHasEditor | `1<<0` |
+| effFlagsIsSynth | `1<<8` |
+
+
+It's a it strange that we have `processReplacing` and `processDoubleReplacing` functions
+(according to our hacked up `AEffect` definition) for each plugin,
+although there are no more flags that are all set to `true` for each plugin.
+This might be a problem with our `AEffect` struct or with the plugins
+(or the information about replacing-functions might not be redundant.)
+
+Another observation is that flag `1<<9` is set to `true` for all JUCE-plugins,
+and to `false` for the rest.
+
+I don't really know what either `effFlagsNoSoundInStop` nor `effFlagsProgramChunks`
+actually mean.
+The former is most likely related to time-based plugins (e.g. a reverb; if you pause playback
+and reset to some other playback position, then you probably don't want the reverb of the
+pre-pause input in your output).
+
+
+## getting some opcodes
+to find out the actual values of the opcodes, we just send various opcodes to the putative `dispatcher`
+function of the `AEffect` and see what happens.
+This is also a good test to see whether we have the address of the `dispatcher` correct.
+I've added a `char[512]` as `ptr` to the dispatcher, because I've seen this in the JUCE's *juce_VSTPluginFormat.cpp*
+(`fillInPluginDescription`).
+
+~~~C
+for(int i=0; i<256; i++) {
+ char buffer[512] = { 0 };
+ VstIntPtr res = effect->dispatcher (effect, i, 0, 0, buffer, 0);
+ const char*str = (const char*)res;
+ printf("%d[%d=0x%X]: %.*s\n", i, res, res, 32, str);
+ if(*buffer)printf("\t'%.*s'\n", 512, buffer);
+}
+~~~
+
+Running this on the `Protoverb` looper, crashes with opecode `1`.
+For now we just ignore this, and don't run crashing opcodes, with something like:
+
+~~~C
+for(int i=0; i<256; i++) {
+ bool skip = false;
+ switch(i) {
+ default: break;
+ case 1:
+ skip = true;
+ }
+ if(true)continue;
+ VstIntPtr res = effect->dispatcher (effect, i, 0, 0, buffer, 0);
+ //...
+}
+~~~
+
+It turns out that the following opcodes `1`, `3`, `4`, `6`, `7`, `8`, `13`,
+`14`, `22`, `23`, `24`, `25`, `26`, `29`, `33`, `34`, `35`, `45`, `47`, `48`,
+`49`, `51`, `58`, `63`, `69`, `71`, `72` and `73` all crash
+(most likely these expect a properly initialized structure at `ptr`).
+
+Additional crashers
+- JUCE-based: `3`, `42`, `44`
+- Digits: `3`
+
+
+The rest behaves nicely.
+
+Just looking at our printout (ignoring the `stderr` generated by the plugin itself),
+doesn't reveal very much.
+The most important thing is probably, that opcode `5` gives us really something interesting in the `buffer`, namely the string
+> initialize
+
+...which is the name of the (only) *program* as displayed in REAPER.
+
+Since this sounds promising, we run the code on another plugin (*tonespace*), which has plenty of programs, the first
+(according to REAPER) is *EDU-C Major Scale*.
+Our code returns (with opcode `5`):
+> EDU-C Major Scale
+
+Hooray, we found `effGetProgramName` or `effProgramNameIndexed`
+(probably the former, as setting the `index` (and/or `ivalue` resp. `fvalue`)
+doesn't make any difference.)
+
+We also notice, that the `VstIntPtr` returned by `AEffect.dispatcher` is always `0`.
+
+
+To proceed, we take a closer look at what else is printed when we run our host with some plugins.
+As said before, the `Protoverb` is very verbose (on stderr),
+giving us hints on what a certain opcode might be supposed to do.
+
+E.g.
+
+| plugin | opcode | printout |
+|-----------|--------|------------------------------------------------------------------------------------------|
+| Protoverb | 10 | AM_AudioMan::reset() |
+| Protoverb | 12 | AM_VST_base::resume() / AM_AudioMan::reset() |
+| Protoverb | 15 | closed editor. |
+| Protoverb | 59 | plugin doesn't use key, returns false (VST) or jumps to default key handler (WindowProc) |
+| Digits | 11 | Block size 2.0000 |
+
+it also calls back to the host:
+
+
+| plugin | when | opcode | notes |
+|-------------|------|--------|-----------------------------------------------------------------------------------|
+| Protoverb | main | 33 | after that, the VST-version is printed; in REAPER it also prints "HOST 'REAPER'" |
+| Protoverb | main | 13 | right before, it prints "Protoverb VST telling unknown about 16 samples latency" |
+| | | | (although no arguments are given to the callback) |
+| Protoverb | 12 | 6 | just before the "resume()" comment is issued. |
+|-------------|------|--------|-----------------------------------------------------------------------------------|
+| BowEcho | 12 | 23 | `(..., 0, 0, NULL, 0.f)` (same for *Danaides*) |
+| BowEcho | 12 | 6 | `(..., 0, 1, NULL, 0.f)` (same for *Danaides*) |
+|-------------|------|--------|-----------------------------------------------------------------------------------|
+| Digits | 12 | 6 | `(..., 0, 1, NULL, 0.f)` |
+|-------------|------|--------|-----------------------------------------------------------------------------------|
+| hypercyclic | 0 | 13 | `(..., 0, 0, NULL, 0.f)` |
+| hypercyclic | 0 | 42 | `(..., 0, 0, NULL, 0.f)` |
+| hypercyclic | 0 | 0 | `(..., 0, i, NULL, f)` for i in range(numParams) and f=[0.f .. 1.f] |
+| hypercyclic | 0 | 13 | `(..., 0, 0, NULL, 0.f)` |
+| hypercyclic | 0 | 42 | `(..., 0, 0, NULL, 0.f)` |
+| hypercyclic | 0 | 0 | `(..., 0, i, NULL, f)` for i in range(numParams) and f=[0.f .. 1.f] |
+| hypercyclic | 2 | 0 | `(..., 0, i, NULL, f)` for i in range(numParams) and f=[0.f .. 1.f] |
+| hypercyclic | 12 | 23 | `(..., 0, 0, NULL, 0.f)` |
+| hypercyclic | 12 | 7 | `(..., 0, 65024, NULL, 0.f)` |
+| hypercyclic | 12 | 6 | `(..., 0, 1, NULL, 0.f)` |
+|-------------|------|--------|-----------------------------------------------------------------------------------|
+| tonespace | | | almost the same as *hypercyclic*, but with an additional final callback |
+| | | | to `FstHost::dispatcher(eff, 0, 5, 0, NULL, 0.)` whenever we iterated over params |
+
+
+
+I guess that host-opcode `33` is meant to query the name of the host application.
+In `juce_VSTPluginFormat.cpp` this is handled with
+the `audioMasterGetProductString` and `audioMasterGetVendorString` opcodes,
+which both return `1` and write a string into the `ptr`.
+The string has a maximum length of `min(kVstMaxProductStrLen, kVstMaxVendorStrLen)`,
+so these two symbols are actually constants, not enums.
+We don't know the actual maximum string lengths, so I tried to compile with `-fsanitize=address -fsanitize=undefined`
+and write as many bytes into the return string as possible.
+This gave me a maximum string length of `197782` bytes. impressive, but i'm not sure i can trust these values...
+
+The host-opcode `0` seems to be used to tell the host the current values for all the parameters.
+In `juce_VSTPluginFormat.cpp::handleCallback()` this is handled in the `audioMasterAutomate` opcode.
+
+## how JUCE handles opcodes
+In order to understand how each opcode is used, we may look at *juce_VST_Wrapper.cpp*
+to find out which parameters are used (and how) for a given opcode, and how values are returned:
+
+### JUCE effect Opcodes
+
+| effect opcode | | IN | OUT | return | notes |
+|-----------------------------|-----|--------------------|-------------------------|-----------------------|-----------------------------|
+| effCanBeAutomated | 26 | index | | 0/1 | can param#idx be automated? |
+| effCanDo | 51 | ptr(char[]) | | 0/1/-1 | |
+| effOpen | 0? | - | - | 0 | |
+| effClose | 1? | - | - | 0 | |
+| effEditClose | 15 | - | | 0 | |
+| effEditGetRect | 13 | - | ptr(&ERect) | ptr | |
+| effEditDraw | | ptr(&ERect) | | | JUCE-ignored |
+| effEditIdle | | | | | JUCE-ignored |
+| effEditMouse | | | | | JUCE-ignored |
+| effEditOpen | 14 | ptr(Window) | | | |
+| effEditSleep | | | | | JUCE-ignored |
+| effEditTop | | | | | JUCE-ignored |
+| effGetChunk | 23 | index | ptr(void[]) | size | |
+| effSetChunk | 24 | index, ivalue, ptr | | 0 | ivalue=size |
+| effGetCurrentMidiProgram | 63? | - | - | -1 | |
+| effGetEffectName | 45 | - | ptr(char[64]) | 1 | |
+| effGetInputProperties | 33 | index | ptr(VstPinProperties[]) | 1/0 | |
+| effGetOutputProperties | 34 | index | ptr(VstPinProperties[]) | 1/0 | |
+| effGetNumMidiInputChannels | | - | | 16/0 | 16 if plugin handles MIDI |
+| effGetNumMidiOutputChannels | | - | | 16/0 | 16 if plugin handles MIDI |
+| effGetParamDisplay | 7 | index | ptr(char[8]) | 0 | |
+| effGetParamLabel | 6 | index | ptr(char[8]) | 0 | |
+| effGetParamName | 8 | index | ptr(char[8]) | 0 | |
+| effGetPlugCategory | 35 | - | | category | |
+| effGetProductString | 48 | - | ptr(char[64]) | 1 | |
+| effGetProgramNameIndexed | 29 | index | ptr(char[24]) | hasProgram#index | |
+| effGetProgramName | 5 | - | ptr(char[24]) | 0 | |
+| effGetProgram | 3 | - | - | current_program | |
+| effGetSpeakerArrangement | 69 | - | ivalue([]), ptr([]) | (!(hasAUX or isMidi)) | in:(SpeakerArrangement[]) |
+| effSetSpeakerArrangement | 42 | ivalue(ptr), ptr | - | 0/1 | |
+| effGetTailSize | | - | - | audiotailInSamples | |
+| effGetVendorString | 47 | - | ptr(char[64]) | 1 | |
+| effGetVendorVersion | 49 | - | - | version | |
+| effGetVstVersion | 58 | - | - | kVstVersion | |
+| effIdentify | 22 | - | - | bigEndianInt("NvEf") | 1316373862=0x4e764566 |
+| effKeysRequired | | - | - | isKbdFocusRequired | |
+| effMainsChanged | 12? | ivalue | - | 0 | ivalue?resume():suspend() |
+| effProcessEvents | 25 | ptr(&VstEvents) | - | isMidiProcessed | |
+| effSetBlockSize | 11 | ivalue | - | 0 | |
+| effSetBypass | | ivalue | - | 0 | |
+| effSetProcessPrecision | 77 | ivalue | - | !isProcessing | |
+| effSetProgram | 2 | ivalue | - | 0 | |
+| effSetProgramName | 4 | ptr(char[]) | - | 0 | |
+| effSetSampleRate | 10 | fvalue | - | 0 | |
+| effSetTotalSampleToProcess | 73? | ivalue | - | ivalue | |
+| effString2Parameter | 27 | index, ptr(char[]) | - | !isLegacy(param[i]) | |
+| effConnectInput | | | | | JUCE-ignored |
+| effConnectOutput | | | | | JUCE-ignored |
+| effIdle | | | | | JUCE-ignored |
+| effVendorSpecific | 50 | ??? | ??? | ??? | |
+| effShellGetNextPlugin | 70 | | | | JUCE-ignored |
+| effStartProcess | 71 | | | | JUCE-ignored |
+| effStopProcess | 72 | | | | JUCE-ignored |
+
+### JUCE host Opcodes
+
+for host opcodes (`audioMaster*`) check out *juce_VSTPluginFormat.cpp*:
+
+| host opcode | | IN | OUT | return | notes |
+|----------------------------------------|----|---------------|-------------|-------------------|-----------------------------------------------------------------|
+| audioMasterAutomate | 0 | index, fvalue | - | 0 | |
+| audioMasterProcessEvents | 8 | ptr | - | 0 | ptr=VstEvents[] |
+| audioMasterGetTime | 7 | - | - | &vsttime | |
+| audioMasterIdle | | - | - | 0 | |
+| audioMasterSizeWindow | 15 | index, value | | 1 | setWindowSize(index,value) |
+| audioMasterUpdateDisplay | | - | - | 0 | triggerAsyncUpdate() |
+| audioMasterIOChanged | | - | - | 0 | setLatencyDelay |
+| audioMasterNeedIdle | | - | - | 0 | startTimer(50) |
+| audioMasterGetSampleRate | 16 | - | - | samplerate | |
+| audioMasterGetBlockSize | 17 | - | - | blocksize | |
+| audioMasterWantMidi | 6 | - | - | 0 | wantsMidi=true |
+| audioMasterGetDirectory | | - | - | (char[])directory | |
+| audioMasterTempoAt | 10 | - | - | 10000*bpm | |
+| audioMasterGetAutomationState | | - | - | 0/1/2/3/4 | 0 = not supported, 1 = off, 2 = read, 3 = write, 4 = read/write |
+| audioMasterBeginEdit | 43 | index | - | 0 | gesture |
+| audioMasterEndEdit | 44 | index | - | 0 | gesture |
+| audioMasterPinConnected | | index,value | - | 0/1 | 0=true; value=direction |
+| audioMasterGetCurrentProcessLevel | 23 | - | - | 4/0 | 4 if not realtime |
+|----------------------------------------|----|---------------|-------------|-------------------|-----------------------------------------------------------------|
+| audioMasterCanDo | 37 | ptr(char[]) | - | 1/0 | 1 if we can handle feature |
+| audioMasterVersion | 1 | - | - | 2400 | |
+| audioMasterCurrentId | 2? | - | - | shellUIDToCreate | ? |
+| audioMasterGetNumAutomatableParameters | | - | - | 0 | |
+| audioMasterGetVendorVersion | 34 | - | - | 0x0101 | |
+| audioMasterGetVendorString | 32 | - | ptr(char[]) | ptr | getHostName() |
+| audioMasterGetProductString | 33 | - | ptr(char[]) | ptr | getHostName() |
+| audioMasterSetOutputSampleRate | | - | - | 0 | |
+|----------------------------------------|----|---------------|-------------|-------------------|-----------------------------------------------------------------|
+| audioMasterGetLanguage | | | | | JUCE-ignored |
+| audioMasterGetOutputSpeakerArrangement | | | | | JUCE-ignored |
+| audioMasterGetParameterQuantization | | | | | JUCE-ignored |
+| audioMasterGetPreviousPlug | | | | | JUCE-ignored |
+| audioMasterGetNextPlug | | | | | JUCE-ignored |
+| audioMasterSetTime | | | | | JUCE-ignored |
+| audioMasterWillReplaceOrAccumulate | | | | | JUCE-ignored |
+| audioMasterGetInputLatency | | | | | JUCE-ignored |
+| audioMasterGetOutputLatency | | | | | JUCE-ignored |
+| audioMasterOpenWindow | | | | | JUCE-ignored |
+| audioMasterCloseWindow | | | | | JUCE-ignored |
+| audioMasterSetIcon | | | | | JUCE-ignored |
+| audioMasterOfflineGetCurrentMetaPass | | | | | JUCE-ignored |
+| audioMasterOfflineGetCurrentPass | | | | | JUCE-ignored |
+| audioMasterOfflineRead | | | | | JUCE-ignored |
+| audioMasterOfflineStart | | | | | JUCE-ignored |
+| audioMasterOfflineWrite | | | | | JUCE-ignored |
+| audioMasterVendorSpecific | | | | | JUCE-ignored |
+
+
+
+# Part: a plugin
+
+~~~C
+/* compile with: 'g++ -shared -IFST -g -O0 FstPlugin.cpp -o FstPlugin.so' */
+#include <stddef.h>
+#include <cstdio>
+#include "fst.h"
+static AEffectDispatcherProc*dispatch = 0;
+static VstIntPtr dispatcher(AEffect*eff, t_fstInt32 opcode, int index, VstIntPtr ivalue, void* const ptr, float fvalue) {
+ printf("FstClient::dispatcher(%p, %d, %d, %d, %p, %f)\n", eff, opcode, index, ivalue, ptr, fvalue);
+ return 0;
+}
+
+static void setParameter(AEffect*eff, int index, float value) {
+ printf("FstClient::setParameter(%p)[%d] -> %f\n", eff, index, value);
+}
+static float getParameter(AEffect*eff, int index) {
+ printf("FstClient::getParameter(%p)[%d]\n", eff, index);
+ return 0.5;
+}
+static void process(AEffect*eff, float**indata, float**outdata, int sampleframes) {
+ printf("FstClient::process(%p, %p, %p, %d\n", eff, indata, outdata, sampleframes);
+}
+static void processReplacing(AEffect*eff, float**indata, float**outdata, int sampleframes) {
+ printf("FstClient::process'(%p, %p, %p, %d\n", eff, indata, outdata, sampleframes);
+}
+static void processDoubleReplacing(AEffect*eff, double**indata, double**outdata, int sampleframes) {
+ printf("FstClient::process2(%p, %p, %p, %d\n", eff, indata, outdata, sampleframes);
+}
+extern "C"
+AEffect*VSTPluginMain(AEffectDispatcherProc*dispatch4host) {
+ dispatch = dispatch4host;
+ printf("FstPlugin::main(%p)\n", dispatch4host);
+ AEffect* eff = new AEffect;
+
+ eff->magic = 0x56737450;
+ eff->dispatcher = dispatcher;
+ eff->process = process;
+ eff->getParameter = getParameter;
+ eff->setParameter = setParameter;
+
+ eff->numPrograms = 1;
+ eff->numParams = 3;
+ eff->numInputs = 2;
+ eff->numOutputs = 2;
+ eff->float1 = 1.;
+ eff->object = eff;
+ eff->uniqueID = 123456;
+ eff->version = 666;
+
+ eff->processReplacing = processReplacing;
+ eff->processDoubleReplacing = processDoubleReplacing;
+ return eff;
+}
+~~~
+
+This simple plugin allows us to query the host for whatever opcodes the host understands:
+
+~~~C
+for(size_t i = 0; i<64; i++) {
+ char buf[512] = {0};
+ VstIntPtr res = dispatch(0, i, 0, 0, buf, 0);
+ if(*buf)
+ printf("\t'%.*s'\n", 512, buf);
+ if(res)
+ printf("\treturned %d\n", res);
+}
+~~~
+
+With REAPER, this returns:
+
+| op | result | buf |
+|------|------------|-----------------------------------|
+| *0* | 1 | |
+| *1* | 2400 | |
+| 6 | 1 | |
+| *8* | 1 | |
+| *10* | 1200000 | |
+| 11 | 65536 | |
+| 12 | 1 | |
+| 13 | 1 | |
+| 19 | 0x2800,... | |
+| 23 | 1 | |
+| *32* | 1 | Cockos |
+| *33* | 1 | REAPER |
+| *34* | 5965 | |
+| 42 | 1 | |
+| 43 | 1 | |
+| 44 | 1 | |
+| 48 | 1 | ~/Documents/REAPER Media/test.RPP |
+
+This table confirms that host-opcode `33` is `audioMasterGetProductString`,
+and we learn that host-opcode `32` is `audioMasterGetVendorString`.
+The number `5969` happens to be the version of REAPER (`5.965`), so
+host-opcode `34` is likely `audioMasterGetVendorVersion`.
+
+The opcode `48` returns the currently opened REAPER session file.
+The `audioMasterGetDirectory` might match, although REAPER returns a
+file rather than a directory.
+More importantly, JUCE will `return` the string address
+in the `return` value, whereas REAPER writes the string into `ptr`.
+
+Opcode `10` is a weirdo number, obviously for humans (rather than computers).
+Reading up a bit on what JUCE does for the various audioMaster-opcodes,
+I stumbled upong the `audioMasterTempoAt`, which - according to the comments
+in the JUCE code - is meant to be the tempo in "BMP * 10000".
+So the universal default of *120bpm* would be 1200000 which is exactly what
+we have here.
+
+TODO: 11
+
+## process functions
+We can also test our process functions, by adding some printout to them.
+
+We notice that REAPER keeps calling our `process` function
+(spamming the console, so we don't see much else).
+Again, this is good news, as it means that we have the address of the `process` member correct.
+
+If we apply the plugin on some track with music and printout the first sample
+of the first channel (`printf("%f\n", indata[0][0]))`) we see that the samples
+are nicely in the range between `-1...+1` which is what we expected.
+
+It also confirms that REAPER is not calling some `processDouble` function.
+Which is probably logical, as we haven't set either of the
+`effFlagsCanReplacing` nor `effFlagsCanDoubleReplacing` flags (we don't know them yet).
+
+If we set the various bits of `AEffect.flags` one by one (carefully leaving out #0 (editor) and #8 (synth))
+and watch which of the `processReplacing` functions are being called, we soon learn new flags:
+
+| flag | value |
+|----------------------------|---------|
+| effFlagsCanReplacing | `1<< 4` |
+| effFlagsCanDoubleReplacing | `1<<12` |
+
+Again, by printing out the first sample of the first channel (either as `float` or as `double`)
+we learn that we already have the order correct (`processReplacing` comes before `processDoubleReplacing`
+at the very end of `AEffect`.)
+
+
+# AEffect initialisation from host
+
+## startup
+
+After the `VSTPluginMain` function has returned to the host,
+REAPER calls our plugin a couple or times, with various opcodes (sorted by opcode number)):
+
+> FstClient::dispatcher(0x3098d20, 0, 0, 0, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 2, 0, 0, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 5, 0, 0, 0x7fffe1b4a2f0, 0.000000)
+> [...]
+> FstClient::dispatcher(0x3098d20, 10, 0, 0, (nil), 44100.000000)
+> FstClient::dispatcher(0x3098d20, 11, 0, 512, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 12, 0, 1, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 35, 0, 0, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 45, 0, 0, 0x7fffe1b4a530, 0.000000)
+> FstClient::dispatcher(0x3098d20, 47, 0, 0, 0x7fffe1b4a530, 0.000000)
+> FstClient::dispatcher(0x3098d20, 51, 0, 0, 0xab4617, 0.000000)
+> [...]
+> FstClient::dispatcher(0x3098d20, 58, 0, 0, (nil), 0.000000)
+> FstClient::dispatcher(0x3098d20, 71, 0, 0, (nil), 0.000000)
+
+Opcode `10` looks like the host samplerate (`effSetSampleRate`),
+and `11` looks like the blocksize (`effSetBlockSize`).
+
+Opcode `5` is what we already suspect to be `effGetProgramName`,
+which we can now confirm by implementing it.
+If we do return a nice string for opcode 5,
+REAPER will then call opcode `2` with a varying index and then calls opcode `5` again
+(so supposedly it tries to change the program and query the new program name.
+In theory it should use `effGetProgramNameIndexed` instead...
+
+To find out what the pointers store, we can just try to print them out
+(making sure to only print the first N bytes, in order to not flood the terminal).
+For most of the opcodes this doesn't print anything (so the `ptr` points to a properly
+zero-initialised memory region), only for opcode `51` we get something:
+
+- `hasCockosExtensions`
+- `hasCockosNoScrollUI`
+- `receiveVstEvents`
+- `receiveVstMidiEvent`
+- `sendVstEvents`
+- `sendVstMidiEvent`
+- `wantsChannelCountNotifications`
+
+This looks very much like a generic interface to either query a property by name
+(could be `effCanDo`) or to allow vendor-specific programs (could be `effVendorSpecific`).
+Looking up how JUCE handles the `effCanDo` in *juce_VST_Wrapper.cpp*, we see that
+it really takes the `ptr` argument as string and compares it to values like
+`bypass`, `sendVstEvents` and `hasCockosExtensions`. Bingo!
+
+If we do *not* sort the output, we notice that after the plugin has been created with
+opcode `0` is run (only) at the very beginning, when the plugin is initialised.
+On the other hand, opcode `1` is run (only) at the very end.
+So, these seem to be some initialisation/deinitialisation opcodes for the plugin.
+`effOpen` and `effClose` look like likely candidates.
+
+
+
+
+
+## inserting the plugin
+
+If we add our new plugin to a REAPER track, we see printout like the following
+(btw: for now i've muted the output for opcodes `53` and `3` as they are spamming
+the console):
+
+> FstClient::setParameter(0x27967d0)[0] -> 0.000000
+> FstClient::setParameter(0x27967d0)[0] -> 0.000000
+> FstClient::dispatcher(0x27967d0, 8, 0, 0, 0x7ffeca15b580, 0.000000)
+> FstClient::dispatcher(0x27967d0, 7, 0, 0, 0x7ffeca15b480, 0.000000)
+> FstClient::dispatcher(0x27967d0, 6, 0, 0, 0x7ffeca15b480, 0.000000)
+> FstClient::setParameter(0x27967d0)[1] -> 279445823603548880896.000000
+> FstClient::setParameter(0x27967d0)[1] -> 0.000000
+> FstClient::dispatcher(0x27967d0, 8, 1, 0, 0x7ffeca15b580, 0.000000)
+> FstClient::dispatcher(0x27967d0, 7, 1, 0, 0x7ffeca15b480, 0.000000)
+> FstClient::dispatcher(0x27967d0, 6, 1, 0, 0x7ffeca15b480, 0.000000)
+> FstClient::setParameter(0x27967d0)[2] -> 279445823603548880896.000000
+> FstClient::setParameter(0x27967d0)[2] -> 0.000000
+> FstClient::dispatcher(0x27967d0, 8, 2, 0, 0x7ffeca15b580, 0.000000)
+> FstClient::dispatcher(0x27967d0, 7, 2, 0, 0x7ffeca15b480, 0.000000)
+> FstClient::dispatcher(0x27967d0, 6, 2, 0, 0x7ffeca15b480, 0.000000)
+
+Since the opcodes `6`, `7` and `8` are the only ones that use the index
+(with an index that goes until `numParams-1`), they are likely to be
+parameter related. Conincidientally we have 3 parameter-related opcodes:
+`effGetParamLabel`, `effGetParamDisplay` and `effGetParamName`.
+
+If we respond to these opcodes with some strings (by copying them into `object`) and
+display the generic FX-GUI in reaper, we notice that our strings appear besides the
+three parameter sliders (`8` to the left, and `7` followed by `6` to the right).
+For other plugins, these strings read like "Gain" (on the left) and "+0.00 dB" (on the right).
+We can pretty safely assume that *Name* refers to the human readable name of the parameter (e.g. "Gain"),
+so *Display* would be a nice string rendering of the value itself (e.g. "+0.00")
+and *Label* would be the units (e.g. "dB").
+
+Playing around with the sliders and watching the calls to `setParameter`,
+we see that whenever we move the slider, both the setter- and the getter-function are called.
+However, we get bogus values, e.g.:
+
+> FstClient::setParameter(0x2a88690)[0] -> -27706476332321908694659177423680045056.000000
+
+This looks like the `setParameter` callback is not called correctly.
+Remember, that we used arbitrary positions for the function pointers in the `AEffect` structure
+(we were just lucky that the `dispatcher` function was at the correct place).
+This means that the host might call the `setParameter` function without providing
+any explicit `value` argument at all )e.g. because the host things this is really the getter).
+So let's play with those, and just revert the order of the two functions:
+
+~~~C
+typedef struct AEffect {
+ t_fstInt32 magic;
+ AEffectDispatcherProc* dispatcher;
+ AEffectProcessProc* process;
+ AEffectSetParameterProc* setParameter;
+ AEffectGetParameterProc* getParameter;
+ // ...
+}
+~~~
+
+And indeed, this seems to have done the trick!
+
+When we move a slider, both parameter getter and setter are called as well as the opcodes `6`, `7` & `8`.
+About 0.5 seconds after the last parameter change happened, the host calls opcode `5` (which we already believe
+to be `effGetProgramName`).
+
+# Part: more enums
+
+## eff[...]Program[...]
+If we set the `numPrograms` to some identifiably value (e.g. 5) and open the plugin with REAPER,
+we can see that it does something like, the following when opening up the generic GUI:
+
+~~~
+FstClient::dispatcher(0x3339ff0, 2, 0, 0, (nil), 0.000000)
+FstClient::dispatcher(0x3339ff0, 5, 0, 0, 0x7ffc1b29af60, 0.000000)
+FstClient::dispatcher(0x3339ff0, 2, 0, 1, (nil), 0.000000)
+FstClient::dispatcher(0x3339ff0, 5, 0, 0, 0x7ffc1b29af60, 0.000000)
+FstClient::dispatcher(0x3339ff0, 2, 0, 2, (nil), 0.000000)
+FstClient::dispatcher(0x3339ff0, 5, 0, 0, 0x7ffc1b29af60, 0.000000)
+FstClient::dispatcher(0x3339ff0, 2, 0, 3, (nil), 0.000000)
+FstClient::dispatcher(0x3339ff0, 5, 0, 0, 0x7ffc1b29af60, 0.000000)
+FstClient::dispatcher(0x3339ff0, 2, 0, 4, (nil), 0.000000)
+FstClient::dispatcher(0x3339ff0, 5, 0, 0, 0x7ffc1b29af60, 0.000000)
+FstClient::dispatcher(0x3339ff0, 2, 0, 0, (nil), 0.000000)
+~~~
+
+If we write a different string into the `ptr` for each call to opcode `5`,
+we can see that the generic GUI displays our strings as the program names.
+So it really seems that REAPER makes each program current and then
+queries the current program name in order to retrieve all program names.
+Which confirms that `effGetProgramName` is `5` and introduces
+`effSetProgram` as `2`.
+
+We can now easily find the opcode for the corresponding `effSetProgramName`,
+by passing a unique string (`"program#%02d" % opcode`) for the opcodes 0..10.
+If we call `effGetProgramName` afterwards, we see that the program name is now
+*program#04*, so `effSetProgramName` is `4`.
+
+We have `effSetProgram`, `effSetProgramName` and `effGetProgramName` as `2`, `4` and `5` resp.,
+so probably `effGetProgram` is `3`.
+This can easily be confirmed by a little test program that first changes the program
+to a known value (using a plugin that actually has that many programs!) via `effSetProgram`,
+and then calling opcode `3` to get that value back (as returned from the dispatcher).
+
+*MrsWatson* uses the `kVstMaxProgNameLen` to allocate memory for the string returned by
+`effGetProgramName`. OTOH, JUCE just will allocate 264 bytes when used as a host,
+but when acting as a plugin it will only copy the 24+1 bytes (including the terminating `\0`).
+For `effSetProgramName`, JUCE will only send 24 bytes.
+REAPER will crash if we attempt to write more than 265 bytes into the program name string.
+
+Most likely `kVstMaxProgNameLen` is just 24 characters. and hosts traditionally accept more,
+in case a plugin misbehaves.
+
+
+## effEdit*
+
+The *Protoverb* plugin prints information, for which we can guess opcode names:
+
+| opcode | printout | guessed opcodeName |
+|--------|-------------------------------------|--------------------|
+| 13 | "AM_VST_Editor::getRect 1200 x 600" | effEditGetRect? |
+| 14 | "AM_VST_Editor::open" | effEditOpen? |
+| 15 | "closed editor." | effEditClose? |
+
+Opcode `13` does not crash if we provide it a ptr to some memory,
+and it will write some address into that provided memory.
+It will also return that same address (just what JUCE does for the `effEditGetRect` opcode.)
+If we read this address as `ERect` we get a garbage rectangle (e.g. `0x78643800+0+0`).
+The memory looks like
+
+> 00 00 00 00 58 02 B0 04 00 00 00 00 00 00 00 00
+
+Given that a GUI rectangle will most likely not have a zero width or height,
+it might be, that the `ERect` members are really only 2-bytes wide (`short`),
+which would then translate to the numbers `0, 0, 600, 1200`,
+which happens to align nicely with the printout of *Protoverb*
+
+~~~C
+typedef struct ERect_ {
+ short left;
+ short top;
+ short right;
+ short bottom;
+} ERect;
+~~~
+
+## effIdentify
+
+the following little helper gives us information, what a plugin returns for the various opcodes:
+
+~~~C
+ for(size_t opcode=16; opcode<64; opcode++) {
+ char buffer[200] = { 0 };
+ VstIntPtr result = effect->dispatcher(effect, opcode, 0, 0, buffer, 0.f);
+ if(result || *buffer)
+ printf("tested %d\n", opcode);
+ if(result)
+ printf("\t%llu 0x%llX\n", result, result);
+ if(*buffer) {
+ printf("\tbuffer '%.*s'\n", 512, buffer);
+ hexprint(buffer, 16);
+ }
+ }
+~~~
+
+I get:
+
+> tested 22 | 1316373862 0x4E764566
+> tested 23 | 791 0x317 | buffer '� ��U'
+> tested 24 | 1 0x1
+> tested 25 | 1 0x1
+> tested 26 | 1 0x1
+> tested 29 | 1 0x1 | buffer 'initialize'
+> tested 33 | 1 0x1 | buffer 'Protoverb-In0'
+> tested 34 | 1 0x1 | buffer 'Protoverb-Out1'
+> tested 35 | 1 0x1
+> tested 45 | 1 0x1 | buffer 'Protoverb'
+> tested 47 | 1 0x1 | buffer 'u-he'
+> tested 48 | 1 0x1 | buffer 'Protoverb 1.0.0'
+> tested 49 | 65536 0x10000
+> tested 51 | 18446744073709551615 0xFFFFFFFFFFFFFFFF
+> tested 58 | 2400 0x960
+> tested 63 | 18446744073709551615 0xFFFFFFFFFFFFFFFF
+
+the value returned by opcode `22` is identical with the once that JUCE returns for the `effIdentify` opcode.
+So we assign that.
+
+## effGetProgramNameIndexed
+The "initialize" string we already know as Protoverbs sole program name.
+The only missing opcode for retrieving program names is `effGetProgramNameIndexed`.
+
+We can confirm this assumption with a plugin that has many programs (e.g. *tonespace*)
+and the following snippet:
+
+~~~C
+ for(int i=0; i<effect->numPrograms; i++) {
+ char buffer[200] = { 0 };
+ effect->dispatcher(effect, 29, i, 0, buffer, 0.f);
+ if(*buffer) {
+ printf("\tbuffer#%d '%.*s'\n", i, 512, buffer);
+ }
+ }
+~~~
+
+## effectName, productString, vendorString, vendorVersion
+
+In the [C-snippet above](#effidentify), we also get nice strings for
+opcodes `45`, `47`, and `48`, that look like they would be good matches for
+`effGetEffectName` (45), `effGetProductString` (48) and `effGetVendorString` (47).
+
+The value returned for `49` looks suspiciously like the plugin version.
+
+E.g. if we compare the values to the ones we assumed to be `AEffect.version`:
+
+| plugin | AEffect.version | returned by `49` |
+|-------------|-----------------|------------------|
+| BowEcho | 110 | 110 |
+| Danaides | 102 | 102 |
+| Digits | 1 | |
+| InstaLooper | 1 | 1 |
+| Protoverb | 1 | 0x10000 |
+| hypercyclic | 150 | 150 |
+| tonespace | 250 | 250 |
+
+So we assign `effGetVendorVersion` the value of `49`
+
+## effGetVstVersion
+
+Opcode `58` returns `2400` for all plugins I've tested so far.
+This is the same value that JUCE pluginhosts return (hardcoded) to the
+`audioMasterVersion` opcode.
+So it's probably save to assume that `58` is the opcode
+that allows the host to query the plugin for it's VST version.
+Which would make it the `effGetVstVersion`.
+
+## Chunks
+In the [C-snippet above](#effgetprogramnameindexed), we also get an interesting result for opcode `23`:
+the return value is a reasonable number (791), and something is written to the `ptr`.
+
+If we take a closer look on what is written, we see that the plugin wrote the address
+of some heap-allocated memory into our buffer.
+Comparing this to how JUCE handles the various opcodes, this could be `effGetChunk`.
+
+Inspecting the data returned by *Protoverb*, we get something like:
+
+~~~
+#pgm=initialize
+#AM=Protoverb
+#Vers=10000
+#Endian=little
+#nm=5
+#ms=none
+#ms=ModWhl
+#ms=PitchW
+#ms=Breath
+#ms=Xpress
+#nv=0
+#cm=main
+CcOp=100.00
+#FDN=1
+#cm=PCore
+[...]
+// Section for ugly compressed binary Data
+// DON'T TOUCH THIS
+[...]
+~~~
+
+This indeed looks very much like some state dump of the plugin.
+Other plugins returns only binary data (*hypercyclic*, *tonespace*).
+
+*InstaLooper* is the only tested plugins that doesn't return anything.
+It also happens to be the only tested plugin that has the bit#6 in
+the `AEffect.flags` structure set to `0` - hinting that
+`effFlagsProgramChunks = (1<<5)` (which would explain that this
+flag has something to do with the ability to get/set chunks).
+
+Of the `effFlags*`, the only reamining unknown flags is `effFlagsNoSoundInStop`.
+Our [test plugins](#flags) have the flags `1<<0`, `1<<4`, `1<<5`, `1<<8` and `1<<9` set,
+of which we haven't used `1<<9` yet.
+Most likely this is the `effFlagsNoSoundInStop`.
+
+## VstPinProperties
+Opcodes `33` and `34` return strings that seem to be related to input and output.
+There is no obvious effect opcode that provides such strings, nor can we find anything
+in the [JUCE effect Opcodes tables](#juce-effect-opcodes).
+
+The only opcodes that deal with input and output are `effGetInputProperties` resp. `effGetOutputProperties`.
+But these should return a `VstPinProperties` struct.
+
+So let us investigate the returned buffer:
+
+~~~
+00000000 50 72 6f 74 6f 76 65 72 62 2d 49 6e 30 00 00 00 |Protoverb-In0...|
+00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+00000040 03 00 00 00 00 00 00 00 50 72 6f 74 6f 76 30 00 |........Protov0.|
+00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+~~~
+
+The first 14 bytes are the 'Protoverb-In1' string.
+The interesting thing is, that at position @44 we see a similar string 'Protov0'.
+
+Investigating the buffer for other plugins, gives similar results (e.g.
+*hypercyclic* has a string 'out0' at position @0 and the same string 'out0' @44).
+
+Compare this with our tentative definition of the `VstPinProperties` struct:
+
+~~~
+typedef struct VstPinProperties_ {
+ int arrangementType;
+ char*label;
+ char*shortLabel;
+ int flags;
+} VstPinProperties;
+~~~
+
+"Protoverb-In0" and "Protov0" could indeed be the `label` resp. `shortLabel`.
+
+At position @40 we have a number `3` (in all tested plugins).
+This could be a `flag` or an `arrangementType`.
+
+So the opcode really filled in a `VstPinProperties` structure,
+rather than a string, as we first thought.
+However, because the structure contains a string at the very beginning
+(that is: the address of the string is the same as the address of the containing struct),
+our naive "print buffer" approach had revealed it.
+
+In order for the struct to be useful, the strings must have a fixed size.
+Since we have other information at @40, the first string must not exceed this byte.
+So it can have a maximum size of 64 bytes.
+The second string (supposedly `shortLabel`) ought to be shorter than the first one.
+If it only occupied 8 bytes, the entire struct would nicely align to 16 byte boundaries.
+
+The only remaining question is, what about the `flags` and `arrangementType` fields.
+Since both would be 4 byte ints (as all `int`s we have seen so far were 4 bytes),
+both would fit into the gap between `label` and `shortLabel`.
+In this case, one would have to be `0`, and the other `3`.
+
+JUCE-4.2.1 will always set the flags to *at least* `kVstPinIsActive | kVstPinUseSpeaker`,
+suggesting that this is the non-0 field:
+
+~~~
+typedef struct VstPinProperties_ {
+ char label[64];
+ int flags; //?
+ int arrangementType; //?
+ char shortLabel[8];
+} VstPinProperties;
+~~~
+
+There are also two related constants `kVstMaxLabelLen` and `kVstMaxShortLabelLen`
+which will have the values `64` and `8` resp.
+
+
+## MIDI
+responding with `1` to `effCanDo receiveVstEvents` resp `effCanDo receiveVstMidiEvents`
+gives us `opcode:25` events correlating to MIDI-events (sent from a MIDI-item
+with a little sequence).
+
+A typical frame received is:
+
+~~~
+00000000 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00000010 50 3e 00 cc 90 7f 00 00 70 3e 00 cc 90 7f 00 00
+00000020 00 00 00 00 00 00 00 00 01 00 00 00 18 00 00 00
+~~~
+
+The frame itself is at 0x7F90CC003E28, which makes it clear that we have
+two addresses at positions @10 (0x7F90CC003E50) and @18 (0x7F90CC003E70).
+
+The first address is only 40 bytes after the frame itself, which is indeed
+position @28.
+
+This looks very much like our `VstEvents` structure, with 2 events.
+The number of events is also in stored in the first 4 bytes of the structure.
+There seems to be a bit of padding between `numEvents` and the `events`,
+depending on the architecture (32bit vs 64bit).
+Since the `events` member is really a varsized array of `VstEvent*` pointers,
+we can refine our struct definition as:
+
+~~~
+typedef struct VstEvents_ {
+ int numEvents;
+ VstIntPtr _pad;//?
+ VstEvent*events[];
+} VstEvents;
+~~~
+
+From the difference of the two `VstEvent` addresses, we gather, that a single `VstEvent`
+(of type `VstMidiEvent`, because this is really what we are dealing with right now)
+can have a maximum size of 32 bytes.
+
+JUCE handles `VstEvents` in the `effProcessEvents` opcode, so we assign it to `opcode:25`
+
+## VstMidiEvent
+
+Setting up a little MIDI sequence in reaper, that plays the notes `C4 G4 C5 F4` (that is `0x3c 0x43 0x48 0x41` in hex),
+we can test the actual `VstMidiEvent` structure.
+
+If we play back the sequence in a loop and print the first 32 bytes of each `VstEvent`, we get something like:
+
+~~~
+[...]
+ 01 00 00 00 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 3C 7F 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3C 00 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 43 7F 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 43 00 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 44 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 48 7F 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 48 00 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 41 7F 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 41 00 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 3C 7F 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3C 00 00 00 00 00 00
+ 01 00 00 00 18 00 00 00 C3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 90 43 7F 00 00 00 00 00
+ [...]
+~~~
+
+The interesting part are the last 8 bytes, where we can see the MIDI NoteOn (`0x90`) and NoteOff (`0x80`) messages for our
+notes. The NoteOn velocity is apparently `0x7f` (127).
+One of the fields should be `byteSize`. If we take the MIDI-bytes into account, we have at least 27 bytes (28, if we allow 4 byte
+MIDI messages, as JUCE suggests), and at most 32 bytes (see above).
+The 5th byte of each line (`0x18` aka 24) is the only value that is close by.
+JUCE uses `sizeof(VstMidiEvent)` for filling in the `byteSize` field.
+If REAPER does the same, we lack (at least) 4 bytes.
+One possible explanation could be that the `midiData` field is declared as a varsized array `char midiData[0]` in the SDK.
+
+The `byteSize`, `deltaFrames` and `type` fields are available in `VstEvent`, `VstMidiEvent` and `VstMidiSysexEvent` structs.
+Since we can cast the latter two to `VstEvent`, the common fields will be at the very beginning.
+
+Since all events are of the same type, the `type` field should be constant for all our events, which happens to be the
+true for the very first bytes. So let's assume `kVstMidiType` to be `0x01`.
+
+Bytes at positions @8-11 vary and might be the `deltaFrames` field (JUCE uses this field to sort the events chronologically).
+The remaining twelve 0-bytes in the middle need to be somehow assigned to `noteLength`, `noteOffset`, `detune` and `noteOffVelocity`.
+I don't know anything about these fields. `noteLength` and `noteOffVelocity` seem to allow scheduling NoteOff events in the future.
+`noteOffVelocity` should not need more than 7bits (hey MIDI).
+
+Anyhow, here's what we have so far:
+
+~~~
+typedef struct VstEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+} VstEvent;
+
+typedef struct VstMidiEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ /* no idea about the position and sizes of the following four members */
+ short noteLength; //?
+ short noteOffset; //?
+ int detune; //?
+ int noteOffVelocity; //?
+ unsigned char midiData[]; //really 4 bytes
+} VstMidiEvent;
+~~~
+
+## VstMidiSysexEvent
+
+Using REAPER's plugin, we can generate SysEx messages.
+I prepared a SysEx message `F0 01 02 03 04 03 02 01 F7` and this is the
+event that our plugin received:
+
+~~~
+06 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00
+09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+78 DB 01 14 3C 7F 00 00 00 00 00 00 00 00 00 00
+F0 01 02 03 04 03 02 01 F7 00 00 00 00 00 00 00
+~~~
+
+Aha: the first element changed to a low value as well, so `0x06` might be the value of `kVstSysExType`.
+The size is `48` bytes, which covers a pointer 0x7F3C1401DB78 at position @20.
+This address is 48 bytes after the current data frame (so right after the data frame, according to the `byteSize` field),
+and it happens to hold the sequence `F0 01 02 03 04 03 02 01 F7`, our SysEx message.
+The SysEx message is 9 bytes long (see position @10).
+The members `flags` and `resvd1` resp. `resvd2` seem to be set to all zeros,
+so we cannot determine their position.
+I guess the `reserved` fields will be at the very end, which gives us:
+
+~~~
+typedef struct VstMidiSysexEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ int _pad; //?
+ int dumpBytes;
+ int flags; //?
+ VstIntPtr resvd1; //?
+ char*sysExDump;
+ VstIntPtr resvd2; //?
+} FST_UNKNOWN(VstMidiSysexEvent);
+~~~
+
+## enter MrsWatson
+When trying to [compile MrsWatson](#compiling-mrswatson) there's an additional complication,
+as it seems there are some more members in the `VstMidiEvent` structure.
+
+| member | type |
+|-------------|---------|
+| `flags` | integer |
+| `reserved1` | integer |
+| `reserved1` | integer |
+
+This is weird because the `VstMidiEvent.byteSize` reported by REAPER is *24*,
+and our structure is already 28 bytes long (if we include the 4-bytes for `midiData`).
+We probably need another host for testing this structure (and see whether it's just REAPER that reports a bogus `byteSize`,
+or whether some of the fields are only `unsigned char`).
+
+The `flags` member is also part of the `VstMidiSysexEvent`, so it might well be *common* to both structs
+(as in: at the same position).
+So far, the two structures share the first 12 bytes (`type`, `byteSize`, `deltaFrames`).
+We have assigned 4-byte `_pad` member to `VstMidiSysexEvent` right after the common members,
+and the `VstMidiEvent` has some arbitrarily `noteLength` and `noteOffset`, which are always *0*.
+So let's revise this and make the underlying `VstEvent` nicely ending on an 8-bytes boundary:
+
+~~~C
+typedef struct VstEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ int flags;
+} VstEvent;
+~~~
+
+The `VstMidiSysexEvent` changes accordingly:
+~~~C
+typedef struct VstMidiSysexEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ int flags;
+ int dumpytes;
+ int _pad;
+ VstIntPtr resvd1;
+ char*sysexDump;
+ VstIntPtr resvd2;
+} VstMidiSysexEvent;
+~~~
+
+This works nicely on 64bit, but when running on 32bit, REAPER core-dumps.
+
+On 64bit the first `byteSize` (48) bytes look like:
+~~~
+0000 06 00 00 00 30 00 00 00 00 00 00 00 00 00 00 00
+0010 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0020 28 EB 04 AC 19 7F 00 00 00 00 00 00 00 00 00 00
+~~~
+
+Whereas on 32bit the first `byteSize` (32) bytes look like:
+~~~
+0000 06 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00
+0010 09 00 00 00 00 00 00 00 B4 E5 E4 ED 00 00 00 00
+~~~
+
+Fiddling around with the type-sizes, it seems we can use `VstIntPtr` as the type for `dumpBytes`,
+and everything will align nicely, without the need for some padding bytes.
+(Using a pointer-sized into for the size of `sysexDump` aligns with the typical use of `size_t` for memory-sizes,
+although i hope that there will never be a need for 6 terabyte dumps of SysEx data...)
+
+~~~C
+typedef struct VstMidiSysexEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ int flags;
+ VstIntPtr dumpytes;
+ VstIntPtr resvd1;
+ char*sysexDump;
+ VstIntPtr resvd2;
+} VstMidiSysexEvent;
+~~~
+
+Since we've added the 4-bytes `flags` to the common `VstEvent` members, we also need to fix the `VstMidiEvent`,
+so that the midiData is still at position @18.
+We already have (quite arbitrarily, to get the alignment right) assigned the `noteLength` and `noteOffset`
+members a `short` type.
+If we also do the same for `noteOffVelocity` and `detune`, we gain exactly 4 bytes and the data aligns again.
+
+We also need to find some place for the `reserved1` and `reserved2` members.
+For now we just arbitrarily use them to fill up the structure,
+so it also ends at an 8-bytes boundary:
+
+~~~C
+typedef struct VstMidiEvent_ {
+ int type;
+ int byteSize;
+ int deltaFrames;
+ int flags;
+ short noteLength; //?
+ short noteOffset; //?
+ short detune; //?
+ short noteOffVelocity; //?
+ unsigned char midiData[4]; /* @0x18 */
+ short reserved1; //?
+ short reserved2; //?
+} VstMidiEvent;
+~~~
+
+Again: note that `noteLength`, `noteOffset`, `noteOffVelocity`, `detune`, `reserved1` and `reserved2` are arbitrarily
+assigned in type and position.
+The `reserved` types will probably always be *0*,
+but we will have to find a plugin host that fills in the `note*` members to learn more about them.
+
+Also note that the size of the `VstMidiEvent` struct is currently 32 bytes,
+rather than the 24 bytes claimed by REAPER.
+
+# some audioMaster opcodes
+time to play get some more opcodes for the host.
+We want the plugin host to be properly running, so the plugin initialisation time is probably too early.
+All real plugins observed so far, call a number of host-opcodes when they receive opcode `12`:
+/*BowEcho*/*Danaides*/*hypercyclic*/*tonespace* run opcode `23`, *hypercyclic*/*tonespace* also call `7`
+and all plugins call `6`.
+
+So opcode:12 seems to be a good place to test the plugin host, which we do
+by simply running again, but this time in the opcode:12 callback.
+~~~
+for(size_t opcode=0; opcode<50; opcode++) {
+ char buffer[1024] = {0};
+ hostDispatch(effect, opcode, 0, 0, buf, 0.f);
+}
+~~~
+
+This time this gives us the following opcodes that don't return `0x0`:
+
+| op | result |
+|----|----------------------|
+| 2 | 57005 (0xDEAD) |
+| 6 | 1 (0x1) |
+| 7 | 60352024 (0x398E618) |
+| 8 | 1 (0x1) |
+| 11 | 3 (0x3) |
+| 12 | 1 (0x1) |
+| 13 | 1 (0x1) |
+| 15 | 1 (0x1) |
+| 23 | 1 (0x1) |
+| 42 | 1 (0x1) |
+| 43 | 1 (0x1) |
+| 44 | 1 (0x1) |
+| 48 | 1 (0x1) |
+
+Opcode:2 is interesting, as it has such a low number *and* such a funny hex-representation
+(which is obviously a magic value).
+The [JUCE host opcode table](#juce-host-opcodes) does not leave many possibilities.
+The only entry that makes sense is the `audioMasterCurrentId`.
+
+Opcode:11, returning `3`, might be `audioMasterGetAutomationState` (unless it is in the list of opcodes not handled by JUCE).
+
+## VstTimeInfo
+
+Opcode:7 returns a pointer. If we print the memory at the given position, we get the first 80 bytes like:
+
+> 09 00 00 00 30 D3 01 41 00 00 00 00 80 88 E5 40
+> 00 90 30 F3 DF FE D2 42 32 EF 75 99 3F 7D 1A 40
+> 00 00 00 00 00 00 5E 40 0A A8 FF FF FF FF 0F 40
+> 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+> 04 00 00 00 04 00 00 00 02 00 00 00 EF BE AD DE
+
+This *might* be `VstTimeInfo` struct as returned by `audioMasterGetTime` (let's assume that!).
+Record that chunk multiple times (luckily the opcode:12 gets called whenever we start playback in REAPER), and inspect it.
+Bytes @08-0f (`00 00 00 00 80 88 E5 40`) are *44100* in double precision, and the `VstTimeInfo` struct has a `sampleRate`
+field, that is supposed to be `double`. That looks very promising!
+
+Bytes @20-27 hold *120* in double (`tempo`). The first 8 bytes @00-07 read as double are monotonically increasing,
+but the values (40448, 109056, 78848, 90624) are certainly not `nanoSeconds` (rather: samples).
+Bytes @28-30 have values like *0*, *3.99999*, *15.9999*, *12.9999* (in double), which seems to be right.
+
+The `VstTimeInfo` struct supposedly has 7 double values and 6 int32 values.
+
+To decode the struct, we start by interpreting it as a simple array of only `double` values,
+and print the values whenever the plugin receices `opcode:53`
+(REAPER calls this opcode about every 50ms whenever the (generic) GUI of the plugin is open).
+We are not entirely sure about the size of the structure, our first estimation of the member types would
+require 80 bytes. For safety we add some more, and check the first 96 bytes
+(but keep in mind that this means that we might also read in garbage at the end of the data).
+
+The following is what I get when playing back a short loop in a *120bpm, 4/4* project.
+The loop starts at beat `1.2.00` (first bar, second quarter note) aka half a second into the project,
+the loop end point is at `4.2.00` aka 6.5 seconds.
+
+~~~
+ 277572.0 44100.0 1.48147e+14 12.5883 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 279108.0 44100.0 1.48147e+14 12.6580 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 281156.0 44100.0 1.48147e+14 12.7508 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 282692.0 44100.0 1.48147e+14 12.8205 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 284740.0 44100.0 1.48147e+14 12.9134 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 22188.0 44100.0 1.48147e+14 1.00626 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 23724.0 44100.0 1.48147e+14 1.07592 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 25772.0 44100.0 1.48148e+14 1.16880 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 27308.0 44100.0 1.48148e+14 1.23846 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ [...]
+ 262212.0 44100.0 1.48203e+14 11.8917 120.0 8.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 263748.0 44100.0 1.48203e+14 11.9614 120.0 8.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ [...]
+ 283716.0 44100.0 1.48204e+14 12.8669 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 285252.0 44100.0 1.48204e+14 12.9366 120.0 12.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 22188.0 44100.0 1.48204e+14 1.00626 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 24236.0 44100.0 1.48204e+14 1.09914 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ [...]
+ 81068.0 44100.0 1.48205e+14 3.67655 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 83116.0 44100.0 1.48205e+14 3.76943 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 84652.0 44100.0 1.48205e+14 3.83909 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 86188.0 44100.0 1.48205e+14 3.90875 120.0 0.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 88236.0 44100.0 1.48205e+14 4.00163 120.0 4.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 90284.0 44100.0 1.48205e+14 4.09451 120.0 4.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 91820.0 44100.0 1.48205e+14 4.16417 120.0 4.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 93356.0 44100.0 1.48205e+14 4.23383 120.0 4.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+ 95404.0 44100.0 1.48205e+14 4.32671 120.0 4.0 1.0 13.0 8.48798e-314 -1.1886e+148 3.42363e-310 1.08646e-311
+~~~
+
+Some columns have very reasonable values (e.g. columns 1, 2, 4, 5, 6, 7 and 8),
+while others are out of bounds (9, 10, 11 and 12).
+The reasonable values indicate that we used the correct type to decode the bytes.
+A value like "8.48798e-314" is hardly useful in the context of audio processing,
+so most likely these bytes just don't represent double values.
+Note that there's a clear divide between reasonable values to the left (the first 8 numbers; aka 64 bytes)
+and out-of-bound values on the right (bytes 65+).
+
+The third column (bytes @10-18) is somewhere in the middle, and requires closer inspection.
+
+Using the same project, but displaying all the values as 32bit `(signed int)`
+
+~~~
+5 1091576768 0 1088784512 -44277760 1122039678 1529826454 1076362265 0 1079902208 -11258 1075838975 0 1072693248 0 1076494336 4 4 3 -559038737 0 16134 2048 512
+6 1091584960 0 1088784512 1427722240 1122039679 1287516282 1076374439 0 1079902208 -5629 1076363263 0 1072693248 0 1076494336 4 4 4 -559038737 0 16134 1023 512
+6 1091591104 0 1088784512 -1747245056 1122039679 -1041699995 1076383569 0 1079902208 -5629 1076363263 0 1072693248 0 1076494336 4 4 4 -559038737 0 16134 1023 512
+[...]
+12 1091650496 0 1088784512 446820352 1122039682 -650965094 1076471830 0 1079902208 -5629 1076363263 0 1072693248 0 1076494336 4 4 4 -559038737 0 16134 2048 512
+12 1091658688 0 1088784512 1918820352 1122039682 -893275266 1076484004 0 1079902208 -5629 1076363263 0 1072693248 0 1076494336 4 4 4 -559038737 0 16134 1535 512
+0 1087755776 0 1088784512 -872146944 1122039682 -494749067 1072707989 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1535 512
+1 1087854080 0 1088784512 247853056 1122039683 -1948610105 1072781033 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1024 512
+[...]
+5 1089680768 0 1088784512 1337147392 1122039692 -1131689788 1074564040 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1024 512
+5 1089713536 0 1088784512 -1485819904 1122039692 -2100930480 1074612736 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1536 512
+5 1089738112 0 1088784512 -365819904 1122039692 1467106297 1074649258 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1536 512
+6 1089770880 0 1088784512 1138180096 1122039693 497865605 1074697954 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1024 512
+6 1089803648 0 1088784512 -1684787200 1122039693 -471375087 1074746649 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1536 512
+6 1089828224 0 1088784512 -564787200 1122039693 -1198305606 1074783171 0 1079902208 0 0 0 1072693248 0 1076494336 4 4 1 -559038737 0 16134 1536 512
+3 1089860992 0 1088784512 907212800 1122039694 -1083773151 1074811133 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1024 512
+1 1089893760 0 1088784512 -1883754496 1122039694 -1568393499 1074835481 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1535 512
+-1 1089918335 0 1088784512 -763754496 1122039694 -1931858760 1074853742 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1535 512
+-3 1089951103 0 1088784512 708245504 1122039695 1878488188 1074878090 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1024 512
+-6 1089983871 0 1088784512 -2114721792 1122039695 1393867840 1074902438 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1535 512
+[...]
+-43 1090475391 0 1088784512 -1285558272 1122039700 -1580470084 1075267656 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1024 512
+-46 1090508159 0 1088784512 186441728 1122039701 -2065090432 1075292004 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 2047 512
+-24 1090529983 0 1088784512 1690441728 1122039701 1745256516 1075316352 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1024 512
+-25 1090542271 0 1088784512 -1484525568 1122039701 1381791255 1075334613 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1024 512
+-26 1090558655 0 1088784512 -12525568 1122039701 897170907 1075358961 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 2047 512
+[...]
+-49 1090857663 0 1088784512 1353670656 1122039708 642784148 1075803310 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 1535 512
+-50 1090869951 0 1088784512 -1821296640 1122039708 279318887 1075821571 0 1079902208 -22518 1074790399 0 1072693248 0 1076494336 4 4 2 -559038737 0 16134 2047 512
+-51 1090886335 0 1088784512 -349296640 1122039708 2044832918 1075842447 0 1079902208 -11258 1075838975 0 1072693248 0 1076494336 4 4 3 -559038737 0 16134 1024 512
+-49 1090902719 0 1088784512 1122703360 1122039709 1802522746 1075854621 0 1079902208 -11258 1075838975 0 1072693248 0 1076494336 4 4 3 -559038737 0 16134 1535 512
+-48 1090919103 0 1088784512 -1668263936 1122039709 1560212574 1075866795 0 1079902208 -11258 1075838975 0 1072693248 0 1076494336 4 4 3 -559038737 0 16134 2048 512
+-47 1090931391 0 1088784512 -548263936 1122039709 -769003703 1075875925 0 1079902208 -11258 1075838975 0 1072693248 0 1076494336 4 4 3 -559038737 0 16134 1023 512
+~~~
+
+Let's first inspect the first 64 bytes (columns 1 till 16):
+columns 2, 9 and 13 are always *0*.
+Columns 3, 4, 10, 14, 15 and 16 don't change, so there's no real information in them. Apart from that, their values don't seem to mean anything.
+The other columns (1, 2, 5, 6, 7, 8, 11, 12) change, but i cannot make head nor tail of them.
+This is somewhat OK, as the first 64 bytes decoded nicely for `double` numbers.
+We were unsure about bytes @10-18 for double decoding, so let's inspect columns 5 and 6:
+alas!, they don't make any sense as `int32` either, so require some more inspection.
+
+The `double` decoding worked kind of nicely for the first 64 bytes and failed afterwards.
+If we look at the int32 values for bytes 65+, we see `4 4 [1-4] -559038737 0 16134`.
+At least the first 3 values are nice. If we change the metrum of the project to something weird like 3/7,
+we get `3 7 [1-8] -559038737 0 16134`.
+So the first two numbers denote the metrum.
+Playing around with the project cursor a bit and watching the third number, it seems to be a bar counter.
+
+The number `-559038737` has a (little endian) hex representation of 0xDEADBEEF and is a typical magic number.
+
+We should also have a look at the bytes @10-18, which - so far - made most sense when decoded as double.
+Because the numbers are so high (*1.48147e+14*) we divide them by 10^9.
+
+This results in the display of a number (e.g. `152197`) that increments by 1 every second.
+It doesn't matter whether the project is playing or not.
+Given that we had to scale the number by 10^-9, the real value is in nanoseconds -
+and our `VstTimeInfo` struct happens to have a `nanoSeconds` member!
+
+So far we can conclude
+~~~
+typedef struct VstTimeInfo_ {
+ double samplePos;
+ double sampleRate;
+ double nanoSeconds;
+ double ppqPos;
+ double tempo;
+ double barStartPos;
+ double cycleStartPos;
+ double cycleEndPos;
+ int timeSigNumerator;
+ int timeSigDenominator;
+ int currentBar;
+ int magic;// 0xDEADBEEF
+ //
+} VstTimeInfo;
+~~~
+
+We do not know the position (and type) of the following struct members yet:
+- `flags`
+- `smpteFrameRate`
+- `smpteOffset`
+
+The `flags` member will be a bitfield.
+JUCE assigns at least `kVstNanosValid` to it (and `kVstAutomationWriting` resp. `kVstAutomationReading`).
+Looking up more constants with similar names, we find:
+~~~
+kVstNanosValid
+kVstBarsValid
+kVstClockValid
+kVstCyclePosValid
+kVstPpqPosValid
+kVstSmpteValid
+kVstTempoValid
+kVstTimeSigValid
+kVstAutomationWriting
+kVstAutomationReading
+~~~
+
+
+
+### more on time flags
+If we keep printing the raw data of the `VstTimeInfo` continuously
+(e.g. as response to `opcode:53`), and start/stop the playback, we notice
+something weird:
+the bytes at position @54-57 (right after the magic `0xDEADBEEF`) keep changing
+according to the playstate:
+
+here's a few examples:
+
+| @50-58 | action |
+|-------------|---------------|
+| 00 2f 00 00 | pause |
+| 02 2f 00 00 | playing |
+| 06 3f 00 00 | looping |
+| 01 2f 00 00 | stopping |
+| 03 2f 00 00 | starting play |
+| 07 3f 00 00 | starting loop |
+
+Could it be, that these bytes actually encode some flags?
+If we read the 4 bytes as int and print that in binary, we get:
+
+| binary | description |
+|-----------------|-------------|
+| 101111 00000000 | pause |
+| 101111 00000010 | playing |
+| 111111 00000110 | looping |
+
+there's some pattern to be observed:
+- the 2nd (least significant) bit is set when the state is playing
+- the 3rd and the 13th bit are set when looping
+- bits 4-8 and 1 are always 0
+- bits 9-12 and 14 are always 1
+
+
+Maybe these are `flags` that are somehow related to the transport state of the system?
+Let's do some more tests:
+
+| binary | description |
+|-------------------|---------------|
+| 00101111 00001010 | recording |
+| 00101111 00000001 | stopping |
+| 00101111 00000011 | starting play |
+| 00111111 00000111 | starting loop |
+
+
+The stopping/playing states can only be observed for a single time frame,
+whenever we start (resp. stop) playback
+E.g. if we go from *paused* to *playing* (unlooped),
+we first see `10111100000000` (while the system is paused),
+when we press the space-bar (to start playing) there's a single time frame
+showing `10111100000011`, and then we see `11111100000111` while the system plays.
+
+So far we got:
+- when starting/stopping playback the 1st (least significant) bit is set.
+- the 2nd bit is set when the (target) state is playing (looped or not)
+- the 3rd and 13th bits are set when looping
+- the 4th bit is set when recording
+
+If we skim through the list of constants for values that might be related to the transport state,
+we find 4 constants starting with `kVstTransport*`, that map neatly to the observed bits:
+
+| flag | value |
+|----------------------------|-------|
+| `kVstTransportChanged` | 1<<0 |
+| `kVstTransportPlaying` | 1<<1 |
+| `kVstTransportCycleActive` | 1<<2 |
+| `kVstTransportRecording` | 1<<3 |
+
+That leaves us with two problems though: we still have the 13th bit that is also
+related to looping, and we have the 8 `kVst*Valid` values and 2 `kVstAutomation*`, that are supposed to be
+in the `VstTimeInfo.flags` field.
+
+There's the `kVstCyclePosValid` flag, that might occupy bit #13.
+JUCE will always assign either both `kVstCyclePosValid` and `kVstTransportCycleActive` or none.
+We have put the `kVstTransportCycleActive` in the group of the 4 least-significant bits,
+because there is it surrounded by other `kVstTransport*` bits.
+
+Most of the double-values in the `VstTimeInfo` struct made sense so far, although we haven't really
+found the fields for *SMPTE* yet; also the `nanoSeconds` field seems to be quantized to seconds.
+So I am not really sure whether `kVstNanosValid` and `kVstSmpteValid` are really set.
+
+To find out some more values, we can use a different VST-Host - `MrsWatson` - that uses the
+`ivalue` field of the callback (using the same flags as the `VstTimeInfo.flags` field)
+to determine which fields of the `VstTimeInfo` struct it fills.
+
+Iterating over etting one bit after the other while asking for the time
+(in the `process` callback),
+we get the following errors/warnings:
+
+| ivalue | binary | warning |
+|--------|--------|----------------------------------------|
+| 0x0100 | 1<< 8 | "plugin asked for time in nanoseconds" |
+| 0x4000 | 1<<14 | "Current time in SMPTE format" |
+| 0x8000 | 1<<15 | "Sample frames until next clock" |
+
+
+A few other `ivalue`s seem to enable the setting specific members:
+
+| ivalue | binary | set data |
+|--------|--------|-------------------------------------|
+| 0x0200 | 1<< 9 | ppqPos |
+| 0x0400 | 1<<10 | tempo |
+| 0x0800 | 1<<11 | barStartPos |
+| 0x2000 | 1<<13 | timeSigNumerator/timeSigDenominator |
+
+Which gives us the following values:
+
+| flag | value |
+|----------------------------|-------|
+| `kVstTransportChanged` | 1<< 0 |
+| `kVstTransportPlaying` | 1<< 1 |
+| `kVstTransportCycleActive` | 1<< 2 |
+| `kVstTransportRecording` | 1<< 3 |
+|----------------------------|-------|
+| `kVstNanosValid` | 1<< 8 |
+| `kVstPpqPosValid` | 1<< 9 |
+| `kVstTempoValid` | 1<<10 |
+| `kVstBarsValid` | 1<<11 |
+| `kVstCyclePosValid` | 1<<12 |
+| `kVstTimeSigValid` | 1<<13 |
+|----------------------------|-------|
+| `kVstAutomationReading` | ?? |
+| `kVstAutomationWriting` | ?? |
+
+This also means, that the `VstTimeInfo.flags` field really is at position @54-57.
+
+The `kVstAutomation*` flags are unknown, but probably occupy bits 4, 5, 6 or 7
+(the names sound as if they were related to system state, like the `kVstTransport*` flags
+that occupy the lowbyte;
+as opposed to the flags related to timestamp validity, in the highbyte.)
+
+
+## MIDI out
+If a plugin wants to send out MIDI data, it needs to pass the MIDI events back to the host.
+The `audioMasterProcessEvents` opcode looks like a likely candidate for this task.
+
+We had to tell the host that we want to receive MIDI data by responding positively
+to the `effCanDo` requests with `receiveVstEvents` and `receiveVstMidiEvent`.
+Likely, if we want to *send* MIDI data, we ought to respond positively to the
+question whether we can do `sendVstEvents` and `sendVstMidiEvent`.
+
+To find out the value of `audioMasterProcessEvents`, we try to find an opcode
+that can read a valid `VstEvents` struct, but crashes with an invalid struct.
+
+In a first attempt, whenever REAPER calls our plugin with `effProcessEvents`,
+we create a new `VstEvents` struct, filled with a single `VstMidiEvent`.
+We then try a number of opcodes (starting with *3*, because we already know that
+opcodes 0..2 have different purposes; ending arbitrary at 30 for now)
+
+~~~
+ VstEvents*events = (VstEvents*)calloc(1, sizeof(VstEvents)+sizeof(VstEvent*));
+ VstMidiEvent*event=(VstMidiEvent*)calloc(1, sizeof(VstMidiEvent));
+ events->numEvents = 1;
+ events->events[0]=(VstEvent*)event;
+ event->type = kVstMidiType;
+ event->byteSize = sizeof(VstMidiEvent);
+ event->midiData[0] = 0x90;
+ event->midiData[0] = 0x40;
+ event->midiData[0] = 0x7f;
+ event->midiData[0] = 0x0;
+
+ for(size_t opcode=3; opcode<30; opcode++)
+ dispatch(effect, opcode, 0, 0, events, 0.f);
+
+ // free the event/events structures!
+~~~
+
+The first run doesn't show much, except that REAPER doesn't crash with any of the tried opcodes.
+So now we taint the passed structure, by using an invalid address for the first event:
+
+~~~
+ events->events[0]=(VstEvent*)0x1;
+~~~
+
+If the host tries to access the `VstEvent` at the invalid position, it should segfault.
+The address is non-null, as the host might do a simple check about the validity of the pointer.
+
+If we iterate again over the various opcodes, we see that it crashes at `opcode:8`
+with a segmentation fault.
+
+So probably the value of `audioMasterProcessEvents` is *8*.
+
+We can confirm this, by connecting REAPER's MIDI output a MIDI monitor (e.g. `gmidimonitor`),
+to see that it reports (after getting the MIDI routing right):
+
+> Note on, E, octave 4, velocity 127
+
+whenever we send a MIDI message {0x90, 0x41, 0x7f, 0} (via a *valid* `VstEvents` structure)
+to `opcode:8`: Q.E.D!
+
+## effMainsChanged, audioMasterWantMidi and audioMasterGetCurrentProcessLevel
+REAPER calls `effcode:12` twice (with varying `ivalue`) whenever playback gets started:
+
+~~~
+FstClient::dispatcher(0x39166c0, 12, 0, 0, (nil), 0.000000);
+FstClient::dispatcher(0x39166c0, 12, 0, 1, (nil), 0.000000);
+~~~
+
+Looking up [JUCE's effect opcodes](#juce-effect-opcodes), the following opcodes could be called:
+- `effMainsChanged`
+- `effSetBypass`
+- `effSetProcessPrecision`
+
+The `effSetBypass` can probably be ruled out, as the host wouldn't *enable* bypassing just before starting playback.
+
+
+If we use play back this sequence with effcode:12 to our test plugins,
+most of them respond immediately by calling the host callback:
+
+
+| plugin | response to 12 |
+|-----------------------|----------------|
+| Digits | 6 |
+| BowEcho/Danaides | 23+6 |
+| hypercyclic/tonespace | 23+7+6 |
+
+We already know that the `hostcode:7` (as called by hypercyclic/tonespace) is `audioMasterGetTime`.
+
+When looking at how JUCE actually handles the two remaining effect opcodes, we see that `effSetProcessPrecision`
+only sets some internal state, leaving little chance to call back to the host.
+
+That leaves us with `effMainsChanged`: and indeed, JUCE potentially does callbacks to the host
+when `resume()`ing operation, which is done when handling this opcode with `ivalue=1`
+- it calls `audioMasterGetCurrentProcessLevel` without arguments (indirectly, via the `isProcessLevelOffline()` method)
+- if the plugin somehow handles MIDI, it will also issue an `audioMasterWantMidi` call (with `ivalue=1`)
+
+Checking how *BowEcho* actually calls the opcodes 23+6, we can log:
+
+~~~
+FstHost::dispatcher(23, 0, 0, (nil), 0.000000);
+FstHost::dispatcher(7, 0, 1, (nil), 0.000000);
+~~~
+
+And *hypercyclic*:
+
+~~~
+FstHost::dispatcher(23, 0, 0, (nil), 0.000000);
+FstHost::dispatcher(audioMasterGetTime, 0, 65024, (nil), 0.000000);
+FstHost::dispatcher(7, 0, 1, (nil), 0.000000);
+~~~
+
+So the calling conventions kind of match the expectations we have for `audioMasterGetCurrentProcessLevel` and `audioMasterWantMidi`,
+giving us three new opcodes:
+
+| opcode | value |
+|-----------------------------------|-------|
+| effMainsChanged | 12 |
+| audioMasterWantMidi | 6 |
+| audioMasterGetCurrentProcessLevel | 23 |
+
+
+## audioMasterCanDo
+We already know that we can use `effCanDo` to query a plugin whether it supports a
+feature given in a string.
+There's also an `audioMasterCanDo` opcode that is supposed to work in the other direction.
+According to the JUCE sources, typical feature strings are
+`supplyIdle`, `sendVstEvents` and `sendVstMidiEvent`.
+
+To find the value of the `audioMasterCanDo` opcode, we iterate a range of opcodes
+while providing a `ptr` to a valid (and likely supported) feature string (like `sendVstEvents`),
+and record the return values.
+After that, we iterate over the same opcodes with some randomly made up (and therefore
+likely unsupported) feature string (like `fudelDudelDei`).
+Finally we compare the return values of the two iterations and find that they always returned
+the same result *except* for `opcode:37`, our likely candidate for `audioMasterCanDo`.
+
+## Speaker Arrangements
+
+Running our test plugin in REAPER, we can also calls to `effcode:42` in the startup phase.
+
+~~~
+FstClient::dispatcher(0x1ec2080, 42, 0, 32252624, 0x1ec2740, 0.000000);
+~~~
+
+in another run this is:
+~~~
+FstClient::dispatcher(0x9e36510, 42, 0, 172519840, 0xa487610, 0.000000);
+~~~
+
+The `ivalue` is a bit strange, unless it is printed in hex (`0x1ec22d0` resp . `0xa4871a0`),
+where it becomes apparent that this is really another address (just compare the hex representation
+with the `ptr` value; there difference is 1136, which is practically nothing in address space)!
+
+According to [JUCE](#juce-effect-opcodes), there are only very few opcodes
+where both `ivalue` and `ptr` point both to addresses:
+- `effGetSpeakerArrangement`
+- `effSetSpeakerArrangement`
+
+Both opcodes get pointers to `VstSpeakerArrangement`.
+
+Here is a dump of the first 96 bytes of the data found at those addresses
+(the data is the same on both addresses,
+at least if our plugin is configured with 2 IN and 2 OUT channels):
+~~~
+01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+~~~
+
+The int32 at position @4-7 is the `numChannels`,
+the int32 at position @0-3 is most likely the `type`.
+I have no explanation for the value at position @58,
+it's probably just uninitialized data.
+
+By setting the `numChannels` of the plugin, REAPER responds
+with following types
+
+| numChannels | type | type(hex) |
+|--------------|------|------------|
+| 1 | 0 | 0x0 |
+| 2 | 1 | 0x1 |
+| 3 | 11 | 0xb |
+| 4 | 11 | 0xb |
+| 5 | 15 | 0xf |
+| 6 | 15 | 0xf |
+| 7 | 23 | 0x17 |
+| 8 | 23 | 0x17 |
+| 12 | 28 | 0x1C |
+| all the rest | -2 | 0xFFFFFFFE |
+
+Since the values are filled in by REAPER, it's likely that `effcode:42` is `effSetSpeakerArrangement`.
+This is somewhat confirmed by *Protoverb*, that prints out
+
+> resulting in SpeakerArrangement $numIns - $numOuts
+
+with $numIns and $numOuts replaced by 0, 1 or 2, depending on the chosen speaker arrangement.
+It seems that *Protoverb* doesn't support more than 2 channels.
+
+
+`effGetSeakerArrangement` is most likely close by (`41` or `43`),
+A simple test would first set the speaker-arrangement, and then try to query it back.
+According to JUCE, the plugin should return `1` in case of success.
+The calling convention is slightly different from `effSetSpeakerArrangement`, as `ptr` and `ivalue`
+hold addresses of pointer-sized memory regions, where the plugin is supposed to write
+the addresses of the `VstSpeakerArrangement` structs to (cf. JUCE code).
+
+~~~C
+for(size_t opcode=40; opcode<45; opcode++) {
+ VstSpeakerArrangement *arrptr[2] = {0,0};
+ if(42 == opcode)continue;
+ dispatch_v(effect, opcode, 0, (VstIntPtr)(arrptr+0), (arrptr+1), 0.f);
+ print_hex(arrptr[0], 32);
+ print_hex(arrptr[1], 32);
+}
+~~~
+
+Unfortunately, this is not very successful.
+Only `opcode:44` returns 1 for *some* plugins, but none write data into our `VstSpeakerArrangement` struct.
+
+| plugin | opcode | result |
+|-----------|--------|--------|
+|Danaides | 44 | 1 |
+|BowEcho | 44 | 1 |
+|hypercyclic| 44 | 1 |
+|tonespace | 44 | 1 |
+|Protoverb | * | 0 |
+|Digits | * | 0 |
+|InstaLooper| * | 0 |
+
+
+If we try a bigger range of opcodes (e.g. 0..80), we need to skip the opcodes 45, 47 and 48 (`effGet*String`)
+to prevent crashes.
+
+Interestingly, *Protoverb* will now react to `opcode:69`, returning the same data we just set via opcode:42.
+So we probably have just found `effGetSeakerArrangement` as well.
+
+### Speaker Arrangement Types
+
+So what do we know about the `VstSpeakerArrangement.type` field?
+In [VstSpeakerArrangement](#vstspeakerarrangement), we found out that this
+member is really of type `t_fstSpeakerArrangementType`, that has (so far) symbolic names starting with `kSpeakerArr*`.
+JUCE uses the following names matching this pattern (in a regex notation):
+
+| VST name | JUCE name |
+|--------------------------------------|--------------|
+| `kSpeakerArrEmpty` | disabled |
+| `kSpeakerArrMono` | mono |
+| `kSpeakerArrStereo` | stereo |
+| `kSpeakerArrStereoCLfe` | |
+| `kSpeakerArrStereoCenter` | |
+| `kSpeakerArrStereoSide` | |
+| `kSpeakerArrStereoSurround` | |
+| `kSpeakerArrUserDefined` | |
+| `kSpeakerArr[34678][01](Cine,Music)` | |
+| `kSpeakerArr30Cine` | LCR |
+| `kSpeakerArr30Music` | LRS |
+| `kSpeakerArr40Cine` | LCRS |
+| `kSpeakerArr60Cine` | 6.0 |
+| `kSpeakerArr61Cine` | 6.1 |
+| `kSpeakerArr60Music` | 6.0Music |
+| `kSpeakerArr61Music` | 6.1Music |
+| `kSpeakerArr70Music` | 7.0 |
+| `kSpeakerArr70Cine` | 7.0SDDS |
+| `kSpeakerArr71Music` | 7.1 |
+| `kSpeakerArr71Cine` | 7.1SDDS |
+| `kSpeakerArr40Music` | quadraphonic |
+| `kSpeakerArr50` | 5.0 |
+| `kSpeakerArr51` | 5.1 |
+| `kSpeakerArr102` | |
+
+
+Comparing these names to [what REAPER fills in for various channel counts](#speaker-arrangements),
+we can at least make some simple guesses.
+We repeat the table from above:
+
+| numChannels | type |
+|--------------|------|
+| 1 | 0 |
+| 2 | 1 |
+| 3 | 11 |
+| 4 | 11 |
+| 5 | 15 |
+| 6 | 15 |
+| 7 | 23 |
+| 8 | 23 |
+| 12 | 28 |
+| all the rest | -2 |
+
+Mono is a single channel, Stereo needs two channels, for which REAPER fills in `0` and `1` (`kSpeakerArrMono` resp `kSpeakerArrStereo`).
+That's a bit unconventional: personally I would have used `0` for `kSpeakerArrEmpty`, and `1` and `2` for Mono and Stereo.
+
+Unfortunately, REAPER doesn't report anything for 0 channels, so we don't know that value of `kSpeakerArrEmpty`.
+I also don't really understand why we always get pairwise assignments for the next 6 number of channels.
+E.g. assigning `11` to both 3 and 4 channels would make sense
+if there was a speaker-arrangement for 4 channels (e.g. quadrophony aka `kSpeakerArr40Music`),
+but none for 3 channels (so REAPER just assumes some "degraded" arrangement).
+But we do have `kSpeakerArr30Music`, which should serve 3 channels just fine.
+It's probably safe to assume that REAPER does the "degraded" arrangement thing nevertheless.
+It's tricky to get the correct assignment though, since we have so many variants with the same
+number of channels.
+E.g. 6 channels could be `kSpeakerArr51`, `kSpeakerArr60Music` and `kSpeakerArr60Cine`.
+
+There's also the catch-all type *-2*. Given the `kSpeakerArr*` names we know so far, `kSpeakerArrUserDefined` might be this catch-all.
+The value for `kSpeakerArrEmpty` might be "special" as well (and therefore not be assigned some ordered value like, say, *7*) -
+it could well be *-1*, but we don't have any evidence of that yet.
+
+Here's some possible assignments (the names for 3, 5 & 7 channels are in parentheses, as the type has the same value as arrangement with one more channel):
+
+| numChannels | type (value) | type (name) |
+|--------------|--------------|----------------------------------------------------------------------------------------|
+| 1 | 0 | `kSpeakerArrMono` |
+| 2 | 1 | `kSpeakerArrStereo` |
+| 3 | 11 | (`kSpeakerArr30Music`, `kSpeakerArr30Cine`) |
+| 4 | 11 | `kSpeakerArr31Music`, `kSpeakerArr31Cine`, `kSpeakerArr40Music`, `kSpeakerArr40Cine` |
+| 5 | 15 | (`kSpeakerArr41Music`, `kSpeakerArr41Cine`, `kSpeakerArr50Music`) |
+| 6 | 15 | `kSpeakerArr51`, `kSpeakerArr60Music`, `kSpeakerArr60Cine` |
+| 7 | 23 | (`kSpeakerArr61Music`, `kSpeakerArr61Cine`, `kSpeakerArr70Music`, `kSpeakerArr70Cine`) |
+| 8 | 23 | `kSpeakerArr71Music`, `kSpeakerArr71Cine`, `kSpeakerArr80Music`, `kSpeakerArr80Cine` |
+| 12 | 28 | `kSpeakerArr102` |
+| all the rest | -2 | `kSpeakerArrUserDefined` |
+
+So we can at least be pretty confident of the values of `kSpeakerArrMono`, `kSpeakerArrStereo`,
+`kSpeakerArr102` & `kSpeakerArrUserDefined`.
+
+
+### Amendment: wantsChannelCountNotifications
+
+REAPER doesn't *always* call the `effSetSpeakerArrangement`.
+It seems the plugin must return `1` for the `effCanDo` opcode with a value of `wantsChannelCountNotifications`
+in order to receive this opcode (which kind of makes sense).
+
+
+# Part: AudioPluginHost
+With many opcodes working, we can start testing on a larger scale.
+
+A good start is by compiling some slightly larger application ourself, e.g. the *AudioPluginHost* that comes with JUCE.
+
+Once started we can load the *Protoverb* plugin, to see that a number of yet unknown opcodes are called in both directions:
+
+~~~ C
+host2plugin(effKeysRequired, 0, 0, (nil), 0.000000);
+host2plugin(effCanBeAutomated, 0, 0, (nil), 0.000000);
+host2plugin(effGetPlugCategory, 0, 0, (nil), 0.000000);
+host2plugin(effStartProcess, 0, 0, (nil), 0.000000);
+host2plugin(effStopProcess, 0, 0, (nil), 0.000000);
+host2plugin(effConnectInput, 0, 1, (nil), 0.000000);
+host2plugin(effConnectOutput, 0, 1, (nil), 0.000000);
+[...]
+plugin2host(13, 0, 0, (nil), 0.000000);
+plugin2host(15, 640, 575, (nil), 0.000000);
+plugin2host(42, 0, 0, (nil), 0.000000);
+plugin2host(43, 3, 0, (nil), 0.000000)
+plugin2host(44, 3, 0, (nil), 0.000000)
+~~~
+
+## audioMasterSizeWindow, audioMasterBeginEdit, audioMasterEndEdit
+
+A low hanging fruit is the `hostCode:15` which is called with `index:640` and `ivalue:575`.
+Esp. the `index` looks suspiciously like some image/window dimension, which leads us to
+the `audioMasterSizeWindow` opcode.
+
+Another one is `hostCode:43` (and it's twin `hostCode:44`), which is called whenever a controller
+is changed in the plugin's GUI: `hostCode:43` is issued when a fader/... is "touched", and
+`hostCode:44` is called when it's released. The `index` parameter changes with the controller
+(so it's likely the *parameter index*).
+In the [JUCE host opcode table](#juce-host-opcodes), there's the `audioMasterBeginEdit` and `audioMasterEndEdit`
+pair that fits nicely.
+
+| opcode | |
+|-----------------------|----|
+| audioMasterSizeWindow | 15 |
+| audioMasterBeginEdit | 43 |
+| audioMasterEndEdit | 44 |
+
+## effStartProcess/effStopProcess
+*AudioPluginHost* calls `effStartProcess` right after the plugin has been initialised
+(and before it starts calling the `process` callbacks).
+When shutting down *AudioPluginHost* the `effStopProcess` opcode is called
+(after the last call to the `process` callback).
+We can probably safely assume, that the opcodes will be close to each other (adjacent numbers).
+
+Comparing this to what *REAPER* does when calling our fake plugin,
+we notice that there are the opcodes `71` and `72` that are used in a similar way.
+
+Most likely these two numbers are indeed the `eff*Process` opcodes:
+
+| opcode | |
+|-----------------|----|
+| effStartProcess | 71 |
+| effStopProcess | 72 |
+
+
+# Part: more enums
+
+## effVendorSpecific
+
+that one is weird.
+If we compile a plugin based on JUCE and load it into REAPER,
+we notice tat REAPER sends `opcode:50` (with `index:45` and `ivalue:80`)
+once the FX window (with the effect) is opened for a track.
+
+ plugin::dispatcher(50, 45, 80, 0x7ffe849f12f0, 0.000000)
+
+In the dump of `ptr` we see two addresses at @58 and @60,
+but it seems there are no doubles, int32 or strings in the data.
+The first 8 bytes are 0, so *maybe* the plugin is supposed to write some
+data (pointer) into this.
+
+What's more, REAPER does *not* send this opcode,
+when we create a plain (none-JUCE based) plugin (even if it has the `effFlagsHasEditor` set).
+So the JUCE plugins must do something to provoke REAPER to send this opcode:
+
+If we hack our JUCE-based plugin to print out each opcode sent from REAPER
+before it starts sending `opcode:50` (because obviously REAPER has to know that
+it can send this opcode before it starts doing so), we get the following
+(on the right side we see what the plugin returns for each command):
+
+~~~ C
+// initialisation
+host2plugin(effOpen, 0, 0, NULL, 0.000000) => 0
+host2plugin(effSetSampleRate, 0, 0, NULL, 44100.000000) => 0
+host2plugin(effSetBlockSize, 0, 512, NULL, 0.000000) => 0
+host2plugin(effGetEffectName, 0, 0, <out>, 0.000000) => 1
+host2plugin(effGetVendorString, 0, 0, <out>, 0.000000) => 1
+host2plugin(effCanDo, 0, 0, "hasCockosNoScrollUI", 0.000000) => 0
+host2plugin(effCanDo, 0, 0, "wantsChannelCountNotifications", 0.000000) => 0
+host2plugin(effCanDo, 0, 0, "hasCockosExtensions", 0.000000) => 0xBEEF0000
+host2plugin(effGetVstVersion, 0, 0, NULL, 0.000000) => 2400
+host2plugin(effMainsChanged, 0, 1, NULL, 0.000000) => 0
+host2plugin(effStartProcess, 0, 0, NULL, 0.000000) => 0
+host2plugin(effCanDo, 0, 0, "receiveVstEvents", 0.000000) => 0xFFFFFFFF
+host2plugin(effCanDo, 0, 0, "receiveVstMidiEvent", 0.000000) => 0xFFFFFFFF
+host2plugin(35, 0, 0, NULL, 0.000000) => 0
+host2plugin(effCanDo, 0, 0, "sendVstEvents", 0.000000) => 0xFFFFFFFF
+host2plugin(effCanDo, 0, 0, "sendMidiVstEvents", 0.000000) => 0xFFFFFFFF
+host2plugin(35, 0, 0, NULL, 0.000000) => 0
+host2plugin(effGetProgram, 0, 0, NULL, 0.000000) => 0
+host2plugin(effGetChunk, 0, 0, <out>, 0.000000) => 0
+host2plugin(effGetProgramName, 0, 0, <out>, 0.000000) => 0
+host2plugin(effCanDo, 0, 0, "cockosLoadingConfigAsParameters", 0.000000) => 0
+host2plugin(effGetProgram, 0, 0, NULL, 0.000000) => 0
+host2plugin(effGetChunk, 0, 0, <out>, 0.000000) => 0
+host2plugin(effGetProgramName, 0, 0, <out>, 0.000000) => 0
+// initialisation done; if we now open the FX window we get:
+host2plugin(50, 45, 80, <ptr>, 0.000000)
+/* [...] */
+~~~
+
+So we create a fake plugin (*not* JUCE-based!) that responds to most opcodes with `0`
+and doesn't really do anything.
+In order to make REAPER do the same as with JUCE-plugins (that is: send an `opcode:50` command),
+we must mimic the behaviour of the JUCE plugin (making REAPER think it is really the same).
+
+We do this incrementally:
+~~~C
+VstIntPtr dispatcher(AEffect*eff, t_fstInt32 opcode, int index, VstIntPtr ivalue, void* const ptr, float fvalue) {
+ switch(opcode) {
+ case effGetVstVersion:
+ return 2400;
+ case effGetVendorString:
+ snprintf((char*)ptr, 16, "SuperVendor");
+ return 1;
+ case effGetEffectName:
+ snprintf((char*)ptr, 16, "SuperEffect");
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+~~~
+
+There's no real effect here (which is expected; REAPER would be really weird if it changed behaviour based on effect *names*).
+
+So let's add some more opcodes:
+
+~~~C
+ case effCanDo:
+ if(!strcmp((char*)ptr, "receiveVstEvents"))
+ return 0xFFFFFFFF;
+ if(!strcmp((char*)ptr, "receiveVstMidiEvents"))
+ return 0xFFFFFFFF;
+ if(!strcmp((char*)ptr, "sendVstEvents"))
+ return 0xFFFFFFFF;
+ if(!strcmp((char*)ptr, "sendVstMidiEvents"))
+ return 0xFFFFFFFF;
+ if(!strcmp((char*)ptr, "hasCockosExtensions"))
+ return 0xBEEF0000;
+ return 0;
+~~~
+
+Oha! Suddenly REAPER sends plenty of `opcode:50` commands:
+
+~~~
+host2plugin(50, 0xDEADBEF0, 0, 0x7ffe7aa23c30, 0.000000);
+host2plugin(50, 45, 80, 0x7ffe7aa23a50, 0.000000);
+host2plugin(50, 0xDEADBEF0, 0, 0x7ffe7aa23d10, 0.000000);
+host2plugin(50, 45, 80, 0x7ffe7aa23b30, 0.000000);
+host2plugin(50, 7, 0, 0x7ffe7aa33f60, 0.500000);
+host2plugin(50, 0xDEADBEF0, 0, 0x7f3c85f32150, 0.000000);
+~~~
+
+Disabling the `effCanDo` features selectively, it becomes apparent that REAPER
+will start sending this opcode only if we reply to `hasCockosExtensions` with `0xBEEF0000`.
+
+Now "Cockos" is the name of the REAPER *vendor*, and `opcode:50` is only sent if
+we claim to support the *Cockos* extensions.
+*Maybe* this is a *vendor specific* opcode, e.g. `effVendorSpecific`?.
+This idea is supported by the neighbouring opcodes: e.g. `opcode:49` is `effGetVendorVersion`.
+
+Also a quick internet search for `effVendorSpecific+hasCockosExtensions` directs us to
+the [Cockos Extensions to VST SDK](https://www.reaper.fm/sdk/vst/vst_ext.php).
+One of the extensions documented there is
+
+~~~
+dispatcher(effect,effVendorSpecific,effGetEffectName,0x50,&ptr,0.0f);
+~~~
+
+We already know that `effGetEffectName` is *45*, and `0x50` is *80*,
+and *that* is exactly how REAPER is using `opcode:50`
+
+
+## effSetProcessPrecision
+
+When running a plugin in a self-compiled JUCE `AudioPluginHost`,
+almost all effCodes our host would like to call in a plugin, have a proper value.
+
+The exceptions are:
+
+~~~
+effCanBeAutomated
+effConnectInput
+effConnectOutput
+effGetPlugCategory
+effSetProcessPrecision
+~~~
+
+Let's tackle those.
+
+According to *juce_VSTPluginFormat.cpp*, the `effSetProcessPrecision` is called,
+if the plugin supports double-precision. It is then called with `kVstProcessPrecision64`
+resp `kVstProcessPrecision32` (depending on whether the host wants to do single or double
+precision.)
+
+Maybe REAPER does something similar, so let's check.
+One of the few remaining opcodes that REAPER calls in our simple plugin
+and that have non-0 arguments (namely `ivalue:1`), is `effcode:77`:
+
+~~~
+dispatcher(effect, 77, 0, 1, (nil), 0.000000);
+~~~
+
+If we compile our plugin without double-precision support (`eff->flags &= ~effFlagsCanDoubleReplacing`),
+`opcode:77` does not get called!
+So it seems that indeed `effSetProcessPrecision` is `77`.
+
+Now the `ivalue` is supposed to be `kVstProcessPrecision64` or `kVstProcessPrecision32`.
+REAPER uses double precision (we know that because the `processDoubleReplacing` callback is called with audio data),
+and uses `ivalue:1`, so that is most likely the value of `kVstProcessPrecision64`.
+
+The value of `kVstProcessPrecision32` can only be guessed. Intuitively, I would suggest `0`
+(so the opcode could be used as a boolean-like on/off switch for double precision rendering)
+
+| opcode | value |
+|------------------------|-------|
+| effSetProcessPrecision | 77 |
+|------------------------|-------|
+| kVstProcessPrecision32 | 0 (?) |
+| kVstProcessPrecision64 | 1 |
+
+## effGetPlugCategory
+
+The `effGetPlugCategory` is closely related to the following constants,
+which a call to this opcode is supposed to return:
+
+~~~
+kPlugCategEffect
+kPlugCategSynth
+kPlugCategAnalysis
+kPlugCategMastering
+kPlugCategSpacializer
+kPlugCategRoomFx
+kPlugSurroundFx
+kPlugCategRestoration
+kPlugCategGenerator
+
+kPlugCategShell
+~~~
+
+REAPER offers a categorization of plugins that includes categories like
+`Delay`, `Surround`, `Synth`, `Tone Generator`...
+While the categories are not the same, there is a significant overlap (and of course the title "Categories")
+which makes me think that the two are related.
+However, so far none of our plugins (neither our self-created plugins, nor the commercially available ones we use for testing)
+is categorised at all.
+
+Anyhow: once we have the `effGetPlugCategory` it should be pretty easy to find out the `kPlugCateg` values by
+iterating over values `0..n` and see how REAPER categorizes the plugin.
+But how do we get the opcode?
+
+Somebody in the [Cockos forum](https://forum.cockos.com/archive/index.php/t-165245.html) (accessed 2020-01-20)
+mentioned, that REAPER would call this opcode when *scanning* for plugins, pretty soon after
+issuing `effOpen`:
+
+~~~
+effOpen
+// ...looks at various elements of AEffect
+effGetPlugCategory
+effGetVendorString
+[...]
+~~~
+
+That was a nice hint, because issuing a `Clear cache/re-scan` in REAPER's Preferences gives us:
+
+~~~
+dispatch(effOpen, 0, 0, (nil), 0.000000);
+dispatch(35, 0, 0, (nil), 0.000000);
+dispatch(effGetVendorString, 0, 0, 0xF00, 0.000000);
+dispatch(effGetEffectName, 0, 0, 0xBA8, 0.000000);
+dispatch(effClose, 0, 0, (nil), 0.000000);
+~~~
+
+... suggesting that `effGetPlugCategory` really is `35`.
+
+So I tried returning a non-0 value on this opcode (starting with `1`), but the plugin is still not categorised :-(.
+But let's try other values:
+- returning `2` suddenly categorises the plugin as *Synth*. This is promising
+(if it weren't for the `effFlagsIsSynth` flag that might allow the same)!
+- continuing to return different values, we see that `3` would make REAPER show the plugin in the (newly created) *Analysis* category.
+Now it seems like we are indeed on the right track.
+
+Playing around we get the following REAPER categories for various return values:
+
+
+| return | category |
+|--------|----------------|
+| 2 | Synth |
+| 3 | Analysis |
+| 4 | Mastering |
+| 5 | Spatial |
+| 6 | Room |
+| 7 | Surround |
+| 8 | Restoration |
+| 11 | Tone Generator |
+
+The return values `1` and `9` (and `0`) leave the plugin in the previous category (whichever that was),
+so it seems these return values are ignored by REAPER and the host just uses some cached values.
+If the plugin returns `10`, it is *not listed* at all in the list of available plugins.
+
+The REAPER categories match nicely with the `kPlugCateg*` constants we need to find:
+
+| name | value |
+|-------------------------|-------|
+| `kPlugCategSynth` | 2 |
+| `kPlugCategAnalysis` | 3 |
+| `kPlugCategMastering` | 4 |
+| `kPlugCategSpacializer` | 5 |
+| `kPlugCategRoomFx` | 6 |
+| `kPlugSurroundFx` | 7 |
+| `kPlugCategRestoration` | 8 |
+| `kPlugCategGenerator` | 11 |
+
+
+We haven't found any values for `kPlugCategEffect` and `kPlugCategShell` yet.
+
+Looking at *juce_VSTPluginFormat.cpp* there are two notable things:
+- `kPlugCategEffect` is the first in a `switch/case` group, that is pretty much ordered in the very same way as the
+`kPlugCateg*` values we already know (`kPlugCategShell` is missing from that `switch/case` group)
+- if the category is `kPlugCategShell`, JUCE will call the `effShellGetNextPlugin` opcode repeatedly
+ to get *shellEffectName*s for the "shell plugin's subtypes".
+
+The first observation *might* hint that the value for `kPlugCategEffect` is `1`.
+
+The second observation can be combined with another obersvation: when a plugin returns `10` for `opcode:35`,
+REAPER will later issue `opcode:70` instead of `effGetEffectName`.
+So `kPlugCategShell` would be `10`, and `effShellGetNextPlugin` would be `70`.
+
+We can confirm this assumption by returning the proper values for the opcodes `35` and `70`:
+
+~~~C
+ switch(opcode) {
+ case 35:
+ return 10;
+ case 70: {
+ static int count = 0;
+ count++;
+ if(count < 5) {
+ snprintf(ptr, 128, "shell%d", count);
+ return count;
+ }
+ }
+ return 0;
+~~~
+
+... which will give use 4 plugins `shell1`..`shell4` in REAPER.
+
+So to conclude, we have the following new values:
+
+| name | value |
+|-------------------------|-------|
+| `effGetPlugCategory` | 35 |
+| `effShellGetNextPlugin` | 70 |
+|-------------------------|-------|
+| `kPlugCategEffect` | 1? |
+| `kPlugCategSynth` | 2 |
+| `kPlugCategAnalysis` | 3 |
+| `kPlugCategMastering` | 4 |
+| `kPlugCategSpacializer` | 5 |
+| `kPlugCategRoomFx` | 6 |
+| `kPlugSurroundFx` | 7 |
+| `kPlugCategRestoration` | 8 |
+| ?? | 9 |
+| `kPlugCategShell` | 10 |
+| `kPlugCategGenerator` | 11 |
+
+
+Scanning through the JUCE sources for strings like `kPlugCategEffect`, we find a list of
+strings in the *Projucer* sources (`jucer_Project.cpp`):
+
+- `kPlugCategUnknown` (!)
+- `kPlugCategEffect`
+- `kPlugCategSynth`
+- `kPlugCategAnalysis`
+- `kPlugCategMastering`
+- `kPlugCategSpacializer`
+- `kPlugCategRoomFx`
+- `kPlugSurroundFx`
+- `kPlugCategRestoration`
+- `kPlugCategOfflineProcess` (!)
+- `kPlugCategShell`
+- `kPlugCategGenerator`
+
+So there's two more categories ("Unknown" and "OfflineProcess").
+
+If we compare the list (in the same order as found in the Projucer sources) with the values we already found above,
+we see that they align nicely.
+`kPlugCategOfflineProcess` just falls between `kPlugCategRestoration` and `kPlugCategShell`,
+where we haven't found a name for the value *9* yet.
+Sorting `kPlugCategUnknown` before `kPlugCategEffect` (aka *1*) would give us *0*, which also makes some sense:
+
+| name | value |
+|----------------------------|-------|
+| `kPlugCategUnknown` | 0 |
+| `kPlugCategOfflineProcess` | 9 |
+
+
+## effGetCurrentMidiProgram / effSetTotalSampleToProcess
+It's starting to get harder harvesting new opcodes.
+
+We can do another attempt at sending yet-unknown opcodes to our
+test plugins and note those were the plugins return non-0 values.
+
+Something like:
+
+~~~
+static int effKnown(VstIntPtr opcode) {
+ if(opcode>=100000)
+ return 0;
+ switch(opcode) {
+ case effCanBeAutomated:
+ case effCanDo:
+ case effClose:
+ case effConnectInput:
+ case effConnectOutput:
+ /* continue for all known opcodes */
+ // ...
+ case effVendorSpecific:
+ return 1;
+ default break;
+ }
+ return 0;
+
+void test(AEffect*effect) {
+ int index = 0;
+ VstIntPtr ivalue = 0;
+ dispatch(effect, effOpen, 0, 0, 0, 0.0);
+ for(size_t opcode=2; opcode<128; opcode++)
+ if(!effKnown(opcode))
+ dispatch(effect, opcode, index, ivalue, 0, 0.f);
+ dispatch(effect, effClose, 0, 0, 0, 0.0);
+}
+~~~
+
+This gives us the following opcodes:
+
+| value | return | plugins |
+|-------|--------|--------------------|
+| 26 | 1 | ALL plugins |
+| 44 | 1 | JUCE-based plugins |
+| 63 | -1 | ALL plugins |
+| 78 | 1 | Digits |
+
+If we set the `index` to something non-0 and compare the returned values, we don't notice any differences.
+If we set the `ivalue` to something non-0 and compare the returned values, we get another opcode,
+that seemingly returns the `ivalue` itself:
+
+| value | return | plugins |
+|-------|----------|-------------|
+| 73 | <ivalue> | ALL plugins |
+
+The [JUCE effect opcode table](#juce-effect-opcodes) lists only
+two opcodes that return `-1` (and `effCanDo` is already known, leaving `effGetCurrentMidiProgram`).
+It also has a single opcode that returns the `ivalue` as is, which is `effSetTotalSampleToProcess`, so we know have:
+
+| opcode | value |
+|------------------------------|-------|
+| `effGetCurrentMidiProgram` | 63 |
+| `effSetTotalSampleToProcess` | 73 |
+
+
+# effString2Parameter
+The `effString2Parameter` is most likely used for setting a parameter via a string representation.
+A simple routine to find the opcode value is to send a string with some numeric value to all unknown opcodes,
+and read back the parameter (e.g. as a string representation via `effGetParamDisplay`).
+If the read back parameter has changed (not necessarily to the value we sent it, but in relation to previous printouts),
+we probably have found the `effString2Parameter` opcode:
+
+~~~C
+ dispatch_v(effect, effOpen, 0, 0, 000, 0.000000);
+ for(VstIntPtr opcode=2; opcode<128; opcode++) {
+ int index = 0;
+ if(effKnown(opcode))
+ continue;
+ char buffer[1024];
+ char outbuffer[1024];
+ outbuffer[0] = buffer[0] = 0;
+ snprintf(buffer, 1024, "0.666");
+ dispatch_v(effect, opcode, index, 0, buffer, 0.);
+ effect->dispatcher(effect, effGetParamDisplay, index, 0, outbuffer, 0.);
+ if(*outbuffer)printf("param:%02d: %s\n", index, outbuffer);
+ }
+ dispatch_v(effect, effClose, 0, 0, 000, 0.000000);
+~~~
+
+Unfortunately for none of our test plugins
+(*BowEcho*, *Danaides*, *Digits*, *InstaLooper*, *Protoverb*, *hypercyclic*, *tonespace*)
+the read back value changes for any of the tested opcodes.
+
+Luckily, a friend of mine pointed me to another set of free-as-in-beer plugins, the [GVST](https://www.gvst.co.uk/index.htm) set.
+
+## kVstMaxVendorStrLen
+Loading the above plugin in a fake host, we get immediate segfaults when trying to write vendor string,
+using our estimate of `kVstMaxVendorStrLen` (197782).
+Gradually lowering the maximum length of the vendor string, the first value where it doesn't crash is `130`.
+This is a much more reasonable length than *197782*, although `128` would be even more plausible.
+Lets used that last value for `kVstMaxVendorStrLen` (and `kVstMaxProductStrLen` as well).
+
+## effString2Parameter
+If we now try run the above snippet to test for `effString2Parameter`, the returned parameter value changes with
+`opcode:27`:
+
+~~~
+dispatch(21, 0, 0, "0.666", 0.000000) => 0; param[0] = "10"
+dispatch(26, 0, 0, "0.666", 0.000000) => 1; param[0] = "10"
+dispatch(27, 0, 0, "0.666", 0.000000) => 1; param[0] = "1"
+dispatch(28, 0, 0, "0.666", 0.000000) => 0; param[0] = "1"
+~~~
+
+We can also test with some bigger value in the string:
+~~~
+dispatch(21, 0, 0, "39", 0.000000) => 0; param[0] = "10"
+dispatch(26, 0, 0, "39", 0.000000) => 1; param[0] = "10"
+dispatch(27, 0, 0, "39", 0.000000) => 1; param[0] = "39"
+dispatch(28, 0, 0, "39", 0.000000) => 0; param[0] = "39"
+~~~
+
+So we have learned that `effString2Parameter` is `27`.
+(`opcode:26` returns `1`, like any other plugin we have seen so far.)
+
+## effCanBeAutomated
+
+We now have another set of plugins to test (the `GVST` set).
+In order to make testing a bit easier, I wrote a little "proxy plugin" (see `src/FstProxy/`)
+that takes an ordinary plugin file via the `FST_PROXYPLUGIN` environment variable.
+When instantiating the proxy plugin, it will return an instance of the plugin
+found in the `FST_PROXYPLUGIN` file, but will re-write the `AEffect.dispatcher` function
+and the host-dispatcher (as passed to `VstPlugMain`), so we can add some additional
+printout, to see how a real-world plugin communicates with a real-world host.
+
+Something like the following:
+
+~~~
+static
+VstIntPtr host2plugin (AEffect* effect, int opcode, int index, VstIntPtr ivalue, void*ptr, float fvalue) {
+ AEffectDispatcherProc h2p = s_host2plugin[effect];
+ char effbuf[256] = {0};
+ printf("Fst::host2plugin(%s, %d, %ld, %p, %f)",
+ effCode2string(opcode, effbuf, 255),
+ index, ivalue, ptr, fvalue);
+ VstIntPtr result = h2p(effect, opcode, index, ivalue, ptr, fvalue);
+ printf(" => %ld\n", result);
+ fflush(stdout);
+ return result;
+}
+~~~
+
+Unfortunately this doesn't reveal *very* much.
+I discovered the following unknown effect opcodes:
+
+~~~
+Fst::host2plugin(19, 0, 0, (nil), 0.000000) => 0
+Fst::host2plugin(26, 0, 0, (nil), 0.000000) => 1
+Fst::host2plugin(26, 1, 0, (nil), 0.000000) => 1
+Fst::host2plugin(53, 0, 0, (nil), 0.000000) => 0
+Fst::host2plugin(56, 2, 0, 0xPTR, 0.000000) => 0
+Fst::host2plugin(62, 0, 0, 0xPTR, 0.000000) => 0
+Fst::host2plugin(66, 0, 0, 0xPTR, 0.000000) => 0
+~~~
+
+And the following unknown audioMaster opcodes:
+
+~~~
+Fst::plugin2host(13, 0, 0, (nil), 0.000000) -> 1
+Fst::plugin2host(42, 0, 0, (nil), 0.000000) -> 1
+~~~
+
+On closer inspection, we see `effCode:26` is called with an index up to `AEffect.numParams`.
+REAPER only calls this opcode, before opening a dialog window where we can select an parameter to
+be automated.
+
+Looking up the [JUCE table](#juce-effect-opcodes), there's only one effect opcode (we know of)
+that takes an index (and which we don't know yet): `effCanBeAutomated`.
+Since the opcode is called when REAPER does something with automation, it is even more likely that this is the correct name.
+
+However, in our minimal fake plugin, this opcode doesn't really get called.
+Probably we don't reply correctly to some prior opcode.
+
+Anyhow, we can hack our proxy plugin, to respond to `effCode:26` with `1` only for indices less than (e.g.) `5`.
+Doing that with a plugin that has more parameters (e.g. *GVST/GSinth* has 30 parameters),
+REAPER will reduce the selection choice for parameters to be automated to the first five.
+
+So indeed, `effCanBeAutomated` seems to have the value `26`.
+
+So why does REAPER query this opcode for the *GSinth* plugin, but not for our own fake plugin?
+Comparing the return values of the *GSinth* plugin with our own, there are not many differences.
+However *GSinth* returns `2400` for the `effGetVstVersion` opcode, whereas our own plugin
+returns the default `0`.
+If we make it return `2400` as well, REAPER starts asking with `effCanBeAutomated`.
+So it seems that this opcode was only introduced later, and requires the plugin to support a minimum version of the API.
+
+
+# Speaker Arrangement revisited
+We still haven't found out the details of the speaker arrangement structs.
+
+So far, we know the `effGetSpeakerArrangement` and `effSetSpeakerArrangement` opcodes, which return (resp. take)
+pointers to the `VstSpeakerArrangement`, which in turn includes an array of type `VstSpeakerProperties`.
+
+We currently only know of a single member (`type`) of the `VstSpeakerProperties` struct, which is certainly wrong
+(a struct only makes sense if there are more members. creating an array of this struct requires it to have a
+fixed size, so the struct cannot be extended "later".)
+
+We already know the positions of `VstSpeakerArrangement.type` resp. `.numChannels`, but don't really know whether
+there are more members in this struct (so we don't know the exact position of the `.speakers` member).
+However, we do now that the `.speakers` member contains `.numChannels` instances of `VstSpeakerProperties`.
+
+~~~C
+typedef struct VstSpeakerProperties_ {
+ /* misses members; order undefined */
+ int type;
+} VstSpeakerProperties;
+
+typedef struct VstSpeakerArrangement_ {
+ int type;
+ int numChannels;
+ /* missing members? */
+ VstSpeakerProperties speakers[];
+} VstSpeakerArrangement;
+~~~
+
+When we [first discovered](#speaker-arrangement) some details of the Speaker Arrangement,
+we noticed non-null values a position @58, which we concluded might be uninitialized data.
+
+We can easily test this assumption: since `VstSpeakerProperties` is at least 4 bytes large
+(assuming that it's `type` member is a 32bit `int` like all other types we have seen so far),
+and REAPER can handle up to 64 channels, we can force the full size of `speakers[]` to 256 bytes
+(64 * 4), which is way beyond the 88 bytes of position @58.
+
+Printing the first 512 bytes a 64channel plugin receives with the `effSetSpeakerArrangement` opcode, gives:
+
+~~~
+0000 FE FF FF FF 40 00 00 00 00 00 00 00 00 00 00 00
+0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0050 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00c0 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
+00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0130 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+0140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01a0 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
+01b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+~~~
+
+This is interesting as we still have a value (`01`) at position @58 -
+which according to the math we did above cannot be "uninitialized memory".
+
+If we set the number of channels our plugin can process to *2*, we get the following instead:
+
+~~~
+0000 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00
+0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0050 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00
+0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00c0 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00
+00d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+0190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+01f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+~~~
+
+Apart from the differing first 8 bytes (that's the `.type` and `.numChannels`
+members, which we already established to be differing), we can *also* see some
+pattern:
+
+The 2-channel plugin gets values `01` and `02` at the positions @58 resp @c8 and after that only zeros,
+whereas the 64-channel version has alternating `01` and `02` values until the printout ends.
+Could it be that we are actually seeing `VstSpeakerProperties` that are not occupying only 4 bytes but rather
+112 bytes in length (112 being the difference between @c8 and @58)?
+
+So printing 8192 bytes (which should cover 64 channels if each really takes 112 bytes), we observe:
+
+| channels | 112-bytes pattern | garbage |
+|----------|-----------------------------------------------------------|-------------|
+| 2 | `01`@58, `02`@c8 | after @468 |
+| 64 | alternating `01`/`02` from @58 to @1be8, every 0x70 bytes | after @1f88 |
+| 3 | `01`@58, `02`@c8, `03`@138 | after @4da |
+
+After a certain point the data is densely filled with non-null bytes, which probably really is "uninitialized memory" (aka "garbage").
+
+The important part to notice is that the position difference between the first lonely byte @58 and the last one (@c8, @138, @1be8)
+is always `112 * (numChannels-1)`.
+So we can conclude, that the `VstSpeakerProperties` has a size of 112 bytes (of which we know that 4 bytes contain the `type` member).
+If we assume that the `VstSpeakerArrangment.speakers` array starts immediately after the first 8 bytes,
+the `VstSpeakerProperties.type` cannot be at the beginning or the end of the `VstSpeakerProperties`,
+but is located at some 80 bytes offset.
+There's no real evidence for *any* position of the `type` field.
+Since the surrounding bytes don't carry information (at least when filled in by REAPER), we just pick one randomly.
+There's also no real evidence whether the stray non-null bytes actually *are* the `type` member,
+but since this is the only member of which we know and the only bytes that carry some information, the guess is probably correct.
+
+
+~~~C
+typedef struct VstSpeakerProperties_ {
+ /* misses members; order undefined */
+ char _padding1[80];
+ int type;
+ char _padding2[28];
+} VstSpeakerProperties;
+
+typedef struct VstSpeakerArrangement_ {
+ int type;
+ int numChannels;
+ /* missing members? */
+ VstSpeakerProperties speakers[];
+} VstSpeakerArrangement;
+~~~
+
+Creating 65 plugins with varying channel count (from 0 to 64) and printing the `VstSpeakerProperties.type` for each channel,
+we get:
+
+| `VstSpeakerArrangement.numChannels` | `VstSpeakerArrangement.type` | `VstSpeakerProperties.type` |
+|-------------------------------------|------------------------------|-----------------------------|
+| 00 | -1 | - |
+| 01 | 0 | 0 |
+| 02 | 1 | 1,2 |
+| 03 | 11 | 1,2,3 |
+| 04 | 11 | 1,2,1,2 |
+| 05 | 15 | 1,2,1,2,3 |
+| 06 | 15 | 1,2,1,2,1,2 |
+| 07 | 23 | 1,2,1,2,1,2,3 |
+| 08 | 23 | 1,2,1,2,1,2,1,2 |
+| 09 | -2 | 1,2,1,2,1,2,1,2,3 |
+| 10 | -2 | 1,2,1,2,1,2,1,2,1,2 |
+| 11 | -2 | 1,2,1,2,1,2,1,2,1,2,3 |
+| 12 | 28 | 1,2,1,2,1,2,1,2,1,2,1,2 |
+| odd | -2 | 1,2,...,1,2,3 |
+| even | -2 | 1,2,...,1,2,1,2 |
+
+The 108 unknown bytes in each `VstSpeakerProperties` struct are always `0x00`.
+So the `VstSpeakerProperties.type` is always pairs of `1` and `2`,
+and if the number of speakers is odd, the leftover (last) speaker has a type `3`.
+
+JUCE (juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp) assigns values like
+`kSpeakerL`, `kSpeakerR` or `kSpeakerLfe` to the `VstSpeakerProperties.type` member.
+Obviously, REAPER doesn't make full use of these values (at least not in my configuration).
+The values `1` and `2` are probably `kSpeakerL` resp. `kSpeakerR`.
+The value `3` could be `kSpeakerC`, `kSpeakerLfe` or `kSpeakerS`, but it's hard to say.
+The value `0` (only seen with the mono setup, tentatively `kSpeakerArrMono`) would be some mono channel,
+either `kSpeakerC`, `kSpeakerUndefined` or - following the naming pattern of the Left/Right channels and
+[confirmed to exist by some random googling](http://convolver.sourceforge.net/vst.html) -
+`kSpeakerM`.
+
+As a sidenote, we also see now that a plugin without channels has a `VstSpeakerArrangement.type` of *-1*,
+which is most likely `kSpeakerArrEmpty` (something we already guessed above, but had no backing yet).
+
+| name | value |
+|--------------------|-------|
+| `kSpeakerArrEmpty` | -1 |
+| `kSpeakerL` | 1 |
+| `kSpeakerR` | 2 |
+
+## enter MrsWatson
+After trying to [compile MrsWatson](#compiling-mrswatson), it becomes a bit clearer, why there's quite an offset
+at the beginning of `VstSpeakerProperties`, as there are some additional members.
+
+*MrsWatson* assigns to the properties like so:
+
+~~~C
+speakerArrangement->speakers[i].azimuth = 0.0f;
+speakerArrangement->speakers[i].elevation = 0.0f;
+speakerArrangement->speakers[i].radius = 0.0f;
+speakerArrangement->speakers[i].reserved = 0.0f;
+speakerArrangement->speakers[i].name[0] = '\0';
+speakerArrangement->speakers[i].type = kSpeakerUndefined;
+~~~
+
+Assuming that the order of members follows the struct definition,
+and that the first four values are indeed single-precision (since the only
+reason why *MrsWatson* would use the `f` specifier is to avoid compiler warnings),
+we get something like:
+
+~~~C
+typedef struct VstSpeakerProperties_ {
+ float azimuth;
+ float elevation;
+ float radius;
+ float reserved;
+ char name[64];
+ int type;
+ char _padding2[28];
+} VstSpeakerProperties;
+~~~
+
+
+# audioMasterGetCurrentProcessLevel
+So far we know that there is a host opcode `audioMasterGetCurrentProcessLevel`, which returns `kVstProcessLevelUnknown` in MrsWatson.
+The presence of the host opcode and the generic name of the value returned by MrsWatson, suggest that there are other, more specific values to return.
+
+After another round of googling for terms `vst` and `processlevel`, I eventually stumbled upon a [post in the "Deprecated REAPER issue tracker"](https://forums.cockos.com/project.php?issueid=3382) (accessed 2020-01-20):
+
+> Offline Render causes glitching with some plugs (Reaper 3.76 sending kVstProcessLevelRealtime to AudioEffectX during Offline Render)
+>
+> First encountered using EW Spaces convolution reverb. The verb always glitches in offline render, and runs much faster than would be expected (i.e. 60x)
+> In further investigation it seems that when Reaper is in offline render mode, for any plug-in that calls AudioEffectX::getCurrentProcessLevel Reaper 3.76 returns kVstProcessLevelRealtime instead of kVstProcessLevelOffline.
+
+and further on:
+
+> Just discovered this isn't a bug. There is a setting in Reaper to enable the sending of this report variable.
+> In the Reaper Preferences under Plug-ins > VST there is a check-box for "inform plug-ins of offline rendering state" ... that fixes the problem.
+
+This post tells us that there are at least two more process levels, named `kVstProcessLevelRealtime` and `kVstProcessLevelOffline`.
+And that REAPER would report the former during offline rendering, but can be made into reporting the latter (by checking some option in the preferences).
+
+Nice. Now we only need to ask the host for the current processLevel (calling `audioMasterGetCurrentProcessLevel` e.g. in the `processReplacing` callback) while telling REAPER to "Render to disk", and we get
+
+| "Inform plug-ins of offline rendering..." | value | name |
+|-------------------------------------------|-------|----------------------------|
+| unchecked (OFF) | 2 | `kVstProcessLevelRealtime` |
+| checked (ON) | 4 | `kVstProcessLevelOffline` |
+
+REAPER also reports a process-level of `1`, but only at the beginning (e.g. while the host calls the plugin with `effMainsChanged`).
+
+| name | value | note |
+|----------------------------|-------|---------------------------------------------|
+| ?? | 0 | returned by JUCE is in realtime mode |
+| ?? | 1 | returned by REAPER during `effMainsChanged` |
+| `kVstProcessLevelRealtime` | 2 | returned by REAPER during normal rendering |
+| ?? | 3 | (in between) |
+| `kVstProcessLevelOffline` | 4 | returned by REAPER when offline-rendering; returned by JUCE if NOT in realtime mode |
+| `kVstProcessLevelUnknown` | ?? | used by MrsWatson |
+
+The value of `kVstProcessLevelUnknown` is most likely something special, like *-1*, or (if we follow the schema used for `kPlugCategUnknown`) *0*.
+I'm not sure, why JUCE would return `kPlugCategUnknown` for the realtime-case though.
+
+
+# JUCE-6.0.7
+
+Time has passed.
+Recently (at the beginning of 2021) I checked compiling `JstHost` and `JstPlugin` against JUCE-6.0.7.
+And it miserably failed, with unknown symbols.
+
+~~~bash
+make -k ... 2>&1 \
+| egrep -i "error: .*vst2" \
+| sed -e 's|.*: error: ||' \
+| sort -u
+~~~
+
+Examining the error messages, we see that the `kPlugCategMaxCount` is missing.
+
+## kPlugCategMaxCount
+
+The `kPlugCategMaxCount` seems to be related to `VstPlugCategory` enumeration (as it matches the
+`kPlugCateg*` naming convention).
+It is a common C-idiom when working with enums to add a final "dummy" entry, that can be used to
+determine the number of (valid) items, as in:
+
+~~~C
+enum foobar {
+ foo,
+ bar,
+ //...
+ last /* when adding new entries, add them before this one */
+};
+~~~
+
+The `kPlugCategMaxCount` most likely falls in this category as well,
+so we can assume it is the last element of the `VstPlugCategory` enumeration.
+We don't really have a known value for this symbol, as we cannot know the
+real size of the enumeration.
+Currently our `VstPlugCategory` enumeration holds 12 items (with values from 0 to 11),
+adding `kPlugCategMaxCount` will give it the value of 12 (but keep in mind that adding
+more items to the enumeration will change this value).
+Not knowing the exact value here is not a big deal,
+as the usual purpose of such an enum-terminator
+is to check whether a given enum value is in the range of known items at the time of compilation,
+so it will be correct in any case.
+
+
+## kVstMaxParamStrLen
+
+We can also do a bit of blind searching in the JUCE-sources,
+for symbols that *look like* those we are interested in
+(but that don't trigger compilation errors, e.g. because they are only used in
+comments).
+
+So far, many variables follow one of these patterns (with the variant part `*` starting with a capital letter):
+
+| pattern | note |
+|-------------|-----------------------|
+| `eff*` | effect opcode |
+| `kVst*` | constant |
+| `kPlug*` | effect category |
+| `kSpeaker*` | speaker configuration |
+| `k*` | constant |
+
+We are only interested in those that have something to do with VST (and not VST3).
+So we simply check for such names in files that somewhere in their path contain the string `vst`.
+
+~~~sh
+for prefix in eff k; do
+ rgrep -h "\b${prefix}" */ 2>/dev/null | sed -e "s|\b\(${prefix}\)|\n\1|g" | egrep "^${prefix}[A-Z]" | sed -e 's|\(.\)\b.*|\1|'
+done | sort -u | while read s; do
+ rgrep -wl "${s}" */ 2>/dev/null | grep -vi vst3 | grep -i vst >/dev/null && echo ${s}
+done | tee symbols.txt
+
+(echo '#include "fst.h"
+int main() {'; cat symbols.txt | sed -e 's|$|;|'; echo 'return 0;}') > fst.c
+
+gcc fst.c -o fst 2>&1 \
+| grep "error:" | sed -e 's|.*: error: .||' -e 's|. .*||' \
+> missing.txt
+~~~
+
+When we try a test-compilation with some dummy use of these symbols, we can conflate the total list
+of 171 symbols extracted from JUCE-6.0.7 to 11 names that we don't know yet:
+
+
+| name | note |
+|-------------------------------|------------------------|
+| `kCFAllocatorDefault` | Apple's CoreFoundation |
+| `kCFStringEncodingUTF8` | Apple's CoreFoundation |
+| `kCFURLPOSIXPathStyle` | Apple's CoreFoundation |
+| `kControlBoundsChangedEvent` | ?? (macOS) |
+| `kEventClassControl` | ?? (macOS) |
+| `kEventControlBoundsChanged` | ?? (macOS) |
+| `kFSCatInfoNone` | macOS |
+| `kHIViewWindowContentID` | macOS |
+| `kVstMaxParamStrLen` | |
+| `kWindowCompositingAttribute` | macOS |
+| `kWindowContentRgn` | macOS |
+
+
+So Apple also uses the `k` prefix for constants.
+Anyhow, only the `kVstMaxParamStrLen` constant appear to be truly VST2-related.
+Checking the JUCE sources for a bit of context, we learn:
+
+> length should technically be `kVstMaxParamStrLen`, which is 8, but hosts will normally allow a bit more
+
+So we found one more symbol, along with its value:
+
+| name | value |
+|----------------------|-------|
+| `kVstMaxParamStrLen` | 8 |
+
+
+
+
+
+# JUCE-7.0.2
+
+Time has passed again.
+Recently (at the middle of 2022) I checked compiling `JstHost` and `JstPlugin`
+against JUCE-7.0.2. And it miserably failed, with unknown symbols (again).
+
+## AudioMasterOpcodesX
+
+This time it was only one, namely a type `AudioMasterOpcodesX` (within the
+`Vst2::` namespace, but that is added by JUCE).
+We also notice that this type is used with the `audioMasterIOChanged` opcode.
+Which makes us think that the `AudioMasterOpcodesX` is simply a typedef for our
+host opcode enumeration.
+
+Note: The trailing `X` in the type name most likely indicates that it is defined
+in the `aeffectx.h` header.
+It's likely, that there is also an `AudioMasterOpcodes` enum type, and they
+contain different enumerations.
+We ignore this for now and simply wrap all our host opcodes into an enumeration
+that is typedefed to `AudioMasterOpcodesX`.
+
+## struct AEffect
+
+JUCE-7.0.2 now explicitly uses the [`AEffect` struct tag](#the-aeffect-struct-tag).
+
+
+## effEditIdle
+JUCE-7.0.2 now issues `effEditIdle` in the host's `VSTPluginInstance::handleIdle()`
+(at least if the plugin is currently being shown).
+On Linux, JUCE-7.0.2 plugins react on the `effEditIdle` by processing any pending events.
+
+These pending events appear to be responsible for updating the GUI,
+at least the GUI now shows nothing if I compile a JUCE-plugin (e.g. the
+*ArpeggiatorTutorial* plugin from the JUCE Plugin examples)
+and load it into REAPER:
+"nothing" being a rectangle that is not being updated at all.
+I'm sure it did show something in older versions of JUCE.
+
+As we want our GUI to be updated continuously, we need to check for a (yet unknown) opcode
+that is sent periodically to our plugin.
+With REAPER, this gives us two potential candidates: `opcode:19` and `opcode:53`.
+Both are called about every 50ms with only `0` as arguments.
+
+We also notice that both are only called when we open the *FX* window in REAPER.
+However, while `opcode:53` is always polled as soon as the *FX* window is open,
+`opcode:19` is only polled if we enable the built-in GUI.
+As soon as we switch to the generic GUI, only `opcode:53` remains.
+
+It's hard to tell which one we should pick, but
+*19* is close to the range of already known `effEdit*` opcodes (e.g. `effEditClose` is 15),
+whereas *53* is somewhere near `effCanDo`.
+
+So let's go for:
+
+| opcode | |
+|-------------|----|
+| effEditIdle | 19 |
+
+Maybe `opcode:53` is for `effIdle` (as it also gets continuously called),
+but why does it only get polled if the *FX* window is open?
+
+NOTE: while this makes the *ArpeggiatorTutorial* and friends work,
+they still crash when opening a second JUCE plugin. hmmm...
+
+
+## audioMasterUpdateDisplay
+Sometimes later (2023-06-01) somebody complained that `audioMasterUpdateDisplay` is not implemented by FST,
+and therefore some DAWs (like REAPER) do not update the display of any parameters that change "under the hood".
+
+Checking the JUCE source code ("modules/juce_audio_plugin_client/VST/juce_VST_Wrapper.cpp"),
+we see that the `audioMasterUpdateDisplay` opcode is used whenever
+- a "parameter info" changes
+- a "program change" occurs
+(we also see that if there is a "latency change", JUCE is going to call the `audioMasterIOChanged` opcode).
+
+So we need to find a host-opcode, that in turn triggers a re-query of the plugin's program and parameter info.
+
+In JUCE, the `audioMasterUpdateDisplay` opcode is called with all parameters set to `0`:
+```
+constexpr FlagPair pairs[] { { Vst2::audioMasterUpdateDisplay, audioMasterUpdateDisplayBit },
+ { Vst2::audioMasterIOChanged, audioMasterIOChangedBit } };
+
+for (const auto& pair : pairs)
+ if ((callbacksToFire & pair.bit) != 0)
+ callback (&owner.vstEffect, pair.opcode, 0, 0, nullptr, 0);
+```
+
+So we hook something like the following in a callback (e.g. to `effEditClose`),
+assuming that the opcode has an ID lower than 256 (which seems to be a safe assumption,
+given that the highest audioMaster opcode we've seen so far is 44...):
+
+
+```C
+for(size_t opcode=0; opcode<256; opcode++) {
+ int res;
+ if(hostKnown(opcode))
+ continue;
+ printf("test opcode(%d)", opcode);
+ res = dispatch_v(eff, opcode, 0, 0, NULL, 0.0);
+ printf("test opcode(%d) -> %d\n", opcode, res);
+}
+```
+
+Only two opcodes have any interesting results:
+
+| opcode | return | side-effect |
+|--------|--------|-----------------------------------------------|
+| 11 | 3 | |
+| 12 | 1 | |
+| 13 | 1 | |
+| 19 | 0x600 | |
+| 42 | 1 | calls `effGetProgram` and `effGetProgramName` |
+| other | 0 | |
+
+So, opcode `42` rescans the the current program and retrieces its name,
+which closely matches our expectation of what `audioMasterUpdateDisplay`
+is supposed to do ("triggers a re-query of the plugin's program [...] info.").
+
+So for now, we assume that we have found a new audioMaster opcode:
+
+| opcode | value |
+|-----------------------------------|-------|
+| audioMasterUpdateDisplay | 42 |
+
+
+# Summary
+
+So far we have discovered quite a few opcodes (and constants):
+
+
+Trying to compile JUCE plugins or plugin-hosts, we still miss a considerable number
+(leaving those out that JUCE handles with a no-op):
+
+| names used by JUCE |
+|------------------------------------------|
+| `audioMasterGetAutomationState` |
+| `audioMasterGetDirectory` |
+| `audioMasterGetNumAutomatableParameters` |
+| `audioMasterIOChanged` |
+| `audioMasterIdle` |
+| `audioMasterNeedIdle` |
+| `audioMasterPinConnected` |
+| `audioMasterSetOutputSampleRate` |
+|------------------------------------------|
+| `effConnect(In,Out)put` |
+| `effEdit(Draw,Mouse,Sleep,Top)` |
+| `effGetNumMidi(In,Out)putChannels` |
+| `effGetTailSize` |
+| `effIdle` |
+| `effKeysRequired` |
+| `effSetBypass` |
+|------------------------------------------|
+| `kVstAutomationReading` |
+| `kVstAutomationWriting` |
+| `kVstPinIsActive` |
+| `kVstPinIsStereo` |
+| `kVstPinUseSpeaker` |
+| `kSpeakerArr*` |
+| `kSpeaker*` |
+| `kVstSmpte*` |
+
+
+On the other hand, running using a self-compiled plugin in a commercial DAW (like REAPER)
+or using commercial plugins in a self-compiled plugin host, we only encounter very few opcodes
+that we don't know yet:
+
+| name | value |
+|------------|-------|
+| audioHost* | 3 |
+| audioHost* | 13 |
+| audioHost* | 14 |
+|------------|-------|
+| eff* | 53 |
+| eff* | 56 |
+| eff* | 62 |
+| eff* | 66 |
+
+
+
+
+
+# misc
+LATER move this to proper sections
+
+
+## hostCode:3
+## hostCode:13
+## hostCode:14
+
+## effCode:53
+An `effEditIdle` opcode is always followed by an `effCode:53`,
+but the reverse is not true: the `effCode:53` comes
+- right from the start during the `effCanDo` cycle (after the `sendVstEvents`?)
+
+
+## effCode:56
+
+~~gets called with automation, whenever the window gets focus?~~
+
+gets called, when a parameter has an automation track, and the window gains/loses focus:
+
+ FstClient::dispatcher(0x2a4c8b0, 56, 2, 0, 0x7fff4a83fb40, 0.000000)...
+
+The address seems to be zeroed-out (at least the first 0x99 bytes).
+The index is the parameter index currently being automated...
+
+Attempting to write something to the buffer does not seem to have any effect (in REAPER),
+unless you exceed the boundary of buffer, which triggers a stack-smashing segfault.
+It is triggered as soon as a non-NULL value is written at `(char*)ptr)[0x98]`.
+
+(This doesn't mean much, apart from the fact that we are writing out-of-bounds.
+esp. it doesn't tell us anything about the valid size of the buffer.)
+
+#### 2023-06-06
+
+also gets called when switching to the generic UI (where it gets called twice for reach parameter).
+
+## effCode:62
+This is called when the MIDI-dialog gets opened (right before effCode:66; but only once)
+
+5*16 (80) bytes == 0x00
+
+### 2023-06-06
+hmm, with REAPER i know get this opcode much more often, namely 102 times, during startup.
+effCode:66 is never called in this case.
+
+The project has a number of MIDI-objects.
+Also, the first time it is called with some address (0x7fff149ae910), the remaining 101 times
+with another address (0x7fff149ae940).
+Since the two addresses are only 48 bytes apart, the data size is most likely at max. 48 bytes.
+
+On the other hand, seems that the byte @+0x44 (68) is incremented by each call...
+
+It seems that this repeated call only happens, if we write something to the memory at ptr.
+We also return '100'.
+If instead we return '10', the opcode is called 10(+2) times instead of 100(+2).
+
+#### walk-through
+- all calls happen at startup
+- first REAPER calls effGetParamName+effGetParamDisplay, immediately afterwards
+- a first call to effCode:62 is issued
+ - address `ptr` is X
+ - `((char*)ptr)[0x44]` == 0 (that is `((int*)ptr)[17]`)
+ - we write something (an adress to a string) to the memory and return N
+- after this REAPER calls effVendorSpecific/effGetEffectName (once)
+- after this we get get N+1 calls to effCode:62
+ - address `ptr` is (always) Y=X+0x30
+ - `((char*)ptr)[0x44]` == n (with n=0..N)
+ - we write something (an address to a string) to the memory and return N
+- finally we get *two* effVendorSpecific/effGetEffectName calls
+
+#### notes
+- if we don't write anything to the ptr, effCode:62 is only called once
+- if we return `0` in the *1st* call, effCode:62 is only called once
+- if we return `0` in the *2nd* call, effCode:62 is only called twice (so it stops after it received 0).
+ also the call the effGetProgramNameIndexed that follows now has some weird value
+- if we decrement the return value (10,9,...) by 1, we only get 5 calls (that is: 1+N/2 in the 2nd round; 1 in the 1st)
+- if we decrement the return value (10,8,...) by 2, we only get 5 calls (that is: 1+N/3 in the 2nd round; 1 in the 1st)
+- if we set the return value to `((char*)ptr)[0x44]+1`, we get 127 calls (that is: 1+127 in the 2nd round; 1 in 1st)
+- if we return 200 (always), we get 127 calls (that is: 1+127 in the 2nd round; 1 in 1st)
+- if we return `10` in the *1st* round and `((char*)ptr)[0x44]+1` in the 2nd round, we get 127 calls (that is: 1+127 in the 2nd round; 1 in 1st)
+- if we change the `((char*)ptr)[0x44]` value, this seems to have no affect (presumably because the host uses a for-loop and just sets that value before calling from it's internal variable)
+
+so it seems that effCode:62 is called at mst 129 times (1 in the 1st round, 128 in the 2nd round), but it only continues to be called if the returned value is larger than `((char*)ptr)[0x44]`
+
+it also seems that **only** `((char*)ptr[0x04])` must be non-0, in order to keep running (that is: if this byte is non-0 we keep running even if the rest is 0; if the rest is non-0 but this byte is 0, running stops).
+Which bits at the bytes are set, seems to be irrelevant.
+
+Opening the MIDI editor (double-clicking on the MIDI object), re-issues the full `effCode:62` call (1+(N+1) times)
+
+## effCode:66
+adding a MIDI-item in REAPER and pressing some keys
+will send plenty of messages with effect opcode `66` to a plugin.
+There's also the occasional opcode `62`:
+
+~~~
+FstClient::dispatcher(0x19b9250, 66, 0, 0, 0xeae040, 0.000000);
+FstClient::dispatcher(0x19b9250, 62, 0, 0, 0x7ffe232a7660, 0.000000);
+FstClient::dispatcher(0x19b9250, 66, 0, 0, 0xeae040, 0.000000);
+FstClient::dispatcher(0x19b9250, 66, 0, 0, 0xeae040, 0.000000);
+~~~
+
+the index is the MIDI channel number (zero-based).
+
+the pointer is an address to a memory region,
+where the first 4 bytes are 0,
+and the 2nd 4 bytes are an int32 between 34 and 72.
+
+i have no idea what the first number is (it is always 0),
+but the second number is a MIDI-note number.
+Writing a string into the buffer right after the first 8 bytes (that hold the two numbers),
+will make REAPER show this string as labels for the given MIDI-note on the
+virtual MIDI keyboard.
+
+Unfortunately we do not now the opcode name for this.
+Given that there's a `effGetCurrentMidiProgram` opcode,
+my guess would be something along the lines of `effGetMidiNoteName`.
+
+## effEditDraw
+
+The `reaper_plugin_fx_embed.h` shipped with JUCE says, about REAPER
+
+```md
+* to support via VST2: canDo("hasCockosEmbeddedUI") should return 0xbeef0000
+* dispatcher will be called with opcode=effVendorSpecific, index=effEditDraw, value=parm2, ptr=(void*)(INT_PTR)parm3, opt=message (REAPER_FXEMBED_WM_*)
+```
+
+This should give us a good guess on `effEditDraw`.
+Arming our dummy plugin to output `0xBEEF0000` when it is asked whether it can do "hasCockosEmbeddedUI", should make REAPER send it an `effVendorSpecific` opcode with the index being some meaningful opcode.
+
+Unfortunately, a quick check shows, that REAPER will send exactly the same to `index` values with the `effVendorSpecific` opcodes as when we answer the "hasCockosEmbeddedUI" with `0x0`:
+
+| opcode | index | value | ptr | opt |
+|-------------------|------------|------------|----------------|-----|
+| effVendorSpecific | 0x73744341 | 0x46554944 | 0x7ffc4fd12ea0 | 0.0 |
+```
+Fst::host2plugin(FstPlugin, effVendorSpecific[50], 1936999233=0x73744341, 1179994436=0x46554944, 0x7ffc4fd12ea0, 0.000000)
+Fst::host2plugin(FstPlugin, effVendorSpecific[50], 45=0x2D, 80=0x50, 0x7ffc4fd133a0, 0.000000)
+```
+
+
+## even more symbols
+
+| | symbol | project |
+|----------------|--------------------------------|------------|
+| opcode | effBeginSetProgram | vstplugin~ |
+| opcode | effEndSetProgram | vstplugin~ |
+|----------------|--------------------------------|------------|
+| constant | kSpeakerM | vstplugin~ |
+|----------------|--------------------------------|------------|
+| member | VstTimeInfo.samplesToNextClock | vstplugin~ |
+|----------------|--------------------------------|------------|
+| type | VstAEffectFlags | vstplugin~ |
diff --git a/source/fst/docs/opcodes.org b/source/fst/docs/opcodes.org
@@ -0,0 +1,257 @@
+
+* opcode tables
+
+** host
+
+| opcode | value | notes |
+|--------+----------------------------------------+-----------------------------------------------------------------------------------|
+| 0 | audioMasterAutomate | |
+| 1 | audioMasterVersion | :JUCE returns 2400 |
+| 2 | audioMasterCurrentId? | :REAPER returns 0xDEAD |
+| | | :REAPER-plugins call this in the ctor |
+| 6 | audioMasterWantMidi | :called as response to effMainsChanged with ivalue=1 |
+| | | :REAPER returns 1 |
+| 7 | audioMasterGetTime | :called by hypercyclic/tonespace as response to effMainsChanged with ivalue=65024 |
+| | | :REAPER returns an address |
+| 8 | audioMasterProcessEvents | :REAPER returns 1 |
+| | | :REAPER crashes with tained VstEvents-data |
+| 10 | audioMasterTempoAt | |
+| 11 | ? | :REAPER returns 0x10000 (65536) |
+| 12 | ? | :REAPER returns 1 |
+| 13 | ? | :called with 0-args in Protoverb:main and hypercyclic:eff:12 |
+| 13? | ? | :Protoverb calls in main (and as response to effcode:30514?) |
+| 14 | ? | :Renoise_Redux calls this (without args) in main |
+| | | :REAPER returns 0 |
+| 15 | audioMasterSizeWindow | |
+| 16 | audioMasterGetSampleRate | |
+| 17 | audioMasterGetBlockSize | |
+| 19 | ? | :REAPER returns 0x2800, 0x33FF, 0x2FFF, 0x3400, 0x3000 when called... |
+| | | :...repeatedly while handling effProcessEvents (args are ignored) |
+| 23 | audioMasterGetCurrentProcessLevel | :called by JUCE-plugins with 0-args as response to effMainsChanged |
+| 32 | audioMasterGetVendorString | |
+| 33 | audioMasterGetProductString | |
+| 34 | audioMasterGetVendorVersion | :JUCE returns 0x0101 (257) |
+| 37 | audioMasterCanDo | |
+| 42 | ? | :Protoverb calls as response to effSetProgramName |
+| 43 | audioMasterBeginEdit | :called by JUCE-plugins with index of a GUI-changed parameter (no more args) |
+| 44 | audioMasterEndEdit | :called by JUCE-plugins with index of a GUI-changed parameter (no more args) |
+| 48 | audioMasterGetDirectory? | :REAPER returns filename of reaper-project file (.RPP) |
+|--------+----------------------------------------+-----------------------------------------------------------------------------------|
+| | audioMasterCurrentId? | |
+| | audioMasterGetDirectory? | |
+| | audioMasterUpdateDisplay | |
+| | audioMasterGetAutomationState | |
+| | audioMasterGetNumAutomatableParameters | |
+| | audioMasterIdle | |
+| | audioMasterIOChanged | |
+| | audioMasterNeedIdle | |
+| | audioMasterPinConnected | |
+| | audioMasterSetOutputSampleRate | |
+|--------+----------------------------------------+-----------------------------------------------------------------------------------|
+| | audioMasterGetLanguage | JUCE-ignore |
+| | audioMasterGetOutputSpeakerArrangement | JUCE-ignore |
+| | audioMasterGetParameterQuantization | JUCE-ignore |
+| | audioMasterGetPreviousPlug | JUCE-ignore |
+| | audioMasterGetNextPlug | JUCE-ignore |
+| | audioMasterSetTime | JUCE-ignore |
+| | audioMasterWillReplaceOrAccumulate | JUCE-ignore |
+| | audioMasterGetInputLatency | JUCE-ignore |
+| | audioMasterGetOutputLatency | JUCE-ignore |
+| | audioMasterOpenWindow | JUCE-ignore |
+| | audioMasterCloseWindow | JUCE-ignore |
+| | audioMasterSetIcon | JUCE-ignore |
+| | audioMasterOfflineGetCurrentMetaPass | JUCE-ignore |
+| | audioMasterOfflineGetCurrentPass | JUCE-ignore |
+| | audioMasterOfflineRead | JUCE-ignore |
+| | audioMasterOfflineStart | JUCE-ignore |
+| | audioMasterOfflineWrite | JUCE-ignore |
+| | audioMasterVendorSpecific | JUCE-ignore |
+| | | |
+
+
+
+** plugin
+
+
+| opcode | value | notes |
+|------------+-----------------------------+---------------------------------------------------------------------------------------------------------|
+| 0 | effOpen | :REAPER calls with 0 (at the very beginning) |
+| 1 | effClose | :CRASH |
+| | | :REAPER calls with 0 (at the very end) |
+| 2 | effSetProgram | set program# via ivalue |
+| 3 | effGetProgram | returns current program# |
+| 4 | effSetProgramName | :needs a valid ptr, Protoverb calls hostCode:42 |
+| 5 | effGetProgramName | writes current program name into ptr |
+| 6 | effGetParamLabel | unit (e.g. "dB") |
+| 7 | effGetParamDisplay | string representation of current value (e.g. "-23") |
+| 8 | effGetParamName | human readable name (e.g. "Gain") |
+| 9 | | |
+| 10 | effSetSampleRate | :Protoverb prints "AM_AudioMan::reset()" |
+| 11 | effSetBlockSize | |
+| 12 | effMainsChanged | :Protoverb does resume/reset and calls audioMasterWantMidi |
+| | | :Digits calls audioMasterWantMidi |
+| | | :BowEcho/Danaides calls audioMasterWantMidi, audioMasterGetCurrentProcessLevel |
+| | | :hypercyclic/tonespace calls audioMasterWantMidi, audioMasterGetTime, audioMasterGetCurrentProcessLevel |
+| | | :REAPER calls with ivalue=1/0 |
+| 13 | effEditGetRect | :Protoverb prints "AM_VST_Editor::getRect" and CRASH |
+| 14 | effEditOpen | :Protoverb prints "AM_VST_Editor::open" and EXIT |
+| 15 | effEditClose | :Protoverb prints "closed editor." |
+| 19 | effEditIdle | :REAPER calls repeatedly every 50ms |
+| | | :JUCE (>=7?) requires polling for GUI |
+| 22 | effIdentify | :Protoverv returns 0x4E764566 |
+| 23 | effGetChunk | :Protoverb returns 0x317 (or 0x307) and writes an address into ptr |
+| 24 | effSetChunk | :Protoverb returns 1 |
+| 25 | effProcessEvents | :Protoverb returns 1 |
+| 26 | | :Protoverb returns 1 |
+| 29 | effGetProgramNameIndexed | :Protoverb returns 1 and ptr:"initialize" |
+| 33 | effGetInputProperties | :Protoverb returns 1 and ptr:"Protoverb-In0" |
+| 34 | effGetOutputProperties | :Protoverb returns 1 and ptr:"Protoverb-Out0" |
+| 35 | effGetPlugCategory | :JUCE RETURNS VstPlugCategory |
+| 35 | | :REAPER calls with 0 |
+| | | :InstaLooper returns 0 |
+| | | :Protoverb/BowEcho/Danaides/reacom.vst returns 1 |
+| | | :Digits/hypercyclic/tonespace returns 2 |
+| 41 | | :crashes when called with (0,0,"name41",0) |
+| 45 | effGetEffectName | :Protoverb returns 1 and ptr:"Protoverb" |
+| 47 | effGetVendorString | :Protoverb returns 1 and ptr:"u-he"; prints getVendorString() |
+| 48 | effGetProductString | :Protoverb returns 1 and ptr:"Protoverb 1.0.0" |
+| 49 | effGetVendorVersion | :Protoverb returns 0x10000 |
+| 50 | effVendorSpecific | :REAPER only calls this if 'hasCockosExtensions' is supported |
+| 51 | effCanDo | :Protoverb returns 0xFFFFFFFFFFFFFFFF (with ptr:"") |
+| 53 | | :REAPER calls repeatedly every 50ms |
+| 56 | | :REAPER calls with ptr to zeroed memory |
+| | | :all return 0 |
+| 58 | effGetVstVersion | :Protoverb returns 2400 (0x960) |
+| 59 | effKeysRequired? | :Protoverb prints "u-he plugin doesn't use key" |
+| 63 | | :Protoverb returns 0xFFFFFFFFFFFFFFFF (with ptr:"") |
+| 71 | | :REAPER calls with 0 |
+| | | :Protoverb returns 1, rest returns 0 |
+| 72 | | :REAPER calls with 0 |
+| 77 | effSetProcessPrecision | :REAPER calls with 1 |
+| | | :Protoverb returns 1, rest returns 0 |
+| 30514 | | :Protoverb calls hostCode:13 |
+| 0xDEADBEEF | | :REAPER ask for function? |
+|------------+-----------------------------+---------------------------------------------------------------------------------------------------------|
+| | effGetTailSize | |
+| | effGetCurrentMidiProgram | |
+| | effGetSpeakerArrangement | |
+| | effGetNumMidiInputChannels | |
+| | effGetNumMidiOutputChannels | |
+| | | |
+| | effCanBeAutomated | :takes index |
+| | effString2Parameter | |
+| | effSetSpeakerArrangement | |
+| | effSetBypass | |
+| | effSetTotalSampleToProcess | |
+| | effConnectInput | |
+| | effConnectOutput | |
+| | effIdle | |
+| | effShellGetNextPlugin | |
+| | effStartProcess | |
+| | effStopProcess | |
+| | effEditDraw | |
+| | effEditMouse | |
+| | effEditSleep | |
+| | effEditTop | |
+
+** flags
+
+| bit | name | notes |
+|-----+----------------------------+-----------------------|
+| 1 | effFlagsHasEditor | |
+| 2 | | always 0 |
+| 3 | | always 0 |
+| 4 | | always 0 |
+| 5 | ?? | always 1 |
+| 6 | ?? | InstaLooper=0, else 1 |
+| 7 | | always 0 |
+| 8 | | always 0 |
+| 9 | effFlagsIsSynth | |
+| 10 | ?? | |
+| 11 | | always 0 |
+| 12 | | always 0 |
+| 13 | | always 0 |
+| 14 | | always 0 |
+| 15 | | always 0 |
+| 16 | | always 0 |
+|-----+----------------------------+-----------------------|
+| ? | effFlagsCanDoubleReplacing | |
+| ? | effFlagsCanReplacing | |
+| ? | effFlagsNoSoundInStop | |
+| ? | effFlagsProgramChunks | |
+
+
+* Host opcodes and how they are handled by JUCE
+
+
+| audioMaster-opcode | IN | OUT | return | notes |
+|----------------------------------------+------------------+-------------+------------------+-----------------------------------------------------------------|
+| audioMasterAutomate | index, fvalue | - | 0 | |
+| audioMasterProcessEvents | ptr(VstEvents*)) | - | 0 | |
+| audioMasterGetTime | - | - | &vsttime | |
+| audioMasterIdle | - | - | 0 | |
+| audioMasterSizeWindow | index, value | | 1 | setWindowSize(index,value) |
+| audioMasterUpdateDisplay | - | - | 0 | triggerAsyncUpdate() |
+| audioMasterIOChanged | - | - | 0 | setLatencyDelay |
+| audioMasterNeedIdle | - | - | 0 | startTimer(50) |
+| audioMasterGetSampleRate | - | - | samplerate | |
+| audioMasterGetBlockSize | - | - | blocksize | |
+| audioMasterWantMidi | - | - | 0 | wantsMidi=true |
+| audioMasterGetDirectory | - | - | (char*)directory | |
+| audioMasterTempoAt | - | - | 10000*bpm | |
+| audioMasterGetAutomationState | - | - | 0/1/2/3/4 | 0 = not supported, 1 = off, 2 = read, 3 = write, 4 = read/write |
+| audioMasterBeginEdit | index | - | 0 | gesture |
+| audioMasterEndEdit | index | - | 0 | gesture |
+| audioMasterPinConnected | index,value | - | 0/1 | 0=true; value=direction |
+| audioMasterGetCurrentProcessLevel | - | - | 4/0 | 4 if not realtime |
+|----------------------------------------+------------------+-------------+------------------+-----------------------------------------------------------------|
+| audioMasterCanDo | ptr(char[]) | - | 1/0 | 1 if we can handle feature |
+| audioMasterVersion | - | - | 2400 | |
+| audioMasterCurrentId | - | - | shellUIDToCreate | |
+| audioMasterGetNumAutomatableParameters | - | - | 0 | |
+| audioMasterGetVendorVersion | - | - | 0x0101 | |
+| audioMasterGetVendorString | - | ptr(char[]) | ptr | getHostName() |
+| audioMasterGetProductString | - | ptr(char[]) | ptr | getHostName() |
+| audioMasterSetOutputSampleRate | - | - | 0 | |
+|----------------------------------------+------------------+-------------+------------------+-----------------------------------------------------------------|
+| audioMasterGetLanguage | | | | JUCE-ignore |
+| audioMasterGetOutputSpeakerArrangement | | | | JUCE-ignore |
+| audioMasterGetParameterQuantization | | | | JUCE-ignore |
+| audioMasterGetPreviousPlug | | | | JUCE-ignore |
+| audioMasterGetNextPlug | | | | JUCE-ignore |
+| audioMasterSetTime | | | | JUCE-ignore |
+| audioMasterWillReplaceOrAccumulate | | | | JUCE-ignore |
+| audioMasterGetInputLatency | | | | JUCE-ignore |
+| audioMasterGetOutputLatency | | | | JUCE-ignore |
+| audioMasterOpenWindow | | | | JUCE-ignore |
+| audioMasterCloseWindow | | | | JUCE-ignore |
+| audioMasterSetIcon | | | | JUCE-ignore |
+| audioMasterOfflineGetCurrentMetaPass | | | | JUCE-ignore |
+| audioMasterOfflineGetCurrentPass | | | | JUCE-ignore |
+| audioMasterOfflineRead | | | | JUCE-ignore |
+| audioMasterOfflineStart | | | | JUCE-ignore |
+| audioMasterOfflineWrite | | | | JUCE-ignore |
+| audioMasterVendorSpecific | | | | JUCE-ignore |
+| | | | | |
+
+
+* effcode:12 effMainsChanged
+
+** JUCE resume
+ - isProcessLevelOffline()
+ - hostCallback (&vstEffect, Vst2::audioMasterGetCurrentProcessLevel, 0, 0, 0, 0);
+ - deleteTempChannels()
+ - hostCallback (&vstEffect, Vst2::audioMasterWantMidi, 0, 1, 0, 0);
+ -
+
+
+23/autioMasterGetTime/6
+- 23: audioMasterGetCurrentProcessLevel
+- 6: audioMasterWantMidi
+
+
+| opcode | value |
+|-----------------------------------+-------|
+| effMainsChanged | 12 |
+| audioMasterWantMidi | 6 |
+| audioMasterGetCurrentProcessLevel | 23 |
diff --git a/source/fst/docs/realworld.org b/source/fst/docs/realworld.org
@@ -0,0 +1,514 @@
+* JUCE host
+
+1. create
+ 1. contructEffect
+ 1. calls =VSTPluginMain= to get a plugin
+ 2. checks =effect->magic=
+ 3. checks =effect->resvd2!=0=
+ 2. set =effect->resvd2= to 0
+ 3. calls =effIdentify(0,0,NULL,0.))= (ignores result)
+ 4. calls =effSetSampleRate(0,0,NULL,sampleRate)=
+ 5. calls =effSetBlockSize(0,blockSize,NULL, 0.f)=
+ 6. calls =effOpen(0,0,NULL,0.))=
+ 7. queryBusIO
+ 1. calls =effSetSpeakerArrangement(0, ?, ?, 0.f)=
+ 2. getSpeakerArrangementWrapper
+ 1. ?
+ 3. =effGetInputProperties(ch=0..n, 0, &pinProps, 0.0f)=
+ 4. =effGetOutputProperties(ch=0..n, 0, &pinProps, 0.0f)=
+ 8. VSTPluginInstance
+ 1. =effCanBeAutomated(param=0..n, 0, 0, 0, 0)=
+
+
+* REAPER host
+
+#+BEGIN_SRC C
+/* init */
+effect->dispatcher(effOpen, 0, 0, (nil), 0.000000);
+effect->dispatcher(effSetSampleRate, 0, 0, (nil), 44100.000000);
+effect->dispatcher(effSetBlockSize, 0, 512, (nil), 0.000000);
+effect->dispatcher(effGetEffectName, 0, 0, 0x7ffcf7237fc0, 0.000000);
+effect->dispatcher(effGetVendorString, 0, 0, 0x7ffcf7237fc0, 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab4617, 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab4af0, 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab4670, 0.000000);
+effect->dispatcher(effGetVstVersion, 0, 0, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 1, (nil), 0.000000);
+effect->dispatcher(effStartProcess, 0, 0, (nil), 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab4684, 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab4695, 0.000000);
+effect->dispatcher(effGetPlugCategory, 0, 0, (nil), 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab46a9, 0.000000);
+effect->dispatcher(effCanDo, 0, 0, 0xab46b7, 0.000000);
+effect->dispatcher(effGetProgram, 0, 0, (nil), 0.000000);
+effect->dispatcher(effGetChunk, 0, 0, 0x7ffcf722fd10, 0.000000);
+effect->dispatcher(effSetProgram, 0, 0, (nil), 0.000000);
+effect->dispatcher(effGetProgramName, 0, 0, 0x7ffcf7237dc0, 0.000000);
+effect->dispatcher(effGetProgram, 0, 0, (nil), 0.000000);
+
+/* start playback */
+effect->dispatcher(effMainsChanged, 0, 0, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 1, (nil), 0.000000);
+
+/* de-init */
+effect->dispatcher(effMainsChanged, 0, 0, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 1, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 0, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 1, (nil), 0.000000);
+effect->dispatcher(StopProcess, 0, 0, (nil), 0.000000);
+effect->dispatcher(effMainsChanged, 0, 0, (nil), 0.000000);
+effect->dispatcher(effClose, 0, 0, (nil), 0.000000);
+#+END_SRC
+
+
+* REAPER plugins
+
+plugins that come with REAPER fail to instantiate.
+however, in the `VSTPluginMain` function they call the host
+with two callbacks:
+#+BEGIN_SRC C
+hostDispatcher(0, 0xDEADBEEF, 0xDEADFOOD, 0, "DB2SLIDER", 0.f);
+hostDispatcher(0, 0xDEADBEEF, 0xDEADFOOD, 0, "SLIDER2DB", 0.f);
+#+END_SRC
+
+I *think* that the plugin is asking for shared functions.
+At least, if we return a function pointer, the `reacomp.vst` plugin loads!
+
+we can then load the following plugins:
+- REAPER/Plugins/FX/reacomp.vst.so
+- REAPER/Plugins/FX/readelay.vst.so
+- REAPER/Plugins/FX/reagate.vst.so
+- REAPER/Plugins/FX/reasyndr.vst.so
+- REAPER/Plugins/FX/reasynth.vst.so
+- REAPER/Plugins/FX/reatune.vst.so
+- REAPER/Plugins/FX/reaverbate.vst.so
+- REAPER/Plugins/FX/reavocode.vst.so
+
+
+
+* Categories as reported by REAPER
+
+** Delay
+- ReaDelay
+** Dynamics
+- ReaComp
+- ReaXComp
+** EQ
+- ReaEQ
+** External
+- ReaCast
+- ReaInsert
+- ReaNINJAM
+- ReaStream (8ch)
+** Gate
+- ReaGate
+** MIDI
+- ReaControlMIDI
+** Pitch Correction
+- ReaPitch
+** Reverb
+- ReaVerb
+- ReaVerbate
+** Sampler
+- ReaSamplOmatic5000
+** Surround
+- ReaSurround
+** Synth
+- Digits
+- hypercyclic
+- tonespace
+- ReaSynDr
+- ReaSynth
+
+** Tools
+- ReaFir
+- ReaVocode
+- ReaVoice
+** Tuner
+- ReaTune
+
+
+
+* VstPinProperties
+
+#+BEGIN_SRC C++
+Vst2::VstPinProperties pinProps;
+if (dispatch (Vst2::effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0)
+ return String (pinProps.label, sizeof (pinProps.label));
+#+END_SRC
+
+** VstPinProperties.flags
+
+if (kVstPinUseSpeaker): layout=VstPinProperties.arrangementType
+else if (kVstPinIsStereo): layout=stereo
+else: layout=mono
+
+** VstPinProperties.arrangementType
+-> SpeakerMappings::vstArrangementTypeToChannelSet
+
+SpeakerMappings::vstArrangementTypeToChannelSet (pinProps.arrangementType, 0);
+const Vst2::VstSpeakerArrangement* arr = (isInput ? inArr : outArr);
+layout = SpeakerMappings::vstArrangementTypeToChannelSet (*arr);
+
+
+#+BEGIN_SRC C
+struct {
+ char label[64];
+ int flags;
+ int arrangmentType;
+ char shortLabel[8];
+}
+#+END_SRC
+
+has =arrangementType=0; flags=3= for our "normal" test plugins
+and =arrangementType=1; flags=2= for our the reaper plugins
+
+this probably means:
+
+kVstPinIsActive=1;
+kVstPinUseSpeaker=2;
+kSpeakerArrMono=0;
+
+
+* Flags and Double
+
+| plugin | process | replacing | replacing2 |
+|--------------------+----------------+----------------+----------------|
+| BowEcho | 0x7fa99438edc0 | 0x7fa99438edd0 | 0x7fa99438ede0 |
+| Danaides | 0x7f69e5e5a940 | 0x7f69e5e5a950 | 0x7f69e5e5a960 |
+| DigitsVST_64 | 0x7f549efad620 | 0x7f549efacc80 | 0x7f549efad650 |
+| hypercyclic | 0x7f9217a2a9be | 0x7f9217a2a9ce | 0x7f9217a2a9dc |
+| InstaLooperVST-x64 | 0x7f7504284660 | 0x7f7504284670 | 0x7f7504284680 |
+| Protoverb.64 | 0x7f2c0ed37d70 | 0x7f2c0ed37d80 | 0x7f2c0ed37d90 |
+| tonespace | 0x7f7f723159be | 0x7f7f723159ce | 0x7f7f723159dc |
+|--------------------+----------------+----------------+----------------|
+| reacomp | 0x7febc27162e0 | 0x7febc27162e0 | 0x7febc271cea0 |
+| readelay | 0x7f3624b55820 | 0x7f3624b55820 | 0x7f3624b59940 |
+| reagate | 0x7fa4303f2cf0 | 0x7fa4303f2cf0 | 0x7fa4303f7fd0 |
+| reasyndr | 0x7f0de838a220 | 0x7f0de838a300 | (nil) |
+| reasynth | 0x7f60ca2d1020 | 0x7f60ca2d3330 | (nil) |
+| reaverbate | 0x7f7d1d15e5c0 | 0x7f7d1d15e5c0 | 0x7f7d1d15e640 |
+| reavocode | 0x7fcc4ab2a130 | 0x7fcc4ab2a130 | 0x7fcc4ab2a1d0 |
+
+
+| plugin | flags |
+|--------------------+-------------------|
+| Protoverb.64 | 00000000 00110001 |
+| DigitsVST_64 | 00000001 00110000 |
+| InstaLooperVST-x64 | 00000000 00010001 |
+| BowEcho | 00000010 00110001 |
+| Danaides | 00000010 00110001 |
+| hypercyclic | 00000011 00110001 |
+| tonespace | 00000011 00110001 |
+|--------------------+-------------------|
+| reacomp | 00010000 00010001 |
+| readelay | 00010000 00110001 |
+| reagate | 00010000 00010001 |
+| reasyndr | 00000001 00010001 |
+| reasynth | 00000001 00010001 |
+| reaverbate | 00010000 00010001 |
+| reavocode | 00010000 00110001 |
+|--------------------+-------------------|
+| FLAGS | ___C__98 __54___0 |
+
+
+| flag | | value | notes |
+|----------------------------+---+-------+------------------------------|
+| effFlagsHasEditor | 0 | 1<< 0 | |
+| effFlagsProgramChunks | 5 | 1<< 5 | |
+| effFlagsIsSynth | 8 | 1<< 8 | |
+|----------------------------+---+-------+------------------------------|
+| effFlagsCanDoubleReplacing | C | 1<<12 | |
+| effFlagsCanReplacing | 4 | 1<<4 | |
+| effFlagsNoSoundInStop | 9 | 1<<9 | |
+|----------------------------+---+-------+------------------------------|
+| | 4 | | ALL |
+| | 9 | | JUCE |
+| | C | | only reaper (except reasyn*) |
+| | | | |
+
+
+* effCode:56
+
+REAPER calls =opcode:56= with a =ptr= that points to a zeroed-out memory region.
+
+** size of zero-memory
+first non-zero byte @
+- 0x99, danach 4D D1 9B 99 06 CC 0C
+- 0x99, danach 6A 7B 8C 9A 80 71 F8
+- 0x99, danach FE 2A 66 F0 46 D2 02
+
+
+* 64bit vs 32bit
+
+FstClient::dispatcher(0x1ec2080, 42, 0, 32252624, 0x1ec2740, 0.000000)... 0x1ec22d0
+FstClient::dispatcher(0x9e36510, 42, 0, 172519840, 0xa487610, 0.000000)... 0xa4871a0
+
+
+dispatch4host(0x1ec2080, audioMasterGetTime, 0, 0, 0x7ffe4d90d2c0, 0.000000)
+dispatch4host: 32248472 (0x1EC1298)
+
+dispatch4host(0x9e36510, audioMasterGetTime, 0, 18443692774323650560, (nil), -nan)
+dispatch4host: 740955491651839768 (0xFFF5285800000000)
+
+
+** VstEvents
+
+- 32bit: =02000000 00 00 00 00 68F9F1ED 88F9F1ED=
+- 64bit: =02000000 00 00 00 00 00 00 00 00 00 00 00 00 806E00146C7F0000 A06E00146C7F0000=
+
+
+* Time/SMTP
+
+** 30/1 fps
+
+#+BEGIN_SRC C
+VstTimeInfo @ 0x18230c8
+ flags: 0
+ smpteFrameRate: 12034
+ smpteOffset: -1
+ currentBar: 2
+ magic: 0xDEADBEEF
+#+END_SRC
+
+#+BEGIN_SRC C
+VstTimeInfo @ 0x16c50a8
+ flags: 0
+ smpteFrameRate: 12034
+ smpteOffset: -1
+ currentBar: 3
+ magic: 0xDEADBEEF
+#+END_SRC
+
+
+** fps 30/1
+#+BEGIN_SRC C
+ 04 00 00 00 04 00 00 00 0B 00 00 00 EF BE AD DE
+ 00 00 00 00 02 2F 00 00 FF FF FF FF 00 02 00 00
+#+END_SRC
+
+** fps 24/1
+#+BEGIN_SRC C
+ 04 00 00 00 04 00 00 00 08 00 00 00 EF BE AD DE
+ 00 00 00 00 02 2F 00 00 FF FF FF FF 00 02 00 00
+#+END_SRC
+
+** fps 25/1
+#+BEGIN_SRC C
+ 04 00 00 00 04 00 00 00 02 00 00 00 EF BE AD DE
+ 00 00 00 00 02 2F 00 00 FF FF FF FF 00 02 00 00
+#+END_SRC
+
+#+BEGIN_SRC C
+#+END_SRC
+
+
+** flags
+
+| flags | flags(bin) | description |
+|-------------+----------------+---------------|
+| 00 2f 00 00 | 10111100000000 | pause |
+| 02 2f 00 00 | 10111100000010 | playing |
+| 0a 2f 00 00 | 10111100001010 | recording |
+| 06 3f 00 00 | 11111100000110 | looping |
+| 01 2f 00 00 | 10111100000001 | stopping |
+| 03 2f 00 00 | 10111100000011 | starting play |
+| 07 3f 00 00 | 11111100000111 | starting loop |
+
+| flags(bin) | description |
+|-----------------+---------------|
+| 101111 00000000 | pause |
+| 101111 00000010 | playing |
+| 111111 00000110 | looping |
+| 101111 00000001 | stopping |
+| 101111 00000011 | starting play |
+| 111111 00000111 | starting loop |
+| 101111 00001010 | recording |
+| | |
+
+| flags | value |
+|----------------------------+-------|
+| `kVstTransportChanged` | 1<<0 |
+| `kVstTransportPlaying` | 1<<1 |
+| `kVstTransportCycleActive` | 1<<2 |
+| `kVstTransportRecording` | 1<<3 |
+| | |
+
+| flag | valid |
+|-------------------+---------|
+| kVstCyclePosValid | looping |
+| kVstBarsValid | yes |
+| kVstPpqPosValid | yes |
+| kVstTempoValid | yes |
+| kVstTimeSigValid | yes |
+| kVstClockValid | ? |
+| kVstNanosValid | ? |
+| kVstSmpteValid | ?- |
+| | |
+
+
+* MrWatson
+
+# plugin asked for time in nanoseconds
+# Plugin requested position in bars, but not PPQ
+
+| flag | binary | warning |
+|--------+--------+----------------------------------------|
+| 0x0100 | 1<< 8 | "plugin asked for time in nanoseconds" |
+| 0x0200 | 1<< 9 | flags=0x0203; ppqPos |
+| 0x0400 | 1<<10 | flags=0x0403; tempo |
+| 0x0800 | 1<<11 | flags=0x0803; barStartPos |
+| 0x2000 | 1<<13 | flags=0x2003; timeSig... |
+| 0x4000 | 1<<14 | "Current time in SMPTE format" |
+| 0x8000 | 1<<15 | "Sample frames until next clock" |
+
+| flag | binary | @54-57 | set data |
+|--------+--------+--------+-------------------------------------|
+| 0x0200 | 1<< 9 | 0x0203 | ppqPos |
+| 0x0400 | 1<<10 | 0x0403 | tempo |
+| 0x0800 | 1<<11 | 0x0803 | barStartPos |
+| 0x2000 | 1<<13 | 0x2003 | timeSigNumerator/timeSigDenominator |
+| | | | |
+
+| flag | value |
+|----------------------------+-------|
+| `kVstTransportChanged` | 1<< 0 |
+| `kVstTransportPlaying` | 1<< 1 |
+| `kVstTransportCycleActive` | 1<< 2 |
+| `kVstTransportRecording` | 1<< 3 |
+|----------------------------+-------|
+| `kVstNanosValid` | 1<< 8 |
+| `kVstPpqPosValid` | 1<< 9 |
+| `kVstTempoValid` | 1<<10 |
+| `kVstBarsValid` | 1<<11 |
+| `kVstCyclePosValid` | 1<<12 |
+| `kVstTimeSigValid` | 1<<13 |
+
+
+** Protoverb
+
+#+BEGIN_BLOCK
+Protoverb VST telling MrsWatson about 16 samples latency
+UNSUPPORTED FEATURE: VST master opcode audioMasterIOChanged
+ This feature is not yet supported. Please help us out and submit a patch! :)
+ Project website: https://github.com/teragonaudio/mrswatson
+ Support email: support@teragonaudio.com
+- 00061952 000181 Finished processing input source
+#+END_BLOCK
+
+where FstHost tells us:
+
+#+BEGIN_BLOCK
+sending latency to host... 16
+Protoverb VST telling FstProduct? about 16 samples latency
+FstHost::dispatcher[0](13, 0, 0, (nil), 0.000000)
+ dyspatcher(0x5611057e4bf0, 13, 0, 0, (nil), 0.000000);
+#+END_BLOCK
+
+** BowEcho/Danaides
+
+#+BEGIN_BLOCK
+UNSUPPORTED FEATURE: Current time in SMPTE format
+ This feature is not yet supported. Please help us out and submit a patch! :)
+ Project website: https://github.com/teragonaudio/mrswatson
+ Support email: support@teragonaudio.com
+#+END_BLOCK
+
+** tonespace
+
+#+BEGIN_BLOCK
+- 00000000 000000 Opening VST2.x plugin '/Net/iem/Benutzer/zmoelnig/src/iem/FST/tmp/vst64/hypercyclic/hypercyclic.so'
+UNSUPPORTED FEATURE: Current time in SMPTE format
+ This feature is not yet supported. Please help us out and submit a patch! :)
+ Project website: https://github.com/teragonaudio/mrswatson
+ Support email: support@teragonaudio.com
+#+END_BLOCK
+
+** hypercyclic
+
+#+BEGIN_BLOCK
+- 00000000 000000 Opening VST2.x plugin '/Net/iem/Benutzer/zmoelnig/src/iem/FST/tmp/vst64/hypercyclic/hypercyclic.so'
+unknown display value id=D
+unknown display value id=B
+UNSUPPORTED FEATURE: Current time in SMPTE format
+ This feature is not yet supported. Please help us out and submit a patch! :)
+ Project website: https://github.com/teragonaudio/mrswatson
+ Support email: support@teragonaudio.com
+#+END_BLOCK
+
+
+* vstplugin~
+
+** TODO
+| opcode | ? |
+|-----------------------------+---|
+| audioMasterIdle | |
+|-----------------------------+---|
+| effEditIdle | |
+| effBeginSetProgram | |
+| effEndSetProgram | |
+| effGetNumMidiInputChannels | |
+| effGetNumMidiOutputChannels | |
+| effGetTailSize | |
+| effSetBypass | |
+| effString2Parameter | |
+|-----------------------------+---|
+| kVstAutomationReading | |
+| kVstAutomationWriting | |
+
+** DONE
+| opcode | |
+|------------------------+----|
+| | |
+|------------------------+----|
+| effSetProcessPrecision | 77 |
+|------------------------+----|
+| kVstProcessPrecision32 | 0 |
+| kVstProcessPrecision64 | 1 |
+| | |
+
+
+* AudioPluginHost
+
+** TODO
+| opcode | value | notes |
+|-------------------+-------+---------|
+| effCanBeAutomated | | |
+| effConnectInput | | |
+| effConnectOutput | | |
+| effKeysRequired | | |
+| effSetBypass | | BowEcho |
+|-------------------+-------+---------|
+| audioHost??? | 3 | |
+| audioHost??? | 13 | |
+| audioHost??? | 42 | |
+
+
+
+* REAPER
+
+** TODO
+| opcode | value | commen |
+|--------+-------+--------------------------|
+| eff??? | 19 | immer abwechselnd mit 53 |
+| eff??? | 53 | |
+|--------+-------+--------------------------|
+| | 56 | |
+| | 62 | |
+
+
+* FstHost
+
+| opcode | value | return | plugins |
+|-----------------------------+-------+----------+---------|
+| | 26 | 1 | * |
+| | 44 | 1 | JUCE |
+| effGetCurrentMidiProgram? | 63 | -1 | * |
+| effSetTotalSampleToProcess? | 73 | <ivalue> | |
+| | 78 | 1 | Digits |
+| | | | |
+
+
+* u-he
+
+- 'u-he doesn't use key [...]'
diff --git a/source/fst/fst/aeffect.h b/source/fst/fst/aeffect.h
@@ -0,0 +1,3 @@
+#pragma once
+#define FST2VST 1
+#include "fst.h"
diff --git a/source/fst/fst/aeffectx.h b/source/fst/fst/aeffectx.h
@@ -0,0 +1,3 @@
+#pragma once
+#define FST2VST 1
+#include "fst.h"
diff --git a/source/fst/fst/fst.h b/source/fst/fst/fst.h
@@ -0,0 +1,677 @@
+/*
+ * Free Studio Technologies - plugin SDK
+ * a plugin SDK that is compatible with a well-known
+ * but no longer available commercial plugin SDK.
+ *
+ * Copyright © 2019, IOhannes m zmölnig, IEM
+ *
+ * This file is part of FST
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with FST. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef FST_fst_h_
+#define FST_fst_h_
+
+#define FST_MAJOR_VERSION 0
+#define FST_MINOR_VERSION 123
+#define FST_MICRO_VERSION 0
+
+#define FST_VERSIONNUM(X, Y, Z) \
+ ((X)*10000 + (Y)*1000 + (Z))
+#define FST_VERSION FST_VERSIONNUM(FST_MAJOR_VERSION, FST_MINOR_VERSION, FST_MICRO_VERSION)
+
+/* helper macros for compiler specifics */
+#define _FST_STRING2(x) #x
+#define _FST_STRING(x) _FST_STRING2(x)
+#define _FST_PRAGMA(x) _Pragma(#x)
+#if defined(__GNUC__) || defined(__clang__)
+# define FST_WARNING(x) _FST_PRAGMA(GCC warning x)
+#elif defined _MSC_VER
+# define FST_WARNING(x) __pragma(message(__FILE__ ":" _FST_STRING(__LINE__) ": warning: " x))
+#else
+# define FST_WARNING(x)
+#endif
+
+
+/* helper macros for marking values as compatible with the original SDK */
+
+/* constants that have not yet been discovered are marked as 'deprecated'
+ * in order to get a notification during build
+ * constants we are not sure about, are marked with an EXPERIMENTAL macro
+ */
+#if defined(__GNUC__) || defined(__clang__)
+# define FST_DEPRECATE_UNKNOWN(x) x __attribute__ ((deprecated))
+#elif defined(_MSC_VER)
+# define FST_DEPRECATE_UNKNOWN(x) __declspec(deprecated) x
+/* on second thought, MSVC doesn't allow to deprecate enum values, so blow it: */
+# define FST_DONT_DEPRECATE_UNKNOWN
+#else
+# define FST_DEPRECATE_UNKNOWN(x) x
+#endif
+
+#ifdef FST_DONT_DEPRECATE_UNKNOWN
+# undef FST_DEPRECATE_UNKNOWN
+# define FST_DEPRECATE_UNKNOWN(x) x
+#endif
+
+#define FST_DEPRECATE_ENUM(x) FST_DEPRECATE_UNKNOWN(x)
+
+#define FST_UNKNOWN(x) x
+#define FST_ENUM(x, y) x = y
+#if 0
+# define FST_ENUM_EXPERIMENTAL(x, y) FST_DEPRECATE_ENUM(x) = y
+#else
+# define FST_ENUM_EXPERIMENTAL(x, y) x = y
+#endif
+#define FST_ENUM_UNKNOWN(x) FST_DEPRECATE_ENUM(x) = (100000 + __LINE__)
+
+/* name mangling */
+#ifdef FST2VST
+# define _fstEffect AEffect
+#endif
+
+# define FST_HOST_OPCODE(x, y) audioMaster##x = y
+# define FST_HOST_OPCODE_EXPERIMENTAL(x, y) FST_ENUM_EXPERIMENTAL( audioMaster##x, y)
+# define FST_HOST_OPCODE_UNKNOWN(x) FST_ENUM_UNKNOWN( audioMaster##x)
+# define FST_EFFECT_OPCODE(x, y) eff##x = y
+# define FST_EFFECT_OPCODE_EXPERIMENTAL(x, y) FST_ENUM_EXPERIMENTAL( eff##x, y)
+# define FST_EFFECT_OPCODE_UNKNOWN(x) FST_ENUM_UNKNOWN( eff##x)
+# define FST_EFFECT_CATEGORY(x, y) kPlugCateg##x = y
+# define FST_EFFECT_CATEGORY_EXPERIMENTAL(x, y) FST_ENUM_EXPERIMENTAL( kPlugCateg##x, y)
+# define FST_EFFECT_CATEGORY_UNKNOWN(x) FST_ENUM_UNKNOWN( kPlugCateg##x)
+# define FST_EFFECT_FLAG(x, y) effFlags##x = (1<<y)
+# define FST_SPEAKER(x, y) kSpeaker##x = y
+# define FST_SPEAKER_EXPERIMENTAL(x, y) FST_ENUM_EXPERIMENTAL( kSpeaker##x, y)
+# define FST_SPEAKER_UNKNOWN(x) FST_ENUM_UNKNOWN( kSpeaker##x)
+# define FST_CONST(x, y) k##x = y
+# define FST_CONSTANT(x, y) kVst##x = y
+# define FST_CONSTANT_EXPERIMENTAL(x, y) FST_ENUM_EXPERIMENTAL( kVst##x, y)
+# define FST_CONSTANT_UNKNOWN(x) FST_ENUM_UNKNOWN( kVst##x)
+# define FST_FLAG(x, y) kVst##x = (1<<y)
+# define FST_FLAG_UNKNOWN(x) FST_DEPRECATE_UNKNOWN(kVst##x) = 0
+# define FST_TYPE(x) Vst##x
+# define FST_TYPE_UNKNOWN(x) FST_UNKNOWN(Vst##x)
+
+#define kVstVersion 2400
+
+#define VSTCALLBACK
+
+ /* t_fstPtrInt: pointer sized int */
+#if defined(_WIN32) && (defined(__x86_64__) || defined (_M_X64))
+typedef long long t_fstPtrInt;
+#else
+typedef long t_fstPtrInt;
+#endif
+
+typedef int t_fstInt32; /* 32bit int */
+
+typedef enum {
+ FST_HOST_OPCODE(Automate, 0), /* IN:index, IN:fvalue, return 0 */
+ FST_HOST_OPCODE(Version, 1), /* return 2400 */
+ FST_HOST_OPCODE_EXPERIMENTAL(CurrentId, 2), /* return shellUIDToCreate */
+
+
+
+ FST_HOST_OPCODE(WantMidi, 6), /* return 0 */
+ FST_HOST_OPCODE(GetTime, 7), /* return (fstTimeInfo*) */
+ FST_HOST_OPCODE(ProcessEvents, 8), /* IN:ptr(fstEvents*), return 0 */
+
+ FST_HOST_OPCODE(TempoAt, 10), /* IN:ivalue, return (10000*BPM) */
+
+
+ /* 13: sending latency?? */
+
+ FST_HOST_OPCODE(SizeWindow, 15), /* IN:index(width), IN:value(height), return 1 */
+ FST_HOST_OPCODE(GetSampleRate, 16), /* return sampleRate */
+ FST_HOST_OPCODE(GetBlockSize, 17), /* return blockSize */
+
+
+
+
+
+ FST_HOST_OPCODE(GetCurrentProcessLevel, 23), /* return (!isRealtime)*4 */
+
+
+
+
+
+
+
+ FST_HOST_OPCODE(GetVendorString, 32), /* OUT:ptr(char[MaxVendorStrLen]), return ptr */
+ FST_HOST_OPCODE(GetProductString, 33), /* OUT:ptr(char[MaxProductStrLen]), return ptr */
+ FST_HOST_OPCODE(GetVendorVersion, 34), /* return 0x0101 */
+
+
+ FST_HOST_OPCODE(CanDo, 37), /* IN:ptr(char*), return *ptr in {"sendFstEvents", "sizeWindow",...} */
+
+
+
+
+ FST_HOST_OPCODE(UpdateDisplay, 42), /* return 1; triggers effGetProgram* */
+ FST_HOST_OPCODE(BeginEdit, 43), /* IN:index, return 0 */
+ FST_HOST_OPCODE(EndEdit, 44), /* IN:index, return 0 */
+
+
+
+
+ FST_HOST_OPCODE_UNKNOWN(CloseWindow), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(OpenWindow), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(SetIcon), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(GetParameterQuantization), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(GetNumAutomatableParameters),
+ FST_HOST_OPCODE_UNKNOWN(GetAutomationState), /* return {unsupported=0, off, read, write, readwrite} */
+
+ FST_HOST_OPCODE_UNKNOWN(GetInputLatency), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(GetOutputLatency), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(GetDirectory), /* return (char*)plugindirectory */
+ FST_HOST_OPCODE_UNKNOWN(GetLanguage), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(GetOutputSpeakerArrangement), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(OfflineGetCurrentMetaPass), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(OfflineGetCurrentPass), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(OfflineRead), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(OfflineStart), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(OfflineWrite), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(GetNextPlug), /* ?, return 0 */
+ FST_HOST_OPCODE_UNKNOWN(GetPreviousPlug), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(Idle), /* return 0 */
+ FST_HOST_OPCODE_UNKNOWN(NeedIdle), /* return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(IOChanged), /* return 0 */
+ FST_HOST_OPCODE_UNKNOWN(PinConnected), /* IN:index, IN:ivalue(isOutput), return isValidChannel */
+
+ FST_HOST_OPCODE_UNKNOWN(SetOutputSampleRate),
+ FST_HOST_OPCODE_UNKNOWN(SetTime), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(WillReplaceOrAccumulate), /* ?, return 0 */
+
+ FST_HOST_OPCODE_UNKNOWN(VendorSpecific), /* ?, return 0 */
+
+ /* the following opcodes are used by MrsWatson */
+ FST_HOST_OPCODE_UNKNOWN(OpenFileSelector),
+ FST_HOST_OPCODE_UNKNOWN(CloseFileSelector),
+ FST_HOST_OPCODE_UNKNOWN(EditFile),
+ FST_HOST_OPCODE_UNKNOWN(GetChunkFile),
+ FST_HOST_OPCODE_UNKNOWN(GetInputSpeakerArrangement),
+
+ fst_audioMasterLast /* last enum */
+} t_fstHostOpcode;;
+
+typedef enum {
+ FST_EFFECT_OPCODE(Open, 0), /* return 0 */
+ FST_EFFECT_OPCODE(Close, 1), /* return 0 */
+ FST_EFFECT_OPCODE(SetProgram, 2), /* IN:ivalue, return 0 */
+ FST_EFFECT_OPCODE(GetProgram, 3), /* return current_program */
+ FST_EFFECT_OPCODE(SetProgramName, 4), /* IN:ptr(char*), return 0 */
+ FST_EFFECT_OPCODE(GetProgramName, 5), /* OUT:ptr(char[24]), return 0 */
+
+ /* JUCE says that MaxParamStrLen is 8, but hosts allow a bit more (24) */
+ FST_EFFECT_OPCODE(GetParamLabel, 6), /* OUT:ptr(char[8]), return 0 */
+ FST_EFFECT_OPCODE(GetParamDisplay, 7), /* OUT:ptr(char[8]), return 0 */
+ FST_EFFECT_OPCODE(GetParamName, 8), /* OUT:ptr(char[8]), return 0 */
+
+ FST_EFFECT_OPCODE(SetSampleRate, 10), /* IN:fvalue, return 0 */
+ FST_EFFECT_OPCODE(SetBlockSize, 11), /* IN:ivalue, return 0 */
+ FST_EFFECT_OPCODE(MainsChanged, 12), /* IN:ivalue, return 0; (handleResumeSuspend) */
+ FST_EFFECT_OPCODE(EditGetRect, 13), /* OUT:ptr(ERect*), return ptr */
+ FST_EFFECT_OPCODE(EditOpen, 14), /* return 0 */
+ FST_EFFECT_OPCODE(EditClose, 15), /* return 0 */
+
+
+
+ FST_EFFECT_OPCODE(EditIdle, 19), /* return 0 */
+
+
+ FST_EFFECT_OPCODE(Identify, 22), /* return ByteOrder::bigEndianInt ("NvEf") 1316373862 or 1715828302 */
+ FST_EFFECT_OPCODE(GetChunk, 23), /* IN:index, OUT:ptr(void*), return size */
+ FST_EFFECT_OPCODE(SetChunk, 24), /* IN:index, IN:ivalue(size), IN:ptr(void*), return 0 */
+ FST_EFFECT_OPCODE(ProcessEvents, 25), /* IN:ptr(fstEvents*), return ((bool)MidiProcessed */
+ FST_EFFECT_OPCODE(CanBeAutomated, 26), /* (can parameter# be automated) IN:index, return 0 */
+ FST_EFFECT_OPCODE(String2Parameter, 27), /* IN:index, IN:ptr(char*), return (hasParam#) */
+
+ FST_EFFECT_OPCODE(GetProgramNameIndexed, 29), /* IN:index, OUT:ptr(char[24], return (hasProg#) */
+
+
+
+ FST_EFFECT_OPCODE(GetInputProperties, 33), /* IN:index, OUT:ptr(fstPinProperties*), return 1|0 */
+ FST_EFFECT_OPCODE(GetOutputProperties, 34), /* IN:index, OUT:ptr(fstPinProperties*), return 1|0 */
+ FST_EFFECT_OPCODE(GetPlugCategory, 35), /* return category */
+
+
+
+
+
+
+ FST_EFFECT_OPCODE(SetSpeakerArrangement, 42), /* IN:ivalue(fstSpeakerArrangement*in) IN:ptr(fstSpeakerArrangement*out) */
+
+
+ FST_EFFECT_OPCODE(GetEffectName, 45), /* OUT:ptr(char[64]), return 1 */
+
+ FST_EFFECT_OPCODE(GetVendorString, 47), /* OUT:ptr(char[64]), return 1 */
+ FST_EFFECT_OPCODE(GetProductString, 48), /* OUT:ptr(char[64]), return 1 */
+ FST_EFFECT_OPCODE(GetVendorVersion, 49), /* return version */
+ FST_EFFECT_OPCODE(VendorSpecific, 50), /* behaviour defined by vendor... */
+ FST_EFFECT_OPCODE(CanDo, 51), /* IN:ptr(char*), returns 0|1|-1 */
+
+
+
+
+
+
+ FST_EFFECT_OPCODE(GetVstVersion, 58), /* return kVstVersion */
+
+
+
+
+ FST_EFFECT_OPCODE_EXPERIMENTAL(GetCurrentMidiProgram, 63), /* return -1 */
+
+ /* we know what this does, but we don't know its name */
+ FST_DEPRECATE_UNKNOWN(fst_effGetMidiNoteName) = 66, /* IN:index=MIDIchannel, IN:ptr({int unknown, int midinote, char*buffer}), OUT:ptr.buffer) */
+
+
+ FST_EFFECT_OPCODE(GetSpeakerArrangement, 69), /* OUT:ivalue(fstSpeakerArrangement*in) OUT:ptr(fstSpeakerArrangement*out), return (!(hasAUX || isMidi)) */
+ FST_EFFECT_OPCODE(ShellGetNextPlugin, 70),
+ FST_EFFECT_OPCODE_EXPERIMENTAL(StartProcess, 71),
+ FST_EFFECT_OPCODE_EXPERIMENTAL(StopProcess, 72),
+ FST_EFFECT_OPCODE(SetTotalSampleToProcess, 73), /* return ivalue */
+
+
+
+ FST_EFFECT_OPCODE(SetProcessPrecision, 77), /* IN:ivalue(ProcessPrecision64,..), return !isProcessing */
+
+
+ FST_EFFECT_OPCODE_UNKNOWN(KeysRequired), /* return ((bool)KeyboardFocusRequireq; 59?? */
+
+
+ FST_EFFECT_OPCODE_UNKNOWN(EditDraw),
+ FST_EFFECT_OPCODE_UNKNOWN(EditMouse),
+ FST_EFFECT_OPCODE_UNKNOWN(EditSleep),
+ FST_EFFECT_OPCODE_UNKNOWN(EditTop),
+
+ FST_EFFECT_OPCODE_UNKNOWN(GetNumMidiInputChannels), /* return 16*isMidi */
+ FST_EFFECT_OPCODE_UNKNOWN(GetNumMidiOutputChannels), /* return 16*isMidi */
+
+ FST_EFFECT_OPCODE_UNKNOWN(SetBypass), /* IN:ivalue, return 0; effCanDo("bypass") */
+ FST_EFFECT_OPCODE_UNKNOWN(GetTailSize), /* return audiotailInSamples */
+
+ FST_EFFECT_OPCODE_UNKNOWN(ConnectInput),
+ FST_EFFECT_OPCODE_UNKNOWN(ConnectOutput),
+
+ FST_EFFECT_OPCODE_UNKNOWN(Idle),
+
+FST_WARNING("document origin of eff*SetProgram")
+ FST_EFFECT_OPCODE_UNKNOWN(BeginSetProgram),
+ FST_EFFECT_OPCODE_UNKNOWN(EndSetProgram),
+
+ fst_effLast, /* the enum */
+} t_fstPluginOpcode;
+
+typedef enum {
+ FST_EFFECT_FLAG(HasEditor, 0),
+
+
+
+ FST_EFFECT_FLAG(CanReplacing, 4),
+ FST_EFFECT_FLAG(ProgramChunks, 5),
+
+
+ FST_EFFECT_FLAG(IsSynth, 8),
+ FST_EFFECT_FLAG(NoSoundInStop, 9),
+
+
+ FST_EFFECT_FLAG(CanDoubleReplacing, 12),
+} t_fstEffectFlags;
+
+typedef enum {
+ FST_EFFECT_CATEGORY_EXPERIMENTAL(Unknown, 0),
+ FST_EFFECT_CATEGORY_EXPERIMENTAL(Effect, 1),
+ FST_EFFECT_CATEGORY(Synth, 2),
+ FST_EFFECT_CATEGORY(Analysis, 3),
+ FST_EFFECT_CATEGORY(Mastering, 4),
+ FST_EFFECT_CATEGORY(Spacializer, 5),
+ FST_EFFECT_CATEGORY(RoomFx, 6),
+ FST_CONST(PlugSurroundFx, 7), /* hmpf, what a stupid name */
+ FST_EFFECT_CATEGORY(Restoration, 8),
+ FST_EFFECT_CATEGORY(OfflineProcess, 9),
+ FST_EFFECT_CATEGORY(Shell, 10),
+ FST_EFFECT_CATEGORY(Generator, 11),
+
+ FST_EFFECT_CATEGORY(MaxCount, 12), /* last enum */
+} t_fstEffectCategories;
+
+typedef enum {
+ FST_SPEAKER(ArrEmpty, -1),
+ FST_SPEAKER(ArrMono, 0),
+ FST_SPEAKER(ArrStereo, 1),
+ FST_SPEAKER_UNKNOWN(ArrStereoSurround),
+ FST_SPEAKER_UNKNOWN(ArrStereoCenter),
+ FST_SPEAKER_UNKNOWN(ArrStereoSide),
+ FST_SPEAKER_UNKNOWN(ArrStereoCLfe),
+ FST_SPEAKER_UNKNOWN(Arr30Cine),
+ FST_SPEAKER_UNKNOWN(Arr30Music),
+ FST_SPEAKER_UNKNOWN(Arr31Cine),
+ FST_SPEAKER_UNKNOWN(Arr31Music),
+ FST_SPEAKER_UNKNOWN(Arr40Cine),
+ FST_SPEAKER_UNKNOWN(Arr40Music),
+ FST_SPEAKER_UNKNOWN(Arr41Cine),
+ FST_SPEAKER_UNKNOWN(Arr41Music),
+ FST_SPEAKER_UNKNOWN(Arr50),
+ FST_SPEAKER_UNKNOWN(Arr51),
+ FST_SPEAKER_UNKNOWN(Arr60Cine),
+ FST_SPEAKER_UNKNOWN(Arr60Music),
+ FST_SPEAKER_UNKNOWN(Arr61Cine),
+ FST_SPEAKER_UNKNOWN(Arr61Music),
+ FST_SPEAKER_UNKNOWN(Arr70Cine),
+ FST_SPEAKER_UNKNOWN(Arr70Music),
+ FST_SPEAKER_UNKNOWN(Arr71Cine),
+ FST_SPEAKER_UNKNOWN(Arr71Music),
+ FST_SPEAKER_UNKNOWN(Arr80Cine),
+ FST_SPEAKER_UNKNOWN(Arr80Music),
+ FST_SPEAKER_UNKNOWN(Arr81Cine),
+ FST_SPEAKER_UNKNOWN(Arr81Music),
+ FST_SPEAKER(Arr102, 28),
+ FST_SPEAKER(ArrUserDefined, -2),
+
+FST_WARNING("document origin of kSpeakerM")
+ FST_SPEAKER_EXPERIMENTAL(M, 0),
+ FST_SPEAKER(L, 1),
+ FST_SPEAKER(R, 2),
+ FST_SPEAKER_UNKNOWN(C),
+ FST_SPEAKER_UNKNOWN(Lfe),
+ FST_SPEAKER_UNKNOWN(Ls),
+ FST_SPEAKER_UNKNOWN(Rs),
+ FST_SPEAKER_UNKNOWN(Lc),
+ FST_SPEAKER_UNKNOWN(Rc),
+ FST_SPEAKER_UNKNOWN(S),
+ FST_SPEAKER_UNKNOWN(Sl),
+ FST_SPEAKER_UNKNOWN(Sr),
+ FST_SPEAKER_UNKNOWN(Tm),
+ FST_SPEAKER_UNKNOWN(Tfl),
+ FST_SPEAKER_UNKNOWN(Tfc),
+ FST_SPEAKER_UNKNOWN(Tfr),
+ FST_SPEAKER_UNKNOWN(Trl),
+ FST_SPEAKER_UNKNOWN(Trc),
+ FST_SPEAKER_UNKNOWN(Trr),
+ FST_SPEAKER_UNKNOWN(Lfe2),
+
+ FST_SPEAKER_UNKNOWN(Undefined),
+ fst_speakerLast /* last enum */
+} t_fstSpeakerArrangementType;
+
+enum { /* fstTimeInfo.flags */
+ FST_FLAG(TransportChanged, 0),
+ FST_FLAG(TransportPlaying, 1),
+ FST_FLAG(TransportCycleActive, 2),
+ FST_FLAG(TransportRecording, 3),
+ FST_FLAG_UNKNOWN(AutomationReading),
+ FST_FLAG_UNKNOWN(AutomationWriting),
+
+ FST_FLAG(NanosValid , 8),
+ FST_FLAG(PpqPosValid , 9),
+ FST_FLAG(TempoValid , 10),
+ FST_FLAG(BarsValid , 11),
+ FST_FLAG(CyclePosValid, 12),
+ FST_FLAG(TimeSigValid , 13),
+ FST_FLAG(SmpteValid , 14),
+ FST_FLAG(ClockValid , 15)
+};
+
+enum {
+/* 197782 is where the array passed at opcode:33 overflows */
+/* GVST/GChorus crashes with MaxVendorStrLen>130 */
+ FST_CONSTANT_EXPERIMENTAL(MaxProductStrLen, 128),
+ FST_CONSTANT_EXPERIMENTAL(MaxVendorStrLen, 128),
+ FST_CONSTANT_EXPERIMENTAL(MaxLabelLen, 64),
+ FST_CONSTANT_EXPERIMENTAL(MaxShortLabelLen, 8),
+ FST_CONSTANT(MaxProgNameLen, 25), // effGetProgramName
+
+ /* JUCE says that MaxParamStrLen is 8, but hosts allow a bit more (24) */
+ FST_CONSTANT(MaxParamStrLen, 8),
+
+ /* returned by audioMasterGetAutomationState: */
+ FST_FLAG_UNKNOWN(AutomationUnsupported),
+
+ /* used as t_fstPinProperties.flags */
+ FST_FLAG_UNKNOWN(PinIsActive),
+ FST_FLAG_UNKNOWN(PinUseSpeaker),
+ FST_FLAG_UNKNOWN(PinIsStereo),
+
+ /* REAPER: used with effVendorSpecific to indicate that parameter values are enums */
+ FST_CONSTANT_UNKNOWN(ParameterUsesIntStep),
+};
+
+typedef enum {
+ /* used as: t_fstTimeInfo.smpteFrameRate */
+ FST_CONSTANT_UNKNOWN(Smpte239fps),
+ FST_CONSTANT_UNKNOWN(Smpte24fps),
+ FST_CONSTANT_UNKNOWN(Smpte249fps),
+ FST_CONSTANT_UNKNOWN(Smpte25fps),
+ FST_CONSTANT_UNKNOWN(Smpte2997dfps),
+ FST_CONSTANT_UNKNOWN(Smpte2997fps),
+ FST_CONSTANT_UNKNOWN(Smpte30dfps),
+ FST_CONSTANT_UNKNOWN(Smpte30fps),
+ FST_CONSTANT_UNKNOWN(Smpte599fps),
+ FST_CONSTANT_UNKNOWN(Smpte60fps),
+ FST_CONSTANT_UNKNOWN(SmpteFilm16mm),
+ FST_CONSTANT_UNKNOWN(SmpteFilm35mm),
+} t_fstSmpteFrameRates;
+
+enum {
+ FST_CONSTANT(ProcessPrecision32, 0),
+ FST_CONSTANT(ProcessPrecision64, 1),
+};
+
+typedef enum {
+ FST_CONSTANT(MidiType, 1),
+ FST_CONSTANT(SysExType, 6)
+} t_fstEventType;
+
+enum {
+ /* returned by audioMasterGetCurrentProcessLevel: */
+ FST_CONSTANT_EXPERIMENTAL(ProcessLevelUnknown, 0),
+
+ FST_CONSTANT(ProcessLevelRealtime, 2),
+
+ FST_CONSTANT(ProcessLevelOffline, 4),
+
+FST_WARNING("document origin of ProcesslevelUser")
+ FST_CONSTANT_UNKNOWN(ProcessLevelUser), /* vstplugin~ */
+};
+
+enum {
+ /* returned by audioMasterGetLanguage: */
+ FST_CONSTANT_UNKNOWN(LangEnglish),
+};
+
+
+/* deltaFrames: used by JUCE as "timestamp" (to sort events) */
+#define FSTEVENT_COMMON \
+ t_fstEventType type; \
+ int byteSize; \
+ int deltaFrames; \
+ int flags
+
+typedef struct fstEvent_ {
+ FSTEVENT_COMMON;
+} t_fstEvent;
+
+typedef struct fstMidiEvent_ {
+ FSTEVENT_COMMON; /* @0x00-0x0b */
+ /* FIXXXME: unknown member order and size */
+ /* REAPER: sets everything to 0
+ * JMZ: size is set to occupy 12bytes (on amd64) for now
+ */
+ FST_UNKNOWN(short) noteLength;
+ FST_UNKNOWN(short) noteOffset;
+ FST_UNKNOWN(short) detune;
+ FST_UNKNOWN(short) noteOffVelocity;
+ unsigned char midiData[4]; /* @0x18 */
+ FST_UNKNOWN(short) reserved1;
+ FST_UNKNOWN(short) reserved2;
+} FST_UNKNOWN(t_fstMidiEvent);
+
+typedef struct fstMidiSysexEvent_ {
+ FSTEVENT_COMMON;
+ /* FIXXXME: unknown member order */
+ t_fstPtrInt dumpBytes; /* size of sysexDump */
+ t_fstPtrInt resvd1; /* ? */
+ char*sysexDump; /* heap allocated memory for sysex-data */
+ t_fstPtrInt resvd2; /* ? */
+} t_fstSysexEvent;
+
+typedef struct fstEvents_ {
+ int numEvents;
+ FST_UNKNOWN(t_fstPtrInt _pad);
+ t_fstEvent*events[];
+} t_fstEvents;
+
+typedef struct fstSpeakerProperties_ {
+ /* azimuth+elevation+radius+reserved+name take up 80 bytes
+ * if the first 4 are doubles, name is char[16]
+ * if they are floats, name is char[48]
+ */
+ FST_UNKNOWN(float) azimuth; /* float? origin:MrsWatson */
+ FST_UNKNOWN(float) elevation; /* float? origin:MrsWatson */
+ FST_UNKNOWN(float) radius; /* float? origin:MrsWatson */
+ FST_UNKNOWN(float) reserved; /* type? origin:MrsWatson */
+ FST_UNKNOWN(char name[64]);
+ int type;
+ char _padding2[28];
+} FST_UNKNOWN(t_fstSpeakerProperties);
+
+typedef struct fstSpeakerArrangement_ {
+ int type;
+ int numChannels;
+ t_fstSpeakerProperties speakers[];
+} t_fstSpeakerArrangement;
+
+typedef struct fstTimeInfo_ {
+ double samplePos; /* OK */
+ double sampleRate;/* = rate; // OK */
+ double nanoSeconds; /* OK */
+ /* ppq: Pulses Per Quaternote */
+ double ppqPos; /* (double)position.ppqPosition; // OK */
+ double tempo; /* OK */
+ double barStartPos; /* (double)ppqPositionOfLastBarStart; // OK */
+ double cycleStartPos; /* (double)ppqLoopStart; // OK */
+ double cycleEndPos; /* (double)ppqLoopEnd; // OK */
+ int timeSigNumerator; /* OK */
+ int timeSigDenominator; /* OK */
+
+ int FST_UNKNOWN(currentBar), FST_UNKNOWN(magic); /* we just made these fields up, as their values seem to be neither flags nor smtp* */
+
+FST_WARNING("document origin of samplesToNextClock")
+ /* this used to be '_pad' */
+ FST_UNKNOWN(int) samplesToNextClock;/* ? */
+
+ FST_UNKNOWN(int) flags;/* = Vst2::kVstNanosValid // ? */
+ FST_UNKNOWN(int) smpteFrameRate; /* int32 // ? */
+ FST_UNKNOWN(int) smpteOffset; /* int32 // ? */
+} FST_UNKNOWN(t_fstTimeInfo);
+
+typedef struct fstPinProperties_ {
+ char label[64];
+ FST_UNKNOWN(int) flags; /* ? kVstPinIsActive | kVstPinUseSpeaker | kVstPinIsStereo */
+ FST_UNKNOWN(int) arrangementType; /* ? */
+ char shortLabel[8];
+} FST_UNKNOWN(t_fstPinProperties);
+
+
+struct _fstEffect;
+
+/* t_fstPtrInt dispatcher(effect, opcode, index, ivalue, ptr, fvalue); */
+typedef t_fstPtrInt (*AEffectDispatcherProc)(struct _fstEffect*, int, int, t_fstPtrInt, void* const, float);
+/* void setParameter(effect, index, fvalue); */
+typedef void (*AEffectSetParameterProc)(struct _fstEffect*, int, float);
+/* float getParameter(effect, index); */
+typedef float (*AEffectGetParameterProc)(struct _fstEffect*, int);
+/* void process(effect, indata, outdata, frames); */
+typedef void (*AEffectProcessProc)(struct _fstEffect*, float**, float**, int);
+/* void processDoubleReplacing(effect, indata, outdata, frames); */
+typedef void (*AEffectProcessDoubleProc)(struct _fstEffect*, double**, double**, int);
+
+typedef FST_UNKNOWN(AEffectDispatcherProc) audioMasterCallback;
+
+typedef struct _fstEffect {
+ t_fstInt32 magic; /* @0 0x56737450, aka 'VstP' */
+ /* auto-padding in place */
+ AEffectDispatcherProc dispatcher; /* (AEffect*, Vst2::effClose, 0, 0, 0, 0); */
+ AEffectProcessProc process;
+ AEffectSetParameterProc setParameter; /* (Aeffect*, int, float) */
+ AEffectGetParameterProc getParameter; /* float(Aeffect*, int) */
+
+ t_fstInt32 numPrograms;
+ t_fstInt32 numParams;
+ t_fstInt32 numInputs;
+ t_fstInt32 numOutputs;
+
+ FST_UNKNOWN(t_fstPtrInt) flags; /* ?? */
+ FST_UNKNOWN(t_fstPtrInt) FST_UNKNOWN(resvd1); /* ?? */
+ FST_UNKNOWN(t_fstPtrInt) FST_UNKNOWN(resvd2); /* ?? */
+ FST_UNKNOWN(t_fstInt32) FST_UNKNOWN(initialDelay); /* ??; latency in samples */
+ char _pad2[8];
+
+ float float1;
+ void* object;
+ void*user;
+ t_fstInt32 uniqueID; /* @112 */
+ t_fstInt32 version;
+
+ AEffectProcessProc processReplacing;
+ AEffectProcessDoubleProc processDoubleReplacing;
+} FST_UNKNOWN(t_fstEffect);
+
+typedef struct _fstRectangle {
+ short top;
+ short left;
+ short bottom;
+ short right;
+} t_fstRectangle;
+
+
+typedef t_fstHostOpcode AudioMasterOpcodesX;
+
+typedef t_fstEvent FST_TYPE(Event);
+typedef t_fstMidiEvent FST_TYPE(MidiEvent);
+typedef t_fstSysexEvent FST_TYPE(MidiSysexEvent);
+typedef t_fstEvents FST_TYPE(Events);
+typedef t_fstSpeakerProperties FST_TYPE(SpeakerProperties);
+typedef t_fstSpeakerArrangement FST_TYPE(SpeakerArrangement);
+typedef t_fstTimeInfo FST_TYPE(TimeInfo);
+typedef t_fstSmpteFrameRates FST_TYPE(SmpteFrameRate);
+typedef t_fstPinProperties FST_TYPE(PinProperties);
+typedef t_fstEffectCategories FST_TYPE(PlugCategory);
+typedef t_fstEffectFlags FST_TYPE(AEffectFlags);
+typedef t_fstEffect AEffect;
+typedef t_fstRectangle ERect;
+
+typedef t_fstPtrInt VstIntPtr;
+typedef t_fstInt32 VstInt32;
+
+const int FST_CONST(EffectMagic, 0x56737450);
+
+/* see https://github.com/steinbergmedia/vst3_pluginterfaces/blob/efcfbf8019a2f1803b7be9936a81124abb583507/base/futils.h#L91-L95
+ * for a GPL-v3 definition of CCONST
+ */
+#ifndef CCONST
+# define CCONST(a,b,c,d) ((((unsigned char)a)<<24) + (((unsigned char)b)<<16) + (((unsigned char)c)<<8) + ((unsigned char)d))
+#endif
+
+#endif /* FST_fst_h_ */
diff --git a/source/fst/src/FstHost/FstHost.cpp b/source/fst/src/FstHost/FstHost.cpp
@@ -0,0 +1,565 @@
+#include "fst.h"
+#include "fst_utils.h"
+#include <stdio.h>
+
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <dlfcn.h>
+#endif
+
+#include <string>
+#include <string.h>
+
+char effectname[1024];
+
+float db2slider(float f) {
+ return f;
+}
+float slider2db(float f) {
+ return f;
+}
+
+static size_t curOpCode = -1;
+
+t_fstPtrInt dispatch (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ if(effect) {
+ effect->resvd2 = opcode;
+ return effect->dispatcher(effect, opcode, index, ivalue, ptr, fvalue);
+ }
+ return 0xDEAD;
+}
+t_fstPtrInt dispatch_v (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ return dispatch_effect(effectname, effect->dispatcher, effect, opcode, index, ivalue, ptr, fvalue);
+}
+
+t_fstPtrInt dispatch_v1 (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ if(effect) {
+ char opcodestr[256];
+ t_fstPtrInt result = effect->dispatcher(effect, opcode, index, ivalue, ptr, fvalue);
+ if(result)
+ printf("AEffect.dispatch(%s, %s, %d, %lu, %p, %f) => %d\n"
+ , effectname
+ , effCode2string(opcode, opcodestr, 255), index, ivalue, ptr, fvalue
+ , result
+ );
+ return result;
+ }
+ return 0xDEAD;
+}
+
+t_fstPtrInt dispatch_v0 (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ if(effect) {
+ char opcodestr[256];
+ t_fstPtrInt result = effect->dispatcher(effect, opcode, index, ivalue, ptr, fvalue);
+ printf("AEffect.dispatch(%s, %s, %d, %lu, %p, %f) => %d\n"
+ , effectname
+ , effCode2string(opcode, opcodestr, 255), index, ivalue, ptr, fvalue
+ , result
+ );
+ return result;
+ }
+ return 0xDEAD;
+}
+
+
+t_fstPtrInt dispatcher (AEffect* effect, int opcode, int index, t_fstPtrInt value, void*ptr, float opt) {
+ char sbuf[256] = {0};
+ printf("FstHost::dispatcher[%d]", effect?effect->resvd2:-1);
+ printf("(%s, %d, %d, %p, %f)\n",
+ hostCode2string(opcode, sbuf, 255),
+ index, (int)value,
+ ptr, opt);
+ if(ptr) {
+ char *str=(char*)ptr;
+ if(*str) {
+ printf("\t'%.*s'\n", 512, str);
+ } else
+ printf("\t<nil>\n");
+ }
+ switch(opcode) {
+ case (int)(0xDEADBEEF):
+ return (t_fstPtrInt)db2slider;
+ case audioMasterCurrentId:
+ return 0xDEAD;
+ case audioMasterVersion:
+ printf("MasterVersion\n");
+ return 2400;
+ case audioMasterAutomate:
+ printf("automate parameter[%d] to %f\n", index, opt);
+ break;
+ case audioMasterGetProductString:
+ for(size_t i=0; i<kVstMaxProductStrLen; i++) {
+ ((char*)ptr)[i] = 64+i%60;
+ }
+ strncpy((char*)ptr, "FstProduct?", kVstMaxProductStrLen);
+ return 1;
+ break;
+ case audioMasterGetVendorString:
+ strncpy((char*)ptr, "FstVendor?", kVstMaxVendorStrLen);
+ return 1;
+ case 42:
+ return 0;
+ default:
+ printf("\tdyspatcher(%p, %d, %d, %d, %p, %f);\n", effect, opcode, index, value, ptr, opt);
+ //printf("(%p, %x, %x, %d, %p, %f);\n", effect, opcode, index, value, ptr, opt);
+ do {
+ char *str=(char*)ptr;
+ if(str && *str) {
+ printf("\t'%.*s'\n", 512, str);
+ }
+ } while(0);
+ break;
+ }
+ return 0;
+}
+
+void test_effCanDo(AEffect*effect, const char*feature) {
+ dispatch_v(effect, effCanDo, 0, 0, (void*)feature, 0.000000);
+}
+
+void test_setSpeakerArrangement(AEffect*effect) {
+ VstSpeakerArrangement*iarr=(VstSpeakerArrangement*)calloc(1, sizeof(VstSpeakerArrangement));
+ VstSpeakerArrangement*oarr=(VstSpeakerArrangement*)calloc(1, sizeof(VstSpeakerArrangement));
+ dispatch_v(effect, effSetSpeakerArrangement, 0, (t_fstPtrInt)iarr, oarr, 0.);
+}
+void test_getSpeakerArrangement(AEffect*effect) {
+ VstSpeakerArrangement*iarr=0, *oarr=0;
+ dispatch_v(effect, effGetSpeakerArrangement, 0, (t_fstPtrInt)(&iarr), &oarr, 0.);
+ printf("gotSpeakerArrangements: %p %p: %d\n", iarr, oarr, (char*)oarr-(char*)iarr);
+}
+void test_SpeakerArrangement1(AEffect*effect) {
+// test_setSpeakerArrangement(effect);
+ test_getSpeakerArrangement(effect);
+}
+
+void test_opcode3334(AEffect*effect) {
+ size_t opcode = 0;
+ VstPinProperties vpp;
+ t_fstPtrInt res = 0;
+ for(int chan=0; chan<effect->numInputs; chan++) {
+ memset(&vpp, 0, sizeof(vpp));
+ opcode = effGetInputProperties;
+ printf("\ntrying: %d [channel:%d]\n", opcode, chan);
+ res = dispatch (effect, opcode, chan, 0, &vpp, 0);
+ printf("returned %lu\n", res);
+ if(res)
+ print_pinproperties(&vpp);
+ }
+ for(int chan=0; chan<effect->numOutputs; chan++) {
+ opcode = effGetOutputProperties;
+ memset(&vpp, 0, sizeof(vpp));
+ printf("\ntrying: %d [channel:%d]\n", opcode, chan);
+ res = dispatch (effect, opcode, chan, 0, &vpp, 0);
+ printf("returned %lu\n", res);
+ if(res) {
+ print_pinproperties(&vpp);
+ //print_hex(&vpp, sizeof(vpp));
+ }
+ }
+}
+
+static float test_setParameterS(AEffect*effect, size_t opcode, int index, char*str) {
+ dispatch_v(effect, opcode, index, 0, str, 0.f);
+ return effect->getParameter(effect, index);
+}
+
+static void test_setParameter(AEffect*effect) {
+ int index = 0;
+ float value = 0.666;
+ printf("testing get/set Parameters for %p..................\n", effect);
+ effect->setParameter(effect, index, value);
+ printf("setParameter(%d, %f)\n", index, value);
+ value = effect->getParameter(effect, index);
+ printf("getParameter(%d) -> %f\n", index, value);
+ for(size_t opcode = 9; opcode < 65536; opcode++) {
+ if(effEditOpen==opcode)continue;
+ if(effProcessEvents==opcode)continue;
+ if(42==opcode)continue;
+ if(69==opcode)continue;
+ char buf[512];
+ snprintf(buf, 511, "%s", "0.2");
+ buf[511] = 0;
+ printf("\ttesting#%d\n", opcode);
+ fflush(stdout);
+ value = test_setParameterS(effect, opcode, 0, buf);
+ printf("\t#%d: '%s' -> %f\n", opcode, buf, value);
+ fflush(stdout);
+ }
+}
+
+static void test_opcode42(AEffect*effect) {
+ VstSpeakerArrangement setarr[10], getarr[10];
+ for(size_t i=0; i<10; i++) memset(setarr+i, 0, sizeof(VstSpeakerArrangement));
+ setarr[0].type = setarr[1].type = 0x1;
+ setarr[0].numChannels = 2;
+ setarr[1].numChannels = 2;
+ dispatch_v(effect, 42, 0, (t_fstPtrInt)(setarr+0), (setarr+1), 0.f);
+ print_hex(setarr+0, 8);
+ print_hex(setarr+1, 8);
+
+ printf("-----------------------------\n");
+ for(size_t opcode=69; opcode<70; opcode++) {
+ VstSpeakerArrangement *arrptr[2] = {0,0};
+ if(42 == opcode)continue;
+ if(effGetEffectName==opcode)continue; if(effGetVendorString==opcode)continue; if(effGetProductString==opcode)continue;
+ dispatch_v(effect, opcode, 0, (t_fstPtrInt)(arrptr+0), arrptr+1, 0.f);
+ print_hex(arrptr[0], 8);
+ print_hex(arrptr[1], 8);
+ }
+}
+
+
+bool skipOpcode(size_t opcode) {
+
+ switch(opcode) {
+ case 1:
+#if PLUGIN_JUCE || PLUGIN_DIGITS
+ /* Digits plugin & JUCE plugins */
+ case 3:
+#endif
+ //case 4:
+
+ //case 5: /* program name [?] */
+
+ //case 10: /* AM_AudioMan::reset() */
+ //case 11: /* blocksize */
+ //case 12: /* AM_VST_base::suspend () */
+ case 13: /* AM_VST_Editor::getRect 1200 x 600 */
+ case 14: /* AM_VST_Editor::open, exits with 1! */
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 29:
+ case 33:
+ case 34:
+ case 35:
+#if PLUGIN_JUCE
+ /* JUCE plugins */
+ case 42:
+ case 44:
+#endif
+ case 45:
+ case 47: /* AM_VST_base::getVendorString (char* text) */
+ case 48:
+ case 49:
+ case 51:
+ case 58:
+ //case 59: /* u-he plugin doesnt use key, returns false (VST) or jumps to default key handler (WindowProc) */
+ case 63:
+ case 69:
+ case 71:
+ case 72:
+ //case 73:
+ return true;
+ default: break;
+ }
+ return false;
+}
+
+void test_opcodes(AEffect*effect, size_t endopcode=78, size_t startopcode=10) {
+ printf("testing dispatcher\n");
+#if 0
+ for(size_t opcode=0; opcode < 10; opcode++) {
+ for(int i=0; i<effect->numPrograms; i++) {
+ char buffer[512] = { 0 };
+ t_fstPtrInt res = dispatch (effect, opcode, i, i, buffer, i);
+ const char*str = (const char*)res;
+ printf("program#%d[%d=0x%X]: %.*s\n", i, res, res, 32, str);
+ if(*buffer)
+ printf("\t'%.*s'\n", 512, buffer);
+ }
+ }
+ return 0;
+#endif
+ // endopcode = 0xDEADF00D+1;
+ for(size_t i=startopcode; i<endopcode; i++) {
+ curOpCode = i;
+ if(!(i%65536)) {
+ printf("=== mark %d ===\n", i>>16);
+ fflush(stdout);
+ }
+ if (skipOpcode(i)
+#ifdef NOSKIP
+ && (NOSKIP != i)
+#endif
+ ) {
+ printf("skipping: %d\n", i);
+ continue;
+ }
+ //printf("\ntrying: %d\n", i);
+ char buffer[200000] = { 0 };
+ snprintf(buffer, 511, "name%d", i);
+ t_fstPtrInt res = dispatch (effect, i, 0, 0, buffer, 0);
+ if(res || (buffer && *buffer))
+ printf("\ntried: %d\n", i);
+
+
+ if(res) {
+ const char*str = (const char*)res;
+ printf("\t[%d=0x%X]: %.*s\n", i, res, res, 32, str);
+ }
+ if(*buffer)
+ printf("\tbuffer '%.*s'\n", 512, buffer);
+ switch(i) {
+ default: break;
+ case 4:
+ print_hex(buffer, 16);
+ }
+ fstpause();
+ }
+ do {
+ char buffer[200000] = { 0 };
+ t_fstPtrInt res = dispatch (effect, 5, 0, 0, buffer, 0);
+ printf("gotProgName: %.*s\n", 20, buffer);
+ } while(0);
+ printf("tested dispatcher with opcodes %u..%u\n", startopcode, endopcode);
+}
+
+bool skipOpcodeJUCE(size_t opcode) {
+ switch(opcode) {
+ default: break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 13: case 14: case 15:
+ case 42:
+ return true;
+ }
+ return false;
+}
+
+void test_SpeakerArrangement0(AEffect*effect) {
+ VstSpeakerArrangement*arr[2];
+ dispatch_v(effect, effGetSpeakerArrangement, 0,
+ (t_fstPtrInt)(&arr[0]), &arr[1], 0.);
+ printf("input : %p\n", arr[0]);
+ printf("output: %p\n", arr[1]);
+}
+
+void test_setChunk(AEffect*effect) {
+ t_fstPtrInt data=0;
+ t_fstPtrInt size=0;
+ int index = 0;
+ /* get data */
+ size = dispatch(effect, effGetChunk, index, 0, &data, 0.f);
+ printf("index#%d: got %d bytes @ 0x%X\n", index, size, data);
+
+ index = 1;
+ size = dispatch(effect, effGetChunk, index, 0, &data, 0.f);
+ printf("index#%d: got %d bytes @ 0x%X\n", index, size, data);
+
+ index = 0;
+ t_fstPtrInt result = dispatch(effect, effSetChunk, index, size, &data, 0.f);
+ printf("index#%d: setChunk[%d] returned %lu\n", index, (int)effSetChunk, result);
+ size = dispatch(effect, effGetChunk, index, 0, &data, 0.f);
+ printf("index#%d: got %d bytes @ 0x%X\n", index, size, data);
+}
+
+void test_opcode23(AEffect*effect) {
+ size_t opcode = 23;
+ int index = 0;
+
+ t_fstPtrInt*buffer[8] = {0};
+ printf("testing OP:%d\n", opcode);
+ t_fstPtrInt result = dispatch(effect, opcode, index, 0, buffer, 0.f);
+ printf("\tresult |\t%lu 0x%lX\n", result, result);
+ if(*buffer) {
+ printf("\tbuffer '%.*s'\n", 512, (char*)*buffer);
+ }
+ //print_hex(*buffer, result);
+}
+
+void test_opcode56(AEffect*effect) {
+ size_t opcode = 56;
+ const size_t bufsize = 1024;
+ char*buffer[bufsize] = {0};
+ for(size_t i=0; i<sizeof(bufsize); i++) {
+ buffer[i] = 0;
+ }
+
+ printf("testing OP:%d\n", opcode);
+ t_fstPtrInt result = dispatch_v(effect, opcode, 0, 0, (void*)0x1, 0.f);
+ printf("\tresult |\t%lu 0x%lX\n", result, result);
+ if(*buffer) {
+ printf("\tbuffer '%.*s'\n", bufsize, (char*)buffer);
+ }
+ print_hex(buffer, bufsize);
+}
+
+void test_opcode29(AEffect*effect) {
+ for(int i=0; i<effect->numPrograms; i++) {
+ size_t opcode = 29; //effGetProgramNameIndexed;
+ char buffer[200] = { 0 };
+ t_fstPtrInt result = dispatch(effect, opcode, i, 0, buffer, 0.f);
+ printf("opcode:%d index:%d -> %lu", opcode, i, result);
+ if(*buffer) {
+ printf("\tbuffer '%.*s'\n", 512, buffer);
+ }
+ }
+}
+
+void test_opcodesJUCE(AEffect*effect) {
+ for(size_t opcode=16; opcode<63; opcode++) {
+ if(skipOpcodeJUCE(opcode))continue;
+ char buffer[200] = { 0 };
+ t_fstPtrInt result = dispatch(effect, opcode, 0, 0, buffer, 0.f);
+ if(result || *buffer)
+ printf("tested %d", opcode);
+ if(result)
+ printf("\t|\t%lu 0x%lX", result, result);
+ if(*buffer) {
+ printf("\t|\tbuffer '%.*s'", 512, buffer);
+ //print_hex(buffer, 16);
+ }
+ if(result || *buffer)
+ printf("\n");
+ }
+}
+void test_opcode25(AEffect*effect) {
+ const unsigned char midi[4] = {0x90, 0x40, 0x7f, 0};
+ VstEvents*ves = create_vstevents(midi);
+ //print_events(ves);
+ dispatch_v(effect, effProcessEvents, 0, 0, ves, 0.);
+}
+static float** makeFSamples(size_t channels, size_t frames) {
+ float**samples = new float*[channels];
+ for(size_t i=0; i<channels; i++) {
+ samples[i] = new float[frames];
+ float*samps=samples[i];
+ for(size_t j=0; j<frames; j++) {
+ samps[j] = 0.f;
+ }
+ }
+ return samples;
+}
+
+
+void test_unknown(AEffect*effect) {
+ char opcodestr[256];
+ fstpause(0.5);
+ int index = 0;
+ t_fstPtrInt ivalue = 0;
+ void*ptr = 0;
+ float fvalue = 0;
+ for(t_fstPtrInt opcode=2; opcode<128; opcode++) {
+ if(effKnown(opcode))
+ continue;
+ dispatch_v1(effect, opcode, index, ivalue, ptr, fvalue);
+ fstpause(0.01);
+ }
+
+ fstpause(0.5);
+}
+
+
+
+void test_reaper(AEffect*effect) {
+ t_fstPtrInt ret=0;
+ char strbuf[1024];
+ const int blockSize = 512;
+ float**insamples = makeFSamples(effect->numInputs, blockSize);
+ float**outsamples = makeFSamples(effect->numOutputs, blockSize);
+
+ printf("vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n");
+ dispatch_v(effect, effGetPlugCategory, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effSetSampleRate, 0, 0, 000, 44100.000000);
+ dispatch_v(effect, effSetBlockSize, 0, blockSize, 000, 0.000000);
+
+ strbuf[0]=0;
+ dispatch_v(effect, effGetEffectName, 0, 0, strbuf, 0.000000);
+ strbuf[0] = 0;
+ dispatch_v(effect, effGetVendorString, 0, 0, strbuf, 0.000000);
+
+ test_effCanDo(effect, "hasCockosNoScrollUI");
+ test_effCanDo(effect, "wantsChannelCountNotifications");
+ //test_opcode42(effect);
+ test_effCanDo(effect, "hasCockosExtensions");
+
+ dispatch_v(effect, effGetVstVersion, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 1, 000, 0.000000);
+ dispatch_v(effect, effStartProcess, 0, 0, 000, 0.000000);
+
+ test_effCanDo(effect, "receiveVstEvents");
+ test_effCanDo(effect, "receiveVstMidiEvents");
+
+ dispatch_v(effect, effGetPlugCategory, 0, 0, 000, 0.000000);
+
+ test_effCanDo(effect, "sendVstEvents");
+ test_effCanDo(effect, "sendVstMidiEvents");
+
+ dispatch_v(effect, effGetProgram, 0, 0, 000, 0.000000);
+
+ ret = dispatch(effect, effGetChunk, 0, 0, strbuf, 0.000000);
+ dispatch_v(effect, effSetProgram, 0, 1, 000, 0.000000);
+ strbuf[0] = 0;
+ dispatch_v(effect, effGetProgramName, 0, 0, strbuf, 0.000000);
+ fflush(stderr); fflush(stdout);
+// fstpause(2.);
+ test_opcode56(effect);
+
+ dispatch_v(effect, effGetProgram, 0, 0, 000, 0.000000);
+
+ printf("=============PROC==============================\n");
+ effect->processReplacing(effect, insamples, outsamples, blockSize);
+ test_opcode25(effect);
+ effect->processReplacing(effect, insamples, outsamples, blockSize);
+ printf("==============================================\n");
+ dispatch_v(effect, effMainsChanged, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 1, 000, 0.000000);
+ printf("==============================================\n");
+ dispatch_v(effect, effMainsChanged, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 1, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 1, 000, 0.000000);
+ dispatch_v(effect, effStopProcess, 0, 0, 000, 0.000000);
+ dispatch_v(effect, effMainsChanged, 0, 0, 000, 0.000000);
+ printf("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n");
+}
+
+int test_plugin(const char*filename) {
+ t_fstMain*vstmain = fstLoadPlugin(filename);
+ if(!vstmain)return printf("'%s' was not loaded\n", filename);
+ AEffect*effect = vstmain(&dispatcher);
+ printf("instantiated effect %p\n", effect);
+ snprintf(effectname, 1024, filename);
+ if(!effect)return printf("unable to instantiate plugin from '%s'\n", filename);
+ print_aeffect(effect);
+ //dump_data(filename, effect, 160);
+ if(effect->magic != 0x56737450) return printf("magic failed: 0x%08X", effect->magic);
+ dispatch_v(effect, effOpen, 0, 0, 000, 0.000000);
+ //print_aeffect(effect);
+ //test_reaper(effect); return 0;
+ //test_opcode29(effect); return 0;
+ test_opcode3334(effect);
+ //test_opcodesJUCE(effect);
+ //test_opcodes(effect, 78);
+
+ //test_unknown(effect);
+ test_SpeakerArrangement1(effect);
+ dispatch_v(effect, effClose, 0, 0, 000, 0.000000);
+ return 0;
+}
+
+int usage(const char*progname) {
+ fprintf(stderr, "usage: %s <pluginfile> [<pluginfile> ...]\n", progname);
+ fprintf(stderr, ""
+ "\n"
+ "\tattempts to load each <pluginfile> as a plugin\n"
+ "\tand runs some basic tests\n"
+ "\n"
+ );
+ return 1;
+}
+
+int main(int argc, const char*argv[]) {
+ if(argc <= 1)
+ return usage(argv[0]);
+ for(int i=1; i<argc; i++) {
+ test_plugin(argv[i]);
+ }
+ return 0;
+}
diff --git a/source/fst/src/FstHost/Makefile b/source/fst/src/FstHost/Makefile
@@ -0,0 +1,16 @@
+LIBS=-ldl -lrt
+
+FST_CPPFLAGS=-I../../fst -I.. -DFST_DONT_DEPRECATE_UNKNOWN=1
+FST_CXXFLAGS=-g -O0
+
+archs: FstHost.32 FstHost.64
+
+FstHost.32: ARCHFLAGS=-m32
+FstHost.64: ARCHFLAGS=-m64
+
+FstHost FstHost.32 FstHost.64: FstHost.cpp ../../fst/fst.h ../fst_utils.h
+ $(CXX) $(FST_CPPFLAGS) $(CPPFLAGS) $(FST_CXXFLAGS) $(CXXFLAGS) $(ARCHFLAGS) $< -o $@ $(LIBS)
+
+.PHONY: clean archs default
+clean:
+ rm -f FstHost FstHost.32 FstHost.64
diff --git a/source/fst/src/FstHost/Makefile.nt b/source/fst/src/FstHost/Makefile.nt
@@ -0,0 +1,16 @@
+LIBS=
+
+FST_CPPFLAGS=-I../../fst -I.. -DFST_DONT_DEPRECATE_UNKNOWN=1
+FST_CXXFLAGS=-g -O0
+
+archs: w32/FstHost.exe w64/FstHost.exe
+
+w32/FstHost.exe: CXX=i686-w64-mingw32-g++
+w64/FstHost.exe: CXX=x86_64-w64-mingw32-g++
+
+FstHost.exe w32/FstHost.exe w64/FstHost.exe: FstHost.cpp ../../fst/fst.h ../fst_utils.h
+ $(CXX) $(FST_CPPFLAGS) $(CPPFLAGS) $(FST_CXXFLAGS) $(CXXFLAGS) $(ARCHFLAGS) $< -o $@ $(LIBS)
+
+.PHONY: clean archs default
+clean:
+ rm -f *.exe */*.exe
diff --git a/source/fst/src/FstPlugin/FstPlugin.cpp b/source/fst/src/FstPlugin/FstPlugin.cpp
@@ -0,0 +1,633 @@
+#include "fst.h"
+#include "fst_utils.h"
+
+#include <cstdio>
+#include <cstring>
+#include <iostream>
+
+typedef void (t_fun0)(void);
+
+static AEffectDispatcherProc dispatch = 0;
+static int curProgram = 0;
+
+static float parameters[3];
+static ERect editorBounds = {0, 0, 320, 240};
+
+static char chunk[] = "This is the chunk for the FstPlugin.";
+
+static VstEvents*s_ves;
+static VstMidiSysexEvent s_sysex;
+static VstMidiEvent s_midi;
+static unsigned char s_sysexDump[] = {0xF0, 0x01, 0x02, 0x03, 0x04, 0x03, 0x02, 0x01, 0xF7};
+static unsigned char s_midiDump[] = {0x80, 0x40, 0x0, 0};
+
+void crash() {
+ t_fun0*f=0;
+ /* crash */
+ fflush(stdout);
+ fflush(stderr);
+ f();
+}
+
+void print_struct7(AEffect* effect) {
+#if 0
+ auto *str = (double*)dispatch(effect, 7, 0, 65024, 0, 0.);
+ for(size_t i=0; i<96/sizeof(*str); i++)
+ std::cout << " " << str[i];
+ std::cout << std::endl;
+#else
+ auto *vti = (VstTimeInfo*)dispatch(effect, 7, 0, 65024, 0, 0.);
+ print_timeinfo(vti);
+#endif
+}
+
+/* send data back to host: with printout */
+t_fstPtrInt dispatch_v (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ bool doprint=true;
+ switch(opcode) {
+ default:
+ doprint=true;
+ break;
+ case audioMasterGetCurrentProcessLevel:
+ doprint=false;
+ break;
+ }
+ if(effect) {
+ if(doprint) {
+ char opcodestr[256];
+ printf("FstPlugin::plugin2host(%p, %s, %d, %lu, %p, %f) => ", effect, hostCode2string(opcode, opcodestr, 255), index, ivalue, ptr, fvalue);
+ }
+ t_fstPtrInt result = dispatch(effect, opcode, index, ivalue, ptr, fvalue);
+ if(doprint)
+ printf("FstPlugin::plugin2host: %lu (0x%lX)\n", result, result);
+ return result;
+ }
+ return 0xBEEF;
+}
+
+void test_opcodes(AEffect*effect, size_t toopcode = 100, size_t fromopcode=0) {
+ printf("testing host's dispatcher\n");
+ for(size_t opcode=fromopcode; opcode<toopcode; opcode++) {
+ char buf[1024] = {0};
+ snprintf(buf, 1023, "%s", "fudelDudelDa");
+ dispatch_v(effect, opcode, 0, 0, buf, 0.f);
+#if 0
+ if(*buf) {
+ printf("\t'%.*s'\n", 1024, buf);
+ }
+#endif
+ }
+ printf("tested hosts's dispatcher with opcodes %d..%d\n", fromopcode, toopcode);
+}
+
+static void test_gettime_(AEffect*eff, t_fstInt32 flags) {
+ printf("getTime -> 0x%04X\n", flags);
+ VstTimeInfo*t=(VstTimeInfo*)dispatch(eff, audioMasterGetTime, 0, flags, 0, 0.f);
+ print_timeinfo(t);
+
+}
+
+static void test_gettime(AEffect*eff) {
+ t_fstInt32 flags = 0xFFFFFFFF;
+ //flags = 0x2E02;
+ //flags |= (1<< 8); // kVstNanosValid
+ //flags |= (1<<14); // kVstSmpteValid (Current time in SMPTE format)
+ //flags |= (1<<15); // kVstClockValid (Sample frames until next clock)
+#ifdef TIME_FLAG
+ flags = (1<<TIME_FLAG);
+#endif
+
+ test_gettime_(eff, flags);
+}
+
+
+static int test_opcode35(AEffect*eff) {
+#ifdef FST_CATEGORY
+ return FST_CATEGORY;
+#endif
+ return kPlugSurroundFx;
+}
+
+static int test_opcode70(AEffect*eff, char*ptr) {
+ static int count = 0;
+ count++;
+ if(count < 5) {
+ snprintf(ptr, 128, "shell%d", count);
+ return count;
+ }
+ return 0;
+}
+
+static int test_opcode66(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ /* this opcodes set the names of they keys of the virtual MIDI-keyboard in REAPER... */
+ //printf("opcode:%d\n", opcode);
+ //print_numbers((int*)ptr, 2);
+ char*str=(char*)ptr + 8;
+ int*iptr=(int*)ptr;
+ //printf("KEY: %d\t%d\n", iptr[0], iptr[1]);
+ snprintf(str, 16, "key[%d]%d=", iptr[0], iptr[1]);
+ for(size_t i=0; i<32; i++) {
+ //str[i+8]=i+64;
+ }
+ return 0;
+}
+static int test_opcode62(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ char*cptr = (char*)ptr;
+ char**ccptr = (char**)ptr;
+ int*iptr = (int*)ptr;
+ static unsigned int retval = 10;
+ int ret = retval;
+ return 0;
+#if 1
+ retval=0;
+ if(!retval)
+ ret = cptr[0x44]+1;
+#endif
+ printf("OPCODE62: %d = %d -> %d\n", iptr[17], cptr[68], ret);
+ //print_hex(ptr, 256);
+ //snprintf((char*)ptr, 128, "OPCODE62");
+ //*ccptr = (char*)"OPCODE62";
+ /* cptr[4] must be "true" in order to keep running */
+ for(int i=0; i<64; i++) cptr[i] = 128-i;
+ /* 'ret' must be > iptr[17] to keep running */
+ return ret;
+}
+static int test_opcode56(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ int i;
+ char*cptr = (char*)ptr;
+ printf("OPCODE56\n");fflush(stdout);
+ print_hex(ptr, 160);
+ snprintf(cptr, 16, "OPCODE56");
+#if 1
+ for(i=0; i<0x99; i++) {
+ cptr[i] = 0;
+ }
+ /* smash the stack */
+ //cptr[0x98] = 1;
+#endif
+ return 100;
+}
+
+static void test_hostVendorSpecific(AEffect*eff) {
+ int listadj[2] = { 1, 3 };
+ int opcode;
+ //audioMasterCallback(audioMasterVendorSpecific, 0xdeadbeef, audioMasterAutomate, listadj, 0.0);
+ for(opcode=0; opcode<100; opcode++) {
+ int skip = 0;
+ if(hostKnown(opcode)) continue;
+ switch (opcode) {
+ case 11: /* returns 3 */
+ case 12: /* returns 1 */
+ case 13: /* returns 1 */
+ case 19: /* returns 0x600 */
+ case 35: /* returns 1 */
+ default: break;
+ case 48:
+ case 53:
+ skip = 1;
+ }
+ if(skip)continue;
+ printf("testing %d\n", opcode); fflush(stdout);
+ dispatch_v(eff, opcode, 0xDEADBEEF, audioMasterAutomate, listadj, 0.0);
+ }
+}
+
+static int test_hostUpdateDisplay(AEffect*eff) {
+ int opcode = audioMasterUpdateDisplay;
+ int res;
+ res = dispatch_v(eff, opcode, 0, 0, NULL, 0.0);
+ printf("%s: opcode(%d) -> %d\n", __FUNCTION__, opcode, res);fflush(stdout);
+ return res;
+}
+
+static int test_opcode26(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ int result = (index<2);
+ printf("OPCODE26[%d]: %d\n", index, result);
+ fflush(stdout);
+ return result;
+}
+
+/* effProcessEvents: handle MIDI */
+static void test_opcode25(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+
+ unsigned char midi[4] = {0x90, 0x40, 0x7f, 0};
+ VstEvents*vse=(VstEvents*)ptr;
+ vse = create_vstevents(midi);
+
+ dispatch_v(eff, audioMasterProcessEvents, index, ivalue, vse, fvalue);
+
+ return;
+ char filename[128];
+ print_hex(ptr, 256);
+ static int count = 0;
+ count++;
+ sprintf(filename, "midi/dump.%d", count);
+ dump_data(filename, ptr, 256);
+}
+
+/* eff*SpeakerArrangement */
+static void test_opcode42(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ return;
+ /* effGetSpeakerArrangement or effGetSpeakerArrangement */
+ /* ptr and ivalue point to VstSpeakerArrangement* */
+ VstSpeakerArrangement*iarr = (VstSpeakerArrangement*)ivalue;
+ VstSpeakerArrangement*oarr = (VstSpeakerArrangement*)ptr;
+ print_hex(iarr, 16);
+ print_hex(oarr, 16);
+ printf("JMZ| %d | 0x%X | %d |\n", iarr->type, iarr->type, iarr->numChannels);
+ printf("JMZ| %d | 0x%X | %d |\n", oarr->type, oarr->type, oarr->numChannels);
+ fflush(stdout);
+#ifdef NUM_INPUTS
+ crash();
+#endif
+}
+
+static void test_processLevel(AEffect*eff) {
+ dispatch_v(eff, audioMasterGetCurrentProcessLevel, 0, 0, 0, 0.);
+}
+
+static bool dispatcher_printEff(AEffect*eff,
+ t_fstInt32 opcode, int index,
+ t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ char opcodestr[512];
+ printf("FstPlugin::dispatch(%s, %d, %ld, %p, %f);\n",
+ effCode2string(opcode, opcodestr, 512), index, ivalue, ptr, fvalue);
+ return true;
+}
+static bool dispatcher_skip(t_fstInt32 opcode) {
+ switch(opcode) {
+ case effEditIdle:
+ /* called permanently, if GUI is visible */
+ return true;
+ case 53:
+ /* REAPER calls this permanently */
+ //printf("53...\n");
+ //print_struct7(eff);
+ return true;
+ }
+ return false;
+}
+static bool dispatcher_noprint(t_fstInt32 opcode) {
+ switch(opcode) {
+ case effGetParamDisplay:
+ case effGetParamLabel:
+ case effGetParamName:
+ case effProcessEvents:
+ case effVendorSpecific:
+ case effEditIdle:
+ case fst_effGetMidiNoteName:
+ return true;
+ }
+ return false;
+}
+static void print_ptr4opcode(t_fstInt32 opcode, void*const ptr) {
+ if(!ptr)return;
+ char*str = (char*)ptr;
+ switch(opcode) {
+ default: break;
+ case effEditOpen:
+ /* 'ptr' is a window-id, we cannot print it */
+ return;
+ case effGetParamName: case effGetParamDisplay: case effGetParamLabel:
+ return;
+ }
+ if(str && *str)
+ printf("\tFtsClient::dispatcher(ptr='%.*s')\n", 64, str);
+ //if(str)print_hex(str, 96);
+}
+static t_fstPtrInt dispatcher(AEffect*eff, t_fstInt32 opcode, int index, t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ if(25==opcode) test_processLevel(eff);
+ //if(53==opcode) test_gettime(eff);
+ if(26==opcode) return test_opcode26(eff, opcode, index, ivalue, ptr, fvalue);
+ if(dispatcher_skip(opcode))return 0;
+ if(!dispatcher_noprint(opcode)) {
+ dispatcher_printEff(eff, opcode, index, ivalue, ptr, fvalue);
+ print_ptr4opcode(opcode, ptr);
+ }
+
+ switch(opcode) {
+ default: break;
+ //case effGetVstVersion: return 2400;
+ case 26:
+ return test_opcode26(eff, opcode, index, ivalue, ptr, fvalue);
+ case effGetPlugCategory:
+ return test_opcode35(eff);
+ case effShellGetNextPlugin:
+ return test_opcode70(eff, (char*)ptr);
+ case effGetVendorString:
+ snprintf((char*)ptr, 16, "SuperVendor");
+ return 1;
+ case effGetEffectName:
+ snprintf((char*)ptr, 16, "SuperEffect");
+ return 1;
+#if 1
+ case effSetSpeakerArrangement:
+ test_opcode42(eff, opcode, index, ivalue, ptr, fvalue);
+ return 0;
+#endif
+#if 1
+ case 56:
+ return test_opcode56(eff, opcode, index, ivalue, ptr, fvalue);
+#endif
+ case effProcessEvents:
+ //test_opcode25(eff, opcode, index, ivalue, ptr, fvalue);
+ test_hostUpdateDisplay(eff);
+ return 1;
+ case 62:
+ return test_opcode62(eff, opcode, index, ivalue, ptr, fvalue);
+ case fst_effGetMidiNoteName:
+ return test_opcode66(eff, opcode, index, ivalue, ptr, fvalue);
+ case effEditGetRect:
+ *((ERect**)ptr) = &editorBounds;
+ return (t_fstPtrInt)&editorBounds;
+ case effGetChunk:
+ {
+ char**strptr=(char**)ptr;
+ *strptr=chunk;
+ }
+ //printf("getChunk: %d bytes @ %p\n", sizeof(chunk), chunk);
+ return sizeof(chunk);
+ case effSetProgram:
+ //printf("setting program to %d\n", ivalue);
+ curProgram = ivalue;
+ return 1;
+ case effGetProgramName:
+ snprintf((char*)ptr, 32, "FstProgram%d", curProgram);
+ //printf("JMZ:setting program-name to %s\n", (char*)ptr);
+ return 1;
+ case effGetParamLabel:
+ snprintf((char*)ptr, 32, "°");
+ return 0;
+ case effGetParamName:
+ if(index>=sizeof(parameters))
+ index=sizeof(parameters);
+ snprintf((char*)ptr, 32, "rotation%c", index+88);
+ return 0;
+ case effGetParamDisplay:
+ if(index>=sizeof(parameters))
+ index=sizeof(parameters);
+ snprintf((char*)ptr, 32, "%+03d", int((parameters[index]-0.5)*360+0.5));
+ return 0;
+ case effEditClose:
+ printf("EDIT Close!\n");
+ test_hostVendorSpecific(eff);
+ return 1;
+ case effCanDo: {
+ typedef struct _cando {
+ const char*ID;
+ unsigned int res;
+ } t_cando;
+ t_cando candos[] = {
+ {"receiveVstEvents", 1},
+ {"receiveVstMidiEvents", 1},
+ {"sendVstEvents", 1},
+ {"sendVstMidiEvents", 1},
+ /* announcing 'wantsChannelCountNotifications' makes REAPER send out effSetSpeakerArrangement */
+ //{"wantsChannelCountNotifications", 1},
+ {"hasCockosExtensions", 0xbeef0000},
+ {"hasCockosEmbeddedUI", 0xbeef0000},
+ {"hasCockosNoScrollUI", 1 /* ? */ },
+ {"hasCockosSampleAccurateAutomation", 1 /* ? */ },
+ {"hasCockosViewAsConfig", 1 /* ? */ },
+ {"cockosLoadingConfigAsParameters", 1 /* ? */ },
+ {0, 0} /* END */
+ };
+ t_cando*cando;
+ printf("canDo '%s'?\n", (char*)ptr);
+ if(!ptr) return 0;
+ for(cando=candos; cando->ID; cando++) {
+ if(!strcmp((char*)ptr, cando->ID))
+ return cando->res;
+ }
+ }
+ return 0;
+ case effMainsChanged:
+ dispatch_v(eff, audioMasterGetCurrentProcessLevel, 0, 0, 0, 0.);
+#if 0
+ do {
+ static bool first=true;
+ if(first) {
+ test_opcodes(eff, 50);
+ } else {
+ auto *str = (char*)dispatch_v(eff, audioMasterGetTime, 0, 65024, 0, 0.);
+ char filename[128];
+ static int icount = 0;
+ snprintf(filename, 127, "./testdump.%d", icount);
+ printf("OUTFILE[%d]: %s\n", icount, filename);
+ icount++;
+ dump_data(filename, str, 512);
+ }
+ first=false;
+ } while(0);
+#endif
+ dispatch_v(eff, audioMasterWantMidi, 0, 1, 0, 0.);
+ break;
+ case effVendorSpecific: {
+ char buf[1024];
+ printf("FstPlugin::%s(effVendorSpecific/%s)\n", __FUNCTION__, effCode2string(index, buf, sizeof(buf)));
+ switch(index) {
+ /* the 'stCA' code seems to be common among hosts ... */
+ case FOURCC("stCA"):
+ printf("FstPlugin::%s(effVendorSpecific/%.4s/%.4s) ", __FUNCTION__, fourcc2str(index), fourcc2str(ivalue, buf));
+ switch(ivalue) {
+ case FOURCC("Whee"):
+ printf("MouseWheel");
+ break;
+ default:
+ printf("(0x%08X)", ivalue);
+ break;
+ }
+ printf("\n");
+ break;
+ /*
+ https://www.reaper.fm/sdk/vst/vst_ext.php
+ */
+ case (int)0xDEADBEF0:
+ if (ptr && ivalue>=0 && ivalue<3)
+ {
+#if 0
+ ((double *)ptr)[0] = double(-180.);
+ ((double *)ptr)[1] = double( 180.);
+ printf("vendorspecific BEEF!\n");
+ return 0xbeef;
+#endif
+ }
+ break;
+ case effGetEffectName: {
+ /*
+ REAPER: override instance name
+ */
+ char**ccptr = (char**)ptr;
+ *ccptr = (char*)"OtherName";
+ return 0xF00D;
+ }
+ break;
+ case effGetParamDisplay:
+ case effString2Parameter:
+ case kVstParameterUsesIntStep:
+ case effCanBeAutomated:
+ case effGetChunk:
+ case effSetChunk:
+ break;
+ default:
+ break;
+ } /* switch (index) */
+ } /* case effVendorSpecific */
+ break;
+ }
+ //printf("FstPlugin::dispatch(%p, %d, %d, %d, %p, %f)\n", eff, opcode, index, ivalue, ptr, fvalue);
+ //printf("JMZ\n");
+
+ return 0;
+}
+static t_fstPtrInt dispatchme(AEffect*eff, t_fstInt32 opcode, int index, t_fstPtrInt ivalue, void* const ptr, float fvalue) {
+ return dispatch_effect ("FstPlugin", dispatcher, eff, opcode, index, ivalue, ptr, fvalue);
+}
+static void find_audioMasterSizeWindow() {
+ for(size_t opcode = 0; opcode<100; opcode++) {
+ char hostcode[512] = {0};
+ int width = 1720;
+ int height = 640;
+ if(37==opcode)
+ continue;
+ printf("trying: %d\n", opcode);
+ t_fstPtrInt res = dispatch(0, opcode, width, height, 0, 0);
+ printf("%s[%dx%d] returned %ld\n", hostCode2string(opcode, hostcode, 512), width, height, res);
+ }
+}
+
+static void setParameter(AEffect*eff, int index, float value) {
+ //printf("FstPlugin::setParameter(%p)[%d] -> %f\n", eff, index, value);
+ if(index>=sizeof(parameters))
+ index=sizeof(parameters);
+ parameters[index] = value;
+
+}
+static float getParameter(AEffect*eff, int index) {
+ if(index>=sizeof(parameters))
+ index=sizeof(parameters);
+ //printf("FstPlugin::getParameter(%p)[%d] <- %f\n", eff, index, parameters[index]);
+ return parameters[index];
+}
+static void process(AEffect*eff, float**indata, float**outdata, int sampleframes) {
+#if 0
+ printf("FstPlugin::process0(%p, %p, %p, %d) -> %f\n", eff, indata, outdata, sampleframes, indata[0][0]);
+ test_gettime(eff);
+#endif
+}
+static void processReplacing(AEffect*eff, float**indata, float**outdata, int sampleframes) {
+#if 1
+ printf("FstPlugin::process1(%p, %p, %p, %d) -> %f\n", eff, indata, outdata, sampleframes, indata[0][0]);
+ test_processLevel(eff);
+ //test_gettime(eff);
+#endif
+}
+static void processDoubleReplacing(AEffect*eff, double**indata, double**outdata, int sampleframes) {
+#if 0
+ printf("FstPlugin::process2(%p, %p, %p, %d) -> %g\n", eff, indata, outdata, sampleframes, indata[0][0]);
+ test_gettime(eff);
+#endif
+}
+
+extern "C"
+AEffect*VSTPluginMain(AEffectDispatcherProc dispatch4host) {
+ dispatch = dispatch4host;
+ printf("FstPlugin::main(%p)\n", dispatch4host);
+ for(size_t i=0; i<sizeof(parameters); i++)
+ parameters[i] = 0.5;
+
+ AEffect* eff = new AEffect;
+ memset(eff, 0, sizeof(AEffect));
+ eff->magic = 0x56737450;
+ eff->dispatcher = dispatchme;
+ eff->process = process;
+ eff->getParameter = getParameter;
+ eff->setParameter = setParameter;
+
+ eff->numPrograms = 1;
+ eff->numParams = 3;
+#ifdef NUM_INPUTS
+ eff->numInputs = NUM_INPUTS;
+ eff->numOutputs = NUM_INPUTS;
+#else
+ eff->numInputs = 6;
+ eff->numOutputs = 6;
+#endif
+ eff->float1 = 1.;
+ eff->object = eff;
+ eff->uniqueID = 0xf00d;
+ eff->version = 666;
+
+ //eff->flags |= effFlagsProgramChunks;
+ eff->flags |= effFlagsCanReplacing;
+ eff->flags |= effFlagsCanDoubleReplacing;
+
+ eff->flags |= effFlagsHasEditor;
+
+ eff->processReplacing = processReplacing;
+ eff->processDoubleReplacing = processDoubleReplacing;
+ print_aeffect(eff);
+
+ const char* canDos[] = { "supplyIdle",
+ "sendVstEvents",
+ "sendVstMidiEvent",
+ "sendVstTimeInfo",
+ "receiveVstEvents",
+ "receiveVstMidiEvent",
+ "supportShell",
+ "sizeWindow",
+ "shellCategory" };
+ for(size_t i = 0; i<(sizeof(canDos)/sizeof(*canDos)); i++) {
+ char buf[512] = {0};
+ char hostcode[512] = {0};
+ snprintf(buf, 511, canDos[i]);
+ buf[511]=0;
+ t_fstPtrInt res = dispatch(0, audioMasterCanDo, 0, 0, buf, 0);
+ if(*buf)
+ printf("%s['%.*s'] returned %ld\n", hostCode2string(audioMasterCanDo, hostcode, 512), 512, buf, res);
+ }
+
+ //find_audioMasterSizeWindow();
+
+ char buf[512] = {0};
+ dispatch(eff, audioMasterGetProductString, 0, 0, buf, 0.f);
+ printf("masterProduct: %s\n", buf);
+
+ s_ves = (VstEvents*)calloc(1, sizeof(VstEvents)+2*sizeof(VstEvent*));
+ s_ves->numEvents = 2;
+ s_ves->events[0] = (VstEvent*)&s_midi;
+ s_ves->events[1] = (VstEvent*)&s_sysex;
+
+ memset(&s_midi, 0, sizeof(s_midi));
+ memset(&s_sysex, 0, sizeof(s_sysex));
+
+ s_midi.type = kVstMidiType;
+ s_midi.byteSize = sizeof(s_midi);
+ s_midi.deltaFrames = 1;
+ for(size_t i=0; i<4; i++)
+ s_midi.midiData[i] = s_midiDump[i];
+
+ s_sysex.type = kVstSysExType;
+ s_sysex.byteSize = sizeof(s_sysex);
+ s_sysex.deltaFrames = 0;
+ s_sysex.dumpBytes = sizeof(s_sysexDump);
+ s_sysex.sysexDump = (char*)s_sysexDump;
+
+ print_events(s_ves);
+
+ printf("=====================================\n\n\n");
+
+ return eff;
+}
diff --git a/source/fst/src/FstPlugin/Makefile b/source/fst/src/FstPlugin/Makefile
@@ -0,0 +1,16 @@
+FST_CPPFLAGS=-I../../fst -I.. -DFST_DONT_DEPRECATE_UNKNOWN=1
+FST_CXXFLAGS=-g -O0 -fPIC
+
+LIBS=
+
+archs: FstPlugin.32.so FstPlugin.64.so
+
+FstPlugin.32.so: ARCHFLAGS=-m32
+FstPlugin.64.so: ARCHFLAGS=-m64
+
+FstPlugin.so FstPlugin.32.so FstPlugin.64.so: FstPlugin.cpp ../../fst/fst.h ../fst_utils.h
+ $(CXX) -shared $(FST_CPPFLAGS) $(CPPFLAGS) $(FST_CXXFLAGS) $(CXXFLAGS) $(ARCHFLAGS) $< -o $@ $(LIBS)
+
+.PHONY: clean archs default
+clean:
+ rm -f *.so
diff --git a/source/fst/src/FstProxy/FstProxy.cpp b/source/fst/src/FstProxy/FstProxy.cpp
@@ -0,0 +1,204 @@
+#include "fst.h"
+#include "fst_utils.h"
+#include <stdio.h>
+
+#include <string>
+#include <string.h>
+
+#include <map>
+
+static std::map<AEffect*, AEffectDispatcherProc>s_host2plugin;
+static std::map<AEffect*, AEffectDispatcherProc>s_plugin2host;
+static std::map<AEffect*, std::string>s_pluginname;
+static AEffectDispatcherProc s_plug2host;
+
+static
+t_fstPtrInt host2plugin (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ //printf("%s(%d, %d, %lld, %p, %f)\n", __FUNCTION__, opcode, index, ivalue, ptr, fvalue); fflush(stdout);
+ switch(opcode) {
+ case effGetVendorString:
+ printf("getVendorString\n");
+ snprintf((char*)ptr, 16, "ProxyVendor");
+ return 1;
+ case effGetEffectName:
+ printf("getEffectName\n");
+ snprintf((char*)ptr, 16, "ProxyEffect");
+ return 1;
+ case 26:
+ printf("OPCODE26: %d\n", index);
+ return (index<5);
+ case 42:
+ printf("OPCODE42: %d, %lld, %p, %f\n", index, ivalue, ptr, fvalue);fflush(stdout);
+ break;
+ case effVendorSpecific:
+ printf("effVendorSpecific(0x%X, 0x%X)\n", index, ivalue);
+ print_hex(ptr, 256);
+ break;
+ case 56:
+#if 0
+ printf("OPCODE56\n");
+ print_hex(ptr, 256);
+ return dispatch_effect("???", s_host2plugin[effect], effect, opcode, index, ivalue, 0, fvalue);
+#endif
+ break;
+ case 62:
+ return 0;
+ printf("OPCODE62?\n");
+ print_hex(ptr, 256);
+ // >=90: stack smashing
+ // <=85: ok
+ snprintf((char*)ptr, 85, "JMZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
+ case 66: {
+#if 0
+ char*cptr = (char*)ptr;
+ int*iptr = (int*)ptr;
+ printf("OPCODE66: %3d %3d\n", iptr[0], iptr[1]);
+ if (60 == iptr[1])
+ snprintf((char*)ptr+8, 52, "middle C", iptr[1]);
+ else
+ snprintf((char*)ptr+8, 52, "note:#%d", iptr[1]);
+ #endif
+ }
+ //return 1;
+ default:
+ break;
+ }
+ AEffectDispatcherProc h2p = s_host2plugin[effect];
+ if(!h2p) {
+ printf("Fst::host2plugin:: NO CALLBACK!\n");
+ return 0xDEAD;
+ }
+ const char*pluginname = 0;
+ if(effect)
+ pluginname = s_pluginname[effect].c_str();
+
+ bool doPrint = true;
+#ifdef FST_EFFKNOWN
+ doPrint = !effKnown(opcode);
+#endif
+ switch(opcode) {
+ default: break;
+ case 56: case 53:
+ case effGetChunk: case effSetChunk:
+ case effVendorSpecific:
+ doPrint = false;
+ break;
+ }
+ t_fstPtrInt result = 0;
+ if(doPrint) {
+ dispatch_effect(pluginname, h2p, effect, opcode, index, ivalue, ptr, fvalue);
+ } else {
+ result = h2p(effect, opcode, index, ivalue, ptr, fvalue);
+ }
+ switch(opcode) {
+ default: break;
+ case 56:
+ print_hex(ptr, 256);
+ break;
+ case 62:
+ printf("OPCODE62!\n");
+ print_hex(ptr, 256);
+ }
+ return result;
+}
+static
+t_fstPtrInt host2plugin_wrapper (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue)
+{
+ t_fstPtrInt result;
+ fflush(stdout);
+ result = host2plugin(effect, opcode, index, ivalue, ptr, fvalue);
+ fflush(stdout);
+
+ return result;
+}
+static
+t_fstPtrInt plugin2host (AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ //printf("%s:%d\n", __FUNCTION__, opcode); fflush(stdout);
+ AEffectDispatcherProc p2h = s_plugin2host[effect];
+ if(!p2h)p2h = s_plug2host;
+ if(effect && !s_host2plugin[effect]) {
+ s_host2plugin[effect] = effect->dispatcher;
+ effect->dispatcher = host2plugin_wrapper;
+ }
+ const char*pluginname = 0;
+ if(effect)
+ pluginname = s_pluginname[effect].c_str();
+
+ bool doPrint = true;
+#ifdef FST_HOSTKNOWN
+ doPrint = !hostKnown(opcode);
+#endif
+ switch(opcode) {
+ default: break;
+ case 14:
+ printf("hostCODE14\n");
+ //return 1;
+ break;
+ case 42:
+ printf("hostCODE42\n");
+ //return 0;
+ break;
+ case audioMasterGetCurrentProcessLevel:
+ case audioMasterGetTime:
+ doPrint = false;
+ break;
+ case (int)0xDEADBEEF:
+ printf("0xDEADBEEF\n");
+ }
+ t_fstPtrInt result = -1;
+
+ fflush(stdout);
+ if(doPrint) {
+ if(0xDEADBEEF ==opcode) {
+ unsigned int uindex = (unsigned int) index;
+ switch(uindex) {
+ case 0xDEADF00D:
+ printf("\t0x%X/0x%X '%s' ->\n", opcode, index, ptr);
+ break;
+ default:
+ printf("\t0x%X/0x%X ->\n", opcode, index);
+ break;
+ }
+ }
+
+ result = dispatch_host(pluginname, p2h, effect, opcode, index, ivalue, ptr, fvalue);
+ } else {
+ result = p2h(effect, opcode, index, ivalue, ptr, fvalue);
+ }
+ fflush(stdout);
+ return result;
+}
+
+
+extern "C"
+AEffect*VSTPluginMain(AEffectDispatcherProc dispatch4host) {
+ char pluginname[512] = {0};
+ char*pluginfile = getenv("FST_PROXYPLUGIN");
+ printf("FstProxy: %s\n", pluginfile);
+ if(!pluginfile)return 0;
+ s_plug2host = dispatch4host;
+
+ t_fstMain*plugMain = fstLoadPlugin(pluginfile);
+ if(!plugMain)return 0;
+
+ AEffect*plug = plugMain(plugin2host);
+ if(!plug)
+ return plug;
+
+ printf("plugin.dispatcher '%p' -> '%p'\n", plug->dispatcher, host2plugin_wrapper);
+ if(plug->dispatcher != host2plugin_wrapper) {
+ s_host2plugin[plug] = plug->dispatcher;
+ plug->dispatcher = host2plugin_wrapper;
+ }
+
+ s_host2plugin[plug](plug, effGetEffectName, 0, 0, pluginname, 0);
+ if(*pluginname)
+ s_pluginname[plug] = pluginname;
+ else
+ s_pluginname[plug] = pluginfile;
+
+ s_plugin2host[plug] = dispatch4host;
+ print_aeffect(plug);
+ fflush(stdout);
+ return plug;
+}
diff --git a/source/fst/src/FstProxy/Makefile b/source/fst/src/FstProxy/Makefile
@@ -0,0 +1,16 @@
+FST_CPPFLAGS=-I../../fst -I.. -DFST_DONT_DEPRECATE_UNKNOWN=1
+FST_CXXFLAGS=-g -O0 -fPIC
+
+LIBS=
+
+archs: FstProxy.32.so FstProxy.64.so
+
+FstProxy.32.so: ARCHFLAGS=-m32
+FstProxy.64.so: ARCHFLAGS=-m64
+
+FstProxy.so FstProxy.32.so FstProxy.64.so: FstProxy.cpp ../../fst/fst.h ../fst_utils.h
+ $(CXX) -shared $(FST_CPPFLAGS) $(CPPFLAGS) $(FST_CXXFLAGS) $(CXXFLAGS) $(ARCHFLAGS) $< -o $@ $(LIBS)
+
+.PHONY: clean archs default
+clean:
+ rm -f *.so
diff --git a/source/fst/src/FstProxy/Makefile.nt b/source/fst/src/FstProxy/Makefile.nt
@@ -0,0 +1,16 @@
+LIBS=
+
+FST_CPPFLAGS=-I../../fst -I.. -DFST_DONT_DEPRECATE_UNKNOWN=1
+FST_CXXFLAGS=-g -O0
+
+archs: w32/FstProxy.dll w64/FstProxy.dll
+
+w32/FstProxy.dll: CXX=i686-w64-mingw32-g++
+w64/FstProxy.dll: CXX=x86_64-w64-mingw32-g++
+
+FstProxy.dll w32/FstProxy.dll w64/FstProxy.dll: FstProxy.cpp ../../fst/fst.h ../fst_utils.h
+ $(CXX) -shared $(FST_CPPFLAGS) $(CPPFLAGS) $(FST_CXXFLAGS) $(CXXFLAGS) $(ARCHFLAGS) $< -o $@ $(LIBS)
+
+.PHONY: clean archs default
+clean:
+ rm -f *.dll */*.dll
diff --git a/source/fst/src/JstHost/JstHost.jucer b/source/fst/src/JstHost/JstHost.jucer
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<JUCERPROJECT id="MPzqul" name="JstHost" projectType="consoleapp" jucerFormatVersion="1">
+ <MAINGROUP id="JJj33p" name="JstHost">
+ <GROUP id="{83D2710B-DCC2-0102-841D-EFC9350BC200}" name="Source">
+ <FILE id="SR5IZk" name="Main.cpp" compile="1" resource="0" file="Source/Main.cpp"/>
+ </GROUP>
+ </MAINGROUP>
+ <EXPORTFORMATS>
+ <LINUX_MAKE targetFolder="Builds/LinuxMakefile">
+ <CONFIGURATIONS>
+ <CONFIGURATION isDebug="1" name="Debug"/>
+ <CONFIGURATION isDebug="0" name="Release"/>
+ </CONFIGURATIONS>
+ <MODULEPATHS>
+ <MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_core" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_events" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
+ </MODULEPATHS>
+ </LINUX_MAKE>
+ </EXPORTFORMATS>
+ <MODULES>
+ <MODULE id="juce_audio_basics" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_audio_processors" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_core" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_data_structures" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_events" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_graphics" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_gui_basics" showAllCode="1" useGlobalPath="1"/>
+ <MODULE id="juce_gui_extra" showAllCode="1" useGlobalPath="1"/>
+ </MODULES>
+ <LIVE_SETTINGS>
+ <LINUX/>
+ </LIVE_SETTINGS>
+ <JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_PLUGINHOST_VST="1"/>
+</JUCERPROJECT>
diff --git a/source/fst/src/JstHost/Makefile b/source/fst/src/JstHost/Makefile
@@ -0,0 +1,5 @@
+.PHONY: default
+default:
+ $(MAKE) -C Builds/LinuxMakefile/
+%:
+ $(MAKE) -C Builds/LinuxMakefile/ $@
diff --git a/source/fst/src/JstHost/Source/Main.cpp b/source/fst/src/JstHost/Source/Main.cpp
@@ -0,0 +1,29 @@
+/*
+ ==============================================================================
+
+ This file was auto-generated!
+
+ It contains the basic startup code for a JUCE application.
+
+ ==============================================================================
+*/
+
+#include "../JuceLibraryCode/JuceHeader.h"
+
+//==============================================================================
+int main (int argc, char* argv[]) {
+ KnownPluginList knownPluginList;
+ VSTPluginFormat formatToScan;
+ FileSearchPath paths;
+ // ..your code goes here!
+ paths.add(File(argv[1]));
+
+
+ pointer_sized_int identify = ByteOrder::bigEndianInt ("NvEf");
+ std::cerr << "identify: "<<identify<<std::endl;
+
+ PluginDirectoryScanner scanner(knownPluginList, formatToScan, paths, true, File(), true);
+ String cur;
+ while(scanner.scanNextFile(false, cur)) {;}
+ return 0;
+}
diff --git a/source/fst/src/JstPlugin/JstPlugin.jucer b/source/fst/src/JstPlugin/JstPlugin.jucer
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<JUCERPROJECT id="yoFRcB" name="JstPlugin" projectType="audioplug" pluginFormats="buildVST"
+ jucerFormatVersion="1">
+ <MAINGROUP id="Tjn5MC" name="JstPlugin">
+ <GROUP id="{8C388FDC-8D12-5E08-1AFB-AFF194D1806D}" name="Source">
+ <FILE id="TQQwiU" name="PluginProcessor.cpp" compile="1" resource="0"
+ file="Source/PluginProcessor.cpp"/>
+ <FILE id="CU24Bv" name="PluginProcessor.h" compile="0" resource="0"
+ file="Source/PluginProcessor.h"/>
+ <FILE id="Wm1KJg" name="PluginEditor.cpp" compile="1" resource="0"
+ file="Source/PluginEditor.cpp"/>
+ <FILE id="jCf3Gy" name="PluginEditor.h" compile="0" resource="0" file="Source/PluginEditor.h"/>
+ </GROUP>
+ </MAINGROUP>
+ <EXPORTFORMATS>
+ <LINUX_MAKE targetFolder="Builds/LinuxMakefile">
+ <CONFIGURATIONS>
+ <CONFIGURATION isDebug="1" name="Debug"/>
+ <CONFIGURATION isDebug="0" name="Release"/>
+ </CONFIGURATIONS>
+ <MODULEPATHS>
+ <MODULEPATH id="juce_audio_basics" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_devices" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_formats" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_plugin_client" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_processors" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_audio_utils" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_core" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_cryptography" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_data_structures" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_events" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_graphics" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_gui_basics" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_gui_extra" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_opengl" path="../../JUCE/modules"/>
+ <MODULEPATH id="juce_video" path="../../JUCE/modules"/>
+ </MODULEPATHS>
+ </LINUX_MAKE>
+ </EXPORTFORMATS>
+ <MODULES>
+ <MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_audio_devices" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_audio_plugin_client" showAllCode="1" useLocalCopy="0"
+ useGlobalPath="1"/>
+ <MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_audio_utils" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_cryptography" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_opengl" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ <MODULE id="juce_video" showAllCode="1" useLocalCopy="0" useGlobalPath="1"/>
+ </MODULES>
+ <LIVE_SETTINGS>
+ <LINUX/>
+ </LIVE_SETTINGS>
+ <JUCEOPTIONS JUCE_VST3_CAN_REPLACE_VST2="0" JUCE_STRICT_REFCOUNTEDPOINTER="1"
+ JUCE_PLUGINHOST_VST="1"/>
+</JUCERPROJECT>
diff --git a/source/fst/src/JstPlugin/Makefile b/source/fst/src/JstPlugin/Makefile
@@ -0,0 +1,5 @@
+.PHONY: default
+default:
+ $(MAKE) -C Builds/LinuxMakefile/
+%:
+ $(MAKE) -C Builds/LinuxMakefile/ $@
diff --git a/source/fst/src/JstPlugin/Source/PluginEditor.cpp b/source/fst/src/JstPlugin/Source/PluginEditor.cpp
@@ -0,0 +1,42 @@
+/*
+ ==============================================================================
+
+ This file was auto-generated!
+
+ It contains the basic framework code for a JUCE plugin editor.
+
+ ==============================================================================
+*/
+
+#include "PluginProcessor.h"
+#include "PluginEditor.h"
+
+//==============================================================================
+JstPluginAudioProcessorEditor::JstPluginAudioProcessorEditor (JstPluginAudioProcessor& p)
+ : AudioProcessorEditor (&p), processor (p)
+{
+ // Make sure that before the constructor has finished, you've set the
+ // editor's size to whatever you need it to be.
+ setSize (400, 300);
+}
+
+JstPluginAudioProcessorEditor::~JstPluginAudioProcessorEditor()
+{
+}
+
+//==============================================================================
+void JstPluginAudioProcessorEditor::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);
+ g.drawFittedText ("Hello World!", getLocalBounds(), Justification::centred, 1);
+}
+
+void JstPluginAudioProcessorEditor::resized()
+{
+ // This is generally where you'll want to lay out the positions of any
+ // subcomponents in your editor..
+}
diff --git a/source/fst/src/JstPlugin/Source/PluginEditor.h b/source/fst/src/JstPlugin/Source/PluginEditor.h
@@ -0,0 +1,35 @@
+/*
+ ==============================================================================
+
+ This file was auto-generated!
+
+ It contains the basic framework code for a JUCE plugin editor.
+
+ ==============================================================================
+*/
+
+#pragma once
+
+#include "../JuceLibraryCode/JuceHeader.h"
+#include "PluginProcessor.h"
+
+//==============================================================================
+/**
+*/
+class JstPluginAudioProcessorEditor : public AudioProcessorEditor
+{
+public:
+ JstPluginAudioProcessorEditor (JstPluginAudioProcessor&);
+ ~JstPluginAudioProcessorEditor();
+
+ //==============================================================================
+ void paint (Graphics&) override;
+ void resized() override;
+
+private:
+ // This reference is provided as a quick way for your editor to
+ // access the processor object that created it.
+ JstPluginAudioProcessor& processor;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JstPluginAudioProcessorEditor)
+};
diff --git a/source/fst/src/JstPlugin/Source/PluginProcessor.cpp b/source/fst/src/JstPlugin/Source/PluginProcessor.cpp
@@ -0,0 +1,191 @@
+/*
+ ==============================================================================
+
+ This file was auto-generated!
+
+ It contains the basic framework code for a JUCE plugin processor.
+
+ ==============================================================================
+*/
+
+#include "PluginProcessor.h"
+#include "PluginEditor.h"
+
+//==============================================================================
+JstPluginAudioProcessor::JstPluginAudioProcessor()
+#ifndef JucePlugin_PreferredChannelConfigurations
+ : AudioProcessor (BusesProperties()
+ #if ! JucePlugin_IsMidiEffect
+ #if ! JucePlugin_IsSynth
+ .withInput ("Input", AudioChannelSet::stereo(), true)
+ #endif
+ .withOutput ("Output", AudioChannelSet::stereo(), true)
+ #endif
+ )
+#endif
+{
+}
+
+JstPluginAudioProcessor::~JstPluginAudioProcessor()
+{
+}
+
+//==============================================================================
+const String JstPluginAudioProcessor::getName() const
+{
+ return JucePlugin_Name;
+}
+
+bool JstPluginAudioProcessor::acceptsMidi() const
+{
+ #if JucePlugin_WantsMidiInput
+ return true;
+ #else
+ return false;
+ #endif
+}
+
+bool JstPluginAudioProcessor::producesMidi() const
+{
+ #if JucePlugin_ProducesMidiOutput
+ return true;
+ #else
+ return false;
+ #endif
+}
+
+bool JstPluginAudioProcessor::isMidiEffect() const
+{
+ #if JucePlugin_IsMidiEffect
+ return true;
+ #else
+ return false;
+ #endif
+}
+
+double JstPluginAudioProcessor::getTailLengthSeconds() const
+{
+ return 0.0;
+}
+
+int JstPluginAudioProcessor::getNumPrograms()
+{
+ return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
+ // so this should be at least 1, even if you're not really implementing programs.
+}
+
+int JstPluginAudioProcessor::getCurrentProgram()
+{
+ return 0;
+}
+
+void JstPluginAudioProcessor::setCurrentProgram (int index)
+{
+}
+
+const String JstPluginAudioProcessor::getProgramName (int index)
+{
+ return {};
+}
+
+void JstPluginAudioProcessor::changeProgramName (int index, const String& newName)
+{
+}
+
+//==============================================================================
+void JstPluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
+{
+ // Use this method as the place to do any pre-playback
+ // initialisation that you need..
+}
+
+void JstPluginAudioProcessor::releaseResources()
+{
+ // When playback stops, you can use this as an opportunity to free up any
+ // spare memory, etc.
+}
+
+#ifndef JucePlugin_PreferredChannelConfigurations
+bool JstPluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
+{
+ #if JucePlugin_IsMidiEffect
+ ignoreUnused (layouts);
+ return true;
+ #else
+ // This is the place where you check if the layout is supported.
+ // In this template code we only support mono or stereo.
+ if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono()
+ && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo())
+ return false;
+
+ // This checks if the input layout matches the output layout
+ #if ! JucePlugin_IsSynth
+ if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
+ return false;
+ #endif
+
+ return true;
+ #endif
+}
+#endif
+
+void JstPluginAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
+{
+ ScopedNoDenormals noDenormals;
+ auto totalNumInputChannels = getTotalNumInputChannels();
+ auto totalNumOutputChannels = getTotalNumOutputChannels();
+
+ // In case we have more outputs than inputs, this code clears any output
+ // channels that didn't contain input data, (because these aren't
+ // guaranteed to be empty - they may contain garbage).
+ // This is here to avoid people getting screaming feedback
+ // when they first compile a plugin, but obviously you don't need to keep
+ // this code if your algorithm always overwrites all the output channels.
+ for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
+ buffer.clear (i, 0, buffer.getNumSamples());
+
+ // This is the place where you'd normally do the guts of your plugin's
+ // audio processing...
+ // Make sure to reset the state if your inner loop is processing
+ // the samples and the outer loop is handling the channels.
+ // Alternatively, you can process the samples with the channels
+ // interleaved by keeping the same state.
+ for (int channel = 0; channel < totalNumInputChannels; ++channel)
+ {
+ auto* channelData = buffer.getWritePointer (channel);
+
+ // ..do something to the data...
+ }
+}
+
+//==============================================================================
+bool JstPluginAudioProcessor::hasEditor() const
+{
+ return true; // (change this to false if you choose to not supply an editor)
+}
+
+AudioProcessorEditor* JstPluginAudioProcessor::createEditor()
+{
+ return new JstPluginAudioProcessorEditor (*this);
+}
+
+//==============================================================================
+void JstPluginAudioProcessor::getStateInformation (MemoryBlock& destData)
+{
+ // You should use this method to store your parameters in the memory block.
+ // You could do that either as raw data, or use the XML or ValueTree classes
+ // as intermediaries to make it easy to save and load complex data.
+}
+
+void JstPluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
+{
+ // You should use this method to restore your parameters from this memory block,
+ // whose contents will have been created by the getStateInformation() call.
+}
+
+//==============================================================================
+// This creates new instances of the plugin..
+AudioProcessor* JUCE_CALLTYPE createPluginFilter()
+{
+ return new JstPluginAudioProcessor();
+}
diff --git a/source/fst/src/JstPlugin/Source/PluginProcessor.h b/source/fst/src/JstPlugin/Source/PluginProcessor.h
@@ -0,0 +1,61 @@
+/*
+ ==============================================================================
+
+ This file was auto-generated!
+
+ It contains the basic framework code for a JUCE plugin processor.
+
+ ==============================================================================
+*/
+
+#pragma once
+
+#include "../JuceLibraryCode/JuceHeader.h"
+
+//==============================================================================
+/**
+*/
+class JstPluginAudioProcessor : public AudioProcessor
+{
+public:
+ //==============================================================================
+ JstPluginAudioProcessor();
+ ~JstPluginAudioProcessor();
+
+ //==============================================================================
+ void prepareToPlay (double sampleRate, int samplesPerBlock) override;
+ void releaseResources() override;
+
+ #ifndef JucePlugin_PreferredChannelConfigurations
+ bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
+ #endif
+
+ void processBlock (AudioBuffer<float>&, MidiBuffer&) override;
+
+ //==============================================================================
+ AudioProcessorEditor* createEditor() override;
+ bool hasEditor() const override;
+
+ //==============================================================================
+ const String getName() const override;
+
+ bool acceptsMidi() const override;
+ bool producesMidi() const override;
+ bool isMidiEffect() const override;
+ double getTailLengthSeconds() const 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;
+
+ //==============================================================================
+ void getStateInformation (MemoryBlock& destData) override;
+ void setStateInformation (const void* data, int sizeInBytes) override;
+
+private:
+ //==============================================================================
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JstPluginAudioProcessor)
+};
diff --git a/source/fst/src/Makefile b/source/fst/src/Makefile
@@ -0,0 +1,9 @@
+subdirs=FstHost FstPlugin JstHost JstPlugin
+
+.PHONY: default
+default:
+ for i in $(subdirs); do make -C $$i; done
+
+%:
+ for i in $(subdirs); do make -C $$i $@; done
+
diff --git a/source/fst/src/fst_utils.h b/source/fst/src/fst_utils.h
@@ -0,0 +1,781 @@
+#ifndef FST_FST_UTILS_H_
+#define FST_FST_UTILS_H_
+
+#include "fst.h"
+
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+#include <string>
+
+#include <unistd.h>
+static void fstpause(float duration=1.0) {
+ usleep(duration * 1000000);
+}
+
+#define FOURCC(fourcc) ((fourcc[0]<<24) | (fourcc[1]<<16) | (fourcc[2]<< 8) | (fourcc[3]<< 0))
+
+static char*fourcc2str(unsigned int fourcc, char buf[4]) {
+ buf[0] = (fourcc >> 24) & 0xFF;
+ buf[1] = (fourcc >> 16) & 0xFF;
+ buf[2] = (fourcc >> 8) & 0xFF;
+ buf[3] = (fourcc >> 0) & 0xFF;
+ return buf;
+}
+static char*fourcc2str(unsigned int fourcc) {
+ static char buf[5];
+ buf[4] = 0;
+ return fourcc2str(fourcc, buf);
+}
+
+static void print_hex(void*ptr, size_t length) {
+ printf("DATA@%p [%d]", ptr, length);
+ unsigned char* data = (unsigned char*)ptr;
+ if(data) {
+ for(size_t i=0; i<length; i++) {
+ if(!(i%16))printf("\n%04x\t", i);
+ if(!(i% 8))printf(" ");
+ printf(" %02X", *data++);
+ }
+ }
+ printf("\n");
+}
+
+static void dump_data(const char*basename, const void*data, size_t length) {
+ const char*ptr = (const char*)data;
+ std::string filename = std::string(basename);
+ filename+=".bin";
+ FILE*f = fopen(filename.c_str(), "w");
+ for(size_t i=0; i<length; i++) {
+ fprintf(f, "%c", *ptr++);
+ }
+ fclose(f);
+}
+
+template <class inttype>
+static void print_binary(inttype data, const char*suffix="") {
+ size_t bits = sizeof(data)*8;
+ while(bits--)
+ printf("%d", (data>>bits)&0x1);
+ printf("%s", suffix);
+}
+
+#include <iostream>
+template <class type>
+static void print_numbers(type*data, size_t length) {
+ while(length--) {
+ std::cout << " " << *data++;
+ if(!(length % 16)) std::cout << std::endl;
+ }
+ std::cout << std::endl;
+}
+
+
+#define FST_UTILS__OPCODESTR(x) \
+ case x: \
+ if(x>100000) \
+ snprintf(output, length, "%d[%s?]", x, #x); \
+ else \
+ snprintf(output, length, "%s[%d]", #x, x); \
+ output[length-1] = 0; \
+ return output
+
+static char*effCode2string(size_t opcode, char*output, size_t length) {
+ switch(opcode) {
+ FST_UTILS__OPCODESTR(effCanBeAutomated);
+ FST_UTILS__OPCODESTR(effCanDo);
+ FST_UTILS__OPCODESTR(effClose);
+ FST_UTILS__OPCODESTR(effConnectInput);
+ FST_UTILS__OPCODESTR(effConnectOutput);
+ FST_UTILS__OPCODESTR(effEditClose);
+ FST_UTILS__OPCODESTR(effEditDraw);
+ FST_UTILS__OPCODESTR(effEditGetRect);
+ FST_UTILS__OPCODESTR(effEditIdle);
+ FST_UTILS__OPCODESTR(effEditMouse);
+ FST_UTILS__OPCODESTR(effEditOpen);
+ FST_UTILS__OPCODESTR(effEditSleep);
+ FST_UTILS__OPCODESTR(effEditTop);
+ FST_UTILS__OPCODESTR(effGetChunk);
+ FST_UTILS__OPCODESTR(effGetCurrentMidiProgram);
+ FST_UTILS__OPCODESTR(fst_effGetMidiNoteName);
+ FST_UTILS__OPCODESTR(effGetEffectName);
+ FST_UTILS__OPCODESTR(effGetInputProperties);
+ FST_UTILS__OPCODESTR(effGetNumMidiInputChannels);
+ FST_UTILS__OPCODESTR(effGetNumMidiOutputChannels);
+ FST_UTILS__OPCODESTR(effGetOutputProperties);
+ FST_UTILS__OPCODESTR(effGetParamDisplay);
+ FST_UTILS__OPCODESTR(effGetParamLabel);
+ FST_UTILS__OPCODESTR(effGetParamName);
+ FST_UTILS__OPCODESTR(effGetPlugCategory);
+ FST_UTILS__OPCODESTR(effGetProductString);
+ FST_UTILS__OPCODESTR(effGetProgram);
+ FST_UTILS__OPCODESTR(effGetProgramName);
+ FST_UTILS__OPCODESTR(effGetProgramNameIndexed);
+ FST_UTILS__OPCODESTR(effGetSpeakerArrangement);
+ FST_UTILS__OPCODESTR(effGetTailSize);
+ FST_UTILS__OPCODESTR(effGetVendorString);
+ FST_UTILS__OPCODESTR(effGetVendorVersion);
+ FST_UTILS__OPCODESTR(effGetVstVersion);
+ FST_UTILS__OPCODESTR(effIdentify);
+ FST_UTILS__OPCODESTR(effIdle);
+ FST_UTILS__OPCODESTR(effKeysRequired);
+ FST_UTILS__OPCODESTR(effMainsChanged);
+ FST_UTILS__OPCODESTR(effOpen);
+ FST_UTILS__OPCODESTR(effProcessEvents);
+ FST_UTILS__OPCODESTR(effSetBlockSize);
+ FST_UTILS__OPCODESTR(effSetBypass);
+ FST_UTILS__OPCODESTR(effSetChunk);
+ FST_UTILS__OPCODESTR(effSetProcessPrecision);
+ FST_UTILS__OPCODESTR(effSetProgram);
+ FST_UTILS__OPCODESTR(effSetProgramName);
+ FST_UTILS__OPCODESTR(effSetSampleRate);
+ FST_UTILS__OPCODESTR(effSetSpeakerArrangement);
+ FST_UTILS__OPCODESTR(effSetTotalSampleToProcess);
+ FST_UTILS__OPCODESTR(effShellGetNextPlugin);
+ FST_UTILS__OPCODESTR(effStartProcess);
+ FST_UTILS__OPCODESTR(effStopProcess);
+ FST_UTILS__OPCODESTR(effString2Parameter);
+ FST_UTILS__OPCODESTR(effVendorSpecific);
+ default: break;
+ }
+ snprintf(output, length, "%llu=0x%X", opcode, opcode);
+ return output;
+}
+static char*hostCode2string(t_fstPtrInt opcode, char*output, size_t length) {
+ switch(opcode) {
+ FST_UTILS__OPCODESTR(audioMasterAutomate);
+ FST_UTILS__OPCODESTR(audioMasterVersion);
+ FST_UTILS__OPCODESTR(audioMasterGetVendorString);
+ FST_UTILS__OPCODESTR(audioMasterGetProductString);
+ FST_UTILS__OPCODESTR(audioMasterGetVendorVersion);
+ FST_UTILS__OPCODESTR(audioMasterBeginEdit);
+ FST_UTILS__OPCODESTR(audioMasterEndEdit);
+ FST_UTILS__OPCODESTR(audioMasterCanDo);
+ FST_UTILS__OPCODESTR(audioMasterCloseWindow);
+ FST_UTILS__OPCODESTR(audioMasterCurrentId);
+ FST_UTILS__OPCODESTR(audioMasterGetAutomationState);
+ FST_UTILS__OPCODESTR(audioMasterGetBlockSize);
+ FST_UTILS__OPCODESTR(audioMasterGetCurrentProcessLevel);
+ FST_UTILS__OPCODESTR(audioMasterGetDirectory);
+ FST_UTILS__OPCODESTR(audioMasterGetInputLatency);
+ FST_UTILS__OPCODESTR(audioMasterGetLanguage);
+ FST_UTILS__OPCODESTR(audioMasterGetNextPlug);
+ FST_UTILS__OPCODESTR(audioMasterGetNumAutomatableParameters);
+ FST_UTILS__OPCODESTR(audioMasterGetOutputLatency);
+ FST_UTILS__OPCODESTR(audioMasterGetOutputSpeakerArrangement);
+ FST_UTILS__OPCODESTR(audioMasterGetParameterQuantization);
+ FST_UTILS__OPCODESTR(audioMasterGetPreviousPlug);
+ FST_UTILS__OPCODESTR(audioMasterGetSampleRate);
+ FST_UTILS__OPCODESTR(audioMasterGetTime);
+ FST_UTILS__OPCODESTR(audioMasterIdle);
+ FST_UTILS__OPCODESTR(audioMasterIOChanged);
+ FST_UTILS__OPCODESTR(audioMasterNeedIdle);
+ FST_UTILS__OPCODESTR(audioMasterOfflineGetCurrentMetaPass);
+ FST_UTILS__OPCODESTR(audioMasterOfflineGetCurrentPass);
+ FST_UTILS__OPCODESTR(audioMasterOfflineRead);
+ FST_UTILS__OPCODESTR(audioMasterOfflineStart);
+ FST_UTILS__OPCODESTR(audioMasterOfflineWrite);
+ FST_UTILS__OPCODESTR(audioMasterOpenWindow);
+ FST_UTILS__OPCODESTR(audioMasterPinConnected);
+ FST_UTILS__OPCODESTR(audioMasterProcessEvents);
+ FST_UTILS__OPCODESTR(audioMasterSetIcon);
+ FST_UTILS__OPCODESTR(audioMasterSetOutputSampleRate);
+ FST_UTILS__OPCODESTR(audioMasterSetTime);
+ FST_UTILS__OPCODESTR(audioMasterSizeWindow);
+ FST_UTILS__OPCODESTR(audioMasterTempoAt);
+ FST_UTILS__OPCODESTR(audioMasterUpdateDisplay);
+ FST_UTILS__OPCODESTR(audioMasterVendorSpecific);
+ FST_UTILS__OPCODESTR(audioMasterWantMidi);
+ FST_UTILS__OPCODESTR(audioMasterWillReplaceOrAccumulate);
+ default: break;
+ }
+ snprintf(output, length, "%llu=0x%X", opcode, opcode);
+ return output;
+}
+
+static int effKnown(t_fstPtrInt opcode) {
+ if(opcode>=100000)
+ return 0;
+ switch(opcode) {
+ case effCanBeAutomated:
+ case effCanDo:
+ case effClose:
+ case effConnectInput:
+ case effConnectOutput:
+ case effEditClose:
+ case effEditDraw:
+ case effEditGetRect:
+ case effEditIdle:
+ case effEditMouse:
+ case effEditOpen:
+ case effEditSleep:
+ case effEditTop:
+ case effGetChunk:
+ case effGetCurrentMidiProgram:
+ case fst_effGetMidiNoteName:
+ case effGetEffectName:
+ case effGetInputProperties:
+ case effGetNumMidiInputChannels:
+ case effGetNumMidiOutputChannels:
+ case effGetOutputProperties:
+ case effGetParamDisplay:
+ case effGetParamLabel:
+ case effGetParamName:
+ case effGetPlugCategory:
+ case effGetProductString:
+ case effGetProgram:
+ case effGetProgramName:
+ case effGetProgramNameIndexed:
+ case effGetSpeakerArrangement:
+ case effGetTailSize:
+ case effGetVendorString:
+ case effGetVendorVersion:
+ case effGetVstVersion:
+ case effIdentify:
+ case effIdle:
+ case effKeysRequired:
+ case effMainsChanged:
+ case effOpen:
+ case effProcessEvents:
+ case effSetBlockSize:
+ case effSetBypass:
+ case effSetChunk:
+ case effSetProcessPrecision:
+ case effSetProgram:
+ case effSetProgramName:
+ case effSetSampleRate:
+ case effSetSpeakerArrangement:
+ case effSetTotalSampleToProcess:
+ case effShellGetNextPlugin:
+ case effStartProcess:
+ case effStopProcess:
+ case effString2Parameter:
+ case effVendorSpecific:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+static int hostKnown(t_fstPtrInt opcode) {
+ if(opcode>=100000)
+ return 0;
+ switch(opcode) {
+ case audioMasterAutomate:
+ case audioMasterVersion:
+ case audioMasterCurrentId:
+ case audioMasterWantMidi:
+ case audioMasterGetTime:
+ case audioMasterProcessEvents:
+ case audioMasterTempoAt:
+ case audioMasterGetSampleRate:
+ case audioMasterSizeWindow:
+ case audioMasterGetBlockSize:
+ case audioMasterGetCurrentProcessLevel:
+ case audioMasterGetVendorString:
+ case audioMasterGetProductString:
+ case audioMasterGetVendorVersion:
+ case audioMasterCanDo:
+ case audioMasterBeginEdit:
+ case audioMasterEndEdit:
+ case audioMasterCloseWindow:
+ case audioMasterOpenWindow:
+ case audioMasterSetIcon:
+ case audioMasterUpdateDisplay:
+ case audioMasterGetParameterQuantization:
+ case audioMasterGetNumAutomatableParameters:
+ case audioMasterGetAutomationState:
+ case audioMasterGetInputLatency:
+ case audioMasterGetOutputLatency:
+ case audioMasterGetDirectory:
+ case audioMasterGetLanguage:
+ case audioMasterGetOutputSpeakerArrangement:
+ case audioMasterOfflineGetCurrentMetaPass:
+ case audioMasterOfflineGetCurrentPass:
+ case audioMasterOfflineRead:
+ case audioMasterOfflineStart:
+ case audioMasterOfflineWrite:
+ case audioMasterGetNextPlug:
+ case audioMasterGetPreviousPlug:
+ case audioMasterIdle:
+ case audioMasterNeedIdle:
+ case audioMasterIOChanged:
+ case audioMasterPinConnected:
+ case audioMasterSetOutputSampleRate:
+ case audioMasterSetTime:
+ case audioMasterWillReplaceOrAccumulate:
+ case audioMasterVendorSpecific:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+static void print_aeffect(AEffect*eff) {
+ printf("AEffect @ %p", eff);
+ if(!eff)return;
+ if(eff->magic != 0x56737450)
+ printf("\n\tmagic=0x%X", eff->magic);
+ else
+ printf("\n\tmagic=VstP");
+ printf("\n\tdispatcherCB=%p", eff->dispatcher);
+ printf("\n\tprocessCB=%p", eff->process);
+ printf("\n\tgetParameterCB=%p", eff->getParameter);
+ printf("\n\tsetParameterCB=%p", eff->setParameter);
+
+ printf("\n\tnumPrograms=%d", eff->numPrograms);
+ printf("\n\tnumParams=%d", eff->numParams);
+ printf("\n\tnumInputs=%d", eff->numInputs);
+ printf("\n\tnumOutputs=%d", eff->numOutputs);
+
+ int flags = eff->flags;
+ printf("\n\tflags="); print_binary(flags);
+#define FST_UTILS__FLAG(x) if(effFlags##x) { \
+ if(effFlags##x & flags)printf("\n\t %s", #x); \
+ flags &= ~effFlags##x; \
+ } \
+ else printf("\n\t ???%s???", #x)
+
+ FST_UTILS__FLAG(HasEditor);
+ FST_UTILS__FLAG(IsSynth);
+ FST_UTILS__FLAG(CanDoubleReplacing);
+ FST_UTILS__FLAG(CanReplacing);
+ FST_UTILS__FLAG(NoSoundInStop);
+ FST_UTILS__FLAG(ProgramChunks);
+ if(flags) {
+ printf("\n\t ");
+ print_binary(flags);
+ }
+
+ printf("\n\tresvd1=0x%X", eff->resvd1);
+ printf("\n\tresvd2=0x%X", eff->resvd2);
+ printf("\n\tinitialDelay=%d", eff->initialDelay);
+
+ printf("\n\tuser=%p", eff->user);
+ printf("\n\tobject=%p", eff->object);
+ printf("\n\tuniqueID=%d", eff->uniqueID);
+ printf("\n\tversion=%d", eff->version);
+
+ printf("\n\tprocessReplacingCB=%p", eff->processReplacing);
+ printf("\n\tprocessDoubleReplacingCB=%p", eff->processDoubleReplacing);
+ printf("\n\n");
+}
+static void print_event(VstEvent*ev, int hexdump, const char*prefix="") {
+ printf("%sVstEvent @ %p", prefix, ev);
+ if(!ev) {
+ printf(" [%d]\n", sizeof(VstEvent));
+ return;
+ }
+ if(hexdump) {
+ printf("\n");
+ print_hex(ev, ev->byteSize + 4*(kVstMidiType == ev->type));
+ }
+
+ if(ev->type == kVstMidiType) {
+ VstMidiEvent*mev = (VstMidiEvent*)ev;
+ printf("%s [%d]\n", prefix, sizeof(VstMidiEvent));
+
+ printf("%s", prefix); printf("\ttype=%d\n", mev->type);
+ printf("%s", prefix); printf("\tbyteSize=%d\n\tdeltaFrames=%d\n", mev->byteSize, mev->deltaFrames);
+ printf("%s", prefix); printf("\tMIDI: %02x %02x %02x %02x\n"
+ , mev->midiData[0]
+ , mev->midiData[1]
+ , mev->midiData[2]
+ , mev->midiData[3]);
+ printf("%s", prefix); printf("\tnote: length=%d\toffset=%d\tvelocity=%d\tdetune=%d\n",
+ mev->noteLength,
+ mev->noteOffset,
+ mev->noteOffVelocity,
+ mev->detune);
+ } else if (ev->type == kVstSysExType) {
+ VstMidiSysexEvent*sev = (VstMidiSysexEvent*)ev;
+ printf("%s", prefix); printf(" [%d]\n", sizeof(VstMidiSysexEvent));
+
+ printf("%s", prefix); printf("\ttype=%d\n", sev->type);
+ printf("%s", prefix); printf("\tbyteSize=%d\n\tdeltaFrames=%d\n", sev->byteSize, sev->deltaFrames);
+ printf("%s", prefix); printf("\tSysEx %d bytes @ %p\n\t", sev->dumpBytes, sev->sysexDump);
+ unsigned char*data=(unsigned char*)sev->sysexDump;
+ printf("%s", prefix);
+ for(int i=0; i<sev->dumpBytes; i++)
+ printf(" %02x", *data++);
+ printf("\n");
+ printf("\tflags=%d\treserved=%lu\t%lu\n",
+ sev->flags, sev->resvd1, sev->resvd2);
+ }
+}
+
+static void print_events(VstEvents*evs, int hexdump=0, const char*prefix="") {
+ printf("%s%d VstEvents @ %p\n", prefix, evs?evs->numEvents:0, evs);
+ if(!evs)return;
+ for(int i=0; i<evs->numEvents; i++) {
+ print_event(evs->events[i], hexdump, prefix);
+ }
+}
+
+static void print_erect(ERect*rect, const char*prefix="") {
+ printf("%sERect[%p]", prefix, rect);
+ if(rect)
+ printf(" = %d|%d - %d|%d", rect->top, rect->left, rect->bottom, rect->right);
+ printf("\n");
+}
+static void print_pinproperties(VstPinProperties*vpp) {
+ printf("VstPinProperties @ %p", vpp);
+ if(!vpp) {
+ printf("\n");
+ return;
+ }
+ printf("\nlabel : '%.*s'", 64, vpp->label);
+ printf("\nlshortabel: '%.*s'", 8, vpp->shortLabel);
+ printf("\narrangtype: %d", vpp->arrangementType);
+ printf("\nflags : %d", vpp->flags);
+ printf("\n");
+}
+
+static char*speakerArrangement2string(int type, char*output, size_t length) {
+ output[0]=0;
+ switch(type) {
+ default:
+ snprintf(output, length, "%d", type);
+ break;
+ FST_UTILS__OPCODESTR(kSpeakerArrEmpty);
+ FST_UTILS__OPCODESTR(kSpeakerArrMono);
+ FST_UTILS__OPCODESTR(kSpeakerArrStereo);
+ FST_UTILS__OPCODESTR(kSpeakerArrStereoSurround);
+ FST_UTILS__OPCODESTR(kSpeakerArrStereoCenter);
+ FST_UTILS__OPCODESTR(kSpeakerArrStereoSide);
+ FST_UTILS__OPCODESTR(kSpeakerArrStereoCLfe);
+ FST_UTILS__OPCODESTR(kSpeakerArr30Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr30Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr31Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr31Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr40Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr40Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr41Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr41Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr50);
+ FST_UTILS__OPCODESTR(kSpeakerArr51);
+ FST_UTILS__OPCODESTR(kSpeakerArr60Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr60Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr61Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr61Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr70Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr70Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr71Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr71Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr80Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr80Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr81Cine);
+ FST_UTILS__OPCODESTR(kSpeakerArr81Music);
+ FST_UTILS__OPCODESTR(kSpeakerArr102);
+ FST_UTILS__OPCODESTR(kSpeakerArrUserDefined);
+ }
+ output[length-1]=0;
+ return output;
+}
+static char*speaker2string(VstSpeakerProperties*props, char*output, size_t length) {
+ output[0]=0;
+ if(props) {
+ switch(props->type) {
+ default:
+ snprintf(output, length, "%d [0x%X]", props->type, props->type);
+ break;
+ FST_UTILS__OPCODESTR(kSpeakerM);
+ FST_UTILS__OPCODESTR(kSpeakerL);
+ FST_UTILS__OPCODESTR(kSpeakerR);
+ FST_UTILS__OPCODESTR(kSpeakerC);
+ FST_UTILS__OPCODESTR(kSpeakerLfe);
+ FST_UTILS__OPCODESTR(kSpeakerLs);
+ FST_UTILS__OPCODESTR(kSpeakerRs);
+ FST_UTILS__OPCODESTR(kSpeakerLc);
+ FST_UTILS__OPCODESTR(kSpeakerRc);
+ FST_UTILS__OPCODESTR(kSpeakerS);
+ FST_UTILS__OPCODESTR(kSpeakerSl);
+ FST_UTILS__OPCODESTR(kSpeakerSr);
+ FST_UTILS__OPCODESTR(kSpeakerTm);
+ FST_UTILS__OPCODESTR(kSpeakerTfl);
+ FST_UTILS__OPCODESTR(kSpeakerTfc);
+ FST_UTILS__OPCODESTR(kSpeakerTfr);
+ FST_UTILS__OPCODESTR(kSpeakerTrl);
+ FST_UTILS__OPCODESTR(kSpeakerTrc);
+ FST_UTILS__OPCODESTR(kSpeakerTrr);
+ FST_UTILS__OPCODESTR(kSpeakerLfe2);
+ }
+ }
+ output[length-1]=0;
+ return output;
+}
+static void print_non0bytes(void*bytes, size_t length) {
+ char*data=(char*)bytes;
+ for(size_t i=0; i<length; i++) {
+ if(data[i]) {
+ printf("\n padding ");
+ print_hex(data, length);
+ return;
+ }
+ }
+}
+static void print_speakerpadding(VstSpeakerProperties*props) {
+ /* print padding bytes if non-0 */
+ print_non0bytes(props, 80);
+ print_non0bytes(props->_padding2, sizeof(props->_padding2));
+}
+
+static void print_speakerarrangement(const char*name, VstSpeakerArrangement*vpp) {
+ char buf[512];
+ printf("SpeakerArrangement[%s] @ %p: %s", name, vpp, (vpp?speakerArrangement2string(vpp->type, buf, 512):0));
+ if(!vpp) {
+ printf("\n");
+ return;
+ }
+ for(int i=0; i < vpp->numChannels; i++) {
+ printf("\n\t#%d: %s", i, speaker2string(&(vpp->speakers[i]), buf, 512));
+ print_speakerpadding(&(vpp->speakers[i]));
+ }
+ printf("\n");
+ //print_hex(vpp, 1024);
+}
+
+
+static void print_timeinfo(VstTimeInfo*vti) {
+ printf("VstTimeInfo @ %p", vti);
+ if(!vti) {
+ printf("\n");
+ return;
+ }
+#define FST_UTILS__VTI_g(ti, x) printf("\n\t%s: %g", #x, ti->x)
+#define FST_UTILS__VTI_d(ti, x) printf("\n\t%s: %d", #x, ti->x)
+#define FST_UTILS__VTI_x(ti, x) printf("\n\t%s: 0x%X", #x, ti->x)
+ FST_UTILS__VTI_g(vti, samplePos);
+ FST_UTILS__VTI_g(vti, sampleRate);
+ FST_UTILS__VTI_g(vti, nanoSeconds);
+ printf("\t= %gsec", (vti->nanoSeconds * 0.000000001));
+ FST_UTILS__VTI_g(vti, ppqPos);
+ FST_UTILS__VTI_g(vti, tempo);
+ FST_UTILS__VTI_g(vti, barStartPos);
+ FST_UTILS__VTI_g(vti, cycleStartPos);
+ FST_UTILS__VTI_g(vti, cycleEndPos);
+ FST_UTILS__VTI_d(vti, timeSigNumerator);
+ FST_UTILS__VTI_d(vti, timeSigDenominator);
+ FST_UTILS__VTI_d(vti, samplesToNextClock);
+ FST_UTILS__VTI_x(vti, flags);
+
+ int flags = vti->flags;
+#define FST_UTILS__VTI_FLAGS(flags, f) do {if(flags & f)printf("\n\t\t%s", #f); flags &= ~f;} while (0)
+ FST_UTILS__VTI_FLAGS(flags, kVstTransportChanged);
+ FST_UTILS__VTI_FLAGS(flags, kVstTransportPlaying);
+ FST_UTILS__VTI_FLAGS(flags, kVstTransportCycleActive);
+ FST_UTILS__VTI_FLAGS(flags, kVstTransportRecording);
+ FST_UTILS__VTI_FLAGS(flags, kVstAutomationReading);
+ FST_UTILS__VTI_FLAGS(flags, kVstAutomationWriting);
+ FST_UTILS__VTI_FLAGS(flags, kVstNanosValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstPpqPosValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstTempoValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstBarsValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstCyclePosValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstTimeSigValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstSmpteValid);
+ FST_UTILS__VTI_FLAGS(flags, kVstClockValid);
+ if(flags)printf("\n\t\tremainder: 0x%04X", flags);
+
+ FST_UTILS__VTI_d(vti, smpteFrameRate);
+ FST_UTILS__VTI_d(vti, smpteOffset);
+
+ FST_UTILS__VTI_d(vti, currentBar);
+ FST_UTILS__VTI_x(vti, magic);
+ printf("\n");
+}
+
+/* direction 1: incoming (pre dispatcher)
+ * direction 2: outgoing (post dispatcher)
+ * retval: return-value for post-dispatcher calls
+ */
+static void print_effPtr(AEffect* effect,
+ int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue,
+ int direction, t_fstPtrInt retval=0) {
+ bool incoming = direction?(direction&1):true;
+ bool outgoing = direction?(direction&2):true;
+ if(incoming) {
+ switch(opcode) {
+ default: break;
+ case effCanDo:
+ printf("\tcanDo: %s?\n", (char*)ptr);
+ break;
+ case effEditOpen:
+ printf("\twindowId: %p\n", ptr);
+ break;
+ case effSetChunk:
+ printf("\tchunk: ");
+ print_hex(ptr, ivalue);
+ break;
+ case effSetSpeakerArrangement:
+ print_speakerarrangement("input>", (VstSpeakerArrangement*)ivalue);
+ print_speakerarrangement("output>", (VstSpeakerArrangement*)ptr);
+ break;
+ case effProcessEvents:
+ printf("\tevents: ");
+ print_events((VstEvents*)ptr);
+ break;
+ case effString2Parameter:
+ case effSetProgramName:
+ printf("\t'%s'\n", (char*)ptr);
+ break;
+ }
+ }
+ if(outgoing) {
+ switch(opcode) {
+ default: break;
+ case effGetChunk:
+ printf("\tchunk: ");
+ print_hex(ptr, retval);
+ break;
+ case effGetParamLabel:
+ case effGetParamDisplay:
+ case effGetParamName:
+ case effGetProductString:
+ case effGetProgramNameIndexed:
+ case effGetProgramName:
+ case effGetVendorString:
+ printf("\t'%s'\n", (char*)ptr);
+ break;
+ case effGetSpeakerArrangement:
+ print_speakerarrangement("input<", ((VstSpeakerArrangement**)ivalue)[0]);
+ print_speakerarrangement("output<", ((VstSpeakerArrangement**)ptr)[0]);
+ break;
+ case effGetInputProperties:
+ case effGetOutputProperties:
+ print_pinproperties((VstPinProperties*)ptr);
+ break;
+ case effEditGetRect:
+ print_erect((ERect*)ptr, "\t");
+ }
+ }
+}
+
+/* direction 1: incoming (pre dispatcher)
+ * direction 2: outgoing (post dispatcher)
+ * retval: return-value for post-dispatcher calls
+ */
+static void print_hostPtr(AEffect* effect,
+ int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue,
+ int direction, t_fstPtrInt retval=0) {
+ bool incoming = direction?(direction|1):true;
+ bool outgoing = direction?(direction|2):true;
+ if(incoming) {
+ switch(opcode) {
+ default: break;
+ case audioMasterProcessEvents:
+ printf("\tevents: ");
+ print_events((VstEvents*)ptr);
+ break;
+ case audioMasterCanDo:
+ printf("\tcanDo: %s?\n", (char*)ptr);
+ break;
+ }
+ }
+ if(outgoing) {
+ switch(opcode) {
+ default: break;
+ case audioMasterGetTime:
+ print_timeinfo((VstTimeInfo*)retval);
+ return;
+ case audioMasterGetDirectory:
+ printf("\t'%s'\n", (char*)retval);
+ break;
+ break;
+ case audioMasterGetVendorString:
+ case audioMasterGetProductString:
+ printf("\t'%s'\n", (char*)ptr);
+ break;
+ }
+ }
+}
+
+
+
+static VstEvents*create_vstevents(const unsigned char midi[4]) {
+ VstEvents*ves = (VstEvents*)calloc(1, sizeof(VstEvents)+sizeof(VstEvent*));
+ VstMidiEvent*ve=(VstMidiEvent*)calloc(1, sizeof(VstMidiEvent));
+ ves->numEvents = 1;
+ ves->events[0]=(VstEvent*)ve;
+ ve->type = kVstMidiType;
+ ve->byteSize = sizeof(VstMidiEvent);
+ for(size_t i=0; i<4; i++)
+ ve->midiData[i] = midi[i];
+
+ return ves;
+}
+
+static
+t_fstPtrInt dispatch_effect (const char*name, AEffectDispatcherProc dispatchcb,
+ AEffect* effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ if(effect) {
+ char effname[64];
+ snprintf(effname, 64, "%p", effect);
+ const char*effectname = name?name:effname;
+ char opcodestr[256];
+ if(!dispatchcb)
+ dispatchcb = effect->dispatcher;
+ printf("Fst::host2plugin(%s, %s, %u=0x%X, %llu=0x%X, %p, %f)\n",
+ effectname, effCode2string(opcode, opcodestr, 255),
+ index, index,
+ ivalue, ivalue,
+ ptr, fvalue);
+ print_effPtr(effect, opcode, index, ivalue, ptr, fvalue, 1);
+ fflush(stdout);
+ t_fstPtrInt result = dispatchcb(effect, opcode, index, ivalue, ptr, fvalue);
+ printf("Fst::host2plugin: %lu (0x%lX)\n", result, result);
+ print_effPtr(effect, opcode, index, ivalue, ptr, fvalue, 2, result);
+ fflush(stdout);
+ return result;
+ }
+ return 0xDEAD;
+}
+static
+t_fstPtrInt dispatch_host (const char*name, AEffectDispatcherProc dispatchcb,
+ AEffect*effect, int opcode, int index, t_fstPtrInt ivalue, void*ptr, float fvalue) {
+ char effname[64];
+ snprintf(effname, 64, "%p", effect);
+ const char*effectname = name?name:effname;
+ char opcodestr[256];
+ printf("Fst::plugin2host(%s, %s, %u=0x%X, %llu=0x%X, %p, %f)\n",
+ effectname, hostCode2string(opcode, opcodestr, 255), index, index, ivalue, ivalue, ptr, fvalue);
+ print_hostPtr(effect, opcode, index, ivalue, ptr, fvalue, 1);
+ fflush(stdout);
+ t_fstPtrInt result = dispatchcb(effect, opcode, index, ivalue, ptr, fvalue);
+ printf("Fst::plugin2host: %lu (0x%lX)\n", result, result);
+ print_hostPtr(effect, opcode, index, ivalue, ptr, fvalue, 2, result);
+
+ fflush(stdout);
+ return result;
+}
+
+typedef AEffect* (t_fstMain)(AEffectDispatcherProc);
+static
+t_fstMain* fstLoadPlugin(const char* filename) {
+ t_fstMain*vstfun = 0;
+#ifdef _WIN32
+ HINSTANCE handle = LoadLibrary(filename);
+ printf("loading %s as %p\n", filename, handle);
+ if(!handle){printf("\tfailed!\n"); return 0; }
+ if(!vstfun)vstfun=(t_fstMain*)GetProcAddress(handle, "VSTPluginMain");
+ if(!vstfun)vstfun=(t_fstMain*)GetProcAddress(handle, "main");
+ if(!vstfun)FreeLibrary(handle);
+#else
+ void*handle = dlopen(filename, RTLD_NOW | RTLD_GLOBAL);
+ printf("loading %s as %p\n", filename, handle);
+ if(!handle){printf("\t%s\n", dlerror()); return 0; }
+ if(!vstfun)vstfun=(t_fstMain*)dlsym(handle, "VSTPluginMain");
+ if(!vstfun)vstfun=(t_fstMain*)dlsym(handle, "main");
+ if(!vstfun)dlclose(handle);
+#endif
+ printf("loaded '%s' @ %p: %p\n", filename, handle, vstfun);
+ fflush(stdout);
+ fstpause(1.);
+ return vstfun;
+}
+
+#endif /* FST_FST_UTILS_H_ */
diff --git a/source/fst/tests/Makefile b/source/fst/tests/Makefile
@@ -0,0 +1,26 @@
+#####################################################################
+## GNUmakefile for GNU Makefiles ('make')
+##
+## see makefile.msvc for Windows' 'nmake'
+#####################################################################
+
+CPPFLAGS=-I..
+
+sources=c_test.c cxx_test.cpp
+programs=$(patsubst %.c,%,$(filter %.c,$(sources))) $(patsubst %.cpp,%,$(filter %.cpp,$(sources)))
+
+.PHONY: all clean preproc
+
+all: $(programs)
+
+clean:
+ -rm $(programs)
+ -rm *.i *.ii
+
+preproc: $(patsubst %.c,%.i,$(filter %.c,$(sources))) $(patsubst %.cpp,%.ii,$(filter %.cpp,$(sources)))
+
+
+%.i: %.c
+ $(CC) -E $(CPPFLAGS) $< > $@
+%.ii: %.cpp
+ $(CXX) -E $(CPPFLAGS) $< > $@
diff --git a/source/fst/tests/c_test.c b/source/fst/tests/c_test.c
@@ -0,0 +1,5 @@
+#include "fst/fst.h"
+
+int main(void) {
+ return 0;
+}
diff --git a/source/fst/tests/cxx_test.cpp b/source/fst/tests/cxx_test.cpp
@@ -0,0 +1,10 @@
+#include "fst/fst.h"
+
+#if defined(_LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus)
+extern "C"
+#endif
+ int main(void);
+
+int main(void) {
+ return 0;
+}
diff --git a/source/fst/tests/makefile.msvc b/source/fst/tests/makefile.msvc
@@ -0,0 +1,17 @@
+CFLAGS=-I..
+CXXFLAGS=-I..
+CPPFLAGS=-I..
+
+RM = del
+
+all: c_test.obj cxx_test.obj
+
+clean:
+ -$(RM) *.obj *.i *.ii
+
+.SUFFIXES: .i .ii
+preproc: c_test.i cxx_test.ii
+.cpp.ii:
+ $(CXX) /E $(CXXFLAGS) $< >$@
+.c.i:
+ $(CC) /E $(CFLAGS) $< >$@
diff --git a/source/fst/tools/bin2typedump.py b/source/fst/tools/bin2typedump.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# print binary data as floats/doubles/ints/pointers
+
+# Copyright © 2019, IOhannes m zmölnig, IEM
+
+# This file is part of FST
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with FST. If not, see <http://www.gnu.org/licenses/>.
+
+import struct
+
+# cmdline arguments
+def parseCmdlineArgs():
+ import argparse
+
+ # dest='types', action='append_const',
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "-d",
+ "--double",
+ dest="format",
+ action="store_const",
+ const=("d", "%16.5g"),
+ help="decode as double",
+ )
+ parser.add_argument(
+ "-f",
+ "--float",
+ dest="format",
+ action="store_const",
+ const=("f", "%16.5g"),
+ help="decode as float",
+ )
+ parser.add_argument(
+ "-i",
+ "--int",
+ dest="format",
+ action="store_const",
+ const=("i", "%16d"),
+ help="decode as 32bit signed int",
+ )
+ parser.add_argument(
+ "-s",
+ "--short",
+ dest="format",
+ action="store_const",
+ const=("h", "%8d"),
+ help="decode as 16bit signed int",
+ )
+ parser.add_argument(
+ "-p",
+ "--pointer",
+ dest="format",
+ action="store_const",
+ const=("P", "0x%016X"),
+ help="decode as pointer",
+ )
+ parser.add_argument("files", nargs="*")
+ args = parser.parse_args()
+ return args
+
+
+def chunks(l, n):
+ """Yield successive n-sized chunks from l."""
+ for i in range(0, len(l), n):
+ yield l[i : i + n]
+
+
+def data2strings(data, structformat, printformat="%s"):
+ return [printformat % v[0] for v in struct.iter_unpack(structformat, data)]
+
+
+def print_data_as(data, structformat, printformat="%s"):
+ datasize = struct.calcsize(structformat)
+ chunksize = max(int(16 / datasize), 1)
+
+ for line in chunks(data2strings(data, structformat, printformat), chunksize):
+ print(" ".join(line))
+
+
+def _main():
+ args = parseCmdlineArgs()
+ for filename in args.files:
+ with open(filename, "rb") as f:
+ data = f.read()
+ print_data_as(data, args.format[0], args.format[1])
+
+
+if __name__ == "__main__":
+ _main()
diff --git a/source/fst/tools/fstversion.sh b/source/fst/tools/fstversion.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+fst_h="$1"
+
+if [ "x${fst_h}" = "x" ]; then
+ fst_h=fst/fst.h
+fi
+
+extract_values() {
+ egrep "FST_(HOST|EFFECT|TYPE|CONST|FLAG|SPEAKER)" "$1" \
+ | egrep -v "# *define " \
+ | grep -v "FST_fst_h"
+}
+
+version=$( \
+extract_values "${fst_h}" \
+| grep -v UNKNOWN \
+| grep -c . \
+)
+unknown=$( \
+extract_values "${fst_h}" \
+| egrep "FST_.*_UNKNOWN" \
+| grep -c . \
+)
+
+
+sed -e "s|\(# *define *FST_MINOR_VERSION *\)[0-9]*$|\1${version}|" -i "${fst_h}"
+echo "version: ${version}"
+echo "identifiers: known:${version} unknown:${unknown}"
diff --git a/source/fst/tools/hexdump2bin.py b/source/fst/tools/hexdump2bin.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+
+import sys
+
+for fname in sys.argv[1:]:
+ with open(fname) as f:
+ for line in f.readlines():
+ data = bytes([int(_,16) for _ in line.split()])
+ sys.stdout.buffer.write(data)