gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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:
Asource/fst/LICENSE | 674+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/README.md | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/docs/REVERSE_ENGINEERING.md | 4300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/docs/opcodes.org | 257+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/docs/realworld.org | 514+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/fst/aeffect.h | 3+++
Asource/fst/fst/aeffectx.h | 3+++
Asource/fst/fst/fst.h | 677+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/FstHost/FstHost.cpp | 565+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/FstHost/Makefile | 16++++++++++++++++
Asource/fst/src/FstHost/Makefile.nt | 16++++++++++++++++
Asource/fst/src/FstPlugin/FstPlugin.cpp | 633+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/FstPlugin/Makefile | 16++++++++++++++++
Asource/fst/src/FstProxy/FstProxy.cpp | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/FstProxy/Makefile | 16++++++++++++++++
Asource/fst/src/FstProxy/Makefile.nt | 16++++++++++++++++
Asource/fst/src/JstHost/JstHost.jucer | 41+++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/JstHost/Makefile | 5+++++
Asource/fst/src/JstHost/Source/Main.cpp | 29+++++++++++++++++++++++++++++
Asource/fst/src/JstPlugin/JstPlugin.jucer | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/JstPlugin/Makefile | 5+++++
Asource/fst/src/JstPlugin/Source/PluginEditor.cpp | 42++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/JstPlugin/Source/PluginEditor.h | 35+++++++++++++++++++++++++++++++++++
Asource/fst/src/JstPlugin/Source/PluginProcessor.cpp | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/JstPlugin/Source/PluginProcessor.h | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/src/Makefile | 9+++++++++
Asource/fst/src/fst_utils.h | 781+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/tests/Makefile | 26++++++++++++++++++++++++++
Asource/fst/tests/c_test.c | 5+++++
Asource/fst/tests/cxx_test.cpp | 10++++++++++
Asource/fst/tests/makefile.msvc | 17+++++++++++++++++
Asource/fst/tools/bin2typedump.py | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asource/fst/tools/fstversion.sh | 29+++++++++++++++++++++++++++++
Asource/fst/tools/hexdump2bin.py | 9+++++++++
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)