diff --git a/apf/.gitattributes b/apf/.gitattributes new file mode 100644 index 00000000..2e51ef09 --- /dev/null +++ b/apf/.gitattributes @@ -0,0 +1,4 @@ +*.cpp diff=cpp +*.h diff=cpp +/git* export-ignore +/.* export-ignore diff --git a/apf/.gitignore b/apf/.gitignore new file mode 100644 index 00000000..5ac2728d --- /dev/null +++ b/apf/.gitignore @@ -0,0 +1,4 @@ +/doc/html +/unit_tests/.dep +/unit_tests/main +/unit_tests/*.o diff --git a/apf/AUTHORS b/apf/AUTHORS new file mode 100644 index 00000000..a28f4797 --- /dev/null +++ b/apf/AUTHORS @@ -0,0 +1,9 @@ +Author: +Matthias Geier + +Contributions by: +Sascha Spors +Jens Ahrens +Torben Hohn +Till Rettberg +Moritz Heppner diff --git a/apf/COPYING b/apf/COPYING new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/apf/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU 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. + + + Copyright (C) + + 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 . + +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: + + Copyright (C) + 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 +. + + 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 +. diff --git a/apf/NEWS b/apf/NEWS new file mode 100644 index 00000000..6d0239fd --- /dev/null +++ b/apf/NEWS @@ -0,0 +1,24 @@ +User-visible changes in the Audio Processing Framework. Recent changes on top. + +0.2.0 (03 July 2013) + + - new: Convolver and BlockDelayLine + + - new: query_policy for getting information out of the audio thread + + - new: fixed_vector and fixed_list, changed Matrix to fixed_matrix + + - new: ScopedThread, DetatchedThread, fftw_allocator, PortAudio policy + + - re-design of the crossfade, tools for parameter interpolation were added. + see CombineChannels, CombineChannelsCrossfade, CombineChannelsInterpolation + + - posix_thread_policy and posix_sync_policy were combined + + - several bug-fixes and many improvements, more unit tests + + - re-organization of the directory structure, some separate files were combined + +0.1.0 (10 April 2012) + + - first release diff --git a/apf/README b/apf/README new file mode 100644 index 00000000..37944a28 --- /dev/null +++ b/apf/README @@ -0,0 +1,10 @@ +This is the source distribution of the Audio Processing Framework (APF), +licensed under the GPLv3+. Please consult the file COPYING for more information +about this license. + +Website: http://AudioProcessingFramework.github.com + +Copyright (c) 2012-2013 Institut für Nachrichtentechnik, Universität Rostock + +Copyright (c) 2006-2012 Quality & Usability Lab + Deutsche Telekom Laboratories, TU Berlin diff --git a/apf/apf/biquad.h b/apf/apf/biquad.h new file mode 100644 index 00000000..38641cc6 --- /dev/null +++ b/apf/apf/biquad.h @@ -0,0 +1,346 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Second order recursive filter and more. + +#ifndef APF_BIQUAD_H +#define APF_BIQUAD_H + +#include // for std::ostream +#include // for std::pow(), std::tan(), std::sqrt(), ... +#include +#include +#include // for assert() + +#include "apf/denormalprevention.h" +#include "apf/math.h" + +namespace apf +{ + +// TODO: make macros for trivial operators (see iterator.h) +// TODO: combine SosCoefficients and LaplaceCoefficients in common class +// template and use typedef with dummy template arguments. + +/// Coefficients of digital recursive filter (second order section). +/// @tparam T Internal data type +template +struct SosCoefficients +{ + SosCoefficients(T b0_ = T(), T b1_ = T(), T b2_ = T() + , T a1_ = T(), T a2_ = T()) + : b0(b0_), b1(b1_), b2(b2_) + , a1(a1_), a2(a2_) + {} + + T b0, b1, b2; + T a1, a2; + + SosCoefficients& operator+=(const SosCoefficients& rhs) + { + b0 += rhs.b0; b1 += rhs.b1; b2 += rhs.b2; + a1 += rhs.a1; a2 += rhs.a2; + return *this; + } + + SosCoefficients operator+(const SosCoefficients& rhs) const + { + auto tmp = SosCoefficients(*this); + return tmp += rhs; + } + + SosCoefficients& operator*=(T rhs) + { + b0 *= rhs; b1 *= rhs; b2 *= rhs; + a1 *= rhs; a2 *= rhs; + return *this; + } + + SosCoefficients operator*(T rhs) const + { + auto tmp = SosCoefficients(*this); + return tmp *= rhs; + } + + SosCoefficients& operator/=(T rhs) + { + b0 /= rhs; b1 /= rhs; b2 /= rhs; + a1 /= rhs; a2 /= rhs; + return *this; + } + + SosCoefficients operator/(T rhs) const + { + auto tmp = SosCoefficients(*this); + return tmp /= rhs; + } + + friend SosCoefficients operator*(T lhs, const SosCoefficients& rhs) + { + auto temp = SosCoefficients(rhs); + return temp *= lhs; + } + + friend SosCoefficients + operator-(const SosCoefficients& lhs, const SosCoefficients& rhs) + { + return {lhs.b0 - rhs.b0, lhs.b1 - rhs.b1, lhs.b2 - rhs.b2 + , lhs.a1 - rhs.a1, lhs.a2 - rhs.a2}; + } + + friend std::ostream& + operator<<(std::ostream& stream, const SosCoefficients& c) + { + stream << "b0: " << c.b0 << ", b1: " << c.b1 << ", b2: " << c.b2 + << ", a1: " << c.a1 << ", a2: " << c.a2; + return stream; + } +}; + +/// Coefficients of analog recursive filter. +/// @tparam T Internal data type +template +struct LaplaceCoefficients +{ + LaplaceCoefficients(T b0_ = T(), T b1_ = T(), T b2_ = T() + , T a1_ = T(), T a2_ = T()) + : b0(b0_), b1(b1_), b2(b2_) + , a1(a1_), a2(a2_) + {} + + T b0, b1, b2; + T a1, a2; +}; + +/** Direct Form II recursive filter of second order. + * @tparam T internal type of states and coefficients + * @tparam DenormalPrevention method of denormal prevention (see apf::dp) + * @see Cascade, bilinear() + **/ +template class DenormalPrevention = apf::dp::ac> +class BiQuad : public SosCoefficients , private DenormalPrevention +{ + public: + using argument_type = T; + using result_type = T; + + BiQuad() : w0(), w1(), w2() {} + + /// Assignment operator. + /// Change coefficients when operator '=' is called with SosCoefficients. + /// @param c New set of coefficients + /// @note state is unchanged! + BiQuad& operator=(const SosCoefficients& c) + { + this->SosCoefficients::operator=(c); + return *this; + } + + /// Process filter on single sample. + /// @param in input sample + /// @return output sample + result_type operator()(argument_type in) + { + w0 = w1; + w1 = w2; + w2 = in - this->a1*w1 - this->a2*w0; + + this->prevent_denormals(w2); + + in = this->b0*w2 + this->b1*w1 + this->b2*w0; + + return in; + } + + T w0, w1, w2; +}; + +/// %Cascade of filter sections. +/// @tparam S section type, e.g. BiQuad +template> +class Cascade +{ + public: + using argument_type = typename S::argument_type; + using result_type = typename S::result_type; + using size_type = typename Container::size_type; + + /// Constructor. + explicit Cascade(size_type n) : _sections(n) {} + + /// Overwrite sections with new content. + /// @tparam I Iterator type for arguments + /// @param first Begin iterator + /// @param last End iterator + template + void set(I first, I last) + { + assert(_sections.size() == size_type(std::distance(first, last))); + + std::copy(first, last, _sections.begin()); + } + + /// Process all sections on single sample. + /// @param in Input sample + /// @return Output sample + result_type operator()(argument_type in) + { + for (auto& section: _sections) + { + in = section(in); + } + return in; + } + + /// Process all sections on audio block. + /// @tparam In Iterator type for input samples + /// @tparam Out Iterator type for output samples + /// @param first Iterator to first input sample + /// @param last Iterator to (one past) last input sample + /// @param result Iterator to first output sample + template + void execute(In first, In last, Out result) + { + using out_t = typename std::iterator_traits::value_type; + + while (first != last) + { + *result++ = static_cast(this->operator()(*first)); + ++first; + } + } + + size_type number_of_sections() const { return _sections.size(); } + + private: + Container _sections; +}; + +namespace internal +{ + +/** Roots-to-polynomial conversion. + * @tparam T precision of data + * @param Roots 2x2 roots matrix + * @param poly1 reference to first-order-coefficient of output polynomial + * @param poly2 reference to second-order-coefficient of output polynomial + * @note zeroth-order is ignored for this special case! + **/ +template +void roots2poly(T Roots[2][2], T& poly1, T& poly2) +{ + T two = 2.0; + + std::complex eig[2]; + + T tmp_arg = std::pow((Roots[0][0]+Roots[1][1])/two, two) + + Roots[0][1]*Roots[1][0] - Roots[0][0]*Roots[1][1]; + + if (tmp_arg > 0) + { + eig[0] = (Roots[0][0]+Roots[1][1])/two + std::sqrt(tmp_arg); + eig[1] = (Roots[0][0]+Roots[1][1])/two - std::sqrt(tmp_arg); + } + else + { + eig[0] = std::complex((Roots[0][0]+Roots[1][1])/two, std::sqrt(-tmp_arg)); + eig[1] = std::complex((Roots[0][0]+Roots[1][1])/two, -std::sqrt(-tmp_arg)); + } + + poly1 = real(-eig[0] - eig[1]); + poly2 = real(-eig[1] * -eig[0]); +} + +} // namespace internal + +/** Bilinear transform. + * @tparam T internal data type + * @param coeffs_in coefficients of filter design in Laplace domain + * @param fs sampling rate + * @param fp prewarping frequency + * @return coefficients in z-domain + * @see BiQuad + **/ +template +SosCoefficients bilinear(LaplaceCoefficients coeffs_in, int fs, int fp) +{ + SosCoefficients coeffs_out; + + T one = 1.0; + T two = 2.0; + + // prewarp + T fp_temp = static_cast(fp) * (two * apf::math::pi()); + T lambda = fp_temp / std::tan(fp_temp / static_cast(fs) / two) / two; + + // calculate state space representation + T A[2][2] = { { -coeffs_in.a1, -coeffs_in.a2 }, { 1.0, 0.0 } }; + T B[2] = { 1.0, 0.0 }; + T C[2] = { coeffs_in.b1-coeffs_in.a1, coeffs_in.b2-coeffs_in.a2 }; + T D = 1.0; + + T t = one / lambda; + T r = std::sqrt(t); + + T T1[2][2] = { { (t/two)*A[0][0] + one, (t/two)*A[0][1] }, + { (t/two)*A[1][0], (t/two)*A[1][1] + one } }; + T T2[2][2] = { { -(t/two)*A[0][0] + one, -(t/two)*A[0][1] }, + { -(t/two)*A[1][0], -(t/two)*A[1][1] + one} }; + + // solve linear equation systems + T det = T2[0][0]*T2[1][1] - T2[0][1]*T2[1][0]; + T Ad[2][2] = { { (T1[0][0]*T2[1][1] - T1[1][0]*T2[0][1]) / det, + (T1[0][1]*T2[1][1] - T1[1][1]*T2[0][1]) / det }, + { (T1[1][0]*T2[0][0] - T1[0][0]*T2[1][0]) / det, + (T1[1][1]*T2[0][0] - T1[0][1]*T2[1][0]) / det } }; + T Bd[2] = { (t/r) * (B[0]*T2[1][1] - B[1]*T2[0][1]) / det, + (t/r) * (B[1]*T2[0][0] - B[0]*T2[1][0]) / det }; + T Cd[2] = { (C[0]*T2[1][1] - C[1]*T2[1][0]) / det, + (C[1]*T2[0][0] - C[0]*T2[0][1]) / det }; + T Dd = (B[0]*Cd[0] + B[1]*Cd[1]) * (t/two) + D; + + Cd[0] *= r; + Cd[1] *= r; + + // convert roots to polynomial + internal::roots2poly(Ad, coeffs_out.a1, coeffs_out.a2); + + T Tmp[2][2] = { { Ad[0][0]-Bd[0]*Cd[0], Ad[0][1]-Bd[0]*Cd[1] }, + { Ad[1][0]-Bd[1]*Cd[0], Ad[1][1]-Bd[1]*Cd[1] } }; + + internal::roots2poly(Tmp, coeffs_out.b1, coeffs_out.b2); + + coeffs_out.b0 = Dd; + coeffs_out.b1 += (Dd-one)*coeffs_out.a1; + coeffs_out.b2 += (Dd-one)*coeffs_out.a2; + + return coeffs_out; +} + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/apf/blockdelayline.h b/apf/apf/blockdelayline.h new file mode 100644 index 00000000..a4dabfd7 --- /dev/null +++ b/apf/apf/blockdelayline.h @@ -0,0 +1,281 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Block-based delay line. + +#ifndef APF_BLOCKDELAYLINE_H +#define APF_BLOCKDELAYLINE_H + +#include // for std::max() +#include // default container + +#include "apf/iterator.h" // for circular_iterator, stride_iterator + +namespace apf +{ + +/** Block-based delay line. + * This is a "write once, read many times" delay line. + * The write operation is simple and fast. + * The desired delay is specified at the more flexible read operation. + **/ +template> +class BlockDelayLine +{ + public: + using size_type = typename Container::size_type; + using pointer = typename Container::pointer; + using circulator = apf::circular_iterator; + + BlockDelayLine(size_type block_size, size_type max_delay); + + /// Return @b true if @p delay is valid + bool delay_is_valid(size_type delay) const + { + return delay <= _max_delay; + } + + /// Advance the internal iterators/pointers to the next block. + void advance() + { + ++_block_circulator; + _data_circulator += _block_size; + } + + template + void write_block(Iterator source); + + template + bool read_block(Iterator destination, size_type delay) const; + + template + bool read_block(Iterator destination, size_type delay, T weight) const; + + pointer get_write_pointer() const; + + circulator get_read_circulator(size_type delay = 0) const; + + protected: + /// Get a circular iterator to the sample with time 0 + circulator _get_data_circulator() const { return _data_circulator; } + + const size_type _block_size; ///< Size of read/write blocks + + private: + const size_type _max_delay; ///< Maximum delay + + const size_type _number_of_blocks; ///< No\. of blocks needed for storage + + Container _data; ///< Internal storage for sample data + + /// Circular iterator which iterates over each sample + circulator _data_circulator; + + /// Circular iterator iterating over the block-beginnings. + apf::stride_iterator _block_circulator; +}; + +/** Constructor. + * @param block_size Block size + * @param max_delay Maximum delay in samples + **/ +template +BlockDelayLine::BlockDelayLine(size_type block_size + , size_type max_delay) + : _block_size(block_size) + , _max_delay(max_delay) + // Minimum number of blocks is 2, even if _max_delay is 0. + // With only one block the circular iterators r and (r + _block_size) would be + // equal and the read...() functions wouldn't work. + // But anyway, who wants a delay line with no delay? Kind of useless ... + , _number_of_blocks( + std::max(size_type(2), (_max_delay + 2 * _block_size - 1) / _block_size)) + , _data(_number_of_blocks * _block_size) // initialized with default ctor T() + , _data_circulator(_data.begin(), _data.end()) + , _block_circulator(_data_circulator, _block_size) +{ + assert(_block_size >= 1); +} + +/** Write a block of data to the delay line. + * Before writing, the read and write pointers are advanced to the next block. + * If you don't want to use this function, you can also call advance(), get the + * write pointer with get_write_pointer() and write directly to it. + * @param source Pointer/iterator where the block of data shall be + * read from. + * @attention In @p source there must be enough data to read from! + * @note @p source must be a random access iterator. If you want to use another + * iterator, you'll have to do it on your own (as written above). + **/ +template +template +void +BlockDelayLine::write_block(Iterator source) +{ + this->advance(); + // Ignore return value, next time get_write_pointer() has to be used again! + std::copy(source, source + _block_size, this->get_write_pointer()); +} + +/** Read a block of data from the delay line. + * @param destination Iterator to destination + * @param delay Delay in samples + * @return @b true on success + **/ +template +template +bool +BlockDelayLine::read_block(Iterator destination, size_type delay) + const +{ + // TODO: try to get a more meaningful error message if source is not a random + // access iterator (e.g. when using a std::list) + if (!this->delay_is_valid(delay)) return false; + circulator source = this->get_read_circulator(delay); + std::copy(source, source + _block_size, destination); + return true; +} + +/// Read from the delay line and multiply each element by a given factor. +template +template +bool +BlockDelayLine::read_block(Iterator destination + , size_type delay, T weight) const +{ + if (!this->delay_is_valid(delay)) return false; + circulator source = this->get_read_circulator(delay); + std::transform(source, source + _block_size, destination + , [weight] (T in) { return in * weight; }); + return true; +} + +/** Get the write pointer. + * @attention Before the write operation, advance() must be called to + * update read and write pointers. + * @attention You must not write more than one block with this pointer! For + * the next block, you have to get a new pointer. + **/ +template +typename BlockDelayLine::pointer +BlockDelayLine::get_write_pointer() const +{ + return &*_block_circulator.base().base(); +} + +/** Get the read circulator. + * @param delay Delay in samples + * @attention There is no check if the delay is in the valid range between + * 0 and @c max_delay. You are responsible for checking that! + **/ +template +typename BlockDelayLine::circulator +BlockDelayLine::get_read_circulator(size_type delay) const +{ + return _get_data_circulator() - delay; +} + +/** A block-based delay line where negative delay is possible. + * This is done by delaying everything by a given initial delay. The (absolute + * value of the) negative delay can be at most as large as the initial delay. + * @see BlockDelayLine + **/ +template> +class NonCausalBlockDelayLine : private BlockDelayLine +{ + private: + using _base = BlockDelayLine; + + public: + using size_type = typename _base::size_type; + using circulator = typename _base::circulator; + using difference_type = typename circulator::difference_type; + + /// Constructor. @param initial_delay initial delay + /// @param block_size Block size + /// @param max_delay Maximum delay in samples + /// @param initial_delay Additional delay to achieve negative delay + /// @see BlockDelayLine::BlockDelayLine() + NonCausalBlockDelayLine(size_type block_size, size_type max_delay + , size_type initial_delay) + : _base(block_size, max_delay + initial_delay) + , _initial_delay(initial_delay) + {} + +#ifdef APF_DOXYGEN_HACK + // This is just for Doxygen documentation: + /// @see BlockDelayLine::advance() + void advance(); + /// @see BlockDelayLine::write_block() + template void write_block(Iterator source); + /// @see BlockDelayLine::get_write_pointer() + pointer get_write_pointer() const; +#else + // This is the real thing: + using _base::advance; + using _base::write_block; + using _base::get_write_pointer; +#endif + + /// @see BlockDelayLine::delay_is_valid() + bool delay_is_valid(difference_type delay) const + { + if (delay < -_initial_delay) return false; + if (delay < 0) return true; + return _base::delay_is_valid(delay + _initial_delay); + } + + /// @see BlockDelayLine::read_block() + template + bool read_block(Iterator destination, difference_type delay) const + { + if (delay < -_initial_delay) return false; + return _base::read_block(destination, delay + _initial_delay); + } + + /// @see BlockDelayLine::read_block() + template + bool read_block(Iterator destination, difference_type delay, T weight) const + { + if (delay < -_initial_delay) return false; + return _base::read_block(destination, delay + _initial_delay, weight); + } + + /// @see BlockDelayLine::get_read_circulator() + circulator get_read_circulator(difference_type delay = 0) const + { + return _base::get_read_circulator(delay + _initial_delay); + } + + private: + const difference_type _initial_delay; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/combine_channels.h b/apf/apf/combine_channels.h new file mode 100644 index 00000000..bba2348c --- /dev/null +++ b/apf/apf/combine_channels.h @@ -0,0 +1,505 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Combine channels, interpolate, crossfade. + +#ifndef APF_COMBINE_CHANNELS_H +#define APF_COMBINE_CHANNELS_H + +#include +#include // for assert() +#include // for std::logic_error +#include // for std::transform(), std::copy(), std::fill() + +#include // for std::bind() +#include // for std::remove_reference + +#include "apf/iterator.h" // for *_iterator, make_*_iterator(), cast_proxy_const +#include "apf/misc.h" // for CRTP + +namespace apf +{ + +namespace CombineChannelsResult +{ + enum type + { + nothing = 0, + constant = 1, + change = 2, + fade_in = 3, + fade_out = 4 + }; +} + +/** Base class for CombineChannels*. + * @tparam Derived Derived class ("Curiously Recurring Template Pattern") + * @tparam ListProxy Proxy class for input list. If no proxy is needed, just use + * a reference to the list (e.g. std::list&). + * @p ListProxy (or the list itself) must have begin() and end() and an inner + * type @c value_type which itself must have begin() and end() and an inner + * type @c iterator. + * @tparam Out Output class. Must have begin() and end() functions. + * + * @see CombineChannels, CombineChannelsCopy, CombineChannelsCrossfade, + * CombineChannelsCrossfadeCopy, CombineChannelsInterpolation + **/ +template +class CombineChannelsBase : public CRTP +{ + protected: + using T = typename std::iterator_traits::type::value_type::iterator>::value_type; + + public: + /// Constructor. + /// @param in List of objects to combine + /// @param out Target object + template + CombineChannelsBase(L& in, Out& out) + : _in(in) + , _out(out) + {} + + /// Do the actual combining. + /// @param f A "special" function object. It has to have a member function + /// @c select() which takes an item of the list as parameter. Depending on + /// the derived class, it may also need other member functions. + template + void process(F f) + { + // We pass f by value because this is common in STL-like algorithms. + // After select() is called, it is passed to case_one() and case_two() as + // non-const reference to avoid a further copy. + + _accumulate = false; + + this->derived().before_the_loop(); + + for (auto& item: _in) + { + using namespace CombineChannelsResult; + + switch (_selection = f.select(item)) + { + case nothing: + continue; // jump to next list item + + case constant: + this->derived().case_one(item, f); + break; + + case change: + case fade_in: + case fade_out: + this->derived().case_two(item, f); + break; + + default: + throw std::runtime_error("Predicate must return 0, 1 or 2!"); + } + } + + this->derived().after_the_loop(); + + if (!_accumulate) + { + std::fill(_out.begin(), _out.end(), T()); + } + } + + void before_the_loop() {} + + template + void case_one(const ItemType&, F&) + { + throw std::logic_error("CombineChannelsBase: case 1 not implemented!"); + } + + template + void case_two(const ItemType&, F&) + { + throw std::logic_error("CombineChannelsBase: case 2 not implemented!"); + } + + void after_the_loop() {} + + private: + ListProxy _in; + + protected: + template + void _case_one_copy(const ItemType& item) + { + if (_accumulate) + { + std::copy(item.begin(), item.end() + , make_accumulating_iterator(_out.begin())); + } + else + { + std::copy(item.begin(), item.end(), _out.begin()); + _accumulate = true; + } + } + + template + void _case_one_transform(const ItemType& item, FunctionType& f) + { + if (_accumulate) + { + std::transform(item.begin(), item.end() + , make_accumulating_iterator(_out.begin()), f); + } + else + { + std::transform(item.begin(), item.end(), _out.begin(), f); + _accumulate = true; + } + } + + Out& _out; + CombineChannelsResult::type _selection; + bool _accumulate; +}; + +/** Combine channels: accumulate. + **/ +template +class CombineChannelsCopy : public CombineChannelsBase< + CombineChannelsCopy, L, Out> +{ + private: + using _base = CombineChannelsBase, L, Out>; + + public: + CombineChannelsCopy(const L& in, Out& out) : _base(in, out) {} + + template + void case_one(const ItemType& item, F&) + { + this->_case_one_copy(item); + } + + // Case 2 is not implemented and shall not be used! +}; + +/** Combine channels: transform and accumulate. + **/ +template +class CombineChannels: public CombineChannelsBase< + CombineChannels, L, Out> +{ + private: + using _base = CombineChannelsBase, L, Out>; + + public: + CombineChannels(const L& in, Out& out) : _base(in, out) {} + + template + void case_one(const ItemType& item, F& f) + { + this->_case_one_transform(item, f); + } + + // Case 2 is not implemented and shall not be used! +}; + +/** Combine channels: interpolate and accumulate. + **/ +template +class CombineChannelsInterpolation: public CombineChannelsBase< + CombineChannelsInterpolation, L, Out> +{ + private: + using _base + = CombineChannelsBase, L, Out>; + using T = typename _base::T; + using _base::_selection; + using _base::_accumulate; + using _base::_out; + + public: + CombineChannelsInterpolation(const L& in, Out& out) : _base(in, out) {} + + template + void case_one(const ItemType& item, F& f) + { + this->_case_one_transform(item, f); + } + + template + void case_two(const ItemType& item, F& f) + { + assert(_selection == CombineChannelsResult::change); + + if (_accumulate) + { + std::transform(item.begin(), item.end(), index_iterator() + , make_accumulating_iterator(_out.begin()), f); + } + else + { + std::transform(item.begin(), item.end(), index_iterator() + , _out.begin(), f); + _accumulate = true; + } + } +}; + +struct fade_out_tag {}; + +/** Base class for CombineChannelsCrossfade*. + **/ +template +class CombineChannelsCrossfadeBase : public CombineChannelsBase +{ + private: + using _base = CombineChannelsBase; + using T = typename _base::T; + using _base::_accumulate; + using _base::_out; + + public: + CombineChannelsCrossfadeBase(const L& in, Out& out, const Crossfade& fade) + : _base(in, out) + , _fade_out_buffer(fade.size()) + , _fade_in_buffer(fade.size()) + , _crossfade_data(fade) + {} + + void before_the_loop() + { + _accumulate_fade_in = _accumulate_fade_out = false; + } + + void after_the_loop() + { + if (_accumulate_fade_out) + { + if (_accumulate) + { + std::transform(_fade_out_buffer.begin(), _fade_out_buffer.end() + , _crossfade_data.fade_out_begin() + , make_accumulating_iterator(_out.begin()) + , std::multiplies()); + } + else + { + std::transform(_fade_out_buffer.begin(), _fade_out_buffer.end() + , _crossfade_data.fade_out_begin() + , _out.begin() + , std::multiplies()); + _accumulate = true; + } + } + if (_accumulate_fade_in) + { + if (_accumulate) + { + std::transform(_fade_in_buffer.begin(), _fade_in_buffer.end() + , _crossfade_data.fade_in_begin() + , make_accumulating_iterator(_out.begin()) + , std::multiplies()); + } + else + { + std::transform(_fade_in_buffer.begin(), _fade_in_buffer.end() + , _crossfade_data.fade_in_begin() + , _out.begin() + , std::multiplies()); + _accumulate = true; + } + } + } + + protected: + bool _accumulate_fade_in, _accumulate_fade_out; + std::vector _fade_out_buffer, _fade_in_buffer; + + private: + const Crossfade& _crossfade_data; +}; + +/** Combine channels: crossfade and accumulate. + **/ +template +class CombineChannelsCrossfadeCopy : public CombineChannelsCrossfadeBase< + CombineChannelsCrossfadeCopy, L, Out, Crossfade> +{ + private: + using _base = CombineChannelsCrossfadeBase, L, Out, Crossfade>; + + using _base::_fade_out_buffer; + using _base::_fade_in_buffer; + using _base::_accumulate_fade_in; + using _base::_accumulate_fade_out; + using _base::_selection; + + public: + CombineChannelsCrossfadeCopy(const L& in, Out& out, const Crossfade& fade) + : _base(in, out, fade) + {} + + template + void case_one(const ItemType& item, F&) + { + this->_case_one_copy(item); + } + + template + void case_two(ItemType& item, F& f) + { + if (_selection != CombineChannelsResult::fade_in) + { + if (_accumulate_fade_out) + { + std::copy(item.begin(), item.end() + , make_accumulating_iterator(_fade_out_buffer.begin())); + } + else + { + std::copy(item.begin(), item.end(), _fade_out_buffer.begin()); + _accumulate_fade_out = true; + } + } + if (_selection != CombineChannelsResult::fade_out) + { + f.update(); + + if (_accumulate_fade_in) + { + std::copy(item.begin(), item.end() + , make_accumulating_iterator(_fade_in_buffer.begin())); + } + else + { + std::copy(item.begin(), item.end(), _fade_in_buffer.begin()); + _accumulate_fade_in = true; + } + } + } +}; + +/** Combine channels: transform, crossfade and accumulate. + **/ +template +class CombineChannelsCrossfade : public CombineChannelsCrossfadeBase< + CombineChannelsCrossfade, L, Out, Crossfade> +{ + private: + using _base = CombineChannelsCrossfadeBase, L, Out, Crossfade>; + using _base::_selection; + using _base::_accumulate_fade_in; + using _base::_accumulate_fade_out; + + public: + CombineChannelsCrossfade(const L& in, Out& out, const Crossfade& fade) + : _base(in, out, fade) + {} + + template + void case_one(const ItemType& item, F& f) + { + this->_case_one_transform(item, f); + } + + template + void case_two(ItemType& item, F& f) + { + if (_selection != CombineChannelsResult::fade_in) + { + if (_accumulate_fade_out) + { + std::transform(item.begin(), item.end() + , make_accumulating_iterator(this->_fade_out_buffer.begin()) + , std::bind(f, std::placeholders::_1, fade_out_tag())); + } + else + { + std::transform(item.begin(), item.end() + , this->_fade_out_buffer.begin() + , std::bind(f, std::placeholders::_1, fade_out_tag())); + _accumulate_fade_out = true; + } + } + if (_selection != CombineChannelsResult::fade_out) + { + f.update(); + + if (_accumulate_fade_in) + { + std::transform(item.begin(), item.end() + , make_accumulating_iterator(this->_fade_in_buffer.begin()), f); + } + else + { + std::transform(item.begin(), item.end() + , this->_fade_in_buffer.begin(), f); + _accumulate_fade_in = true; + } + } + } +}; + +/** Crossfade using a raised cosine. + **/ +template +class raised_cosine_fade +{ + private: + using iterator_type + = transform_iterator, math::raised_cosine>; + + public: + using iterator = typename std::vector::const_iterator; + using reverse_iterator = typename std::vector::const_reverse_iterator; + + raised_cosine_fade(size_t block_size) + : _crossfade_data( + iterator_type(index_iterator() + , math::raised_cosine(static_cast(2 * block_size))), + // block_size + 1 because we also use it in reverse order + iterator_type(index_iterator(static_cast(block_size + 1)))) + , _size(block_size) + {} + + iterator fade_out_begin() const { return _crossfade_data.begin(); } + reverse_iterator fade_in_begin() const { return _crossfade_data.rbegin(); } + size_t size() const { return _size; } + + private: + const std::vector _crossfade_data; + const size_t _size; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/commandqueue.h b/apf/apf/commandqueue.h new file mode 100644 index 00000000..66d35d3e --- /dev/null +++ b/apf/apf/commandqueue.h @@ -0,0 +1,235 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Command queue. + +#ifndef APF_COMMANDQUEUE_H +#define APF_COMMANDQUEUE_H + +#include // for usleep() +#include // for assert() + +#include "apf/lockfreefifo.h" + +namespace apf +{ + +/** Manage command queue from non-realtime thread to realtime thread. + * Commands can be added in the non-realtime thread with push(). + * + * Commands are executed when process_commands() is called from the realtime + * thread. + **/ +class CommandQueue : NonCopyable +{ + public: + /// Abstract base class for realtime commands. + /// These commands are passed through queues into the realtime thread and + /// after execution back to the non-realtime thread for cleanup. + struct Command : NonCopyable + { + /// Empty virtual destructor. + virtual ~Command() {} + + /// The actual implementation of the command. This is called from the + /// realtime thread. Overwritten in the derived class. + virtual void execute() = 0; + + /// Cleanup of resources. This is called from the non-realtime thread. + /// Overwritten in the derived class. + virtual void cleanup() = 0; + }; + + /// Dummy command to synchronize with non-realtime thread. + class WaitCommand : public Command + { + public: + /// Constructor. @param done is set to @b true when cleanup() is called. + WaitCommand(bool& done) : _done(done) {} + + private: + virtual void execute() { } + virtual void cleanup() { _done = true; } + + bool& _done; + }; + + /// @name Functions to be called from the non-realtime thread + /// If there are multiple non-realtime threads, access has to be locked! + //@{ + + /// Constructor. + /// @param size maximum number of commands in queue. + explicit CommandQueue(size_t size) + : _in_fifo(size) + , _out_fifo(size) + , _active(true) + {} + + /// Destructor. + /// @attention Commands in the cleanup queue are cleaned up, but commands in + /// the process queue are ignored and their memory is not freed! + ~CommandQueue() + { + this->cleanup_commands(); + // TODO: warning if process queue is not empty? + // TODO: if inactive -> process commands (if active -> ???) + } + + inline void push(Command* cmd); + + inline void wait(); + + /// Clean up all commands in the cleanup-queue. + /// @note This function must be called from the non-realtime thread. + void cleanup_commands() + { + Command* cmd; + while ((cmd = _out_fifo.pop()) != nullptr) { _cleanup(cmd); } + } + + // TODO: avoid return value? + /// Deactivate queue; process following commands in the non-realtime thread. + /// @return @b true on success + /// @note The queue must be empty. If not, the queue is @em not deactivated + /// and @b false is returned. + inline bool deactivate() + { + this->cleanup_commands(); + if (_in_fifo.empty()) _active = false; + return !_active; + } + + /// Re-activate queue. @see deactivate(). + inline void reactivate() + { + this->cleanup_commands(); + assert(_in_fifo.empty()); + _active = true; + } + + //@} + + /// @name Functions to be called from the realtime thread + //@{ + + /// Execute all commands in the queue. + /// After execution, the commands are queued for cleanup in the non-realtime + /// thread. + /// @note This function must be called from the realtime thread. + void process_commands() + { + Command* cmd; + while ((cmd = _in_fifo.pop()) != nullptr) + { + cmd->execute(); + bool result = _out_fifo.push(cmd); + // If _out_fifo is full, cmd is not cleaned up! + // This is very unlikely to happen (if not impossible). + assert(result && "Error in _out_fifo.push()!"); + (void)result; // avoid "unused-but-set-variable" warning + } + } + + /// Check if commands are available. + /// @return @b true if commands are available. + bool commands_available() const + { + return !_in_fifo.empty(); + } + + //@} + + private: + /// Clean up and delete a command @p cmd + void _cleanup(Command* cmd) + { + assert(cmd != nullptr); + cmd->cleanup(); + delete cmd; + } + + /// Queue of commands to execute in realtime thread + LockFreeFifo _in_fifo; + /// Queue of executed commands to delete in non-realtime thread + LockFreeFifo _out_fifo; + + bool _active; ///< default: true +}; + +/** Push a command to be executed in the realtime thread. + * The command will be cleaned up when it comes back from the + * realtime thread. + * If the CommandQueue is inactive, the command is not queued but executed and + * cleaned up immediately. + * @param cmd The command to be executed. + **/ +void CommandQueue::push(Command* cmd) +{ + if (!_active) + { + cmd->execute(); + _cleanup(cmd); + return; + } + + // First remove all commands from _out_fifo. + // This ensures that it's not going to be full which would block + // process_commands() and its calling realtime thread. + this->cleanup_commands(); + + // Now push the command on _in_fifo; if the FIFO is full: retry, retry, ... + while (!_in_fifo.push(cmd)) + { + // We don't really know if that ever happens, so we abort in debug-mode: + assert(false && "Error in _in_fifo.push()!"); + // TODO: avoid this usleep()? + usleep(50); + } +} + +/** Wait for realtime thread. + * Push an empty command and wait for its return. + **/ +void CommandQueue::wait() +{ + bool done = false; + this->push(new WaitCommand(done)); + + this->cleanup_commands(); + while (!done) + { + // TODO: avoid this usleep()? + usleep(50); + this->cleanup_commands(); + } +} + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/container.h b/apf/apf/container.h new file mode 100644 index 00000000..d04da895 --- /dev/null +++ b/apf/apf/container.h @@ -0,0 +1,661 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Some containers. + +#ifndef APF_CONTAINER_H +#define APF_CONTAINER_H + +#include // for std::allocator +#include +#include +#include // for std::logic_error +#include // for std::find + +#include "apf/iterator.h" // for stride_iterator, ... + +namespace apf +{ + +// TODO: move metaprogramming stuff into separate file? +namespace internal +{ + template struct first { using type = T1; }; + + // This didn't work with GCC 4.8.2 (segmentation fault during compilation) + //template using first = T1; + + template struct last : last {}; + template struct last { using type = T1; }; + + template using if_first_not_integral + = typename std::enable_if< + !std::is_integral::type>::value>::type; + template using if_integral + = typename std::enable_if::value>::type; + + template using if_last_not_convertible + = typename std::enable_if< + !std::is_convertible::type, Arg>::value>::type; +} + +/** Derived from @c std::vector, but without memory re-allocations. + * Non-copyable types can be used as long as they are movable. + * Normally, the size is specified in the constructor and doesn't change ever. + * If you need to initialize the fixed_vector before you know its final size, + * there is one exception: You can initialize the fixed_vector with the default + * constructor, at a later time you can call reserve() and afterwards + * emplace_back(). In this case the size grows, but the memory is still never + * re-allocated. + * @par Differences to @c std::vector: + * - There are slightly different constructors, especially one with a + * size-argument and further arbitrary arguments which are forwarded to the + * constructor of each element. + * - reserve() and emplace_back() have different semantics. + * - all other functions which (potentially) change size are disabled. + **/ +template> +class fixed_vector : public std::vector +{ + private: + using _base = typename std::vector; + + public: + using value_type = typename _base::value_type; + using size_type = typename _base::size_type; + + fixed_vector() = default; + fixed_vector(fixed_vector&&) = default; + fixed_vector(const fixed_vector&) = delete; + fixed_vector& operator=(const fixed_vector&) = delete; + fixed_vector& operator=(fixed_vector&&) = delete; + + /// Constructor that forwards everything except if first type is integral. + template> + explicit fixed_vector(Args&&... args) + : _base(std::forward(args)...) + {} + + template> + fixed_vector(Size n, const Allocator& a = Allocator()) + : _base(n, a) + {} + + template> + fixed_vector(Size n, Arg&& arg, const Allocator& a) + : _base(n, std::forward(arg), a) + {} + + /// Constructor from size and initialization arguments. + /// This can be used for initializing nested containers, for example. + template + , typename = internal::if_last_not_convertible> + explicit fixed_vector(Size n, Args&&... args) + : _base() + { + _base::reserve(static_cast(n)); + for (Size i = 0; i < n; ++i) + { + // Note: std::forward is not used here, because it's called repeatedly + _base::emplace_back(args...); + } + } + + // Perfect forwarding doesn't cover initializer lists: + explicit fixed_vector(std::initializer_list il + , const Allocator& a = Allocator()) + : _base(il, a) + {} + + /** Reserve space for new elements and default-construct them. + * In contrast to @c std::vector::resize(), this can only be called @e once + * and only on an empty fixed_vector (i.e. iff capacity == 0). + * Thus, resize() will allocate memory, but never @e re-allocate. + * @throw std::logic_error if capacity != 0 + **/ + void resize(size_type n) + { + if (this->capacity() == 0) + { + _base::resize(n); + } + else + { + throw std::logic_error( + "Bug: fixed_vector::resize() is only allowed if capacity == 0!"); + } + } + + /** Reserve space for new elements. + * In contrast to @c std::vector::reserve(), this can only be called @e once + * and only on an empty fixed_vector (i.e. iff capacity == 0). + * Thus, reserve() will allocate memory, but never @e re-allocate. + * @throw std::logic_error if capacity != 0 + **/ + void reserve(size_type n) + { + if (this->capacity() == 0) + { + _base::reserve(n); + } + else + { + throw std::logic_error( + "Bug: fixed_vector::reserve() is only allowed if capacity == 0!"); + } + } + + /** Construct element at the end. + * In contrast to @c std::vector::emplace_back() this can @e only be called + * after reserve() and at most as many times as specified in reserve() (and + * is typically called @e exactly as many times). + * Thus, memory will never be allocated. + * @throw std::logic_error if capacity would be exceeded + **/ + template + void emplace_back(Args&&... args) + { + if (this->size() < this->capacity()) + { + _base::emplace_back(std::forward(args)...); + } + else + { + throw std::logic_error( + "Bug: fixed_vector::emplace_back() " + "is only allowed if size < capacity!"); + } + } + + private: + // Hide all base class functions which would change size: + void resize(); + void assign(); + void push_back(); + void pop_back(); + void insert(); + void erase(); + void swap(); + void clear(); + void emplace(); +}; + +/** Derived from std::list, but without re-sizing. + * Items cannot be added/removed, but they can be re-ordered with move(). + **/ +template> +class fixed_list : public std::list +{ + private: + using _base = typename std::list; + + public: + using value_type = typename _base::value_type; + using iterator = typename _base::iterator; + + fixed_list() = default; + fixed_list(fixed_list&&) = default; + fixed_list(const fixed_list&) = delete; + fixed_list& operator=(const fixed_list&) = delete; + fixed_list& operator=(fixed_list&&) = delete; + + /// Constructor that forwards everything except if first type is integral. + template> + explicit fixed_list(Args&&... args) + : _base(std::forward(args)...) + {} + + /// Constructor from size and initialization arguments. + template> + explicit fixed_list(Size n, Args&&... args) + : _base() + { + for (Size i = 0; i < n; ++i) + { + // Note: std::forward is not used here, because it's called repeatedly + _base::emplace_back(args...); + } + } + + explicit fixed_list(std::initializer_list il + , const Allocator& a = Allocator()) + : _base(il, a) + {} + + /// Move list element @p from one place @p to another. + /// @p from is placed in front of @p to. + /// No memory is allocated/deallocated, no content is copied. + void move(iterator from, iterator to) + { + _base::splice(to, *this, from); + } + + /// Move range (from @p first to @p last) to @p target. + /// The range is placed in front of @p target. + /// No memory is allocated/deallocated, no content is copied. + void move(iterator first, iterator last, iterator target) + { + _base::splice(target, *this, first, last); + } + + private: + // Hide all base class functions which would change size: + void assign(); + void emplace_front(); + void emplace_back(); + void push_front(); + void pop_front(); + void push_back(); + void pop_back(); + void emplace(); + void insert(); + void erase(); + void swap(); + void resize(); + void clear(); + void splice(); + void remove(); + void remove_if(); + void unique(); + void merge(); +}; + +/** Two-dimensional data storage for row- and column-wise access. + * The two dimensions have following properties: + * -# Channel + * - stored in contiguous memory + * - fixed_matrix can be iterated from channels.begin() to channels.end() + * (using fixed_matrix::channels_iterator) + * - resulting channel can be iterated from .begin() to .end() + * (using fixed_matrix::channel_iterator) + * -# Slice + * - stored in memory locations with constant step size + * - fixed_matrix can be iterated from slices.begin() to slices.end() + * (using fixed_matrix::slices_iterator) + * - resulting slice can be iterated from .begin() to .end() + * (using fixed_matrix::slice_iterator) + * + * @tparam T Type of stored data + **/ +template> +class fixed_matrix : public fixed_vector +{ + private: + using _base = fixed_vector; + + public: + using pointer = typename _base::pointer; + using size_type = typename _base::size_type; + + /// Proxy class for returning one channel of the fixed_matrix + using Channel = has_begin_and_end; + /// Iterator within a Channel + using channel_iterator = typename Channel::iterator; + + /// Proxy class for returning one slice of the fixed_matrix + using Slice = has_begin_and_end>; + /// Iterator within a Slice + using slice_iterator = typename Slice::iterator; + + class channels_iterator; + class slices_iterator; + + /// Default constructor. + /// Only initialize() makes sense after this. + explicit fixed_matrix(const Allocator& a = Allocator()) + : _base(a) + { + this->initialize(0, 0); + } + + fixed_matrix(fixed_matrix&&) = default; + fixed_matrix(const fixed_matrix&) = delete; + fixed_matrix& operator=(const fixed_matrix&) = delete; + fixed_matrix& operator=(fixed_matrix&&) = delete; + + /** Constructor. + * @param max_channels Number of Channels + * @param max_slices Number of Slices + * @param a Optional allocator + **/ + fixed_matrix(size_type max_channels, size_type max_slices + , const Allocator& a = Allocator()) + : fixed_matrix(a) + { + this->initialize(max_channels, max_slices); + } + + /// Allocate memory for @p max_channels x @p max_slices elements and + /// default-construct them. + /// @pre empty() == true + void initialize(size_type max_channels, size_type max_slices) + { + _base::resize(max_channels * max_slices); + + this->channels = make_begin_and_end( + channels_iterator(_base::data(), max_slices), max_channels); + this->slices = make_begin_and_end( + slices_iterator(_base::data(), max_channels, max_slices), max_slices); + + _channel_ptrs.reserve(max_channels); + for (const auto& channel: this->channels) + { + _channel_ptrs.emplace_back(&*channel.begin()); + } + assert(_channel_ptrs.size() == max_channels); + } + + template + void set_channels(const Ch& ch); + + /// Get array of pointers to the channels. This can be useful to interact + /// with functions which use plain pointers instead of iterators. + pointer const* get_channel_ptrs() const { return _channel_ptrs.data(); } + + /// Access to Channels; use channels.begin() and channels.end() + has_begin_and_end channels; + /// Access to Slices; use slices.begin() and slices.end() + has_begin_and_end slices; + + private: + // Hide functions from fixed_vector: + void emplace_back(); + void reserve(); + void resize(); + + fixed_vector _channel_ptrs; +}; + +/// Iterator over fixed_matrix::Channel%s. +template +class fixed_matrix::channels_iterator +{ + private: + using self = channels_iterator; + using _base_type = stride_iterator; + + /// Helper class for operator->() + struct ChannelArrowProxy : Channel + { + ChannelArrowProxy(const Channel& ch) : Channel(ch) {} + Channel* operator->() { return this; } + }; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = Channel; + using reference = Channel; + using difference_type = typename _base_type::difference_type; + using pointer = ChannelArrowProxy; + + /// Default constructor. + /// @note This constructor creates a singular iterator. Another + /// channels_iterator can be assigned to it, but nothing else works. + channels_iterator() + : _size(0) + {} + + /// Constructor. + channels_iterator(channel_iterator base_iterator, size_type step) + : _base_iterator(base_iterator, step) + , _size(step) + {} + + /// Dereference operator. + /// @return a proxy object of type fixed_matrix::Channel + reference operator*() const + { + auto temp = _base_iterator.base(); + assert(apf::no_nullptr(temp)); + return Channel(temp, temp + _size); + } + + /// Arrow operator. + /// @return a proxy object of type fixed_matrix::ChannelArrowProxy + pointer operator->() const + { + return this->operator*(); + } + + APF_ITERATOR_RANDOMACCESS_EQUAL(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_LESS(_base_iterator) + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + APF_ITERATOR_BASE(_base_type, _base_iterator) + + private: + _base_type _base_iterator; + size_type _size; +}; + +/// Iterator over fixed_matrix::Slice%s. +template +class fixed_matrix::slices_iterator +{ + private: + using self = slices_iterator; + + /// Helper class for operator->() + struct SliceArrowProxy : Slice + { + SliceArrowProxy(const Slice& sl) : Slice(sl) {} + Slice* operator->() { return this; } + }; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = Slice; + using reference = Slice; + using pointer = SliceArrowProxy; + using difference_type + = typename std::iterator_traits::difference_type; + + /// Default constructor. + /// @note This constructor creates a singular iterator. Another + /// slices_iterator can be assigned to it, but nothing else works. + slices_iterator() + : _max_channels(0) + , _max_slices(0) + {} + + /// Constructor. + slices_iterator(channel_iterator base_iterator + , size_type max_channels, size_type max_slices) + : _base_iterator(base_iterator) + , _max_channels(max_channels) + , _max_slices(max_slices) + {} + + /// Dereference operator. + /// @return a proxy object of type fixed_matrix::Slice + reference operator*() const + { + assert(apf::no_nullptr(_base_iterator)); + slice_iterator temp(_base_iterator, _max_slices); + return Slice(temp, temp + _max_channels); + } + + /// Arrow operator. + /// @return a proxy object of type fixed_matrix::SliceArrowProxy + pointer operator->() const + { + return this->operator*(); + } + + APF_ITERATOR_RANDOMACCESS_EQUAL(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_LESS(_base_iterator) + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + APF_ITERATOR_BASE(channel_iterator, _base_iterator) + + private: + channel_iterator _base_iterator; + size_type _max_channels; + size_type _max_slices; +}; + +/** Copy channels from another matrix. + * @param ch channels (or slices) to copy from another fixed_matrix + * @note A plain copy may be faster with @c std::copy() from + * fixed_matrix::begin() to fixed_matrix::end(). + * @note Anyway, a plain copy of a fixed_matrix is rarely needed, the main + * reason for this function is that if you use slices instead of channels, + * you'll get a transposed matrix. + * @pre The dimensions must be correct beforehand! + * @warning If the dimensions are not correct, bad things will happen! + **/ +template +template +void +fixed_matrix::set_channels(const Ch& ch) +{ + assert(std::distance(ch.begin(), ch.end()) + == std::distance(this->channels.begin(), this->channels.end())); + assert((ch.begin() == ch.end()) ? true : + std::distance(ch.begin()->begin(), ch.begin()->end()) == + std::distance(this->channels.begin()->begin() + , this->channels.begin()->end())); + + auto target = this->channels.begin(); + + for (const auto& i: ch) + { + std::copy(i.begin(), i.end(), target->begin()); + ++target; + } +} + +/// Append pointers to the elements of the first list to the second list. +/// @note @c L2::value_type must be a pointer to @c L1::value_type! +template +void append_pointers(L1& source, L2& target) +{ + for (auto& i: source) + { + target.push_back(&i); + } +} + +/// Const-version of append_pointers() +/// @note @c L2::value_type must be a pointer to @b const @c L1::value_type! +template +void append_pointers(const L1& source, L2& target) +{ + for (const auto& i: source) + { + target.push_back(&i); + } +} + +/// Splice list elements from @p source to member lists of @p target. +/// @param source Elements of this list are distributed to the corresponding +/// @p member lists of @p target. This must have the same type as @p member. +/// @param target Each element of this list receives one element of @p source. +/// @param member The distributed elements are appended at @c member.end(). +/// @note Lists must have the same size. If not, an exception is thrown. +/// @note There is no const version, both lists are modified. +/// @post @p source will be empty. +/// @post The @p member of each @p target element will have one more element. +template +void distribute_list(L1& source, L2& target, DataMember member) +{ + if (source.size() != target.size()) + { + throw std::logic_error("distribute_list: Different sizes!"); + } + + auto in = source.begin(); + + for (auto& out: target) + { + (out.*member).splice((out.*member).end(), source, in++); + } +} + +/// The opposite of distribute_list() -- sorry for the strange name! +/// @param source Container of items which will be removed from @p member of the +/// corresponding @p target elements. +/// @param target Container of elements which have a @p member. +/// @param member Member container from which elements will be removed. Must +/// have a splice() member function (like @c std::list). +/// @param garbage Removed elements are appended to this list. Must have the +/// same type as @p member. +/// @throw std::logic_error If any element isn't found in the corresponding +/// @p member. +/// @attention If a list element is not found, an exception is thrown and the +/// original state is @b not restored! +// TODO: better name? +template +void +undistribute_list(const L1& source, L2& target, DataMember member, L3& garbage) +{ + if (source.size() != target.size()) + { + throw std::logic_error("undistribute_list(): Different sizes!"); + } + + auto in = source.begin(); + + for (auto& out: target) + { + auto delinquent + = std::find((out.*member).begin(), (out.*member).end(), *in++); + if (delinquent == (out.*member).end()) + { + throw std::logic_error("undistribute_list(): Element not found!"); + } + garbage.splice(garbage.end(), out.*member, delinquent); + } +} + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/convolver.h b/apf/apf/convolver.h new file mode 100644 index 00000000..091e2948 --- /dev/null +++ b/apf/apf/convolver.h @@ -0,0 +1,827 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Convolution engine. + +#ifndef APF_CONVOLVER_H +#define APF_CONVOLVER_H + +#include // for std::transform() +#include + +#ifdef __SSE__ +#include // for SSE instrinsics +#endif + +#include "apf/math.h" +#include "apf/fftwtools.h" // for fftw_allocator and fftw traits +#include "apf/container.h" // for fixed_vector, fixed_list +#include "apf/iterator.h" // for make_*_iterator() + +namespace apf +{ + +/** Convolution engine. + * A convolution engine normally consists of an Input, a Filter and + * an Output / StaticOutput. + * There are also combinations: + * Input + Output = Convolver; Input + StaticOutput = StaticConvolver + * + * Uses (uniformly) partitioned convolution. + * + * TODO: describe thread (un)safety + **/ +namespace conv +{ + +/// Calculate necessary number of partitions for a given filter length +static size_t min_partitions(size_t block_size, size_t filter_size) +{ + return (filter_size + block_size - 1) / block_size; +} + +/// Two blocks of time-domain or FFT (half-complex) data. +struct fft_node : fixed_vector> +{ + explicit fft_node(size_t n) + : fixed_vector>(n) + , zero(true) + {} + + fft_node(const fft_node&) = delete; + fft_node(fft_node&&) = default; + + fft_node& operator=(const fft_node& rhs) + { + assert(this->size() == rhs.size()); + + if (rhs.zero) + { + this->zero = true; + } + else + { + std::copy(rhs.begin(), rhs.end(), this->begin()); + this->zero = false; + } + return *this; + } + + // WARNING: The 'zero' flag allows saving computation power, but it also + // raises the risk of programming errors! Handle with care! + + /// To avoid unnecessary FFTs and filling buffers with zeros. + /// @note If zero == true, the buffer itself is not necessarily all zeros! + bool zero; +}; + +/// Container holding a number of FFT blocks. +struct Filter : fixed_vector +{ + /// Constructor; create empty filter. + Filter(size_t block_size_, size_t partitions_) + : fixed_vector(partitions_, block_size_ * 2) + { + assert(this->partitions() > 0); + } + + /// Constructor from time domain coefficients. + template + Filter(size_t block_size_, In first, In last, size_t partitions_ = 0); + // Implementation below, after definition of Transform + + size_t block_size() const { return this->front().size() / 2; } + size_t partition_size() const { return this->front().size(); } + size_t partitions() const { return this->size(); } +}; + +/// Forward-FFT-related functions +class TransformBase +{ + public: + template + void prepare_filter(In first, In last, Filter& filter) const; + + size_t block_size() const { return _block_size; } + size_t partition_size() const { return _partition_size; } + + template + In prepare_partition(In first, In last, fft_node& partition) const; + + protected: + explicit TransformBase(size_t block_size_); + + TransformBase(TransformBase&&) = default; + ~TransformBase() = default; + + using scoped_plan = fftw::scoped_plan; + using plan_ptr = std::unique_ptr; + + plan_ptr _create_plan(float* array) const; + + /// In-place FFT + void _fft(float* first) const + { + fftw::execute_r2r(*_fft_plan, first, first); + _sort_coefficients(first); + } + + plan_ptr _fft_plan; + + private: + void _sort_coefficients(float* first) const; + + const size_t _block_size; + const size_t _partition_size; +}; + +TransformBase::TransformBase(size_t block_size_) + : _block_size(block_size_) + , _partition_size(2 * _block_size) +{ + if (_block_size % 8 != 0) + { + throw std::logic_error("Convolver: block size must be a multiple of 8!"); + } +} + +/** Create in-place FFT plan for halfcomplex data format. + * @note FFT plans are not re-entrant except when using FFTW_THREADSAFE! + * @note Once a plan of a certain size exists, creating further plans + * is very fast because "wisdom" is shared (and therefore the creation of + * plans is not thread-safe). + * It is not necessary to re-use plans in other convolver instances. + **/ +TransformBase::plan_ptr +TransformBase::_create_plan(float* array) const +{ + return plan_ptr(new scoped_plan(fftw::plan_r2r_1d, int(_partition_size) + , array, array, FFTW_R2HC, FFTW_PATIENT)); +} + +/** %Transform time-domain samples. + * If there are too few input samples, the rest is zero-padded, if there are + * too few blocks in the container @p c, the rest of the samples is ignored. + * @param first Iterator to first time-domain sample + * @param last Past-the-end iterator + * @param[out] filter Target container + **/ +template +void +TransformBase::prepare_filter(In first, In last, Filter& filter) const +{ + for (auto& partition: filter) + { + first = this->prepare_partition(first, last, partition); + } +} + +/** FFT of one block. + * If there are too few coefficients, the rest is zero-padded. + * @param first Iterator to first coefficient + * @param last Past-the-end iterator + * @param[out] partition Target partition + * @tparam In Forward iterator + * @return Iterator to the first coefficient of the next block (for the next + * iteration, if needed) + **/ +template +In +TransformBase::prepare_partition(In first, In last, fft_node& partition) const +{ + assert(size_t(std::distance(partition.begin(), partition.end())) + == _partition_size); + + auto chunk = std::min(_block_size, size_t(std::distance(first, last))); + + if (chunk == 0) + { + partition.zero = true; + // No FFT has to be done (FFT of zero is also zero) + } + else + { + std::copy(first, first + chunk, partition.begin()); + std::fill(partition.begin() + chunk, partition.end(), 0.0f); // zero padding + _fft(partition.data()); + partition.zero = false; + } + return first + chunk; +} + +/** Sort the FFT coefficients to be in proper place for the efficient + * multiplication of the spectra. + **/ +void +TransformBase::_sort_coefficients(float* data) const +{ + auto buffer = fixed_vector(_partition_size); + + size_t base = 8; + + buffer[0] = data[0]; + buffer[1] = data[1]; + buffer[2] = data[2]; + buffer[3] = data[3]; + buffer[4] = data[_block_size]; + buffer[5] = data[_partition_size - 1]; + buffer[6] = data[_partition_size - 2]; + buffer[7] = data[_partition_size - 3]; + + for (size_t i = 0; i < (_partition_size / 8-1); i++) + { + for (size_t ii = 0; ii < 4; ii++) + { + buffer[base+ii] = data[base/2+ii]; + } + + for (size_t ii = 0; ii < 4; ii++) + { + buffer[base+4+ii] = data[_partition_size-base/2-ii]; + } + + base += 8; + } + + std::copy(buffer.begin(), buffer.end(), data); +} + +/// Helper class to prepare filters +struct Transform : TransformBase +{ + Transform(size_t block_size_) + : TransformBase(block_size_) + { + // Temporary memory area for FFTW planning routines + fft_node planning_space(this->partition_size()); + _fft_plan = _create_plan(planning_space.data()); + } +}; + +template +Filter::Filter(size_t block_size_, In first, In last, size_t partitions_) + : fixed_vector(partitions_ ? partitions_ + : min_partitions(block_size_, std::distance(first, last)) + , block_size_ * 2) +{ + assert(this->partitions() > 0); + + Transform(block_size_).prepare_filter(first, last, *this); +} + +/** %Input stage of convolution. + * New audio data is fed in here, further processing happens in Output. + **/ +struct Input : TransformBase +{ + /// @param block_size_ audio block size + /// @param partitions_ number of partitions + Input(size_t block_size_, size_t partitions_) + : TransformBase(block_size_) + // One additional list element for preparing the upcoming partition: + , spectra(partitions_ + 1, this->partition_size()) + { + assert(partitions_ > 0); + + _fft_plan = _create_plan(spectra.front().data()); + } + + template + void add_block(In first); + + size_t partitions() const { return spectra.size() - 1; } + + /// Spectra of the partitions (double-blocks) of the input signal to be + /// convolved. The first element is the most recent signal chunk. + fixed_list spectra; +}; + +/** Add a block of time-domain input samples. + * @param first Iterator to first sample. + * @tparam In Forward iterator + **/ +template +void +Input::add_block(In first) +{ + In last = first; + std::advance(last, this->block_size()); + + // rotate buffers (this->spectra.size() is always at least 2) + this->spectra.move(--this->spectra.end(), this->spectra.begin()); + + auto& current = this->spectra.front(); + auto& next = this->spectra.back(); + + if (math::has_only_zeros(first, last)) + { + next.zero = true; + + if (current.zero) + { + // Nothing to be done, actual data is ignored + } + else + { + // If first half is not zero, second half must be filled with zeros + std::fill(current.begin() + this->block_size(), current.end(), 0.0f); + } + } + else + { + if (current.zero) + { + // First half must be actually filled with zeros + std::fill(current.begin(), current.begin() + this->block_size(), 0.0f); + } + + // Copy data to second half of the current partition + std::copy(first, last, current.begin() + this->block_size()); + current.zero = false; + // Copy data to first half of the upcoming partition + std::copy(first, last, next.begin()); + next.zero = false; + } + + if (current.zero) + { + // Nothing to be done, FFT of zero is also zero + } + else + { + _fft(current.data()); + } +} + +/// Base class for Output and StaticOutput +class OutputBase +{ + public: + float* convolve(float weight = 1.0f); + + size_t block_size() const { return _input.block_size(); } + size_t partitions() const { return _filter_ptrs.size(); } + + protected: + explicit OutputBase(const Input& input); + + // This is non-const to allow automatic move-constructor: + fft_node _empty_partition; + + using filter_ptrs_t = fixed_vector; + filter_ptrs_t _filter_ptrs; + + private: + void _multiply_spectra(); + void _multiply_partition_cpp(const float* signal, const float* filter); +#ifdef __SSE__ + void _multiply_partition_simd(const float* signal, const float* filter); +#endif + + void _unsort_coefficients(); + + void _ifft(); + + const Input& _input; + + const size_t _partition_size; + + fft_node _output_buffer; + fftw::scoped_plan _ifft_plan; +}; + +OutputBase::OutputBase(const Input& input) + : _empty_partition(0) + // Initialize with empty partition + , _filter_ptrs(input.partitions(), &_empty_partition) + , _input(input) + , _partition_size(input.partition_size()) + , _output_buffer(_partition_size) + , _ifft_plan(fftw::plan_r2r_1d, int(_partition_size) + , _output_buffer.data() + , _output_buffer.data(), FFTW_HC2R, FFTW_PATIENT) +{ + assert(_filter_ptrs.size() > 0); +} + +/** Fast convolution of one audio block. + * %Input data has to be supplied with Input::add_block(). + * @param weight amplitude weighting factor for current audio block. + * The filter has to be set in the constructor of StaticOutput or via + * Output::set_filter(). + * @return pointer to the first sample of the convolved (and weighted) signal + **/ +float* +OutputBase::convolve(float weight) +{ + _multiply_spectra(); + + // The first half will be discarded + auto second_half = make_begin_and_end( + _output_buffer.begin() + _input.block_size(), _output_buffer.end()); + + assert(static_cast( + std::distance(second_half.begin(), second_half.end())) + == _input.block_size()); + + if (_output_buffer.zero) + { + // Nothing to be done, IFFT of zero is also zero. + // _output_buffer was already reset to zero in _multiply_spectra(). + } + else + { + _ifft(); + + // normalize buffer (fftw3 does not do this) + const auto norm = weight / float(_partition_size); + for (auto& x: second_half) + { + x *= norm; + } + } + return &second_half[0]; +} + +void +OutputBase::_multiply_partition_cpp(const float* signal, const float* filter) +{ + // see http://www.ludd.luth.se/~torger/brutefir.html#bruteconv_4 + + auto d1s = _output_buffer[0] + signal[0] * filter[0]; + auto d2s = _output_buffer[4] + signal[4] * filter[4]; + + for (size_t nn = 0; nn < _partition_size; nn += 8) + { + // real parts + _output_buffer[nn+0] += signal[nn+0] * filter[nn + 0] - + signal[nn+4] * filter[nn + 4]; + _output_buffer[nn+1] += signal[nn+1] * filter[nn + 1] - + signal[nn+5] * filter[nn + 5]; + _output_buffer[nn+2] += signal[nn+2] * filter[nn + 2] - + signal[nn+6] * filter[nn + 6]; + _output_buffer[nn+3] += signal[nn+3] * filter[nn + 3] - + signal[nn+7] * filter[nn + 7]; + + // imaginary parts + _output_buffer[nn+4] += signal[nn+0] * filter[nn + 4] + + signal[nn+4] * filter[nn + 0]; + _output_buffer[nn+5] += signal[nn+1] * filter[nn + 5] + + signal[nn+5] * filter[nn + 1]; + _output_buffer[nn+6] += signal[nn+2] * filter[nn + 6] + + signal[nn+6] * filter[nn + 2]; + _output_buffer[nn+7] += signal[nn+3] * filter[nn + 7] + + signal[nn+7] * filter[nn + 3]; + + } // for + + _output_buffer[0] = d1s; + _output_buffer[4] = d2s; +} + +#ifdef __SSE__ +void +OutputBase::_multiply_partition_simd(const float* signal, const float* filter) +{ + // 16 byte alignment is needed for _mm_load_ps()! + // This should be the case anyway because fftwf_malloc() is used. + + auto dc = _output_buffer[0] + signal[0] * filter[0]; + auto ny = _output_buffer[4] + signal[4] * filter[4]; + + for(size_t i = 0; i < _partition_size; i += 8) + { + // load real and imaginary parts of signal and filter + __m128 sigr = _mm_load_ps(signal + i); + __m128 sigi = _mm_load_ps(signal + i + 4); + __m128 filtr = _mm_load_ps(filter + i); + __m128 filti = _mm_load_ps(filter + i + 4); + + // multiply and subtract + __m128 res1 = _mm_sub_ps(_mm_mul_ps(sigr, filtr), _mm_mul_ps(sigi, filti)); + + // multiply and add + __m128 res2 = _mm_add_ps(_mm_mul_ps(sigr, filti), _mm_mul_ps(sigi, filtr)); + + // load output data for accumulation + __m128 acc1 = _mm_load_ps(&_output_buffer[i]); + __m128 acc2 = _mm_load_ps(&_output_buffer[i + 4]); + + // accumulate + acc1 = _mm_add_ps(acc1, res1); + acc2 = _mm_add_ps(acc2, res2); + + // store output data + _mm_store_ps(&_output_buffer[i], acc1); + _mm_store_ps(&_output_buffer[i + 4], acc2); + } + + _output_buffer[0] = dc; + _output_buffer[4] = ny; +} +#endif + +/// Complex multiplication of input and filter spectra +void +OutputBase::_multiply_spectra() +{ + // Clear IFFT buffer + std::fill(_output_buffer.begin(), _output_buffer.end(), 0.0f); + _output_buffer.zero = true; + + assert(_filter_ptrs.size() == _input.partitions()); + + auto input = _input.spectra.begin(); + + for (const auto filter: _filter_ptrs) + { + assert(filter != nullptr); + + if (input->zero || filter->zero) continue; + +#ifdef __SSE__ + _multiply_partition_simd(input->data(), filter->data()); +#else + _multiply_partition_cpp(input->data(), filter->data()); +#endif + + _output_buffer.zero = false; + ++input; + } +} + +void +OutputBase::_unsort_coefficients() +{ + fixed_vector buffer(_partition_size); + + size_t base = 8; + + buffer[0] = _output_buffer[0]; + buffer[1] = _output_buffer[1]; + buffer[2] = _output_buffer[2]; + buffer[3] = _output_buffer[3]; + buffer[_input.block_size()] = _output_buffer[4]; + buffer[_partition_size-1] = _output_buffer[5]; + buffer[_partition_size-2] = _output_buffer[6]; + buffer[_partition_size-3] = _output_buffer[7]; + + for (size_t i=0; i < (_partition_size / 8-1); i++) + { + for (size_t ii = 0; ii < 4; ii++) + { + buffer[base/2+ii] = _output_buffer[base+ii]; + } + + for (size_t ii = 0; ii < 4; ii++) + { + buffer[_partition_size-base/2-ii] = _output_buffer[base+4+ii]; + } + + base += 8; + } + + std::copy(buffer.begin(), buffer.end(), _output_buffer.begin()); +} + +void +OutputBase::_ifft() +{ + _unsort_coefficients(); + fftw::execute(_ifft_plan); +} + +/** Convolution engine (output part). + * @see Input, StaticOutput + **/ +class Output : public OutputBase +{ + public: + Output(const Input& input) + : OutputBase(input) + , _queues(apf::make_index_iterator(size_t(1)) + , apf::make_index_iterator(input.partitions())) + {} + + void set_filter(const Filter& filter); + + bool queues_empty() const; + void rotate_queues(); + + private: + fixed_vector _queues; +}; + +/** Set a new filter. + * The first filter partition is updated immediately, the later partitions are + * updated with rotate_queues(). + * @param filter Container with filter partitions. If too few partitions are + * given, the rest is set to zero, if too many are given, the rest is ignored. + **/ +void +Output::set_filter(const Filter& filter) +{ + auto partition = filter.begin(); + + // First partition has no queue and is updated immediately + if (partition != filter.end()) + { + _filter_ptrs.front() = &*partition++; + } + + for (size_t i = 0; i < _queues.size(); ++i) + { + _queues[i][i] + = (partition == filter.end()) ? &_empty_partition : &*partition++; + } +} + +/** Check if there are still valid partitions in the queues. + * If this function returns @b false, rotate_queues() should be called. + * @note This is important for crossfades: even if set_filter() wasn't used, + * older partitions may still change! If the queues are empty, no crossfade is + * necessary (except @p weight was changed in convolve()). + **/ +bool +Output::queues_empty() const +{ + if (_queues.empty()) return true; + + // It may not be obvious, but that's what the following code does: + // If some non-null pointer is found in the last queue, return false + + auto first = _queues.rbegin()->begin(); + auto last = _queues.rbegin()->end(); + return std::find_if(first, last, math::identity()) == last; +} + +/** Update filter queues. + * If queues_empty() returns @b true, calling this function is unnecessary. + * @note This can lead to artifacts, so a crossfade is recommended. + **/ +void +Output::rotate_queues() +{ + auto target = _filter_ptrs.begin(); + // Skip first element, it doesn't have a queue + ++target; + + for (auto& queue: _queues) + { + // If first element is valid, use it + if (queue.front()) *target = queue.front(); + + std::copy(queue.begin() + 1, queue.end(), queue.begin()); + *queue.rbegin() = nullptr; + ++target; + } +} + +/** %Convolver output stage with static filter. + * The filter coefficients are set in the constructor(s) and cannot be changed. + * @see Output + **/ +class StaticOutput : public OutputBase +{ + public: + /// Constructor from time domain samples + template + StaticOutput(const Input& input, In first, In last) + : OutputBase(input) + { + _filter.reset(new Filter(input.block_size(), first, last + , input.partitions())); + + _set_filter(*_filter); + } + + /// Constructor from existing frequency domain filter coefficients. + /// @attention The filter coefficients are not copied, their lifetime must + /// exceed that of the StaticOutput! + StaticOutput(const Input& input, const Filter& filter) + : OutputBase(input) + { + _set_filter(filter); + } + + private: + void _set_filter(const Filter& filter) + { + auto from = filter.begin(); + + for (auto& to: _filter_ptrs) + { + // If less partitions are given, the rest is set to zero + to = (from == filter.end()) ? &_empty_partition : &*from++; + } + // If further partitions are available, they are ignored + } + + // This is only used for the first constructor! + std::unique_ptr _filter; +}; + +/// Combination of Input and Output +struct Convolver : Input, Output +{ + Convolver(size_t block_size_, size_t partitions_) + : Input(block_size_, partitions_) + // static_cast to resolve ambiguity + , Output(*static_cast(this)) + {} +}; + +/// Combination of Input and StaticOutput +struct StaticConvolver : Input, StaticOutput +{ + template + StaticConvolver(size_t block_size_, In first, In last, size_t partitions_ = 0) + : Input(block_size_, partitions_ ? partitions_ + : min_partitions(block_size_, std::distance(first, last))) + , StaticOutput(*this, first, last) + {} + + StaticConvolver(const Filter& filter, size_t partitions_ = 0) + : Input(filter.block_size() + , partitions_ ? partitions_ : filter.partitions()) + , StaticOutput(*this, filter) + {} +}; + +/// Apply @c std::transform to a container of fft_node%s +template +void transform_nested(const Filter& in1, const Filter& in2, Filter& out + , BinaryFunction f) +{ + auto it1 = in1.begin(); + auto it2 = in2.begin(); + + for (auto& result: out) + { + if (it1 == in1.end() || it1->zero) + { + if (it2 == in2.end() || it2->zero) + { + result.zero = true; + } + else + { + assert(it2->size() == result.size()); + std::transform(it2->begin(), it2->end(), result.begin() + , std::bind(f, 0, std::placeholders::_1)); + result.zero = false; + } + } + else + { + if (it2 == in2.end() || it2->zero) + { + assert(it1->size() == result.size()); + std::transform(it1->begin(), it1->end(), result.begin() + , std::bind(f, std::placeholders::_1, 0)); + result.zero = false; + } + else + { + assert(it1->size() == it2->size()); + assert(it1->size() == result.size()); + std::transform(it1->begin(), it1->end(), it2->begin(), result.begin() + , f); + result.zero = false; + } + } + if (it1 != in1.end()) ++it1; + if (it2 != in2.end()) ++it2; + } +} + +} // namespace conv + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/denormalprevention.h b/apf/apf/denormalprevention.h new file mode 100644 index 00000000..0449389f --- /dev/null +++ b/apf/apf/denormalprevention.h @@ -0,0 +1,239 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Different methods to prevent denormal numbers. + +#ifndef APF_DENORMALPREVENTION_H +#define APF_DENORMALPREVENTION_H + +#include // for std::numeric_limits() +#include // for std::abs() + +#ifdef __SSE__ +#include // for SSE intrinsics +#endif +#ifdef __SSE3__ +#include // for SSE3 intrinsics +#endif + +namespace apf +{ + +/// Denormal prevention +/// @see Laurent de Soras, "Denormal numbers in floating point signal processing +/// applications": http://ldesoras.free.fr/doc/articles/denormal-en.pdf +namespace dp +{ + +/// Disable denormal prevention. +template +struct none +{ + void prevent_denormals(T&) {} +}; + +template struct dc; // default case not implemented! + +/// Add DC signal (float specialization). +template<> +struct dc +{ + static void prevent_denormals(float& val) { val += 1e-18f; } +}; + +/// Add DC signal (double specialization). +template<> +struct dc +{ + static void prevent_denormals(double& val) { val += 1e-30; } +}; + +template struct ac; // default case not implemented! + +/// Add sine component at nyquist frequency (float specialization). +template<> +struct ac +{ + public: + ac() : _anti_denorm(1e-18f) {} + + void prevent_denormals(float& val) + { + _anti_denorm = -_anti_denorm; + val += _anti_denorm; + } + + private: + float _anti_denorm; +}; + +/// Add sine component at nyquist frequency (double specialization). +template<> +struct ac +{ + public: + ac() : _anti_denorm(1e-30) {} + + void prevent_denormals(double& val) + { + _anti_denorm = -_anti_denorm; + val += _anti_denorm; + } + + private: + double _anti_denorm; +}; + +template struct quantization; // default case not implemented! + +/// Quantize denormal numbers (float specialization). +template<> +struct quantization +{ + static void prevent_denormals(float& val) + { + val += 1e-18f; + val -= 1e-18f; + } +}; + +/// Quantize denormal numbers (double specialization). +template<> +struct quantization +{ + static void prevent_denormals(double& val) + { + val += 1e-30; + val -= 1e-30; + } +}; + +/// Detect denormals and set 0. +template +struct set_zero_1 +{ + static void prevent_denormals(T& val) + { + if (std::abs(val) < std::numeric_limits::min() && (val != 0)) val = 0; + } +}; + +/// Detect denormals and set 0. +template +struct set_zero_2 +{ + static void prevent_denormals(T& val) + { + if ((val != 0) && std::abs(val) < std::numeric_limits::min()) val = 0; + } +}; + +/// Detect denormals and set 0. +template +struct set_zero_3 +{ + static void prevent_denormals(T& val) + { + if (std::abs(val) < std::numeric_limits::min()) val = 0; + } +}; + +#if 0 +// add noise component; equally distributed spectrum +// NOTE: noise appears to be kind of deterministic +// - temporarily deactivated due to warnings + +template struct NoisePrevention; + +template<> +struct NoisePrevention +{ + public: + NoisePrevention() : _rand_state(1) {} + + void prevent_denormals(float& val) + { + _rand_state = _rand_state * 1234567UL + 890123UL; + int mantissa = _rand_state & 0x807F0000; // Keep only most significant bits + int flt_rnd = mantissa | 0x1E000000; // Set exponent + val += *reinterpret_cast(&flt_rnd); + } + + private: + unsigned int _rand_state; +}; +#endif + +#ifdef __SSE__ +// The compiler must be set to generate SSE instructions automatically! +// In GCC, this is done with -mfpmath=sse (which is on by default on amd64) + +/// Set Flush-To-Zero (FTZ). +/// @note requires SSE support +inline void ftz_on() +{ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); +} + +/// Unset Flush-To-Zero (FTZ). +/// @note requires SSE support +inline void ftz_off() +{ + _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF); +} + +#ifdef __SSE3__ +/// Set Denormals-Are-Zero (DAZ). +/// @note requires SSE3 support +/// +/// From http://softpixel.com/~cwright/programming/simd/sse.php: +/// +/// DAZ wasn't available in the first version of SSE. Since setting a reserved +/// bit in MXCSR causes a general protection fault, we need to be able to check +/// the availability of this feature without causing problems. To do this, one +/// needs to set up a 512-byte area of memory to save the SSE state to, using +/// fxsave, and then one needs to inspect bytes 28 through 31 for the MXCSR_MASK +/// value. If bit 6 is set, DAZ is supported, otherwise, it isn't. +void daz_on() +{ + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); +} + +/// Unset Denormals-Are-Zero (DAZ). +/// @note requires SSE3 support +void daz_off() +{ + _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF); +} +#endif +#endif + +} // namespace dp + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/apf/dummy_thread_policy.h b/apf/apf/dummy_thread_policy.h new file mode 100644 index 00000000..75458225 --- /dev/null +++ b/apf/apf/dummy_thread_policy.h @@ -0,0 +1,154 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Dummy thread policy class. + +#ifndef APF_DUMMY_THREAD_POLICY_H +#define APF_DUMMY_THREAD_POLICY_H + +#include // for std::logic_error + +#ifndef APF_MIMOPROCESSOR_THREAD_POLICY +#define APF_MIMOPROCESSOR_THREAD_POLICY apf::dummy_thread_policy +#endif + +#define APF_DUMMY_THREAD_POLICY_ERROR throw std::logic_error( \ + "'dummy_thread_policy' can only be used with a single thread!") + +namespace apf +{ + +/// @c thread_policy without functionality. Can only be used for single-threaded +/// processing. +/// @see MimoProcessor +/// @ingroup apf_policies +class dummy_thread_policy +{ + public: + using useconds_type = int; + + class Thread; + template struct ScopedThread; + template struct DetachedThread; + class Lock; + class Semaphore; + + protected: + dummy_thread_policy() = default; ///< Protected ctor. + ~dummy_thread_policy() = default; ///< Protected dtor. +}; + +class dummy_thread_policy::Thread +{ + public: + using native_handle_type = int; + + void create(void* (*f)(void*), void* data) + { + (void)f; // avoid "unused parameter" warning + (void)data; + APF_DUMMY_THREAD_POLICY_ERROR; + } + + bool join() + { + APF_DUMMY_THREAD_POLICY_ERROR; + return false; + } + + native_handle_type native_handle() const + { + APF_DUMMY_THREAD_POLICY_ERROR; + return -1; + } +}; + +template +struct dummy_thread_policy::ScopedThread : Thread +{ + ScopedThread(F, useconds_type) + { + APF_DUMMY_THREAD_POLICY_ERROR; + } +}; + +template +struct dummy_thread_policy::DetachedThread : Thread +{ + explicit DetachedThread(F) + { + APF_DUMMY_THREAD_POLICY_ERROR; + } +}; + +class dummy_thread_policy::Lock +{ + public: + Lock() {} + + int lock() + { + APF_DUMMY_THREAD_POLICY_ERROR; + return -1; + } + + int unlock() + { + APF_DUMMY_THREAD_POLICY_ERROR; + return -1; + } +}; + +class dummy_thread_policy::Semaphore +{ + public: + using value_type = unsigned int; + + explicit Semaphore(value_type = 0) + { + APF_DUMMY_THREAD_POLICY_ERROR; + } + + bool post() + { + APF_DUMMY_THREAD_POLICY_ERROR; + return false; + } + + bool wait() + { + APF_DUMMY_THREAD_POLICY_ERROR; + return false; + } +}; + +} // namespace apf + +#undef APF_DUMMY_THREAD_POLICY_ERROR + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/fftwtools.h b/apf/apf/fftwtools.h new file mode 100644 index 00000000..cd7ef161 --- /dev/null +++ b/apf/apf/fftwtools.h @@ -0,0 +1,137 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Some tools for the use with the FFTW library. + +#ifndef APF_FFTWTOOLS_H +#define APF_FFTWTOOLS_H + +#include + +#include // for std::forward +#include // for std::numeric_limits + +namespace apf +{ + +/// Traits class to select float/double/long double versions of FFTW functions +/// @see fftw, fftw, fftw, APF_FFTW_TRAITS +/// @note This is by far not complete, but it's trivial to extend. +template struct fftw {}; // Most general case is empty, see below! + +/// Macro to create traits classes for float/double/long double +#define APF_FFTW_TRAITS(longtype, shorttype) \ +/** longtype specialization of the traits class @ref fftw **/ \ +/** @see APF_FFTW_TRAITS **/ \ +template<> \ +struct fftw { \ + using plan = fftw ## shorttype ## plan; \ + static void* malloc(size_t n) { return fftw ## shorttype ## malloc(n); } \ + static void free(void* p) { fftw ## shorttype ## free(p); } \ + static void destroy_plan(plan p) { \ + fftw ## shorttype ## destroy_plan(p); p = nullptr; } \ + static void execute(const plan p) { fftw ## shorttype ## execute(p); } \ + static void execute_r2r(const plan p, longtype *in, longtype *out) { \ + fftw ## shorttype ## execute_r2r(p, in, out); } \ + static plan plan_r2r_1d(int n, longtype* in, longtype* out \ + , fftw_r2r_kind kind, unsigned flags) { \ + return fftw ## shorttype ## plan_r2r_1d(n, in, out, kind, flags); } \ + class scoped_plan { \ + public: \ + template \ + scoped_plan(Func func, Args... args) \ + : _plan(func(std::forward(args)...)), _owning(true) {} \ + scoped_plan(scoped_plan&& other) \ + : _plan(std::move(other._plan)), _owning(true) { \ + other._owning = false; } \ + ~scoped_plan() { if (_owning) destroy_plan(_plan); } \ + operator const plan&() { return _plan; } \ + private: \ + plan _plan; bool _owning; }; \ +}; + +APF_FFTW_TRAITS(float, f_) +APF_FFTW_TRAITS(double, _) +APF_FFTW_TRAITS(long double, l_) + +#undef APF_FFTW_TRAITS + +/// @note: This only works for containers with contiguous memory (e.g. vector)! +template +struct fftw_allocator +{ + using size_type = size_t; + using difference_type = ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using value_type = T; + + pointer allocate(size_type n, const void* hint = nullptr) + { + (void)hint; + return static_cast(fftw::malloc(sizeof(value_type) * n)); + } + + void deallocate(pointer p, size_type n) + { + (void)n; + fftw::free(p); + } + + void construct(pointer p, const T& t) { new (p) T(t); } + void destroy(pointer p) { p->~T(); } + + size_type max_size() const + { + return std::numeric_limits::max() / sizeof(T); + } + + template + struct rebind { using other = fftw_allocator; }; + + // Not sure if the following are necessary ... + + fftw_allocator() {} + fftw_allocator(const fftw_allocator&) {} + template fftw_allocator(const fftw_allocator&) {} + + pointer address(reference value) { return &value; } + const_pointer address(const_reference value) { return &value; } + + template + bool operator==(const fftw_allocator&) { return true; } + + template + bool operator!=(const fftw_allocator&) { return false; } +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/iterator.h b/apf/apf/iterator.h new file mode 100644 index 00000000..b15bb1a5 --- /dev/null +++ b/apf/apf/iterator.h @@ -0,0 +1,1395 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Several more or less useful iterators and some macros. + +#ifndef APF_ITERATOR_H +#define APF_ITERATOR_H + +#include // for assert() +#include // for std::iterator_traits, std::output_iterator_tag, ... +#include // for std::remove_reference, std::result_of + +#include "apf/math.h" // for wrap() + +/** @defgroup apf_iterators Iterators + * TODO: overview of iterators? + * + * @see apf_iterator_macros + **/ + +/** @defgroup apf_iterator_macros Iterator macros + * Some macros to avoid code duplication in iterator (adaptor) classes. + * For most of the macros you need special typedefs in your iterator class: + * @c self, @c reference, @c pointer, @c difference_type, ... + * + * The assignment operator isn't provided here because normally the + * auto-generated assignment operator can be used. + * + * @note A default constructor is a requirement for every iterator. If you + * implement a special constructor, you also have to implement a default + * constructor! + * + * @see apf_iterators + **/ + +namespace apf +{ +/// Check for null-pointer +/// @return @b true if @p in != 0, else @b false +template bool no_nullptr(T* in) { return in != nullptr; } + +/// Dummy overload for non-pointers +/// @return Always @b true +/// @note We can only check if plain pointers are NULL, use @c _GLIBCXX_DEBUG +/// to check for singular iterators! +template bool no_nullptr(T&) { return true; } +} + +/// Straightforward default constructor and constructor from base iterator. +/// @param iterator_name Name of the iterator class +/// @param base_iterator_type Typename of the base iterator +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_CONSTRUCTORS(iterator_name, base_iterator_type, base_member) \ + /** Constructor from base iterator. @param base_iterator base iterator **/ \ + explicit iterator_name(base_iterator_type base_iterator) \ + : base_member(base_iterator) {} \ + /** Default constructor. @note This constructor creates a singular iterator. Another iterator_name can be assigned to it, but nothing else works. **/ \ + iterator_name() : base_member() {} + +/// Get the base iterator. +/// @param base_iterator_type Typename of the base iterator +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BASE(base_iterator_type, base_member) \ + /** Get the base iterator, inspired by std::reverse_iterator::base(). **/ \ + base_iterator_type base() const { assert(apf::no_nullptr(base_member)); \ + return (base_member); } + +// Output Iterator Requirements + +/// Straightforward dereference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_OUTPUT_DEREFERENCE(base_member) \ + /** Straightforward dereference operator. **/ \ + reference operator*() const { assert(apf::no_nullptr(base_member)); \ + return *(base_member); } + +/// Straightforward preincrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_OUTPUT_PREINCREMENT(base_member) \ + /** Straightforward preincrement operator. **/ \ + self& operator++() { assert(apf::no_nullptr(base_member)); \ + ++(base_member); return *this; } + +/// Postincrement operator (using preincrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_OUTPUT_POSTINCREMENT \ + /** Postincrement operator (using preincrement operator). **/ \ + self operator++(int) { self tmp = *this; ++(*this); return tmp; } + +// Input Iterator Requirements + +/// Straightforward dereference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_DEREFERENCE APF_ITERATOR_OUTPUT_DEREFERENCE + +/// Straightforward arrow operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_ARROW(base_member) \ + /** Straightforward arrow operator. **/ \ + pointer operator->() const { assert(apf::no_nullptr(base_member)); \ + return (base_member); } + +/// Straightforward equality operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_EQUAL(base_member) \ + /** Straightforward equality operator. **/ \ + bool operator==(const self& rhs) const { \ + return ((base_member) == (rhs.base_member)); } + +/// Straightforward preincrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_PREINCREMENT APF_ITERATOR_OUTPUT_PREINCREMENT + +/// Postincrement operator (using preincrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_POSTINCREMENT APF_ITERATOR_OUTPUT_POSTINCREMENT + +/// Unequality operator (using equality operator) +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_INPUT_UNEQUAL \ + /** Unequality operator (using equality operator). **/ \ + bool operator!=(const self& rhs) const { return !operator==(rhs); } \ + +// Forward Iterator Requirements + +/// Straightforward equality operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_EQUAL APF_ITERATOR_INPUT_EQUAL +/// Straightforward dereference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_DEREFERENCE APF_ITERATOR_INPUT_DEREFERENCE +/// Straightforward arrow operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_ARROW APF_ITERATOR_INPUT_ARROW +/// Straightforward preincrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_PREINCREMENT APF_ITERATOR_INPUT_PREINCREMENT + +/// Postincrement operator (using preincrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_POSTINCREMENT APF_ITERATOR_INPUT_POSTINCREMENT +/// Unequality operator (using equality operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_FORWARD_UNEQUAL APF_ITERATOR_INPUT_UNEQUAL + +// Bidirectional Iterator Requirements + +/// Straightforward equality operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_EQUAL APF_ITERATOR_FORWARD_EQUAL +/// Straightforward dereference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_DEREFERENCE APF_ITERATOR_FORWARD_DEREFERENCE +/// Straightforward arrow operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_ARROW APF_ITERATOR_FORWARD_ARROW +/// Straightforward preincrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_PREINCREMENT APF_ITERATOR_FORWARD_PREINCREMENT + +/// Straightforward predecrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_PREDECREMENT(base_member) \ + /** Straightforward predecrement operator. **/ \ + self& operator--() { assert(apf::no_nullptr(base_member)); \ + --(base_member); return *this; } + +/// Postincrement operator (using preincrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_POSTINCREMENT APF_ITERATOR_FORWARD_POSTINCREMENT +/// Unequality operator (using equality operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_UNEQUAL APF_ITERATOR_FORWARD_UNEQUAL + +/// Postdecrement operator (using predecrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_BIDIRECTIONAL_POSTDECREMENT \ + /** Postdecrement operator (using predecrement operator). **/ \ + self operator--(int) { self tmp = *this; --(*this); return tmp; } + +// Random Access Iterator Requirements + +/// Straightforward equality operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_EQUAL APF_ITERATOR_BIDIRECTIONAL_EQUAL +/// Straightforward dereference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_DEREFERENCE APF_ITERATOR_BIDIRECTIONAL_DEREFERENCE +/// Straightforward arrow operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_ARROW APF_ITERATOR_BIDIRECTIONAL_ARROW +/// Straightforward preincrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_PREINCREMENT APF_ITERATOR_BIDIRECTIONAL_PREINCREMENT +/// Straightforward predecrement operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_PREDECREMENT APF_ITERATOR_BIDIRECTIONAL_PREDECREMENT + +/// Straightforward addition/assignment operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(base_member) \ + /** Straightforward addition/assignment operator. **/ \ + self& operator+=(difference_type n) { \ + assert(!n || apf::no_nullptr(base_member)); \ + (base_member) += n; return *this; } + +/// Straightforward difference operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_DIFFERENCE(base_member) \ + /** Straightforward difference operator. **/ \ + friend difference_type operator-(const self& lhs, const self& rhs) { \ + assert(apf::no_nullptr(lhs.base_member) \ + && apf::no_nullptr(rhs.base_member)); \ + return ((lhs.base_member) - (rhs.base_member)); } + +/// Straightforward subscript operator (using + and dereference operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_SUBSCRIPT \ + /** Straightforward subscript operator (using + and dereference op). **/ \ + reference operator[](difference_type n) const { \ + return *(*this + n); } + +/// Straightforward less-than operator. +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_LESS(base_member) \ + /** Straightforward less-than operator. **/ \ + friend bool operator<(const self& lhs, const self& rhs) { \ + assert(apf::no_nullptr(lhs.base_member) \ + && apf::no_nullptr(rhs.base_member)); \ + return (lhs.base_member) < (rhs.base_member); } + +/// Unequality operator (using equality operator). +/// @param base_member Name of the member variable holding the base iterator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_UNEQUAL APF_ITERATOR_BIDIRECTIONAL_UNEQUAL + +/// Other comparisons (>, <=, >=). +/// All are using the less-than operator. +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS \ + /** Straightforward greater-than operator (using less-than operator). **/ \ + friend bool operator>(const self& lhs, const self& rhs) \ + { return rhs < lhs; } \ + /** Straightforward less-or-equal operator (using less-than operator). **/ \ + friend bool operator<=(const self& lhs, const self& rhs) \ + { return !(rhs < lhs); } \ + /** Straightforward greater-or-equal operator (using less-than operator). **/\ + friend bool operator>=(const self& lhs, const self& rhs) \ + { return !(lhs < rhs); } + +/// Postincrement operator (using preincrement operator). +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_POSTINCREMENT APF_ITERATOR_BIDIRECTIONAL_POSTINCREMENT + +/// Postdecrement operator (using predecrement operator) +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_POSTDECREMENT APF_ITERATOR_BIDIRECTIONAL_POSTDECREMENT + +/// The rest of the random access iterator requirements. +/// - Addition operator (iterator plus integer, using +=) +/// - Addition operator (integer plus iterator, using +=) +/// - Subtraction/assignment operator (using +=) +/// - Subtraction operator (using +=) +/// @ingroup apf_iterator_macros +#define APF_ITERATOR_RANDOMACCESS_THE_REST \ + /** Addition operator (iterator plus integer, using +=). **/ \ + self operator+(difference_type n) const { self tmp(*this); return tmp += n; }\ + /** Addition operator (integer plus iterator, using +=). **/ \ + friend self operator+(difference_type n, const self& it) { \ + self temp(it); return temp += n; } \ + /** Subtraction/assignment operator (using +=). **/ \ + self& operator-=(difference_type n) { *this += -n; return *this; } \ + /** Subtraction operator (using +=). **/ \ + self operator-(difference_type n) const { self tmp(*this); return tmp += -n; } + +namespace apf +{ + +/// Convenience class providing begin() and end(). +/// The derived class can manipulate the protected members _begin and _end. +/// @warning If you use this as base class, don't use a base class pointer (or +/// reference). This class has a public non-virtual destructor, which is +/// generally @b not recommended for base classes. +template +class has_begin_and_end +{ + public: + using iterator = I; + using reference = typename std::iterator_traits::reference; + using difference_type = typename std::iterator_traits::difference_type; + + /// Default constructor. Singular iterators are created. + has_begin_and_end() : _begin(), _end() {} + + /// Constructor with begin and end + has_begin_and_end(iterator b, iterator e) : _begin(b), _end(e) {} + + /// Constructor with begin and length + template + has_begin_and_end(iterator b, Distance length) + : _begin(b) + , _end(b) + { + std::advance(_end, length); + } + + // auto-generated copy constructor is used + + /// Get begin. + /// @note There is no const-ness propagation. The const-ness of the original + /// iterator I is maintained. + iterator begin() const { return _begin; } + + /// Get end. + /// @note There is no const-ness propagation. The const-ness of the original + /// iterator I is maintained. + iterator end() const { return _end; } + + /// Subscript operator. + /// @note This only works if @p I is a random access iterator! + reference operator[](difference_type n) const { return _begin[n]; } + + protected: + iterator _begin, _end; +}; + +template +has_begin_and_end make_begin_and_end(I first, Args&&... args) +{ + return {first, std::forward(args)...}; +} + +/// Helper class for apf::cast_proxy and apf::transform_proxy. +/// @see iterator_proxy_const +template +class iterator_proxy +{ + public: + using iterator = I; + using reverse_iterator = std::reverse_iterator; + using size_type = typename Container::size_type; + using value_type = typename std::iterator_traits::value_type; + + // implicit conversion is desired, therefore no "explicit" keyword + iterator_proxy(Container& l) : _l(l) {} + + iterator begin() const { return iterator(_l.begin()); } + iterator end() const { return iterator(_l.end()); } + reverse_iterator + rbegin() const { return reverse_iterator(iterator(_l.end())); } + reverse_iterator + rend() const { return reverse_iterator(iterator(_l.begin())); } + size_type size() const { return _l.size(); } + + private: + Container& _l; +}; + +/// Helper class for cast_proxy_const and transform_proxy_const. +/// @see iterator_proxy +template +class iterator_proxy_const +{ + public: + using iterator = I; + using reverse_iterator = std::reverse_iterator; + using size_type = typename Container::size_type; + using value_type = typename std::iterator_traits::value_type; + + // implicit conversion is desired, therefore no "explicit" keyword + iterator_proxy_const(const Container& l) : _l(l) {} + + iterator begin() const { return iterator(_l.begin()); } + iterator end() const { return iterator(_l.end()); } + reverse_iterator + rbegin() const { return reverse_iterator(iterator(_l.end())); } + reverse_iterator + rend() const { return reverse_iterator(iterator(_l.begin())); } + size_type size() const { return _l.size(); } + + private: + const Container& _l; +}; + +/** An output iterator which adds on assignment. + * Whenever the operator= of a pointee is called, its operator+= is invoked. + * This is done by the helper class output_proxy. + * + * The idea to this iterator comes from boost::function_output_iterator: + * http://www.boost.org/doc/libs/release/libs/iterator/doc/function_output_iterator.html + * @tparam I Base iterator type + * @see make_accumulating_iterator (helper function) + * @ingroup apf_iterators + **/ +template +class accumulating_iterator +{ + private: + using self = accumulating_iterator; + + public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + + APF_ITERATOR_CONSTRUCTORS(accumulating_iterator, I, _base_iterator) + + /// Helper class. + class output_proxy + { + public: + /// Constructor. @param i the base iterator + explicit output_proxy(I& i) : _i(i) {} + + /// Assignment operator. + /// Value @p v is added to the current value on assignment. + template + output_proxy& operator=(const V& v) { *_i += v; return *this; } + + private: + I& _i; + }; + + /// Dereference operator. + /// @return a temporary object of type output_proxy + output_proxy operator*() + { + assert(no_nullptr(_base_iterator)); + return output_proxy(_base_iterator); + } + + // operator-> doesn't make sense! + + // straightforward operators: + APF_ITERATOR_OUTPUT_PREINCREMENT(_base_iterator) + APF_ITERATOR_OUTPUT_POSTINCREMENT + + APF_ITERATOR_BASE(I, _base_iterator) + + private: + I _base_iterator; +}; + +/** Helper function to create an accumulating_iterator. + * The template parameter is optional because it can be inferred from the + * parameter @p base_iterator. Example: + * @code make_accumulating_iterator(some_iterator) @endcode + * @param base_iterator the base iterator + * @return an accumulating_iterator + * @ingroup apf_iterators + **/ +template +accumulating_iterator +make_accumulating_iterator(I base_iterator) +{ + return accumulating_iterator(base_iterator); +} + +/** Iterator that casts items to @p T* on dereferenciation. + * There is an @em extra dereference inside the dereference operator, similar to + * @c boost::indirect_iterator + * (http://boost.org/doc/libs/release/libs/iterator/doc/indirect_iterator.html). + * But before that, the pointer is casted to the desired type @p T. + * @tparam T Pointee type to be casted to + * @tparam I Base iterator type + * @pre @c value_type of @p I must be a pointer to a compatible type! + * @see make_cast_iterator() (helper function) + * @see cast_proxy, cast_proxy_const + * @ingroup apf_iterators + **/ +template +class cast_iterator +{ + private: + using self = cast_iterator; + + public: + using value_type = T; + using pointer = T*; + using reference = T&; + using difference_type = typename std::iterator_traits::difference_type; + using iterator_category + = typename std::iterator_traits::iterator_category; + + APF_ITERATOR_CONSTRUCTORS(cast_iterator, I, _base_iterator) + + // auto-generated copy ctor and assignment operator are OK. + + /// Dereference operator. + /// @return Reference to current item, casted to @p T. + reference operator*() const + { + assert(no_nullptr(_base_iterator)); + return *this->operator->(); + } + + /// Arrow operator. + /// @return Pointer to current item, casted to @p T. + pointer operator->() const + { + assert(no_nullptr(_base_iterator)); + // I::value_type must be a pointer to something! + return _cast_helper(*_base_iterator); + } + + /// Subscript operator. + /// @param n index + /// @return Reference to n-th item, casted to @p T. + reference operator[](difference_type n) const + { + assert(no_nullptr(_base_iterator)); + return *_cast_helper(_base_iterator[n]); + } + + // straightforward operators: + APF_ITERATOR_RANDOMACCESS_EQUAL(_base_iterator) + APF_ITERATOR_RANDOMACCESS_LESS(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_THE_REST + + APF_ITERATOR_BASE(I, _base_iterator) + + private: + /// Do the actual cast. + /// @param ptr Pointer to be casted + /// @return @p ptr casted to T* + /// @pre @p ptr must be a pointer to something. This is automagically + /// asserted (at compile time). + template + pointer _cast_helper(X* ptr) const + { + return static_cast(ptr); + } + + I _base_iterator; +}; + +/** Helper function to create a cast_iterator. + * The second template parameter is optional because it can be inferred from the + * parameter @p base_iterator. Example: + * @code make_cast_iterator(some_iterator) @endcode + * @param base_iterator the base iterator + * @return a cast_iterator + * @ingroup apf_iterators + **/ +template +cast_iterator +make_cast_iterator(I base_iterator) +{ + return cast_iterator(base_iterator); +} + +/** Encapsulate a container of base pointers. + * @tparam T Target type + * @tparam Container type of (STL-like) container + * @see cast_proxy_const, cast_iterator + **/ +template +struct cast_proxy : iterator_proxy< + cast_iterator, Container> +{ + using value_type = T; + + cast_proxy(Container& l) + : iterator_proxy, Container>(l) + {} +}; + +/** Helper function to create a cast_proxy. + **/ +template +cast_proxy +make_cast_proxy(Container& l) +{ + return cast_proxy(l); +} + +/** Encapsulate a container of base pointers (const version). + * The underlying container cannot be modified. + * @see cast_proxy + **/ +template +struct cast_proxy_const : iterator_proxy_const< + cast_iterator, Container> +{ + using value_type = T; + + cast_proxy_const(const Container& l) + : iterator_proxy_const, Container>(l) + {} +}; + +/** Helper function to create a cast_proxy_const. + **/ +template +cast_proxy_const +make_cast_proxy_const(Container& l) +{ + return cast_proxy_const(l); +} + +/** Circular iterator class. + * Creates an iterator which can be infinitely iterated. When reaching the end + * of the range, it just starts again at the beginning. And vice versa. + * @tparam I the iterator type on which the circular iterator is based on. + * circular_iterator@ has the same @c iterator_category as @c I + * itself. It only really works with random access iterators, however. + * @ingroup apf_iterators + **/ +template +class circular_iterator +{ + private: + using self = circular_iterator; + + public: + /// @name Type Definitions from the Underlying Iterator + //@{ + using iterator_category + = typename std::iterator_traits::iterator_category; + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = typename std::iterator_traits::pointer; + using reference = typename std::iterator_traits::reference; + //@} + + /// @name Constructors + /// Copy ctor and assignment operator are auto-generated by the compiler. + //@{ + + /// Constructor with explicit current iterator. + /// @param begin begin of the original iterator range + /// @param end end of said range + /// @param current current iterator within the range + circular_iterator(I begin, I end, I current) + : _begin(begin) + , _end(end) + , _current((current == end) ? begin : current) // wrap around + { + assert(no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current)); + assert(_begin != _end); + assert(_current != _end); + } + + /// Constructor with implicit current iterator. + /// @param begin begin of the original iterator range + /// @param end end of said range + /// @note The current iterator is set to @a begin. + circular_iterator(I begin, I end) + : _begin(begin) + , _end(end) + , _current(begin) + { + assert(no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current)); + assert(_begin != _end); + } + + // Although this constructor is normally useless, it's good for unit tests. + /// Constructor from one iterator. + /// @param begin begin of the original iterator range + /// @note The resulting iterator is of limited use, because there is only + /// one location where it will ever point to. + /// Let's call it a @em stagnant iterator. + explicit circular_iterator(I begin) + : _begin(begin) + , _end(begin + 1) + , _current(begin) + { + assert(no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current)); + assert(_begin != _end); + } + + /// Default constructor. + /// @note This constructor creates a singular iterator. Another + /// circular_iterator can be assigned to it, but nothing else works. + circular_iterator() + : _begin() + , _end() + , _current() + {} + + //@} + + /// @name Operators + /// operator<, operator<=, operator>, operator>= don't make sense! + //@{ + + /// Preincrement operator. + self& + operator++() + { + assert(no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current)); + ++_current; + if (_current == _end) _current = _begin; + return *this; + } + + /// Predecrement operator. + self& + operator--() + { + assert(no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current)); + if (_current == _begin) _current = _end; + --_current; + return *this; + } + + /// Addition/assignment operator. + self& + operator+=(difference_type n) + { + assert(!n + || (no_nullptr(_begin) && no_nullptr(_end) && no_nullptr(_current))); + difference_type length = _end - _begin; + difference_type index = _current - _begin; + index += n; + _current = _begin + apf::math::wrap(index, length); + return *this; + } + + /// Difference operator. + /// @note Always returns a non-negative difference. + /// @warning This operator only works when @a a and @a b are iterators for + /// the same data (i.e. they were constructed with identical @a begin and + /// @a end iterators)! This is not checked internally, @b you have to do + /// that! + /// @param lhs the iterator to the left of the minus sign + /// @param rhs the iterator on the right side + friend + difference_type + operator-(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._begin) && no_nullptr(rhs._begin)); + assert(no_nullptr(lhs._end) && no_nullptr(rhs._end)); + assert(no_nullptr(lhs._current) && no_nullptr(rhs._current)); + assert(lhs._begin == rhs._begin); + assert(lhs._end == rhs._end ); + + difference_type d = lhs._current - rhs._current; + + if (d < 0) + { + d = lhs._current - lhs._begin + rhs._end - rhs._current; + } + // if lhs and rhs are the same, the difference is of course zero. + return d; + } + + // straightforward operators: + APF_ITERATOR_RANDOMACCESS_DEREFERENCE(_current) + APF_ITERATOR_RANDOMACCESS_ARROW(_current) + APF_ITERATOR_RANDOMACCESS_EQUAL(_current) + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + //@} + + APF_ITERATOR_BASE(I, _current) + + private: + I _begin; ///< begin of the underlying iterator range + I _end; ///< end of said range + I _current; ///< current position in that range +}; + +/** Helper function to create a circular_iterator. + * The template parameter is optional because it can be inferred from the + * parameter(s). Example: + * @code make_circular_iterator(some_begin, some_end) @endcode + * @param some_begin the first iterator + * @param some_end past-the-end iterator + * @return a circular_iterator + * @ingroup apf_iterators + **/ +template +circular_iterator +make_circular_iterator(I begin, I end) +{ + return circular_iterator(begin, end); +} + +/** Helper function to create a circular_iterator. + * The template parameter is optional because it can be inferred from the + * parameter(s). Example: + * @code make_circular_iterator(some_begin, some_end, some_current) @endcode + * @param some_begin the first iterator + * @param some_end past-the-end iterator + * @param some_current current iterator + * @return a circular_iterator + * @ingroup apf_iterators + **/ +template +circular_iterator +make_circular_iterator(I begin, I end, I current) +{ + return circular_iterator(begin, end, current); +} + +/** Iterator adaptor with a function call at dereferenciation. + * @tparam I type of base iterator + * @tparam F Unary function object which takes an @p I::value_type. + * Example: math::raised_cosine + * @warning The result of @p F can be any type, but operator->() only + * works if it is a reference! + * If it's not a reference, you will get an error similar to this: + * @code error: lvalue required as unary ‘&’ operand @endcode + * @see boost::transform_iterator: same idea, but much fancier implementation! + * http://boost.org/doc/libs/release/libs/iterator/doc/transform_iterator.html + * @see transform_proxy, transform_proxy_const + * @ingroup apf_iterators + **/ +template +class transform_iterator +{ + private: + using self = transform_iterator; + // NOTE: value_type& works for by-value, by-reference and by-const-reference + using _signature = F(typename std::iterator_traits::value_type&); + + public: + using iterator_category + = typename std::iterator_traits::iterator_category; + /// Can be a reference, but doesn't necessarily have to + using reference = typename std::result_of<_signature>::type; + using value_type = typename std::remove_reference::type; + using pointer = value_type*; + using difference_type = typename std::iterator_traits::difference_type; + + /// Constructor. + explicit transform_iterator(I base_iterator = I(), F f = F()) + : _base_iterator(base_iterator) + , _f(f) + {} + + /// Dereference operator. + /// Dereference the base iterator, use it as argument to the stored function + /// and return the result. + /// @note This is non-const because _f might have state + reference operator*() { return _f(*_base_iterator); } + + /// Arrow operator. + /// Dereference the base iterator, use it as argument to the stored function + /// and return the address of the result. + /// @warning Only works if the result of @p F is a reference! + pointer operator->() + { + return &this->operator*(); + } + + APF_ITERATOR_RANDOMACCESS_EQUAL(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_LESS(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_THE_REST + + APF_ITERATOR_BASE(I, _base_iterator) + + private: + I _base_iterator; + F _f; +}; + +/** Helper function to create a transform_iterator. + * The template parameters are optional because they can be inferred from the + * parameters @p base_iterator and @p f. Example: + * @code make_transform_iterator(some_iterator, some_function) @endcode + * @param base_iterator the base iterator + * @param f the function (normally a function object) + * @return a transform_iterator + * @ingroup apf_iterators + **/ +template +transform_iterator +make_transform_iterator(I base_iterator, F f) +{ + return transform_iterator(base_iterator, f); +} + +/** Wrap a container and provide a transform_iterator instead of the normal one. + * @tparam F Function to be used for the transform_iterator + * @tparam Container Type of the container + * @see transform_proxy_const, transform_iterator + **/ +template +struct transform_proxy : iterator_proxy< + transform_iterator, Container> +{ + transform_proxy(Container& l) + : iterator_proxy, Container>(l) + {} +}; + +/** Wrap a container and provide a transform_iterator (const version). + * The underlying container cannot be modified. + * @see transform_proxy + **/ +template +struct transform_proxy_const : iterator_proxy_const< + transform_iterator, Container> +{ + transform_proxy_const(const Container& l) + : iterator_proxy_const, Container>(l) + {} +}; + +/** Iterator with a built-in number. + * This can be used, for example, as a base iterator in transform_iterator. + * @tparam T type of the number + * @warning If @p T is an unsigned type, strange things may happen! + * @ingroup apf_iterators + **/ +template +class index_iterator +{ + private: + using self = index_iterator; + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using reference = T; + using difference_type = T; + using pointer = void; + + /// Constructor. @param start Starting index + explicit index_iterator(T start = T()) + : _number(start) + {} + + /// Dereference operator. @return The current index + reference operator*() const { return _number; } + + // operator-> doesn't make sense! + + APF_ITERATOR_RANDOMACCESS_EQUAL(_number) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_number) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_number) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_number) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_number) + APF_ITERATOR_RANDOMACCESS_LESS(_number) + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + private: + T _number; +}; + +/** Helper function to create an index_iterator. + * The template parameter is optional because it can be inferred from the + * parameter @p start. Example: + * @code make_index_iterator(42) @endcode + * @param start the start index + * @return an index_iterator + * @ingroup apf_iterators + **/ +template +index_iterator +make_index_iterator(T start) +{ + return index_iterator(start); +} + +/** A stride iterator. + * @tparam I Base iterator type + * + * Most of the code is taken from the C++ Cookbook, recipe 11.13: + * http://flylib.com/books/en/2.131.1.171/1/ + * + * Some modifications are based on this: + * http://stackoverflow.com/questions/1649529/sorting-blocks-of-l-elements/1649650#1649650 + * @ingroup apf_iterators + **/ +template +class stride_iterator +{ + private: + using self = stride_iterator; + + public: + /// @name Type Definitions from the Underlying Iterator + //@{ + using iterator_category + = typename std::iterator_traits::iterator_category; + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using reference = typename std::iterator_traits::reference; + using pointer = typename std::iterator_traits::pointer; + //@} + + /// This is the normal constructor. + /// @param base_iterator the base iterator + /// @param step the step size + explicit stride_iterator(I base_iterator, difference_type step +#ifdef APF_STRIDE_ITERATOR_DEFAULT_STRIDE + = APF_STRIDE_ITERATOR_DEFAULT_STRIDE +#endif + ) + : _iter(base_iterator) + , _step(step) + {} + + /// Create a stride iterator from another stride iterator. + /// @param base_iterator the base (stride) iterator + /// @param step the step size (in addition to the already present step size) + stride_iterator(stride_iterator base_iterator, difference_type step) + : _iter(base_iterator.base()) + , _step(base_iterator.step_size() * step) + {} + + /// Default constructor. + /// @note This constructor creates a singular iterator. Another + /// circular_iterator can be assigned to it, but nothing else works. + stride_iterator() + : _iter() + , _step(0) + {} + + bool + operator==(const self& rhs) const + { + return _iter == rhs._iter && _step == rhs._step; + } + + self& + operator++() + { + assert(no_nullptr(_iter)); + std::advance(_iter, _step); + return *this; + } + + self& + operator--() + { + assert(no_nullptr(_iter)); + std::advance(_iter, -_step); + return *this; + } + + reference + operator[](difference_type n) const + { + assert(no_nullptr(_iter)); + return _iter[n * _step]; + } + + self& + operator+=(difference_type n) + { + assert(!n || no_nullptr(_iter)); + std::advance(_iter, n * _step); + return *this; + } + + friend + difference_type + operator-(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._iter) && no_nullptr(rhs._iter)); + assert(lhs._step == rhs._step); + return (lhs._iter - rhs._iter) / lhs._step; + } + + friend + bool + operator<(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._iter) && no_nullptr(rhs._iter)); + assert(lhs._step == rhs._step); + return lhs._iter < rhs._iter; + } + + friend + bool + operator<=(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._iter) && no_nullptr(rhs._iter)); + assert(lhs._step == rhs._step); + return lhs._iter <= rhs._iter; + } + + friend + bool + operator>(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._iter) && no_nullptr(rhs._iter)); + assert(lhs._step == rhs._step); + return lhs._iter > rhs._iter; + } + + friend + bool + operator>=(const self& lhs, const self& rhs) + { + assert(no_nullptr(lhs._iter) && no_nullptr(rhs._iter)); + assert(lhs._step == rhs._step); + return lhs._iter >= rhs._iter; + } + + // straightforward operators: + APF_ITERATOR_RANDOMACCESS_DEREFERENCE(_iter) + APF_ITERATOR_RANDOMACCESS_ARROW(_iter) + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + APF_ITERATOR_BASE(I, _iter) + + /// Get step size + difference_type step_size() const { return _step; } + + private: + I _iter; ///< Base iterator + // This has to be non-const to allow automatic assignment operator: + difference_type _step; ///< Iterator increment +}; + +/** Iterate over two iterators at once. + * For now, the dual_iterator has the features of a forward iterator, but it + * could be upgraded if need should arise. + * + * dual_iterator has some special features (see output_proxy): + * - on dereferenciation and assignment, the value is assigned to both iterators + * - if a @c std::pair is assigned, the two values are assigned to the two + * iterators, respectively. + * - The @c += operator can also be used with similar behaviour + * - a dual_iterator can be dereferenced and assigned to a @c std::pair (if the + * @c value_type%s are convertible). + * @ingroup apf_iterators + **/ +template +class dual_iterator +{ + private: + using self = dual_iterator; + + public: + // TODO: what if I1 or I2 are only input or output iterators? + // TODO: some witty metaprogram to find the greatest common denominator? + using iterator_category = std::forward_iterator_tag; + + using value_type = std::pair::value_type + , typename std::iterator_traits::value_type>; + + class output_proxy; // For implementation see below + using reference = output_proxy; + + using difference_type = std::ptrdiff_t; + using pointer = void; + + dual_iterator() = default; + + /// Constructor from two iterators + dual_iterator(I1 i1, I2 i2) : _i1(i1), _i2(i2) {} + + /// Dereference operator. + /// @return a temporary object of type output_proxy + reference operator*() + { + assert(no_nullptr(_i1) && no_nullptr(_i2)); + return reference(_i1, _i2); + } + + // operator-> doesn't really make sense + + /// Pre-increment operator + self& operator++() + { + assert(no_nullptr(_i1) && no_nullptr(_i2)); + ++_i1; + ++_i2; + return *this; + } + + /// Equality operator + bool operator==(const self& rhs) const + { + return (_i1 == rhs._i1) && (_i2 == rhs._i2); + } + + APF_ITERATOR_FORWARD_POSTINCREMENT + APF_ITERATOR_FORWARD_UNEQUAL + + private: + I1 _i1; + I2 _i2; +}; + +/// Helper class for dual_iterator +template +class dual_iterator::output_proxy +{ + public: + explicit output_proxy(I1& i1, I2& i2) : _i1(i1), _i2(i2) {} + + /// Conversion operator to std::pair. + /// @note @p T1 and @p T2 must be convertible to @c I1::value_type and + /// @c I2::value_type, respectively! + template + operator std::pair() + { + return {*_i1, *_i2}; + } + + /// Special assignment operator for std::pair + template + output_proxy& operator=(const std::pair& rhs) + { + *_i1 = rhs.first; + *_i2 = rhs.second; + return *this; + } + + /// Default assignment operator + template + output_proxy& operator=(const T& rhs) + { + *_i1 = rhs; + *_i2 = rhs; + return *this; + } + + /// Special addition and assignment operator for std::pair + template + output_proxy& operator+=(const std::pair& rhs) + { + *_i1 += rhs.first; + *_i2 += rhs.second; + return *this; + } + + /// Default addition and assignment operator + template + output_proxy& operator+=(const T& rhs) + { + *_i1 += rhs; + *_i2 += rhs; + return *this; + } + + private: + I1& _i1; + I2& _i2; +}; + +/** Helper function to create an dual_iterator. + * The template parameters are optional because they can be inferred from the + * parameters @p i1 and @p i2. Example: + * @code apf::make_dual_iterator(some_iterator, some_other_iterator) @endcode + * @param i1 one base iterator + * @param i2 another base iterator + * @return a dual_iterator + * @ingroup apf_iterators + **/ +template +dual_iterator +make_dual_iterator(I1 i1, I2 i2) +{ + return dual_iterator(i1, i2); +} + +/** An iterator which does nothing. + * The discard_iterator has the features of an output iterator, but + * additionally, the += operator can be used. + * Two discard_iterator%s can be compared with @c == and @c !=, but they are + * always equal! + * - on dereferenciation and assignment, the assigned value is discarded. + * - The @c += operator can also be used with similar behaviour + * + * @see output_proxy + * @ingroup apf_iterators + **/ +class discard_iterator +{ + private: + using self = discard_iterator; + + public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + + /// Helper class for discard_iterator + struct output_proxy + { + /// Assignment operator (does nothing!) + template + output_proxy& operator=(const T&) { return *this; } + + /// Addition and assignment operator (does nothing!) + template + output_proxy& operator+=(const T&) { return *this; } + }; + + using reference = output_proxy; + + /// Dereference operator. + /// @return a temporary object of type output_proxy + reference operator*() + { + return reference(); + } + + /// Pre-increment operator (does nothing!) + self& operator++() { return *this; } + + /// Equality operator -- always true! + bool operator==(const self&) const { return true; } + + APF_ITERATOR_OUTPUT_POSTINCREMENT + APF_ITERATOR_FORWARD_UNEQUAL +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/jack_policy.h b/apf/apf/jack_policy.h new file mode 100644 index 00000000..020ca1c0 --- /dev/null +++ b/apf/apf/jack_policy.h @@ -0,0 +1,264 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// JACK policy for MimoProcessor's interface_policy. + +#ifndef APF_JACK_POLICY_H +#define APF_JACK_POLICY_H + +#ifdef APF_JACK_POLICY_DEBUG +#include // for printf() +#endif + +#include // for assert() + +#include "apf/jackclient.h" +#include "apf/parameter_map.h" +#include "apf/stringtools.h" +#include "apf/iterator.h" // for has_begin_and_end + +#ifndef APF_MIMOPROCESSOR_INTERFACE_POLICY +#define APF_MIMOPROCESSOR_INTERFACE_POLICY apf::jack_policy +#endif + +namespace apf +{ + +/// @c interface_policy using JACK. +/// Some of the functions are directly taken from JackClient. +/// @see MimoProcessor +/// @ingroup apf_policies +class jack_policy : public JackClient +{ + public: + using sample_type = sample_t; + class Input; + class Output; + + using JackClient::activate; + using JackClient::deactivate; + using JackClient::sample_rate; + + nframes_t block_size() const { return this->buffer_size(); } + + protected: + /// Constructor + /// @param p Parameters, only the parameter @c "name" (for the name of the + /// JACK client) is supported. + explicit jack_policy(const parameter_map& p = parameter_map()) + : JackClient(p.get("name", "MimoProcessor"), use_jack_process_callback) + {} + + virtual ~jack_policy() = default; + + private: + template class Xput; + + struct i_am_in + { + using iterator = const sample_type*; + static const bool is_input = true; + static std::string prefix_name() { return "input_prefix"; } + static std::string default_prefix() { return "in_"; } + }; + + struct i_am_out + { + using iterator = sample_type*; + static const bool is_input = false; + static std::string prefix_name() { return "output_prefix"; } + static std::string default_prefix() { return "out_"; } + }; + + virtual int jack_process_callback(nframes_t nframes) + { + (void)nframes; + assert(nframes == this->block_size()); + + // call virtual member function which is implemented in derived class + // (template method design pattern) + this->process(); + + return 0; + } + + virtual void process() = 0; +}; + +template +struct thread_traits; // definition in mimoprocessor.h + +template<> +struct thread_traits +{ + static void set_priority(const jack_policy& obj, pthread_t thread_id) + { + if (obj.is_realtime()) + { + struct sched_param param; + param.sched_priority = obj.get_real_time_priority(); + if (pthread_setschedparam(thread_id, SCHED_FIFO, ¶m)) + { + throw std::runtime_error("Can't set scheduling priority for thread!"); + } + } + else + { + // do nothing + } +#ifdef APF_JACK_POLICY_DEBUG + struct sched_param param; + int policy; + pthread_getschedparam(thread_id, &policy, ¶m); + printf("worker thread: policy=%s, priority=%d\n", + (policy == SCHED_FIFO) ? "SCHED_FIFO" : + (policy == SCHED_RR) ? "SCHED_RR" : + (policy == SCHED_OTHER) ? "SCHED_OTHER" : + "???", + param.sched_priority); +#endif + } +}; + +// Helper class to avoid code duplication in Input and Output +template +class jack_policy::Xput +{ + public: + using iterator = typename X::iterator; + + struct buffer_type : has_begin_and_end { friend class Xput; }; + + void fetch_buffer() + { + this->buffer._begin = static_cast( + jack_port_get_buffer(_port, _parent.block_size())); + this->buffer._end = this->buffer._begin + _parent.block_size(); + } + + std::string port_name() const { return _port_name; } + + buffer_type buffer; + + protected: + Xput(jack_policy& parent, const parameter_map& p); + + ~Xput() { _parent.unregister_port(_port); } + + private: + Xput(const Xput&); Xput& operator=(const Xput&); // deactivated + + jack_policy& _parent; + JackClient::port_t* _port; // JACK port corresponding to this object. + + JackClient::port_t* _init_port(const parameter_map& p, jack_policy& parent); + + const std::string _port_name; // actual JACK port name +}; + +template +JackClient::port_t* +jack_policy::Xput::_init_port(const parameter_map& p, jack_policy& parent) +{ + auto name = std::string(); + + // first, try port_name + if (p.has_key("port_name")) + { + name = p["port_name"]; + } + else + { + // then concatenate "input_prefix"/"output_prefix" with "id" + // if the prefix isn't specified, it's replaced by a default string + // worst case: duplicate string -> port registration will fail! + + auto id = std::string(); + + if (p.has_key("id")) + { + id = p.get("id", ""); + } + else + { + static int next_id = 1; + id = str::A2S(next_id++); + } + + name = p.get(X::prefix_name(), X::default_prefix()) + id; + } + + return X::is_input + ? parent.register_in_port(name) : parent.register_out_port(name); +} + +template +jack_policy::Xput::Xput(jack_policy& parent, const parameter_map& p) + : _parent(parent) + , _port(_init_port(p, _parent)) + // get actual port name and save it to member variable + , _port_name(_port ? jack_port_name(_port) : "") +{ + // optionally connect to jack_port + std::string connect_to = p.get("connect_to", ""); + if (connect_to != "") + { + if (X::is_input) + { + _parent.connect_ports(connect_to, _port_name); + } + else + { + _parent.connect_ports(_port_name, connect_to); + } + } +} + +class jack_policy::Input : public Xput +{ + protected: + Input(jack_policy& parent, const parameter_map& p) + : Xput(parent, p) + {} + + ~Input() = default; +}; + +class jack_policy::Output : public Xput +{ + protected: + Output(jack_policy& parent, const parameter_map& p) + : Xput(parent, p) + {} + + ~Output() = default; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/jackclient.h b/apf/apf/jackclient.h new file mode 100644 index 00000000..8590e068 --- /dev/null +++ b/apf/apf/jackclient.h @@ -0,0 +1,539 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// JACK client (C++ wrapper for JACK). + +#ifndef APF_JACKCLIENT_H +#define APF_JACKCLIENT_H + +#include +#include +#include +#include +#include +#include // for std::pair +#include // for std::runtime_error +#include // for assert() +#include // for EEXIST + +#ifdef APF_JACKCLIENT_DEBUG +#include +#include // for jack_get_xrun_delayed_usecs() +#define APF_JACKCLIENT_DEBUG_MSG(str) \ + do { std::cout << "apf::JackClient: " << str << std::endl; } while (false) +#else +#define APF_JACKCLIENT_DEBUG_MSG(str) do { } while (false) +#endif + +namespace apf +{ + +/** C++ wrapper for a JACK client. + * @warning When several JACK clients are running and one of them is closed, + * this can lead to a segmentation fault in the callback function + * (jack_port_get_buffer() delivers bad data). We couldn't really track down the + * error, but to be on the sure side, delete your JackClients only on the very + * end. + * \par + * A related issue may be that after calling jack_client_close() the + * corresponding thread is not closed, in the end it becomes a "zombie thread". + * But maybe this is a different story ... + * \par + * Any comments on this topic are very welcome! + **/ +class JackClient +{ + public: + typedef jack_default_audio_sample_t sample_t; + typedef jack_nframes_t nframes_t; + typedef jack_port_t port_t; + + /// Select if JACK's audio callback function shall be called + enum callback_usage_t + { + /// JACK audio callback is never called + dont_use_jack_process_callback = 0, + /// JACK audio callback (jack_process_callback()) is called after activate() + use_jack_process_callback + }; + + /// exception to be thrown at various occasions. + struct jack_error : std::runtime_error + { + jack_error(const std::string& s) + : std::runtime_error("JackClient: " + s) + {} + + jack_error(jack_status_t s) + : std::runtime_error(std::string("JackClient: ") + + ((s & JackInvalidOption) + ? "The operation contained an invalid or unsupported option!" + : (s & JackShmFailure) ? "Unable to access shared memory!" + : (s & JackVersionError) ? "Client's protocol version does not match!" + : (s & JackClientZombie) ? "Zombie!" + : (s & JackNoSuchClient) ? "Requested client does not exist!" + : (s & JackLoadFailure) ? "Unable to load internal client!" + : (s & JackInitFailure) ? "Unable to initialize client!" + : (s & JackBackendError) ? "Backend error!" + : (s & JackNameNotUnique & JackFailure) + ? "The client name is not unique!" + : (s & JackServerFailed) ? "Unable to connect to the JACK server!" + : (s & JackServerError) ? "Communication error with the JACK server!" + : (s & JackFailure) ? "Overall operation failed!" + : "Unknown error!")) + {} + }; + + explicit inline JackClient(const std::string& name = "JackClient" + , callback_usage_t callback_usage = dont_use_jack_process_callback); + + virtual ~JackClient() + { + if (_client) + { + // this->deactivate() has to be called in the derived dtor or earlier! + jack_client_close(_client); + // return value is ignored, because we're shutting down anyway ... + } + } + + /// Activate JACK client. + /// @return @b true on success + /// @see jack_activate() + bool activate() const + { + if (!_client || jack_activate(_client)) return false; + + this->connect_pending_connections(); + + // TODO: Still pending connections are ignored! + + return true; + } + + /// Deactivate JACK client. + /// @return @b true on success + /// @see jack_deactivate() + bool deactivate() const + { + APF_JACKCLIENT_DEBUG_MSG("pending connections @ deactivate(): " + << _pending_connections.size()); + return _client ? !jack_deactivate(_client) : false; + } + + /// @name manage JACK ports + //@{ + + /// Register JACK input port. + /// @param name desired port name + /// @return JACK port + /// @see register_port() + port_t* register_in_port(const std::string& name) const + { + return this->register_port(name, JackPortIsInput); + } + + /// Register JACK output port. + /// @param name desired port name + /// @return JACK port + /// @see register_port() + port_t* register_out_port(const std::string& name) const + { + return this->register_port(name, JackPortIsOutput); + } + + /// Register JACK port (input or output). + /// @param name desired port name + /// @param flags JACK port flags + /// @return JACK port + /// @see jack_port_register() + port_t* register_port(const std::string& name, unsigned long flags) const + { + return jack_port_register(_client, name.c_str(), JACK_DEFAULT_AUDIO_TYPE + , flags, 0); + } + + /// Unregister JACK port. + /// @param port JACK port + /// @return @b true on success + /// @see jack_port_unregister() + bool unregister_port(port_t* port) const + { + if (!jack_port_unregister(_client, port)) + { + port = nullptr; // port still points somewhere. Set to NULL. + return true; + } + else + { + return false; + } + } + + /// Connect two JACK ports. + /// @param source source port name + /// @param destination destination port name + /// @return @b true on success + bool connect_ports(const std::string& source + , const std::string& destination) const + { + return _connect_ports_helper(source, destination, _pending_connections); + } + + /// Disconnect two JACK ports. + /// @param source source port name + /// @param destination destination port name + /// @return @b true on success + bool disconnect_ports(const std::string& source + , const std::string& destination) const + { + return !jack_disconnect(_client, source.c_str(), destination.c_str()); + } + + /// Make connections which are still pending from a previous + /// call to connect_ports(). This is needed if connect_ports() has been + /// called while the JackClient wasn't activated yet. + bool connect_pending_connections() const + { + _pending_connections_t still_pending_connections; + + APF_JACKCLIENT_DEBUG_MSG("Connecting " << _pending_connections.size() + << " pending connections ..."); + while (_pending_connections.size() > 0) + { + _connect_ports_helper(_pending_connections.back().first + , _pending_connections.back().second, still_pending_connections); + _pending_connections.pop_back(); + } + APF_JACKCLIENT_DEBUG_MSG("Still pending connections: " + << still_pending_connections.size()); + + _pending_connections.swap(still_pending_connections); + return _pending_connections.empty(); + } + + //@} // manage JACK ports + + /// @name manage JACK transport + //@{ + + /// Start JACK transport + void transport_start() const + { + if (_client) jack_transport_start(_client); + } + + /// Stop JACK transport + void transport_stop() const + { + if (_client) jack_transport_stop(_client); + } + + /// Set JACK transport location. + /// @param frame location + /// @return @b true on success + /// @see get_transport_state(), jack_transport_locate() + bool transport_locate(nframes_t frame) const + { + return _client ? !jack_transport_locate(_client, frame) : false; + } + + /// Get JACK transport state. + /// @return a pair: first element is @b true if transport is rolling, + /// second is the current position. + std::pair get_transport_state() const + { + std::pair result(false, 0); + + if (_client) + { + jack_position_t jp; + jack_transport_state_t jts = jack_transport_query(_client, &jp); + result.first = (jts == JackTransportRolling); + result.second = jp.frame; + } + return result; + } + + //@} + + /// Set JACK freewheeling mode. + /// @param onoff non-zero: start; zero: stop + /// @return @b true on success + bool set_freewheel(int onoff) const + { + return _client ? !jack_set_freewheel(_client, onoff) : false; + } + + /// @return JACK client name + std::string client_name() const { return _client_name; } + /// @return JACK sample rate + nframes_t sample_rate() const { return _sample_rate; } + /// @return JACK buffer size + nframes_t buffer_size() const { return _buffer_size; } + + bool is_realtime() const + { + return _client ? jack_is_realtime(_client) : false; + } + + int get_real_time_priority() const + { + return _client ? jack_client_real_time_priority(_client) : -1; + } + + float get_cpu_load() const + { + return _client ? jack_cpu_load(_client) : 100.0f; + } + + pthread_t client_thread_id() const + { + return _client ? jack_client_thread_id(_client) : 0; + } + +#ifdef APF_DOXYGEN_HACK + protected: +#else + private: +#endif + + /// @name callback functions + /// can be overwritten in derived classes + //@{ + + /// JACK process callback function. + /// This function is empty in the JackClient base class. Derived classes + /// should overwrite it if needed. + /// @param nframes Number of frames (~samples) in the current block. This + /// value is delivered by the JACK server. + /// @return message to JACK: 0 means call me again, 1 don't call me anymore. + /// @throw jack_error if not implemented + /// @see callback_usage_t + virtual int jack_process_callback(nframes_t nframes) + { + (void)nframes; // avoid "unused parameter" warning + throw jack_error("jack_process_callback() not implemented!"); + } + + /// JACK shutdown callback. + /// By default, this is throwing a jack_error exception. If you don't like + /// this, you can overwrite this function in your derived class. + /// @param code status code, see JackInfoShutdownCallback + /// @param reason a string describing the shutdown reason + /// @see JackInfoShutdownCallback and jack_on_info_shutdown() + /// @note There is also JackShutdownCallback and jack_on_shutdown(), but + /// this one is more useful. + virtual void jack_shutdown_callback(jack_status_t code, const char* reason) + { + (void)code; // avoid "unused parameter" warning + throw jack_error("JACK shutdown! Reason: " + std::string(reason)); + } + + /// JACK sample rate callback. + /// @param sr new sample rate delivered by JACK + /// @throw jack_error if not implemented + /// @return 0 on success. + virtual int jack_sample_rate_callback(nframes_t sr) + { + (void)sr; + throw jack_error("Sample rate changes are not supported!"); + } + + /// JACK buffer size callback. + /// @throw jack_error if not implemented + /// @return 0 on success. + virtual int jack_buffer_size_callback(nframes_t bs) + { + (void)bs; + throw jack_error("Buffer size changes are not supported!"); + } + + /// JACK xrun callback. + /// @return zero on success, non-zero on error + virtual int jack_xrun_callback() + { + APF_JACKCLIENT_DEBUG_MSG("JACK server reports xrun of " + << jack_get_xrun_delayed_usecs(_client)/1000.0f << " msecs."); + return 0; + } + + //@} + + private: + // Internal redirection functions for callbacks + // Map void pointers to class instances and call member functions + + static int _jack_process_callback(nframes_t nframes, void* arg) + { + return static_cast(arg)->jack_process_callback(nframes); + } + + static void _jack_shutdown_callback(jack_status_t code + , const char* reason, void* arg) + { + static_cast(arg)->jack_shutdown_callback(code, reason); + } + + static int _jack_sample_rate_callback(nframes_t sr, void* arg) + { + return static_cast(arg)->jack_sample_rate_callback(sr); + } + + static int _jack_buffer_size_callback(nframes_t bs, void* arg) + { + return static_cast(arg)->jack_buffer_size_callback(bs); + } + + static int _jack_xrun_callback(void* arg) + { + return static_cast(arg)->jack_xrun_callback(); + } + + typedef std::vector> + _pending_connections_t; + + bool _connect_ports_helper(const std::string& source + , const std::string& destination + , _pending_connections_t& pending_connections) const + { + if (_client == nullptr) return false; + int success = jack_connect(_client, source.c_str(), destination.c_str()); + switch (success) + { + case 0: + break; + case EEXIST: + APF_JACKCLIENT_DEBUG_MSG("Connection already exists! (" + << source << " -> " << destination << ")"); + break; + default: + APF_JACKCLIENT_DEBUG_MSG("Unable to connect " + << source << " -> " << destination + << "! Adding this to pending connections ..."); + pending_connections.push_back(std::make_pair(source, destination)); + // TODO: return something else than true/false? + return false; + } + return true; + } + + std::string _client_name; // Name of JACK client + jack_client_t* _client; // Pointer to JACK client. + nframes_t _sample_rate; // sample rate of JACK server + nframes_t _buffer_size; // buffer size of JACK server + + mutable _pending_connections_t _pending_connections; + + JackClient(const JackClient&); // deactivated + JackClient& operator=(const JackClient&); // deactivated +}; + +/** Constructor. + * @param name client name of the JACK client to be created. + * @param callback_usage if @p use_jack_process_callback, the member + * function jack_process_callback() is called by JACK in each audio cycle. + * @warning @p name should not include a colon. This doesn't cause + * an error directly, but it messes up the JACK client- and portnames. + * @throw jack_error if something goes wrong + **/ +JackClient::JackClient(const std::string& name + , callback_usage_t callback_usage) + : _client_name(name) + , _client(nullptr) // will be set after connecting to JACK + , _sample_rate(0) // -- " -- + , _buffer_size(0) // -- " -- +{ + // check if client name is too long + // jack_client_name_size() returns the size including the terminating \0 + // character, hence the ">=". + if (name.size() + >= static_cast(jack_client_name_size())) + { + throw jack_error("Client name is too long! ('" + name + "')"); + } + + // TODO: make parameters for these options: + //jack_options_t options = JackNoStartServer; + jack_options_t options = JackUseExactName; + //jack_options_t options = JackNullOption; + + // JackNoStartServer: see also JACK_NO_START_SERVER + // TODO: JackServerName? needs extra parameter in jack_client_open()! + + jack_status_t status; + _client = jack_client_open(name.c_str(), options, &status); + if (!_client) throw jack_error(status); + + if (options & JackUseExactName) + { + assert(_client_name == jack_get_client_name(_client)); + } + else + { + _client_name = jack_get_client_name(_client); + } + + // TODO: error callback + //jack_set_error_function(default_jack_error_callback); + + if (callback_usage == use_jack_process_callback) + { + if (jack_set_process_callback(_client, _jack_process_callback, this)) + { + throw jack_error("Could not set process callback function for '" + + _client_name + "'!"); + } + } + + jack_on_info_shutdown(_client, _jack_shutdown_callback, this); + + if (jack_set_xrun_callback(_client, _jack_xrun_callback, this)) + { + throw jack_error("Could not set xrun callback function for '" + + _client_name + "'!"); + } + + // TODO: is the following still valid? + // sometimes, jack_activate() returns successful although an error occured and + // the thing "zombified". if the shutdown handler is called, _client is reset + // to zero which we can check now: + if (!_client) + { + throw jack_error("\"" + _client_name + "\" was killed somehow!"); + } + + _sample_rate = jack_get_sample_rate(_client); + _buffer_size = jack_get_buffer_size(_client); +} + +} // namespace apf + +#undef APF_JACKCLIENT_DEBUG_MSG + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/lockfreefifo.h b/apf/apf/lockfreefifo.h new file mode 100644 index 00000000..d004d4ab --- /dev/null +++ b/apf/apf/lockfreefifo.h @@ -0,0 +1,147 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Lock-free first-in-first-out queue. + +#ifndef APF_LOCKFREEFIFO_H +#define APF_LOCKFREEFIFO_H + +#include "apf/math.h" // for next_power_of_2() +#include "apf/misc.h" // for NonCopyable +#include "apf/container.h" // for fixed_vector + +namespace apf +{ + +template class LockFreeFifo; // undefined, use LockFreeFifo! + +/** Lock-free first-in-first-out (FIFO) queue. + * It is thread-safe for single reader/single writer access. + * One thread may use push() to en-queue items and another thread may use pop() + * to de-queue items. + * @note This FIFO queue is implemented as a ring buffer. + * @note This class is somehow related to the JACK ringbuffer implementation: + * http://jackaudio.org/files/docs/html/ringbuffer_8h.html + **/ +template +class LockFreeFifo : NonCopyable +{ + public: + explicit LockFreeFifo(size_t size); + + bool push(T* item); + T* pop(); + bool empty() const; + + private: + volatile size_t _write_index; ///< Write pointer + volatile size_t _read_index; ///< Read pointer + const size_t _size; ///< Size of the ringbuffer + const size_t _size_mask; ///< Bit mask used in modulo operation + fixed_vector _data; ///< Actual ringbuffer data +}; + +/** ctor. + * @param size desired ring buffer size, gets rounded up to the next power of 2. + **/ +template +LockFreeFifo::LockFreeFifo(size_t size) + : _write_index(0) + , _read_index(0) + , _size(apf::math::next_power_of_2(size)) + , _size_mask(_size - 1) + , _data(_size) +{} + +/** Add an item to the queue. + * @param item pointer to an item to be added. + * @return @b true on success, @b false if queue is full. + * @attention You have to check the return value to be sure the item has + * actually been added. + **/ +template +bool +LockFreeFifo::push(T* item) +{ + if (item == nullptr) return false; + + // Concurrent reading and writing is safe for one reader and writer. Once + // the _read_index is read the _write_index won't change before reading + // it, because it is modified only in this function. This won't work for + // multiple readers/writers. + auto r = _read_index; + auto w = _write_index; + + // Move write pointer by FIFO-size in order to compute the distance to + // the read pointer in next step. + if (w < r) w += _size; + + // Check if FIFO is full and return false instead of waiting until space is + // freed. (Prevent read pointer to overtake write pointer.) + if (w-r > _size-2) return false; + + _data[w & _size_mask] = item; + + // Set _write_index to next memory location (applying modulo operation) + _write_index = ++w & _size_mask; + return true; +} + +/** Get an item and remove it from the queue. + * @return Pointer to the item, @b 0 if queue is empty. + **/ +template +T* +LockFreeFifo::pop() +{ + T* retval = nullptr; + + if (this->empty()) return retval; + + auto r = _read_index; + + retval = _data[r]; + + // Set _read_index to next memory location (applying modulo operation) + _read_index = ++r & _size_mask; + return retval; +} + +/** Check if queue is empty. + * @return @b true if empty. + **/ +template +bool +LockFreeFifo::empty() const +{ + return _read_index == _write_index; +} + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/math.h b/apf/apf/math.h new file mode 100644 index 00000000..f5421fb0 --- /dev/null +++ b/apf/apf/math.h @@ -0,0 +1,338 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Mathematical constants and helper functions. + +#ifndef APF_MATH_H +#define APF_MATH_H + +#include // for std::pow(), ... +#include // for std::iterator_traits +#include // for std::accumulate + +namespace apf +{ +/// Mathematical constants and helper functions +namespace math +{ + +/// \f$\pi\f$. +/// Undefined general case. +template inline T pi(); + +/// \f$\pi\f$. +/// Specialization for float. +template<> +inline float pi() +{ + // 9 digits are needed for float, 17 digits for double, 21 for long double + // TODO: But this may be different on other hardware platforms ...? + return 3.1415926535897932384626433832795f; +} + +/// \f$\pi\f$. +/// Specialization for double. +template<> +inline double pi() +{ + return 3.1415926535897932384626433832795; +} + +/// \f$\pi\f$. +/// Specialization for long double. +template<> +inline long double pi() +{ + return 3.1415926535897932384626433832795l; +} + +/// \f$\pi/180\f$ +template +inline T pi_div_180() +{ + // local variable to avoid warning message about conversion + T q = 180; + return pi() / q; +} + +/** Product of a number with itself + * @param x number (any numeric type) + * @return @a x squared. + **/ +template inline T square(T x) { return x*x; } + +/** Convert a level in decibel to a linear gain factor. + * @param L level + * @param power if @b true, a factor of 10 is used, if @b false (default), the + * factor is 20. + * @return the linear counterpart to @a L. + **/ +template +inline T dB2linear(T L, bool power = false) +{ + T f = 20; if (power) f = 10; + T base = 10; + return std::pow(base, L / f); +} + +/** Convert a linear gain factor to a level in dB. + * @param x gain factor + * @param power if @b true, a factor of 10 is used, if @b false (default), the + * factor is 20. + * @return the logarithmic counterpart to @a x. + * @attention returns -Inf for x=0 and NaN for x<0. + **/ +template +inline T linear2dB(T x, bool power = false) +{ + T f = 20; if (power) f = 10; + return f * std::log10(x); +} + +/** Convert an angle in degrees to an angle in radians. + * @param angle dito + * @return angle in radians. + **/ +template +inline T deg2rad(T angle) +{ + return angle * pi_div_180(); +} + +/** Convert an angle in radians to an angle in degrees. + * @param angle dito + * @return angle in degrees. + **/ +template +inline T rad2deg(T angle) +{ + return angle / pi_div_180(); +} + +/// Wrap @p x into the interval [0, @p full). +/// Helper function for float, double and long double. +/// @see wrap() +template +inline T fwrap(T x, T full) +{ + x = std::fmod(x, full); + if (x < 0) x += full; + return x; +} + +/// Wrap @p x into the interval [0, @p full). +/// Unspecialized case, works only for integral types. +template +inline T wrap(T x, T full) +{ + x %= full; + if (x < 0) x += full; + return x; +} + +/// Wrap @p x into the interval [0, @p full). +/// Template specialization for float. +template<> +inline float wrap(float x, float full) +{ + return fwrap(x, full); +} + +/// Wrap @p x into the interval [0, @p full). +/// Template specialization for double. +template<> +inline double wrap(double x, double full) +{ + return fwrap(x, full); +} + +/// Wrap @p x into the interval [0, @p full). +/// Template specialization for long double. +template<> +inline long double wrap(long double x, long double full) +{ + return fwrap(x, full); +} + +template +inline T wrap_two_pi(T x) +{ + // local variable to avoid warning message about conversion + T two = 2; + return wrap(x, two * pi()); +} + +/** Find a power of 2 which is >= a given number. + * @param number number for which to find next power of 2 + * @return power of 2 above (or equal to) \b number + * @note For all @p number%s <= 1 the result is 1; + **/ +template +inline T next_power_of_2(T number) +{ + T power_of_2 = 1; + while (power_of_2 < number) power_of_2 *= 2; + return power_of_2; +} + +/** Return the absolute maximum of a series of numbers. + * @param begin beginning of range + * @param end end of range + * @return maximum, this is always >= 0. + **/ +template +inline typename std::iterator_traits::value_type +max_amplitude(I begin, I end) +{ + using T = typename std::iterator_traits::value_type; + return std::accumulate(begin, end, T() + , [] (T current, T next) { return std::max(current, std::abs(next)); }); +} + +/** Root Mean Square (RMS) value of a series of numbers. + * @param begin beginning of range + * @param end end of range + * @return RMS value + **/ +template +inline typename std::iterator_traits::value_type rms(I begin, I end) +{ + using T = typename std::iterator_traits::value_type; + + // inner product: sum of squares + // divided by number: mean + // sqrt: root + return std::sqrt(std::inner_product(begin, end, begin, T()) + / static_cast(std::distance(begin, end))); +} + +/** Check if there are only zeros in a range. + * @return @b false as soon as a non-zero value is encountered + **/ +template +bool has_only_zeros(I first, I last) +{ + while (first != last) if (*first++ != 0) return false; + return true; +} + +/** Raised cosine (function object). + * Result ranges from 0 to 1. + **/ +template +class raised_cosine +{ + public: + using argument_type = T; + using result_type = T; + + /// Constructor. @param period of a full cosine oscillation + explicit raised_cosine(T period = 0) : _period(period) {} + + /// Function call operator + T operator()(T in) const + { + // local variable to avoid warning about conversion + T half = 0.5; + return std::cos(in * 2 * pi() / _period) * half + half; + } + + private: + const T _period; +}; + +/** Function object for linear interpolation. + * @tparam T result type + * @tparam U input type + * @warning If @p U is an integral type and @p T is floating point, don't + * forget that the cast operation takes some processing time. It may be more + * efficient to use the same floating point type for both @p T and @p U. + **/ +template +class linear_interpolator +{ + public: + using argument_type = U; + using result_type = T; + + /// Default constructor + linear_interpolator() : _first(), _increment() {} + + /// Constructor with range and optional length. + /// @see set() + linear_interpolator(result_type first, result_type last + , argument_type length = argument_type(1)) + { + this->set(first, last, length); + } + + /// Set range and optional length. + /// @param first output value if input is zero + /// @param last output value if input is @p length + /// @param length length of interval on which to interpolate + void set(result_type first, result_type last + , argument_type length = argument_type(1)) + { + _first = first; + _increment = (last - first) / length; + } + + /// Function call operator + result_type operator()(argument_type x) + { + return _first + result_type(x) * _increment; + } + + private: + result_type _first, _increment; +}; + +/// Helper function for automatic template type deduction +template +linear_interpolator +make_linear_interpolator(T first, T last) +{ + return linear_interpolator(first, last); +} + +/// Helper function for automatic template type deduction +template +linear_interpolator +make_linear_interpolator(T first, T last, U length) +{ + return linear_interpolator(first, last, length); +} + +/// Identity function object. Function call returns a const reference to input. +template +struct identity { const T& operator()(const T& in) { return in; } }; + +} // namespace math +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/mimoprocessor.h b/apf/apf/mimoprocessor.h new file mode 100644 index 00000000..a6c44bfc --- /dev/null +++ b/apf/apf/mimoprocessor.h @@ -0,0 +1,608 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Multi-threaded MIMO (multiple input, multiple output) processor. + +#ifndef APF_MIMOPROCESSOR_H +#define APF_MIMOPROCESSOR_H + +#include // for assert() +#include // for std::logic_error + +#include "apf/rtlist.h" +#include "apf/parameter_map.h" +#include "apf/misc.h" // for NonCopyable +#include "apf/iterator.h" // for *_iterator, make_*_iterator(), cast_proxy_const +#include "apf/container.h" // for fixed_vector + +#define APF_MIMOPROCESSOR_TEMPLATES template +#define APF_MIMOPROCESSOR_BASE MimoProcessor + +#ifndef APF_MIMOPROCESSOR_DEFAULT_THREADS +#define APF_MIMOPROCESSOR_DEFAULT_THREADS 1 +#endif + +/** Macro to create a @c Process struct and a corresponding member function. + * @param name Name of the containing class + * @param parent Parent class (must have an inner class @c Process). + * The class apf::MimoProcessor::ProcessItem can be used. + * + * Usage examples: + * @code + * // In 'public' section of Output: + * APF_PROCESS(Output, MimoProcessorBase::Output) + * { + * // do something here, you have access to members of Output + * } + * + * // In 'public' section of MyItem: + * APF_PROCESS(MyItem, ProcessItem) + * { + * // do something here, you have access to members of MyItem + * // MyItem has to be publicly derived from ProcessItem + * } + * @endcode + **/ +#define APF_PROCESS(name, parent) \ +struct Process : parent::Process { \ + explicit Process(name& ctor_arg) : parent::Process(ctor_arg) { \ + ctor_arg.APF_PROCESS_internal(); } }; \ +void APF_PROCESS_internal() + +namespace apf +{ + +// the default implementation does nothing +template +struct thread_traits +{ + static void set_priority(const interface_policy&, native_handle_type) {} +}; + +class enable_queries +{ + protected: + enable_queries(size_t fifo_size) : _query_fifo(fifo_size) {} + CommandQueue _query_fifo; +}; + +class disable_queries +{ + protected: + disable_queries(size_t) {} + struct { void process_commands() {} } _query_fifo; +}; + +/** Multi-threaded multiple-input-multiple-output (MIMO) processor. + * Derive your own class from MimoProcessor and also use it as first template + * argument. This is called the "Curiously Recurring Template Pattern" (CRTP). + * The rest of the template arguments are \ref apf_policies ("Policy-based + * Design"). + * + * @tparam Derived Your derived class -> CRTP! + * @tparam interface_policy Policy class. You can use existing policies (e.g. + * jack_policy, pointer_policy) or write your own policy class. + * @tparam thread_policy Policy for threads, locks and semaphores. + * + * Example: @ref MimoProcessor + **/ +template +class MimoProcessor : public interface_policy + , public thread_policy + , public query_policy + , public CRTP + , NonCopyable +{ + public: + using sample_type = typename interface_policy::sample_type; + using query_policy::_query_fifo; + + class Input; + class Output; + class DefaultInput; + class DefaultOutput; + + /// Abstract base class for list items. + struct Item : NonCopyable + { + virtual ~Item() = default; + + /// to be overwritten in the derived class + virtual void process() = 0; + }; + + /** Base class for items which have a @c Process class. + * Usage: + * @code + * class MyItem : public ProcessItem + * { + * public: + * APF_PROCESS(MyItem, ProcessItem) + * { + * // do something here, you have access to members of MyItem + * } + * }; + * @endcode + * Multiple layers of inheritance are possible, but ProcessItem must + * be instantiated with the most derived class! + **/ + template + struct ProcessItem : Item, public CRTP + { + struct Process { explicit Process(ProcessItem&) {} }; + + virtual void process() + { + typename X::Process(this->derived()); + } + }; + + using rtlist_t = RtList; + + /// Proxy class for accessing an RtList. + /// @note This is for read-only access. Write access is only allowed in the + /// process() member function from within the object itself. + template + struct rtlist_proxy : cast_proxy_const + { + rtlist_proxy(const rtlist_t& l) : cast_proxy_const(l) {} + }; + + /// Lock is released when it goes out of scope. + class ScopedLock : NonCopyable + { + public: + explicit ScopedLock(typename thread_policy::Lock& obj) + : _obj(obj) + { + _obj.lock(); + } + + ~ScopedLock() { _obj.unlock(); } + + private: + typename thread_policy::Lock& _obj; + }; + + class QueryThread + { + public: + QueryThread(CommandQueue& fifo) : _fifo(fifo) {}; + void operator()() { _fifo.cleanup_commands(); } + + private: + CommandQueue& _fifo; + }; + + template + class QueryCommand : public CommandQueue::Command + { + public: + QueryCommand(F& query_function, Derived& parent) + : _query_function(query_function) + , _parent(parent) + {} + + virtual void execute() + { + _query_function.query(); + } + + virtual void cleanup() + { + _query_function.update(); + _parent.new_query(_query_function); // "recursive" call! + } + + private: + F& _query_function; + Derived& _parent; + }; + + template + void new_query(F& query_function) + { + _query_fifo.push(new QueryCommand(query_function, this->derived())); + } + + bool activate() + { + _fifo.reactivate(); // no return value + return interface_policy::activate(); + } + + bool deactivate() + { + if (!interface_policy::deactivate()) return false; + + // All audio threads should be stopped now. + + // Inputs/Outputs push commands in their destructors -> we need a loop. + do + { + // Exceptionally, this is called from the non-realtime thread: + _fifo.process_commands(); + _fifo.cleanup_commands(); + } + while (_fifo.commands_available()); + // The queue should be empty now. + if (!_fifo.deactivate()) throw std::logic_error("Bug: FIFO not empty!"); + return true; + + // The lists can now be manipulated safely from the non-realtime thread. + } + + void wait_for_rt_thread() { _fifo.wait(); } + + template + X* add() + { + return this->add(typename X::Params()); + } + + // TODO: find a way to get the outer type automatically + template + typename P::outer* add(const P& p) + { + using X = typename P::outer; + auto temp = p; + temp.parent = &this->derived(); + return static_cast(_add_helper(new X(temp))); + } + + void rem(Input* in) { _input_list.rem(in); } + void rem(Output* out) { _output_list.rem(out); } + + const rtlist_t& get_input_list() const { return _input_list; } + const rtlist_t& get_output_list() const { return _output_list; } + + const parameter_map params; + + template + static typename thread_policy::template ScopedThread* + new_scoped_thread(F f, typename thread_policy::useconds_type usleeptime) + { + return new typename thread_policy::template ScopedThread(f,usleeptime); + } + + protected: + using MimoProcessorBase = APF_MIMOPROCESSOR_BASE; + + using rtlist_iterator = typename rtlist_t::iterator; + using rtlist_const_iterator = typename rtlist_t::const_iterator; + + struct Process { Process(Derived&) {} }; + + explicit MimoProcessor(const parameter_map& params = parameter_map()); + + /// Protected non-virtual destructor + ~MimoProcessor() + { + this->deactivate(); + _input_list.clear(); + _output_list.clear(); + } + + void _process_list(rtlist_t& l); + void _process_list(rtlist_t& l1, rtlist_t& l2); + + CommandQueue _fifo; + + private: + class WorkerThreadFunction; + + class WorkerThread : NonCopyable + { + private: + using DetachedThread = typename thread_policy::template DetachedThread< + WorkerThreadFunction>; + + public: + WorkerThread(int thread_number, MimoProcessor& parent) + : cont_semaphore(0) + , wait_semaphore(0) + , _thread(WorkerThreadFunction(thread_number, parent, *this)) + { + // Set thread priority from interface_policy, if available + thread_traits::set_priority(parent + , _thread.native_handle()); + } + + typename thread_policy::Semaphore cont_semaphore; + typename thread_policy::Semaphore wait_semaphore; + + private: + DetachedThread _thread; // Thread must be initialized after semaphores + }; + + class WorkerThreadFunction + { + public: + WorkerThreadFunction(int thread_number, MimoProcessor& parent + , WorkerThread& thread) + : _thread_number(thread_number) + , _parent(parent) + , _thread(thread) + {} + + void operator()() + { + // wait for main thread + _thread.cont_semaphore.wait(); + + _parent._process_selected_items_in_current_list(_thread_number); + + // report to main thread + _thread.wait_semaphore.post(); + } + + private: + int _thread_number; + MimoProcessor& _parent; + WorkerThread& _thread; + }; + + class Xput; + + // This is called from the interface_policy + virtual void process() + { + _fifo.process_commands(); + _process_list(_input_list); + typename Derived::Process(this->derived()); + _process_list(_output_list); + _query_fifo.process_commands(); + } + + void _process_current_list_in_main_thread(); + void _process_selected_items_in_current_list(int thread_number); + + Input* _add_helper(Input* in) { return _input_list.add(in); } + Output* _add_helper(Output* out) { return _output_list.add(out); } + + // TODO: make "volatile"? + rtlist_t* _current_list; + + /// Number of threads (main thread plus worker threads) + const int _num_threads; + + fixed_vector _thread_data; + + rtlist_t _input_list, _output_list; +}; + +/// @throw std::logic_error if CommandQueue cannot be deactivated. +APF_MIMOPROCESSOR_TEMPLATES +APF_MIMOPROCESSOR_BASE::MimoProcessor(const parameter_map& params_) + : interface_policy(params_) + , query_policy(params_.get("fifo_size", 1024)) + , params(params_) + , _fifo(params.get("fifo_size", 1024)) + , _current_list(nullptr) + , _num_threads(params.get("threads", APF_MIMOPROCESSOR_DEFAULT_THREADS)) + , _input_list(_fifo) + , _output_list(_fifo) +{ + assert(_num_threads > 0); + + // deactivate FIFO for non-realtime initializations + if (!_fifo.deactivate()) throw std::logic_error("Bug: FIFO not empty!"); + + // Create N-1 worker threads. NOTE: Number 0 is reserved for the main thread. + _thread_data.reserve(_num_threads - 1); + for (int i = 1; i < _num_threads; ++i) + { + _thread_data.emplace_back(i, *this); + } +} + +APF_MIMOPROCESSOR_TEMPLATES +void +APF_MIMOPROCESSOR_BASE::_process_list(rtlist_t& l) +{ + _current_list = &l; + _process_current_list_in_main_thread(); +} + +APF_MIMOPROCESSOR_TEMPLATES +void +APF_MIMOPROCESSOR_BASE::_process_list(rtlist_t& l1, rtlist_t& l2) +{ + // TODO: extend for more than two lists? + + // Note: this was not conforming to C++03. + // According to C++03 iterators to the spliced elements are invalidated! + // In C++11 this was fixed. + // see http://stackoverflow.com/q/143156 + + // see also http://stackoverflow.com/q/7681376 + + auto temp = l2.begin(); + l2.splice(temp, l1); // join lists: "L2 = L1 + L2" + _process_list(l2); + l1.splice(l1.end(), l2, l2.begin(), temp); // restore original lists + + // not exception-safe (original lists are not restored), but who cares? +} + +APF_MIMOPROCESSOR_TEMPLATES +void +APF_MIMOPROCESSOR_BASE::_process_selected_items_in_current_list(int thread_number) +{ + assert(_current_list); + + int n = 0; + for (auto& i: *_current_list) + { + if (thread_number == n++ % _num_threads) + { + assert(i); + i->process(); + } + } +} + +APF_MIMOPROCESSOR_TEMPLATES +void +APF_MIMOPROCESSOR_BASE::_process_current_list_in_main_thread() +{ + assert(_current_list); + if (_current_list->empty()) return; + + // wake all threads + for (auto& it: _thread_data) it.cont_semaphore.post(); + + _process_selected_items_in_current_list(0); + + // wait for worker threads + for (auto& it: _thread_data) it.wait_semaphore.wait(); +} + +APF_MIMOPROCESSOR_TEMPLATES +class APF_MIMOPROCESSOR_BASE::Xput : public Item +{ + public: + // Parameters for an Input or Output. + // You can add your own parameters by deriving from it. + struct Params : parameter_map + { + Params() : parent(nullptr) {} + Derived* parent; + + Params& operator=(const parameter_map& p) + { + this->parameter_map::operator=(p); + return *this; + } + }; + + Derived& parent; + + protected: + /// Protected Constructor. + /// @throw std::logic_error if parent == NULL + explicit Xput(const Params& p) + : parent(*(p.parent + ? p.parent + : throw std::logic_error("Bug: In/Output: parent == 0!"))) + {} +}; + +/// %Input class. +APF_MIMOPROCESSOR_TEMPLATES +class APF_MIMOPROCESSOR_BASE::Input : public Xput + , public interface_policy::Input + , public CRTP +{ + public: + struct Params : Xput::Params + { + using Xput::Params::operator=; + using outer = typename Derived::Input; // see add() + }; + + struct Process { Process(Input&) {} }; + + explicit Input(const Params& p) + : Xput(p) + , interface_policy::Input(*p.parent, p) + {} + + private: + virtual void process() + { + this->fetch_buffer(); + typename Derived::Input::Process(this->derived()); + } +}; + +/// %Input class with begin() and end(). +APF_MIMOPROCESSOR_TEMPLATES +class APF_MIMOPROCESSOR_BASE::DefaultInput : public Input +{ + public: + using Params = typename Input::Params; + using iterator = typename Input::iterator; + + DefaultInput(const Params& p) : Input(p) {} + + iterator begin() const { return this->buffer.begin(); } + iterator end() const { return this->buffer.end(); } +}; + +/// %Output class. +APF_MIMOPROCESSOR_TEMPLATES +class APF_MIMOPROCESSOR_BASE::Output : public Xput + , public interface_policy::Output + , public CRTP +{ + public: + struct Params : Xput::Params + { + using Xput::Params::operator=; + using outer = typename Derived::Output; // see add() + }; + + struct Process { Process(Output&) {} }; + + explicit Output(const Params& p) + : Xput(p) + , interface_policy::Output(*p.parent, p) + {} + + private: + virtual void process() + { + this->fetch_buffer(); + typename Derived::Output::Process(this->derived()); + } +}; + +/// %Output class with begin() and end(). +APF_MIMOPROCESSOR_TEMPLATES +class APF_MIMOPROCESSOR_BASE::DefaultOutput : public Output +{ + public: + using Params = typename Output::Params; + using iterator = typename Output::iterator; + + DefaultOutput(const Params& p) : Output(p) {} + + iterator begin() const { return this->buffer.begin(); } + iterator end() const { return this->buffer.end(); } +}; + +} // namespace apf + +#undef APF_MIMOPROCESSOR_TEMPLATES +#undef APF_MIMOPROCESSOR_BASE + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/mimoprocessor_file_io.h b/apf/apf/mimoprocessor_file_io.h new file mode 100644 index 00000000..bfef2b51 --- /dev/null +++ b/apf/apf/mimoprocessor_file_io.h @@ -0,0 +1,123 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Helper function for multichannel soundfile reading/writing + +#include // C++ interface to libsndfile +#include + +#include "stopwatch.h" +#include "apf/container.h" // for fixed_matrix + +namespace apf +{ + +/// Use MimoProcessor-based object with multichannel audio file input and output +/// @param processor Object derived from MimoProcessor +/// @param infilename Input audio file name +/// @param outfilename Output audio file name (will be overwritten if it exists) +template +int mimoprocessor_file_io(Processor& processor + , const std::string& infilename + , const std::string& outfilename) +{ + std::cout << "Opening file \"" << infilename << "\" ..." << std::endl; + + auto in = SndfileHandle(infilename, SFM_READ); + + if (int err = in.error()) + { + std::cout << in.strError() << std::endl; + return err; + } + + if (in.samplerate() != static_cast(processor.sample_rate())) + { + std::cout << "Samplerate mismatch!" << std::cout; + return 42; + } + + if (in.channels() != processor.in_channels()) + { + std::cout << "Input channel mismatch!" << std::cout; + return 666; + } + + auto out = SndfileHandle(outfilename, SFM_WRITE + , in.format(), processor.out_channels(), in.samplerate()); + + if (int err = out.error()) + { + std::cout << out.strError() << std::endl; + return err; + } + + auto format_info = SF_FORMAT_INFO(); + format_info.format = in.format(); + in.command(SFC_GET_FORMAT_INFO, &format_info, sizeof(format_info)); + + std::cout << "format: " << format_info.name << std::endl; + + std::cout << "frames: " << in.frames() + << " (" << in.frames()/in.samplerate() << " seconds)" << std::endl; + std::cout << "channels: " << in.channels() << std::endl; + std::cout << "samplerate: " << in.samplerate() << std::endl; + + int blocksize = processor.block_size(); + + // these matrices are used for de-interleaving and interleaving + fixed_matrix m_in(blocksize, in.channels()); + fixed_matrix m_in_transpose(in.channels(), blocksize); + fixed_matrix m_out(blocksize, processor.out_channels()); + fixed_matrix m_out_transpose(processor.out_channels(), blocksize); + + processor.activate(); + + StopWatch watch("processing"); + + size_t actual_frames = 0; + while ((actual_frames = in.readf(m_in.data(), blocksize)) != 0) + { + m_in_transpose.set_channels(m_in.slices); + + processor.audio_callback(blocksize + , m_in_transpose.get_channel_ptrs() + , m_out_transpose.get_channel_ptrs()); + + m_out.set_channels(m_out_transpose.slices); + + out.writef(m_out.data(), actual_frames); + } + + //out.writeSync(); // write cache buffers to disk + + processor.deactivate(); + + return 0; +} + +} // namespace apf + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/apf/misc.h b/apf/apf/misc.h new file mode 100644 index 00000000..2d572d29 --- /dev/null +++ b/apf/apf/misc.h @@ -0,0 +1,202 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Miscellaneous helper classes. + +#ifndef APF_MISC_H +#define APF_MISC_H + +#include // for std::forward +#include // for std::is_base_of + +namespace apf +{ + +/// Curiously Recurring Template Pattern (CRTP) base class. +/// The idea for derived() is stolen from the Eigen library. +template +class CRTP +{ + public: + Derived& derived() + { + static_assert(std::is_base_of::value + , "Derived must be derived from CRTP (as the name suggests)!"); + + return *static_cast(this); + } + + protected: + CRTP() = default; + ~CRTP() = default; ///< Protected destructor to avoid base class pointers +}; + +/// Classes derived from this class cannot be copied (but still moved). +class NonCopyable +{ + protected: + NonCopyable() = default; ///< Protected default constructor + ~NonCopyable() = default; ///< Protected non-virtual destructor + + public: + /// @name Deactivated copy ctor and assignment operator + //@{ + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(const NonCopyable&) = delete; + //@} + + /// @name Default move ctor and move assignment operator + //@{ + NonCopyable(NonCopyable&&) = default; + NonCopyable& operator=(NonCopyable&&) = default; + //@} +}; + +/// Hold current and old value of any type. +/// A new value can be assigned with operator=(). +/// The current and old value can be obtained with get() and old(), +/// respectively. +/// To find out if the old and current value are different, use changed(). +/// BlockParameter is supposed to avoid pairs of variables where one +/// represents an old value and the other a new one. The @e old value of +/// BlockParameter is updated in operator=() (and only there). +/// @attention It's @e not possible to use operator=() without updating the +/// @e old value. To avoid the unintended use of operator=(), use +/// @code +/// #include +/// BlockParameter bp(42); +/// bp = 23; +/// assert(bp.exactly_one_assignment()); +/// @endcode +/// This is of course only checked if NDEBUG is undefined. +/// @tparam T The contained type. +template +class BlockParameter +{ + public: + /// Constructor. Any arguments are forwarded to both old and current value. + template + explicit BlockParameter(Args&&... args) + : _current{args...} + , _old{std::forward(args)...} + {} + + /// Assignment operator. + /// As a side effect, the old value is updated to the former current value. + template + T& operator=(Arg&& arg) + { +#ifndef NDEBUG + ++_assignments; +#endif + + _old = std::move(_current); + return _current = std::forward(arg); + } + + const T& get() const { return _current; } ///< Get current value. + const T& old() const { return _old; } ///< Get old value. + + /// Conversion operator. For avoiding to call get() all the time. + operator const T&() const { return this->get(); } + + /// Check if value has changed between before the last assignment and now. + bool changed() const { return this->get() != this->old(); } + + /// @name Operators which do not change the old value: + /// @{ + BlockParameter& operator+=(const T& rhs) { _current += rhs; return *this; } + BlockParameter& operator-=(const T& rhs) { _current -= rhs; return *this; } + BlockParameter& operator*=(const T& rhs) { _current *= rhs; return *this; } + BlockParameter& operator/=(const T& rhs) { _current /= rhs; return *this; } + BlockParameter& operator%=(const T& rhs) { _current %= rhs; return *this; } + BlockParameter& operator&=(const T& rhs) { _current &= rhs; return *this; } + BlockParameter& operator|=(const T& rhs) { _current |= rhs; return *this; } + BlockParameter& operator<<=(const T& rhs){ _current <<= rhs; return *this; } + BlockParameter& operator>>=(const T& rhs){ _current >>= rhs; return *this; } + + BlockParameter& operator++() { ++_current; return *this; } + BlockParameter& operator--() { --_current; return *this; } + T operator++(int) { return _current++; } + T operator--(int) { return _current--; } + /// @} + + private: + class both_proxy + { + public: + explicit both_proxy(const BlockParameter& param) : _p(param) {} + +#define APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(opstring) \ + friend bool operator opstring(const both_proxy& lhs, const T& rhs) { \ + return lhs._p.get() opstring rhs && lhs._p.old() opstring rhs; } \ + friend bool operator opstring(const T& lhs, const both_proxy& rhs) { \ + return lhs opstring rhs._p.get() && lhs opstring rhs._p.old(); } + + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(==) + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(!=) + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(<) + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(>) + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(<=) + APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS(>=) +#undef APF_BLOCKPARAMETER_BOTH_PROXY_OPERATORS + + private: + const BlockParameter& _p; + }; + + public: + + both_proxy both() const { return both_proxy(*this); } + +#ifndef NDEBUG + bool no_multiple_assignments() const + { + auto result = (_assignments <= 1); + _assignments = 0; + return result; + } + + bool exactly_one_assignment() const + { + auto result = (_assignments == 1); + _assignments = 0; + return result; + } +#endif + + private: + T _current, _old; + +#ifndef NDEBUG + mutable int _assignments = 0; +#endif +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/apf/parameter_map.h b/apf/apf/parameter_map.h new file mode 100644 index 00000000..ea2673e3 --- /dev/null +++ b/apf/apf/parameter_map.h @@ -0,0 +1,207 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// A "dictionary" for parameters. + +#ifndef APF_PARAMETER_MAP_H +#define APF_PARAMETER_MAP_H + +#include +#include // for std::range_error +#include "stringtools.h" + +namespace apf +{ + +/** A "dictionary" for parameters. + * All values are saved as @c std::string's. + * + * Usage examples: + * @code + * parameter_map params; + * params["one"] = "first value"; + * params["two"] = "2"; + * params.set("three", 3.1415); + * std::string val1, val5; + * int val2, val3, val4; + * val1 = params["one"]; + * val2 = params.get("two"); + * val3 = params.get("one"); // val3 = 0 (default int ctor) + * val4 = params.get("one", 42); // default value 42 if conversion fails + * // template argument is deduced from 2nd arg + * if (params.has_key("four")) + * { + * // this is not done because there is no key named "four": + * do_something(); + * } + * val5 = params["four"]; // throws std::range_error exception! + * @endcode + **/ +struct parameter_map +{ + /** "Getter". + * @param k Name of the parameter which should be retrieved. + * @return const reference to the value referenced by @p k. + * @throw std::range_error if the key @p k doesn't exist. You should've + * checked beforehand with has_key() ... + **/ + const std::string& operator[](const std::string& k) const + throw (std::range_error) + { + auto it = _map.find(k); + if (it == _map.end()) + { + throw std::range_error("Parameter \"" + k + "\" does not exist in map!"); + } + return it->second; + } + + /** "Setter". Well, not really. It just gives you a reference where you can + * assign stuff to. + * @param k Name of the parameter which should be set. If it doesn't exist + * yet, it's created especially for you. + * @return non-const reference to the value referenced by @p k. + * You can assign a @c std::string to actually set a new value. + **/ + std::string& operator[](const std::string& k) + { + return _map[k]; + } + + /** Get value converted to given type. + * @tparam T The desired type. Can be omitted if @p def is specified. + * @param k Name of the parameter which should be retrieved. + * @param def Default value for cases where conversion fails. + * @return Value referenced by @p k, converted to type @p T. If the key @p k + * isn't available, or if the conversion failed, @p def is returned. + * @warning If the result is equal to the default value @p def, there is no + * way to know ... + * - if the key was available + * - if the conversion was successful + * @par + * To check the former, you can use has_key(), for the latter you have to + * get the value as string (with operator[]()) and convert it yourself + * (e.g. with apf::str::S2A()). + * Or you can use the throwing version of get(). + **/ + template + T get(const std::string& k, const T& def) const + { + try + { + return this->get(k); + } + catch (const std::range_error&) + { + return def; + } + catch (const std::invalid_argument&) + { + return def; + } + } + + /** Overloaded function for character array (aka C-string). + * This is mainly used to specify a string literal as default value, which + * wouldn't work with the other get() version, e.g. + * @code + * parameter_map params; + * params["id"] = "item42"; + * std::string id1, id2, name1, name2; + * id1 = params.get("id" , "no_id_available"); // id1 = "item42"; + * id2 = params.get("id" , "item42"); // id2 = "item42"; + * name1 = params.get("name", "Default Name"); // name1 = "Default Name"; + * name2 = params.get("name", ""); // name2 = ""; + * @endcode + * @tparam T The given character type. Can be omitted if @p def is specified + * (which is normally the case!). + * @param k Name of the parameter which should be retrieved. + * @param def Default value for cases where conversion fails. + * @return Value referenced by @p k, converted to a + * @c std::basic_string. If the key @p k isn't available, or if the + * conversion failed, @p def is returned. + * @warning Same warning as for the other get() version with default value. + **/ + template + std::basic_string + get(const std::string& k, const char_T* const def) const + { + return this->get(k, std::basic_string(def)); + } + + /** Throwing getter. + * @tparam T Desired output type + * @param k Name of the parameter which should be retrieved. + * @throw std::range_error if the key @p k is not available + * @throw std::invalid_argument if the content cannot be converted to @p T + **/ + template + T get(const std::string& k) const + { + T temp; + try + { + temp = str::S2RV(this->operator[](k)); + } + catch (std::invalid_argument& e) + { + throw std::invalid_argument( + "parameter_map key \"" + k + "\": " + e.what()); + } + return temp; + } + + /** Set value. + * @tparam T Input type + * @param k Name of parameter to be set + * @param v Value. Will be converted to @c std::string. + * @return const reference to the @c std::string representation of @p v. + * If "" is returned, the conversion failed (or @p v was "" originally). + **/ + template + const std::string& set(const std::string& k, const T& v) + { + return _map[k] = str::A2S(v); + } + + /** Check if a given parameter is available. + * @param k The parameter name + * @return @b true if @p k is available + **/ + bool has_key(const std::string& k) const + { + return _map.count(k) > 0; + } + + private: + std::map _map; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/pointer_policy.h b/apf/apf/pointer_policy.h new file mode 100644 index 00000000..c1c036db --- /dev/null +++ b/apf/apf/pointer_policy.h @@ -0,0 +1,192 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// C policy (= pointer based) for MimoProcessor's audio_interface. + +#ifndef APF_POINTER_POLICY_H +#define APF_POINTER_POLICY_H + +#include // for assert() +#include "apf/parameter_map.h" +#include "apf/iterator.h" // for has_begin_and_end + +#ifndef APF_MIMOPROCESSOR_SAMPLE_TYPE +#define APF_MIMOPROCESSOR_SAMPLE_TYPE float +#endif +#ifndef APF_MIMOPROCESSOR_INTERFACE_POLICY +#define APF_MIMOPROCESSOR_INTERFACE_POLICY apf::pointer_policy +#endif + +namespace apf +{ + +template class pointer_policy; // no implementation, use ! + +/// @c interface_policy which uses plain pointers. +/// @see MimoProcessor +/// @ingroup apf_policies +template +class pointer_policy +{ + public: + using sample_type = T; + + class Input; + class Output; + + void audio_callback(int n, T* const* in, T* const* out); + + // for now, do nothing: + bool activate() const { return true; } + bool deactivate() const { return true; } + + int block_size() const { return _block_size; } + int sample_rate() const { return _sample_rate; } + + int in_channels() const { return _next_input_id; } + int out_channels() const { return _next_output_id; } + + protected: + explicit pointer_policy(const parameter_map& params = parameter_map()) + : _sample_rate(params.get("sample_rate")) + , _block_size(params.get("block_size")) + , _next_input_id(0) + , _next_output_id(0) + , _in(0) + , _out(0) + {} + + virtual ~pointer_policy() = default; + + private: + virtual void process() = 0; + + /// Generate next higher input ID. + /// @warning This function is \b not re-entrant! + int get_next_input_id() { return _next_input_id++; } + + /// @see get_next_input_id() + int get_next_output_id() { return _next_output_id++; } + + const int _sample_rate; + const int _block_size; + + int _next_input_id; + int _next_output_id; + T* const* _in; + T* const* _out; +}; + +/** This has to be called for each audio block. + * @attention You must make sure that there is enough memory available for input + * and output data. Inputs and outputs can be added, but @p in and @p out must + * be enlarged accordingly. + * @warning @p in and @p out can only grow bigger, if inputs/outputs are @em + * removed, the corresponding pointer of @p in/@p out must remain at its + * place! + * @param n block size + * @param in pointer to an array of pointers to input channels + * @param out pointer to an array of pointers to output channels + **/ +template +void +pointer_policy::audio_callback(int n, T* const* in, T* const* out) +{ + assert(n == this->block_size()); + (void)n; // avoid "unused parameter" warning + + _in = in; + _out = out; + this->process(); +} + +template +class pointer_policy::Input +{ + public: + using iterator = T const*; + + struct buffer_type : has_begin_and_end { friend class Input; }; + + void fetch_buffer() + { + this->buffer._begin = _parent._in[_id]; + this->buffer._end = this->buffer._begin + _parent.block_size(); + } + + buffer_type buffer; + + protected: + Input(pointer_policy& parent, const parameter_map&) + : _parent(parent) + , _id(_parent.get_next_input_id()) + {} + + ~Input() = default; + + private: + Input(const Input&); Input& operator=(const Input&); // deactivated + + pointer_policy& _parent; + const int _id; +}; + +template +class pointer_policy::Output +{ + public: + using iterator = T*; + + struct buffer_type : has_begin_and_end { friend class Output; }; + + void fetch_buffer() + { + this->buffer._begin = _parent._out[_id]; + this->buffer._end = this->buffer._begin + _parent.block_size(); + } + + buffer_type buffer; + + protected: + Output(pointer_policy& parent, const parameter_map&) + : _parent(parent) + , _id(_parent.get_next_output_id()) + {} + + ~Output() = default; + + private: + Output(const Output&); Output& operator=(const Output&); // deactivated + + pointer_policy& _parent; + const int _id; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/portaudio_policy.h b/apf/apf/portaudio_policy.h new file mode 100644 index 00000000..d9369ccc --- /dev/null +++ b/apf/apf/portaudio_policy.h @@ -0,0 +1,256 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// PortAudio policy for MimoProcessor's interface_policy. + +#ifndef APF_PORTAUDIO_POLICY_H +#define APF_PORTAUDIO_POLICY_H + +#include +#include // for assert() + +#include "apf/parameter_map.h" +#include "apf/stringtools.h" +#include "apf/iterator.h" // for has_begin_and_end + +#ifndef APF_MIMOPROCESSOR_INTERFACE_POLICY +#define APF_MIMOPROCESSOR_INTERFACE_POLICY apf::portaudio_policy +#endif + +namespace apf +{ + +/// @c interface_policy using PortAudio. +/// @see MimoProcessor +/// @ingroup apf_policies +class portaudio_policy +{ + public: + using sample_type = float; + class Input; + class Output; + + struct portaudio_error : std::runtime_error + { + portaudio_error(PaError error) + : std::runtime_error("PortAudio: "+std::string(Pa_GetErrorText(error))) + {} + }; + + // const PaStreamInfo * Pa_GetStreamInfo (PaStream *stream) + + static std::string device_info() + { + auto err = Pa_Initialize(); + if (err != paNoError) throw portaudio_error(err); + + std::string result; + for (int i = 0; i < Pa_GetDeviceCount(); ++i) + { + result = result + "Device ID " + str::A2S(i) + ": " + + Pa_GetDeviceInfo(i)->name + "\n"; + } + return result; + } + + bool activate() + { + // the original definition causes a warning message (old-style cast). + // 32bit float, non-interleaved + unsigned long sample_format = 0x00000001 | 0x80000000; + + auto inputParameters = PaStreamParameters(); + auto outputParameters = PaStreamParameters(); + + inputParameters.channelCount = _next_input_id; + inputParameters.device = _device_id; + inputParameters.hostApiSpecificStreamInfo = nullptr; + inputParameters.sampleFormat = sample_format; + inputParameters.suggestedLatency + = 0; //Pa_GetDeviceInfo(_device_id)->defaultLowInputLatency ; + + outputParameters.channelCount = _next_output_id; + outputParameters.device = _device_id; + outputParameters.hostApiSpecificStreamInfo = nullptr; + outputParameters.sampleFormat = sample_format; + outputParameters.suggestedLatency + = 0; //Pa_GetDeviceInfo(_device_id)->defaultLowOutputLatency ; + + auto err = Pa_OpenStream(&_stream, &inputParameters, &outputParameters + , _sample_rate, _block_size, 0, _pa_callback, this); + + if (err != paNoError) throw portaudio_error(err); + + err = Pa_StartStream(_stream); + if (err != paNoError) throw portaudio_error(err); + return true; + } + + bool deactivate() + { + auto err = Pa_StopStream(_stream); + if (err != paNoError) throw portaudio_error(err); + return true; + } + + int block_size() const { return _block_size; } + int sample_rate() const { return _sample_rate; } + + int in_channels() const { return _next_input_id; } + int out_channels() const { return _next_output_id; } + + // this is just temporary! + // TODO: find a clean solution regarding audio and thread policies + int get_real_time_priority() const { return -1; } + + protected: + /// Constructor + explicit portaudio_policy(const parameter_map& p = parameter_map()) + : _sample_rate(p.get("sample_rate")) + , _block_size(p.get("block_size")) + , _device_id(p.get("device_id", 0)) + , _next_input_id(0) + , _next_output_id(0) + { + auto err = Pa_Initialize(); + if (err != paNoError) throw portaudio_error(err); + } + + /// Protected destructor + ~portaudio_policy() + { + Pa_CloseStream(_stream); // ignore errors + Pa_Terminate(); // ignore errors + } + + private: + static int _pa_callback(const void *input, void *output + , unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo + , PaStreamCallbackFlags statusFlags, void *userData) + { + (void)timeInfo; // not used + (void)statusFlags; // not used + + return static_cast(userData)->pa_callback(input + , output, frameCount); + } + + int pa_callback(const void *input, void *output, unsigned long frameCount) + { + (void)frameCount; + assert(static_cast(frameCount) == this->block_size()); + + _in = static_cast(input); + _out = static_cast(output); + + this->process(); + + return paContinue; + // possible return values: paContinue, paComplete, paAbort + } + + virtual void process() = 0; + + /// Generate next higher input ID. + /// @warning This function is \b not re-entrant! + int get_next_input_id() { return _next_input_id++; } + + /// @see get_next_input_id() + int get_next_output_id() { return _next_output_id++; } + + const int _sample_rate; + const int _block_size; + const int _device_id; + + int _next_input_id; + int _next_output_id; + sample_type* const* _in; + sample_type* const* _out; + + PaStream* _stream; +}; + +class portaudio_policy::Input +{ + public: + using iterator = sample_type const*; + + struct buffer_type : has_begin_and_end { friend class Input; }; + + void fetch_buffer() + { + this->buffer._begin = _parent._in[_id]; + this->buffer._end = this->buffer._begin + _parent.block_size(); + } + + buffer_type buffer; + + protected: + Input(portaudio_policy& parent, const parameter_map&) + : _parent(parent) + , _id(_parent.get_next_input_id()) + {} + + ~Input() = default; + + private: + portaudio_policy& _parent; + const int _id; +}; + +class portaudio_policy::Output +{ + public: + using iterator = sample_type*; + + struct buffer_type : has_begin_and_end { friend class Output; }; + + void fetch_buffer() + { + this->buffer._begin = _parent._out[_id]; + this->buffer._end = this->buffer._begin + _parent.block_size(); + } + + buffer_type buffer; + + protected: + Output(portaudio_policy& parent, const parameter_map&) + : _parent(parent) + , _id(_parent.get_next_output_id()) + {} + + ~Output() = default; + + private: + portaudio_policy& _parent; + const int _id; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/posix_thread_policy.h b/apf/apf/posix_thread_policy.h new file mode 100644 index 00000000..80df6250 --- /dev/null +++ b/apf/apf/posix_thread_policy.h @@ -0,0 +1,256 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// POSIX thread policy class. + +#ifndef APF_POSIX_THREAD_POLICY_H +#define APF_POSIX_THREAD_POLICY_H + +#ifndef _REENTRANT +#error You need to compile with _REENTRANT defined since this uses threads! +#endif + +#ifndef APF_MIMOPROCESSOR_THREAD_POLICY +#define APF_MIMOPROCESSOR_THREAD_POLICY apf::posix_thread_policy +#endif + +// Unnamed semaphores are not implemented on Mac OS X, so we use named +// semaphores with an auto-generated name. +// That's a hack. +// But it was easier than to use some OSX-specific stuff. +// If you want to use proper unnamed semaphores, define APF_UNNAMED_SEMAPHORES +// TODO: proper synchronisation for OSX, go back to unnamed for Linux. +#ifndef APF_UNNAMED_SEMAPHORES +#define APF_PSEUDO_UNNAMED_SEMAPHORES +#endif + +#include // for std::runtime_error +#include // for std::strerror() +#include +#include +#include +#include // for usleep() + +#ifdef APF_PSEUDO_UNNAMED_SEMAPHORES +#include // for O_CREAT, O_EXCL +#include "apf/stringtools.h" // for apf::str::A2S() +#endif + +#include "apf/misc.h" // for NonCopyable + +namespace apf +{ + +/// @c thread_policy using the POSIX thread library. +/// @see MimoProcessor +/// @ingroup apf_policies +class posix_thread_policy +{ + public: + using useconds_type = useconds_t; + + template class ScopedThread; + template class DetachedThread; + class Lock; // TODO: read-write lock? + class Semaphore; + + protected: + posix_thread_policy() = default; ///< Protected ctor. + ~posix_thread_policy() = default; ///< Protected dtor. + + private: + class ThreadBase; +}; + +class posix_thread_policy::ThreadBase +{ + public: + using native_handle_type = pthread_t; + + void create(void* (*f)(void*), void* data) + { + if (pthread_create(&_thread_id, 0, f, data)) + { + throw std::runtime_error("Can't create thread!"); + } + } + + bool join() { return !pthread_join(_thread_id, 0); } + + native_handle_type native_handle() const { return _thread_id; } + + protected: + ThreadBase() = default; + ~ThreadBase() = default; + + private: + native_handle_type _thread_id; +}; + +template +class posix_thread_policy::ScopedThread : public ThreadBase, NonCopyable +{ + public: + ScopedThread(F f, useconds_type usleeptime) + : _kill_thread(false) + , _function(f) + , _usleeptime(usleeptime) + { + this->create(&_thread_aux, this); + } + + ~ScopedThread() + { + _kill_thread = true; + this->join(); + } + + private: + static void* _thread_aux(void *arg) + { + static_cast(arg)->_thread(); + return nullptr; + } + + void _thread() + { + while (!_kill_thread) + { + _function(); + usleep(_usleeptime); + } + } + + volatile bool _kill_thread; + F _function; + useconds_type _usleeptime; +}; + +template +class posix_thread_policy::DetachedThread : public ThreadBase +{ + public: + explicit DetachedThread(F f) + : _function(f) + { + this->create(&_thread_aux, this); + pthread_detach(this->native_handle()); // return value is ignored! + } + + private: + static void* _thread_aux(void* arg) + { + static_cast(arg)->_thread(); + return nullptr; + } + + void _thread() + { + for (;;) + { + _function(); + } + } + + F _function; +}; + +/** Inner type Lock. + * Wrapper class for a mutex. + **/ +class posix_thread_policy::Lock : NonCopyable +{ + public: + // TODO: parameter: initial lock state? + Lock() + { + if (pthread_mutex_init(&_lock, nullptr)) + { + throw std::runtime_error("Can't init mutex. (impossible !!!)"); + } + } + + // TODO: change return type to bool? + int lock() { return pthread_mutex_lock( &_lock); } + int unlock() { return pthread_mutex_unlock(&_lock); } + + // TODO: trylock? + + private: + pthread_mutex_t _lock; +}; + +class posix_thread_policy::Semaphore : NonCopyable +{ + public: + using value_type = unsigned int; + + explicit Semaphore(value_type value = 0) +#ifdef APF_PSEUDO_UNNAMED_SEMAPHORES + // Create a unique dummy name from object pointer + : _name("/apf_" + apf::str::A2S(this)) + , _sem_ptr(sem_open(_name.c_str(), O_CREAT | O_EXCL, 0600, value)) + { + if (!_sem_ptr) +#else + : _sem_ptr(&_semaphore) + { + if (sem_init(_sem_ptr, 0, value)) +#endif + { + throw std::runtime_error("Error initializing Semaphore! (" + + std::string(std::strerror(errno)) + ")"); + } + } + + Semaphore(Semaphore&&) = default; + + ~Semaphore() + { +#ifdef APF_PSEUDO_UNNAMED_SEMAPHORES + sem_unlink(_name.c_str()); // Only for named semaphores +#else + sem_destroy(_sem_ptr); // Only for unnamed semaphores +#endif + } + + bool post() { return sem_post(_sem_ptr) == 0; } + bool wait() { return sem_wait(_sem_ptr) == 0; } + + private: +#ifdef APF_PSEUDO_UNNAMED_SEMAPHORES + const std::string _name; +#else + sem_t _semaphore; +#endif + sem_t* const _sem_ptr; +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/rtlist.h b/apf/apf/rtlist.h new file mode 100644 index 00000000..e3caed7e --- /dev/null +++ b/apf/apf/rtlist.h @@ -0,0 +1,282 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// A (under certain circumstances) realtime-safe list. + +#ifndef APF_RTLIST_H +#define APF_RTLIST_H + +#include +#include // for std::find() + +#include "apf/commandqueue.h" + +namespace apf +{ + +template class RtList; // no implementation, use ! + +/** A list for realtime access and non-realtime modification. + * It is normally used by a realtime thread and a non-realtime thread at the + * same time. + * + * The list is created and modified (using add(), rem(), ...) by the + * non-realtime thread. These functions are not safe for multiple non-realtime + * threads; access has to be locked in this case. + * + * Before the realtime thread can access the list elements, it has to call + * CommandQueue::process_commands() to synchronize. + * + * TODO: more information about which functions are allowed in which thread. + **/ +template +class RtList : NonCopyable +{ + public: + using list_t = typename std::list; + using value_type = typename list_t::value_type; + using size_type = typename list_t::size_type; + using iterator = typename list_t::iterator; + using const_iterator = typename list_t::const_iterator; + + class AddCommand; // no implementation, use ! + class RemCommand; // no implementation, use ! + class ClearCommand; // no implementation, use ! + + // Default constructor is not allowed! + + /// Constructor. + /// @param fifo the CommandQueue + explicit RtList(CommandQueue& fifo) + : _fifo(fifo) + {} + + /// Destructor. + /// Elements can be removed - via rem() or clear() - before + /// the destructor is called. But if not, they are deleted here. + ~RtList() + { + for (auto& delinquent: _the_actual_list) delete delinquent; + _the_actual_list.clear(); + } + + /// Add an element to the list. + /// @param item Pointer to the list item + /// @return the same pointer + /// @note Ownership is passed to the list! + template + X* add(X* item) + { + _fifo.push(new AddCommand(_the_actual_list, item)); + return item; + } + + /// Add a range of elements to the list. + /// @param first Begin of range to be added + /// @param last End of range to be added + /// @note Ownership is passed to the list! + template + void add(ForwardIterator first, ForwardIterator last) + { + _fifo.push(new AddCommand(_the_actual_list, first, last)); + } + + /// Remove an element from the list. + void rem(T* to_rem) + { + _fifo.push(new RemCommand(_the_actual_list, to_rem)); + } + + /// Remove a range of elements from the list. + /// @param first Iterator to the first item + /// @param last Past-the-end iterator + template + void rem(ForwardIterator first, ForwardIterator last) + { + _fifo.push(new RemCommand(_the_actual_list, first, last)); + } + + /// Remove all elements from the list. + void clear() + { + _fifo.push(new ClearCommand(_the_actual_list)); + } + + /// Splice another RtList into the RtList. @see @c std::list::splice() + void splice(iterator position, RtList& x) + { + assert(&_fifo == &x._fifo); + _the_actual_list.splice(position, x._the_actual_list); + } + + /// Splice a part of another RtList into the RtList. + void splice(iterator position, RtList& x, iterator first, iterator last) + { + assert(&_fifo == &x._fifo); + _the_actual_list.splice(position, x._the_actual_list, first, last); + } + + ///@{ @name Functions to be called from the realtime thread + iterator begin() { return _the_actual_list.begin(); } + const_iterator begin() const { return _the_actual_list.begin(); } + iterator end() { return _the_actual_list.end(); } + const_iterator end() const { return _the_actual_list.end(); } + bool empty() const { return _the_actual_list.empty(); } + size_type size() const { return _the_actual_list.size(); } + ///@} + + private: + CommandQueue& _fifo; + list_t _the_actual_list; +}; + +/// Command to add an element to a list. +template +class RtList::AddCommand : public CommandQueue::Command +{ + public: + /// Constructor to add a single item. + /// @param dst_list List to which the element will be added + /// @param element Pointer to the element that will be added + AddCommand(list_t& dst_list, T* element) + : _splice_list(1, element) // make list with one element + , _dst_list(dst_list) + { + assert(element != nullptr); + } + + /// Constructor to add a bunch of items at once. + /// @param dst_list List to which the elements will be added + /// @param first Iterator to the first item to be added + /// @param last Past-the-end iterator + template + AddCommand(list_t& dst_list, InputIterator first, InputIterator last) + : _splice_list(first, last) + , _dst_list(dst_list) + {} + + virtual void execute() + { + _dst_list.splice(_dst_list.end(), _splice_list); + } + + // Empty function, because no cleanup is necessary + virtual void cleanup() {} + + private: + list_t _splice_list; // List of elements to be added + list_t& _dst_list; // Destination list +}; + +/// Command to remove an element from a list. +template +class RtList::RemCommand : public CommandQueue::Command +{ + public: + /// Constructor to remove a single item. + /// @param dst_list List from which the item will be removed + /// @param delinquent Pointer to the item which will be removed + RemCommand(list_t& dst_list, T* delinquent) + : _dst_list(dst_list) + , _delinquents(1, delinquent) + {} + + /// Constructor to remove a bunch of items at once. + /// @param dst_list List from which the elements will be removed + /// @param first Iterator to first item to be removed + /// @param last Past-the-end iterator + template + RemCommand(list_t& dst_list, InputIterator first, InputIterator last) + : _dst_list(dst_list) + , _delinquents(first, last) + {} + + /// The actual implementation of the command. + /// @throw std::logic_error if item(s) is/are not found + virtual void execute() + { + for (auto& i: _delinquents) + { + auto delinquent = std::find(_dst_list.begin(), _dst_list.end(), i); + if (delinquent != _dst_list.end()) + { + // Note: destruction order is reverse + _splice_list.splice(_splice_list.begin(), _dst_list, delinquent); + } + else + { + throw std::logic_error("RemCommand: Item not found!"); + } + } + } + + // this might be dangerous/unexpected. + // but i would need a synchronized Command, which waited + // for the operation to complete, otherwise. + virtual void cleanup() + { + for (auto& delinquent: _splice_list) delete delinquent; + _splice_list.clear(); + } + + private: + list_t _splice_list; // List of elements to be removed + list_t& _dst_list; // Destination list + list_t _delinquents; // Temporary list of elements to be removed +}; + +/// Command to remove all elements from a list. +template +class RtList::ClearCommand : public CommandQueue::Command +{ + public: + /// Constructor. + /// @param dst_list List from which all elements will be removed + ClearCommand(list_t& dst_list) + : _dst_list(dst_list) + {} + + virtual void execute() + { + _delinquents.swap(_dst_list); + } + + virtual void cleanup() + { + for (auto& delinquent: _delinquents) delete delinquent; + _delinquents.clear(); + } + + private: + list_t _delinquents; ///< List of elements to be removed + list_t& _dst_list; ///< Destination list +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/shareddata.h b/apf/apf/shareddata.h new file mode 100644 index 00000000..5273a345 --- /dev/null +++ b/apf/apf/shareddata.h @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Shared data. + +#ifndef APF_SHAREDDATA_H +#define APF_SHAREDDATA_H + +#include "apf/commandqueue.h" + +namespace apf +{ + +template +class SharedData +{ + public: + class SetCommand; + + explicit SharedData(CommandQueue& fifo, const X& def = X()) + : _fifo(fifo) + , _data(def) + { + // TODO: use reserve() for std::strings and std::vectors? using traits? + // the size can be given as third (optional) argument. + // see src/scene.h for container_traits. + // ... or maybe use swap() instead of assignment? + } + + /// Get contained data. Use this if the conversion operator cannot be used. + const X& get() const { return _data; } + + operator const X&() const { return this->get(); } + + void operator=(const X& rhs) + { + _fifo.push(new SetCommand(&_data, rhs)); + } + + private: + CommandQueue& _fifo; + X _data; +}; + +template +class SharedData::SetCommand : public CommandQueue::Command +{ + public: + SetCommand(X* pointer, const X& data) + : _pointer(pointer) + , _data(data) + { + assert(pointer != nullptr); + } + + private: + virtual void execute() { *_pointer = _data; } + virtual void cleanup() {} + + X* _pointer; + X _data; ///< copy of data! +}; + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/sndfiletools.h b/apf/apf/sndfiletools.h new file mode 100644 index 00000000..ec570e3e --- /dev/null +++ b/apf/apf/sndfiletools.h @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Some tools for the use with libsndfile. + +#ifndef APF_SNDFILETOOLS_H +#define APF_SNDFILETOOLS_H + +#include // C++ bindings for libsndfile + +#include "apf/stringtools.h" + +namespace apf +{ + +/** Load sound file, throw exception if something's wrong + * @param name file name + * @param sample_rate expected sample rate + * @param channels expected number of channels + * @throw std::logic_error whenever something is wrong + **/ +inline SndfileHandle load_sndfile(const std::string& name, size_t sample_rate + , size_t channels) +{ + // TODO: argument for read/write? + + if (name == "") + { + throw std::logic_error("apf::load_sndfile(): Empty file name!"); + } + + auto handle = SndfileHandle(name, SFM_READ); + +#if 0 + // rawHandle() is available since libsndfile version 1.0.24 + if (!handle.rawHandle()) +#else + if (!handle.channels()) +#endif + { + throw std::logic_error( + "apf::load_sndfile(): \"" + name + "\" couldn't be loaded!"); + } + + if (sample_rate) + { + const size_t true_sample_rate = handle.samplerate(); + if (sample_rate != true_sample_rate) + { + throw std::logic_error("apf::load_sndfile(): \"" + name + + "\" has sample rate " + str::A2S(true_sample_rate) + " instead of " + + str::A2S(sample_rate) + "!"); + } + } + + if (channels) + { + const size_t true_channels = handle.channels(); + + if (channels != true_channels) + { + throw std::logic_error("apf::load_sndfile(): \"" + name + "\" has " + + str::A2S(true_channels) + " channels instead of " + + str::A2S(channels) + "!"); + } + } + + return handle; +} + +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/apf/stopwatch.h b/apf/apf/stopwatch.h new file mode 100644 index 00000000..dda95881 --- /dev/null +++ b/apf/apf/stopwatch.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// A simple stopwatch + +// TODO: check C++11 timing tools: +// http://bstamour.ca/2012/05/13/timing-functions-with-chrono/ +// http://solarianprogrammer.com/2012/10/14/cpp-11-timing-code-performance/ +// http://ideone.com/clone/SCRI6 +// http://kjellkod.wordpress.com/2012/02/06/exploring-c11-part-1-time/ + +#include +#include + +namespace apf +{ + +/// A simple stopwatch +class StopWatch +{ + public: + StopWatch(const std::string& name = "this activity") : + _start(std::clock()), + _name(name) + {} + + ~StopWatch() + { + clock_t total = std::clock() - _start; + std::cout << _name << " took " << double(total)/CLOCKS_PER_SEC + << " seconds." << std::endl; + } + + private: + std::clock_t _start; + std::string _name; +}; + +} // namespace apf + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/apf/stringtools.h b/apf/apf/stringtools.h new file mode 100644 index 00000000..2f73c544 --- /dev/null +++ b/apf/apf/stringtools.h @@ -0,0 +1,440 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +/// @file +/// Helper functions for string conversion. + +#ifndef APF_STRINGTOOLS_H +#define APF_STRINGTOOLS_H + +#include +#include +#include // for std::invalid_argument + +namespace apf +{ +/// Helper functions for string manipulation +namespace str +{ + +/** Converter "Anything to String". + * Converts a numeric value to a @c std::string. + * A boolean value is converted to either @c "true" or @c "false". + * @tparam T given input type + * @param input anything you want to convert to a @c std::string + * @return If @c std::stringstream could handle it, a @c std::string + * representation of @a input, if something went wrong, an empty string. + **/ +template +std::string A2S(const T& input) +{ + std::ostringstream converter; + converter << std::boolalpha << input; + return converter.str(); +} + +/** Convert a stream to a given type. + * If the item of interest is followed by anything non-whitespace, the + * conversion is invalid and @b false is returned. + * @tparam out_T desired output type + * @param[in,out] input input stream + * @param[out] output output + * @return @b true on success + * @note If @b false is returned, @a output is unchanged. + **/ +template +inline bool convert(std::istream& input, out_T& output) +{ + auto result = out_T(); + input >> result >> std::ws; + if (!input.fail() && input.eof()) + { + output = result; + return true; + } + return false; +} + +/** This is a overloaded function for boolean output. + * @see convert() + * @param[in,out] input input stream + * @param[out] output output (boolean) + * @return @b true on success + * @note If @b false is returned, @a output is unchanged. + **/ +inline bool convert(std::istream& input, bool& output) +{ + bool result; + // first try: if input == "1" or "0": + input >> result >> std::ws; + if (input.fail()) + { + input.clear(); // clear error flags + input.seekg(0); // go back to the beginning of the stream + // second try: if input == "true" or "false": + input >> std::boolalpha >> result >> std::ws; + } + if (!input.fail() && input.eof()) + { + output = result; + return true; + } + return false; +} + +/** Converter "String to Anything". + * Convert a string (in this case meant as an array of a certain character + * type) to a given numeric type (or, if you insist on it, to a + * @c std::string, in which case it would not be very efficient). + * Also, convert @c "1", @c "true", @c "0" and @c "false" to the respective + * boolean values. + * @tparam char_T character type of the input array + * @tparam out_T desired output type (must have an input stream operator!) + * @param input pointer to a zero-terminated string of any character type. + * @param[out] output result converted to the desired type. + * @return @b true on success + * @note If @b false is returned, @a output is unchanged. + **/ +template +bool S2A(const char_T* input, out_T& output) +{ + std::basic_istringstream converter(input); + return convert(converter, output); +} + +/** Overloaded function for a STL-like string type. + * @see S2A() + * @tparam in_T input string type (e.g. @c std::string) + * @tparam char_T character type of the input string (e.g. @c char) + * @tparam traits traits class for the string type @p in_T + * @tparam Allocator allocator for the string type @p in_T + * @tparam out_T desired output type (must have an input stream operator!) + * @param input a string object like @c std::string or @c std::wstring or ... + * @param[out] output result converted to the desired type. + * @return @b true on success + * @note If @b false is returned, @a output is unchanged. + * @note This uses a template template to allow any input type that has + * the same structure as @c std::string + **/ +template class in_T, + typename char_T, typename traits, typename Allocator, typename out_T> +bool S2A(const in_T& input, out_T& output) +{ + std::basic_istringstream converter(input); + return convert(converter, output); +} + +/** Converter "String to Return Value". + * Convert a string (either an array of a certain character type or an + * STL-style string class) to a given numeric type and return the result. + * @tparam int_T string type + * @tparam out_T desired output type (must have an input stream operator!) + * @param input string to be converted. + * @param def default value. + * @return @p input converted to the desired type. If conversion failed, the + * default value @p def is returned. + * @warning If the result is equal to the default value @p def, there is no + * way to know if the conversion was successful, sorry! You have to use + * S2A() if you want to make sure. + * @see S2A() + **/ +template +out_T S2RV(const in_T& input, out_T def) +{ + S2A(input, def); // ignore return value + return def; +} + +/** Throwing version of S2RV(). + * @tparam int_T string type + * @tparam out_T desired output type (must have an input stream operator!) + * @param input string to be converted. + * @return @p input converted to the desired type. If conversion failed, an + * exception is thrown. + * @throw std::invalid_argument if @p input cannot be converted to @p out_T. + **/ +template +out_T S2RV(const in_T& input) +{ + auto result = out_T(); + if (!S2A(input, result)) + { + throw std::invalid_argument( + "S2RV(): Couldn't convert \"" + S2RV(input, std::string()) + "\"!"); + } + return result; +} + +/** Remove a specified number of characters from a stream and convert them to + * a numeric type. + * If the stream flag @c skipws is set, leading whitespace is removed first. + * @tparam digits number of digits to read. The rest of the template + * parameters are determined automatically. + * @tparam char_T character type of the stream + * @tparam traits traits class for @c std::basic_istream + * @tparam out_T desired (numerical) output type + * @param[in,out] input input stream + * @param[out] output resulting number + * @return a reference to the modified stream. + * @attention You have to check the returned stream for + * @c std::ios_base::failbit to know if the conversion was successful. + **/ +template +std::basic_istream& +convert_chars(std::basic_istream& input, out_T& output) +{ + // if an error bit is set on the input, just return without doing anything: + if (input.fail()) return input; + // skip whitespace if std::skipws is set + if (input.flags() & std::ios_base::skipws) input >> std::ws; + + char_T ch[digits]; + if (!input.read(ch, digits)) return input; // error bits are set! + + out_T factor = 1, result = 0; + for (int i = digits - 1; i >= 0; --i) + { + // only numbers are allowed: + if (ch[i] < input.widen('0') || ch[i] > input.widen('9')) + { + input.setstate(std::ios_base::failbit); + return input; // error bits are set! + } + // character type is implicitly cast to out_T + result += (ch[i] - '0') * factor; + factor *= 10; + } + output = result; + return input; +} + +/** Remove a character from a stream and check if it is the one given as + * parameter. + * If the stream flag @c skipws is set, leading whitespace is removed first. + * @tparam char_T character type of the stream (also used for the output) + * @tparam traits traits class for @c std::basic_istream + * @param[in,out] input input stream + * @param character character to remove + * @return a reference to the modified stream. + * @attention You have to check the returned stream for + * @c std::ios_base::failbit to know if the conversion was successful. + **/ +template +std::basic_istream& +remove_char(std::basic_istream& input, const char_T character) +{ + // if an error bit is set on the input, just return without doing anything: + if (input.fail()) return input; + // skip whitespace if std::skipws is set + if (input.flags() & std::ios_base::skipws) input >> std::ws; + + char_T ch; + if (input.get(ch) && (ch != character)) + { + input.setstate(std::ios_base::failbit); + } + return input; +} + +/** Remove a colon from an input stream. + * This function is just a convenient shortcut for + * remove_char(stream, ':'). + * Contrary to remove_char(), this can be used as a stream modifier like this: + * @code + * int i; float f; + * my_stream >> i >> remove_colon >> f; + * @endcode + * If the stream flag @c skipws is set, leading whitespace is removed first. + * @tparam char_T character type of the stream + * @tparam traits traits class for @c std::basic_istream + * @param[in,out] input input stream + * @return a reference to the modified stream. + * @attention You have to check the returned stream for + * @c std::ios_base::failbit to know if there actually was a colon and that + * is was successfully removed. + **/ +template +std::basic_istream& +remove_colon(std::basic_istream& input) +{ + remove_char(input, input.widen(':')); + return input; +} + +/** Convert time string to numeric value in seconds. + * @a input can be in format @c "h:mm:ss.x" or "xx.x h|min|s|ms" or + * just in seconds. Decimals and hours are optional. Time can also be + * negative. Multiple whitespace is allowed before and after. + * See http://www.w3.org/TR/SMIL2/smil-timing.html#Timing-ClockValueSyntax + * for the similar SMIL time syntax. + * @tparam in_T input string type (e.g. @c std::string) + * @tparam char_T character type of the input string (e.g. @c char) + * @tparam traits traits class for the string type @p in_T + * @tparam Allocator allocator for the string type @p in_T + * @tparam out_T desired output type + * @param input time string (similar to SMIL format) + * @param[out] output numeric result in seconds. This can be either of an + * integer or a floating point type. Conversion to an integer only works if + * the resulting value in seconds is a whole number. + * @return @b true if conversion was successful. + * @since r404 + **/ +template class in_T, + typename char_T, typename traits, typename Allocator, typename out_T> +bool string2time(const in_T& input, out_T& output) +{ + // first of all, check if there are 0, 1 or 2 colons: + char colons = 0; + for (size_t found = 0 + ; (found = input.find(':', found + 1)) != std::string::npos + ; ++colons) {} + + std::basic_istringstream iss(input); + out_T seconds = 0; // initialisation is needed for the case (colons == 1)! + + if (colons == 0) + { + // no colons, but maybe suffixes like "s", "min", "h" or "ms" + out_T number; + if ((iss >> number >> std::ws).fail()) return false; + if (iss.eof()) // that's everything, no suffixes! + { + seconds = number; + } + else // still something left ... + { + auto the_rest = std::basic_string(); + // read the rest and remove following whitespace (see below why) + if ((iss >> the_rest >> std::ws).fail()) return false; + // now we check if some garbage is left after the time-string. + // whitespace was removed in the previous line. + if (!iss.eof()) return false; + + // now check for possible suffixes: + if (the_rest == "h") seconds = number * 60 * 60; + else if (the_rest == "min") seconds = number * 60; + else if (the_rest == "s") seconds = number; + else if (the_rest == "ms") + { + // check if milliseconds can be represented by the type out_T: + // TODO: hopefully this isn't optimized away! + if (number / 1000 * 1000 != number) return false; + else seconds = number / 1000; + } + else return false; // no other suffix is allowed + } + } + else if (colons == 1 || colons == 2) + { + // check if there is a plus or minus sign + bool negative = false; + iss >> std::ws; // remove leading whitespace + if (iss.peek() == '-') + { + iss.ignore(); + negative = true; + } + // it doesn't matter if there is a '+' sign. + + long int hours = 0; + int minutes; // maximum: 59 + + if (colons == 1) + { + if ((iss >> minutes).fail()) return false; + // attention: the sign was already removed before! + if (minutes < 0 || minutes > 59) return false; + } + else if (colons == 2) + { + iss >> hours + >> std::noskipws // from now on, no whitespace is allowed + >> remove_colon; // read one character and check if it's a colon + convert_chars<2>(iss, minutes); // read minutes as two characters + if (iss.fail()) return false; + if (hours < 0) return false; // the sign was already removed before! + if (minutes > 59) return false; + } + + out_T whole_seconds; + out_T fraction_of_second(0); + + iss + >> std::noskipws // no whitespace is allowed + >> remove_colon; // read one character and check if it's a colon + convert_chars<2>(iss, whole_seconds); // read first two digits of seconds + if (iss.fail()) return false; + + if (whole_seconds > 59) return false; + + if (iss.peek() == '.') + { + if ((iss >> fraction_of_second).fail()) return false; + } + + if (!(iss >> std::ws).eof()) return false; // nothing else is allowed! + + auto the_rest = whole_seconds + fraction_of_second; + + // the seconds part must be smaller than 60 + if (the_rest >= 60) return false; + + if (negative) + { + hours = -hours; + minutes = -minutes; + the_rest = -the_rest; + } + + seconds = static_cast(hours * 60 * 60); + seconds += static_cast(minutes * 60); + seconds += the_rest; + } + else return false; // more than three colons are a deal-breaker! + + output = seconds; + return true; +} + +/** Overloaded function for a character array. + * @see string2time() + * @tparam char_T character type of the input array + * @tparam out_T desired output type + * @param input character array holding time string + * @param[out] output see above + * @return @b true if conversion was successful. + **/ +template +bool string2time(const char_T* input, out_T& output) +{ + return string2time(std::basic_string(input), output); +} + +} // namespace str +} // namespace apf + +#endif + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent +// vim:fdm=expr:foldexpr=getline(v\:lnum)=~'/\\*\\*'&&getline(v\:lnum)!~'\\*\\*/'?'a1'\:getline(v\:lnum)=~'\\*\\*/'&&getline(v\:lnum)!~'/\\*\\*'?'s1'\:'=' diff --git a/apf/doc/Doxyfile b/apf/doc/Doxyfile new file mode 100644 index 00000000..49d4284d --- /dev/null +++ b/apf/doc/Doxyfile @@ -0,0 +1,85 @@ +# Doxyfile 1.8.1.2 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for the Audio Processing Framework (APF) + +PROJECT_NAME = "Audio Processing Framework (APF)" +PROJECT_NUMBER = "version n/a" + +OUTPUT_DIRECTORY = . + +ALWAYS_DETAILED_SEC = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = .. +STRIP_FROM_INC_PATH = .. +JAVADOC_AUTOBRIEF = YES +TAB_SIZE = 2 +BUILTIN_STL_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = YES +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +HIDE_UNDOC_CLASSES = YES +HIDE_UNDOC_MEMBERS = NO +SORT_MEMBER_DOCS = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = NO +WARN_NO_PARAMDOC = NO +HIDE_SCOPE_NAMES = NO + +# files with macro definitions have to be read first +INPUT = ../apf/iterator.h . ../apf +FILE_PATTERNS = +RECURSIVE = YES +EXCLUDE = +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = + +EXAMPLE_PATH = ../examples +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO + +IMAGE_PATH = + +SOURCE_BROWSER = YES +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +ALPHABETICAL_INDEX = NO + +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_DYNAMIC_SECTIONS = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +SEARCHENGINE = YES + +GENERATE_LATEX = NO +#LATEX_OUTPUT = latex +#COMPACT_LATEX = NO +#PAPER_TYPE = a4wide +#PDF_HYPERLINKS = NO +#LATEX_SOURCE_CODE = NO + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +PREDEFINED = APF_DOXYGEN_HACK __SSE__ __SSE3__ +EXPAND_ONLY_PREDEF = NO +SKIP_FUNCTION_MACROS = YES +INCLUDE_PATH = + +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = NO +GROUP_GRAPHS = YES +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO + +CALL_GRAPH = NO +CALLER_GRAPH = NO + +DOT_TRANSPARENT = YES diff --git a/apf/doc/documentation.cpp b/apf/doc/documentation.cpp new file mode 100644 index 00000000..004e393e --- /dev/null +++ b/apf/doc/documentation.cpp @@ -0,0 +1,89 @@ +// THIS FILE CONTAINS SOME DOXYGEN DOCUMENTATION: + +/// @file +/// Some Doxygen documentation + +// MAIN PAGE + +/** @mainpage + +The Audio Processing Framework (APF) is a collection of C++ code which was +written in the context of multichannel audio applications. However, many modules +have a more generic scope. + +Website: http://AudioProcessingFramework.github.com + +This documentation: http://AudioProcessingFramework.github.com/apf-doc + +Development pages: http://github.com/AudioProcessingFramework/apf + +Blog: http://spatialaudio.net + +Components: + +- Multithreaded Multichannel Audio Processing Framework: @subpage MimoProcessor + +- C++ wrapper for the JACK Audio Connection Kit (http://jackaudio.org/): +apf::JackClient + +- Convolution engine using (uniformly) partitioned convolution: apf::conv + +- IIR second order filter (and cascade of filters): apf::BiQuad and apf::Cascade + +- Block-based delay line: apf::BlockDelayLine and its slightly awkward cousin + apf::NonCausalBlockDelayLine + +- Several @ref apf_iterators and @ref apf_iterator_macros + +- Some simple containers: apf::fixed_vector, apf::fixed_list, apf::fixed_matrix + +- Several different methods to prevent denormals: apf::dp + +- Some mathematical functions: apf::math + +- Functions for string manipulation (and conversion): apf::str + +- Parameter map with a few conversion functions: apf::parameter_map + +- Some tools for using the FFTW library: fftwtools.h + +- Simple stopwatch: apf::StopWatch + +- Miscellaneous stuff: misc.h + + +Copyright (c) 2012-2013 Institut für Nachrichtentechnik, Universität Rostock + +Copyright (c) 2006-2012 Quality & Usability Lab, + Deutsche Telekom Laboratories, TU Berlin +**/ + +// GROUPS/MODULES + +/** +@defgroup apf_policies Policies +Policies for apf::MimoProcessor + +New policies can be provided in user code! +**/ + +// EXAMPLES + +/** +@example simpleprocessor.h +@example audiofile_simpleprocessor.cpp +@example flext_simpleprocessor.cpp +@example jack_simpleprocessor.cpp +@example mex_simpleprocessor.cpp +@example jack_dynamic_inputs.cpp +@example jack_dynamic_outputs.cpp +@example jack_minimal.cpp +@example dummy_example.cpp +**/ + +// APF NAMESPACE + +/// @namespace apf +/// Audio Processing Framework + +// vim:textwidth=80 diff --git a/apf/doc/mimoprocessor.cpp b/apf/doc/mimoprocessor.cpp new file mode 100644 index 00000000..0b3619c8 --- /dev/null +++ b/apf/doc/mimoprocessor.cpp @@ -0,0 +1,74 @@ +// This file contains documentation about the MimoProcessor class. + +/// @file +/// Some documentation about apf::MimoProcessor + +/** @page MimoProcessor + +For class documentation see apf::MimoProcessor. + +@dontinclude dummy_example.cpp + +How to use the class MimoProcessor? + +You can see the full example there: examples/dummy_example.cpp + +First, we include the main header file ... + +@skipline mimoprocessor.h + +... then some policies (see @ref apf_policies for more information). + +@until dummy_thread_policy.h + +[class definition] + +@until { + +[input typedef] + +@until Input + +[MyIntermediateThing] + +@until { + +[MyIntermediateThing process] + +@until } + +@skipline }; + +[output class] + +@until { + +[output ctor] + +@until } + +[Process] + +@until } + +@skipline }; + +[ctor] + +@until { + +[inside ctor] + +@until } + +[Process] + +@until } + +[private members] + +@until }; + +And that's it! That's you own class based on apf::MimoProcessor! + +**/ diff --git a/apf/examples/Makefile b/apf/examples/Makefile new file mode 100644 index 00000000..b5f46f5f --- /dev/null +++ b/apf/examples/Makefile @@ -0,0 +1,111 @@ +JACK_STUFF += jack_simpleprocessor jack_dynamic_inputs jack_dynamic_outputs +JACK_STUFF += jack_minimal jack_matrix jack_query_thread +JACK_STUFF += jack_convolver +JACK_STUFF += jack_change_volume + +PORTAUDIO_STUFF += portaudio_simpleprocessor + +SNDFILE_STUFF += audiofile_simpleprocessor +SNDFILE_STUFF += jack_convolver + +FFTW_STUFF += jack_convolver + +EXECUTABLES += $(SNDFILE_STUFF) $(JACK_STUFF) $(PORTAUDIO_STUFF) $(FFTW_STUFF) +EXECUTABLES += dummy_example + +MEX_FILES = mex_simpleprocessor.mex + +OPT ?= -O3 + +CXXFLAGS += $(OPT) +CXXFLAGS += -g + +CXXFLAGS += -std=c++11 + +LDLIBS += -lpthread + +# show all warnings +CXXFLAGS += -Wall -Wextra +CXXFLAGS += -pedantic +# warnings are errors +CXXFLAGS += -pedantic-errors +CXXFLAGS += -Werror +# even more warnings: +CXXFLAGS += -Wpointer-arith +CXXFLAGS += -Wcast-align +CXXFLAGS += -Wwrite-strings +CXXFLAGS += -Wredundant-decls +CXXFLAGS += -Wshadow +CXXFLAGS += -Wold-style-cast +CXXFLAGS += -Wlong-long +CXXFLAGS += -Wconversion +#CXXFLAGS += -Winline +#CXXFLAGS += -Weffc++ + +CPPFLAGS += -I.. -D_REENTRANT + +#### no more setting below here #### + +FLEXTPATH ?= /usr/local/src/flext + +# without this, intermediate .o files are generated: +.SUFFIXES: +.SUFFIXES: .cpp .o + +# this adds (very slow) runtime checks for many STL functions: +debug: CPPFLAGS += -D_GLIBCXX_DEBUG +debug: all + +no-debug: CPPFLAGS += -DNDEBUG +no-debug: all + +all: $(EXECUTABLES) + +.PHONY: debug no-debug all + +$(JACK_STUFF): LDLIBS += -ljack +$(JACK_STUFF): CPPFLAGS += -DAPF_JACK_POLICY_DEBUG + +$(SNDFILE_STUFF): LDLIBS += -lsndfile + +$(PORTAUDIO_STUFF): LDLIBS += -lportaudio + +$(FFTW_STUFF): LDLIBS += -lfftw3f + +# For Puredata stuff see also package.txt + +pd: $(FLEXTPATH) package.txt + $(FLEXTPATH)/build.sh pd gcc + +.PHONY: pd + +$(FLEXTPATH): + @test -d $(FLEXTPATH) || \ + ( echo \"$(FLEXTPATH)\" not found! Set FLEXTPATH! ; false ) + +mex: CPPFLAGS += -DNDEBUG +mex: $(MEX_FILES) + +mex-double: CPPFLAGS += -DMEX_USE_DOUBLE +mex-double: mex + +.PHONY: mex mex-double + +%.mex: %.cpp + CXXFLAGS="$(CXXFLAGS)" mkoctfile --mex $< $(CPPFLAGS) + $(RM) $*.o + +clean: + $(RM) $(EXECUTABLES) $(OBJECTS) + test -d $(FLEXTPATH) && $(FLEXTPATH)/build.sh pd gcc clean || true + rmdir pd-linux || true + $(RM) $(MEX_FILES) + +.PHONY: clean + +# rebuild everything when Makefile changes +$(OBJECTS) $(EXECUTABLES) $(PD_EXTERNALS) $(MEX_FILES): Makefile + +DEPENDENCIES = $(EXECUTABLES) $(OBJECTS) + +include ../misc/Makefile.dependencies diff --git a/apf/examples/README b/apf/examples/README new file mode 100644 index 00000000..f25ccb20 --- /dev/null +++ b/apf/examples/README @@ -0,0 +1,11 @@ +Some examples of how the Audio Processing Framework (APF) can be used. + +Compilation: + +make + +make pd + +make mex + +make mex-double diff --git a/apf/examples/audiofile_simpleprocessor.cpp b/apf/examples/audiofile_simpleprocessor.cpp new file mode 100644 index 00000000..e04ab8c1 --- /dev/null +++ b/apf/examples/audiofile_simpleprocessor.cpp @@ -0,0 +1,66 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Usage example for the MimoProcessor reading from and writing to multichannel +// audio files. + +#include "apf/mimoprocessor_file_io.h" + +// First the policies ... +#include "apf/pointer_policy.h" +#include "apf/posix_thread_policy.h" +// ... then the SimpleProcessor. +#include "simpleprocessor.h" + +int main(int argc, char *argv[]) +{ + const size_t blocksize = 65536; + + if (argc < 4) + { + std::cerr << "Error: too few arguments!" << std::endl; + std::cout << "Usage: " << argv[0] + << " infilename outfilename outchannels [threads]" << std::endl; + return 42; + } + + std::string infilename = argv[1]; + std::string outfilename = argv[2]; + + apf::parameter_map e; + e.set("threads", (argc >= 5) ? argv[4] : "1"); + + SndfileHandle in(infilename, SFM_READ); + e.set("in_channels", in.channels()); + e.set("out_channels", apf::str::S2RV(argv[3])); + + e.set("block_size", blocksize); + e.set("sample_rate", in.samplerate()); + + SimpleProcessor engine(e); + + return mimoprocessor_file_io(engine, infilename , outfilename); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/dummy_example.cpp b/apf/examples/dummy_example.cpp new file mode 100644 index 00000000..22686865 --- /dev/null +++ b/apf/examples/dummy_example.cpp @@ -0,0 +1,91 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// This example is used in the Doxygen documentation to MimoProcessor. + +#include "apf/mimoprocessor.h" +#include "apf/pointer_policy.h" +#include "apf/dummy_thread_policy.h" + +class MyProcessor : public apf::MimoProcessor, apf::dummy_thread_policy> +{ + public: + using Input = MimoProcessorBase::DefaultInput; + + class MyIntermediateThing : public ProcessItem + { + public: + // you can create other classes and use them in their own RtList, as + // long as they are derived from ProcessItem and have a + // Process class publicly derived from ProcessItem::Process. + + // This can be facilitated with this macro call: + APF_PROCESS(MyIntermediateThing, ProcessItem) + { + // do your processing here! + } + }; + + class Output : public MimoProcessorBase::DefaultOutput + { + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + // this->buffer.begin() and this->buffer.end(): access to audio data + } + }; + + MyProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) + , _intermediate_list(_fifo) + { + this->add(); + _intermediate_list.add(new MyIntermediateThing()); + this->add(); + this->activate(); + } + + ~MyProcessor() { this->deactivate(); } + + APF_PROCESS(MyProcessor, MimoProcessorBase) + { + // input/output lists are processed automatically before/after this: + _process_list(_intermediate_list); + } + + private: + rtlist_t _intermediate_list; +}; + +int main() +{ + // For now, this does nothing, we just want it to compile ... +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/flext_simpleprocessor.cpp b/apf/examples/flext_simpleprocessor.cpp new file mode 100644 index 00000000..71730810 --- /dev/null +++ b/apf/examples/flext_simpleprocessor.cpp @@ -0,0 +1,140 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Example for the MimoProcessor running as a Pd/MaxMSP external using flext. +// +// Compile/install Pd external with +// $FLEXTPATH/build.sh pd gcc +// $FLEXTPATH/build.sh pd gcc install +// +// Clean up with +// $FLEXTPATH/build.sh pd gcc clean + +#include + +#define APF_MIMOPROCESSOR_SAMPLE_TYPE t_sample + +#include "apf/pointer_policy.h" +#include "apf/posix_thread_policy.h" + +#include "simpleprocessor.h" + +// check for appropriate flext version (CbSignal was introduced in 0.5.0) +#if !defined(FLEXT_VERSION) || (FLEXT_VERSION < 500) +#error You need at least flext version 0.5.0! +#endif + +namespace // anonymous +{ + // this function is only used in the constructor's initialization list + apf::parameter_map engine_params(int inputs, int outputs, int threads + , int block_size, int sample_rate) + { + apf::parameter_map temp; + temp.set("in_channels", inputs); + temp.set("out_channels", outputs); + temp.set("threads", threads); + temp.set("block_size", block_size); + temp.set("sample_rate", sample_rate); + return temp; + } +} + +class simpleprocessor: public flext_dsp +{ + FLEXT_HEADER_S(simpleprocessor, flext_dsp, setup) + + public: + simpleprocessor(int inputs, int outputs, int threads) + : _engine(engine_params(inputs, outputs, threads + , Blocksize(), Samplerate())) + { + AddInSignal(inputs); + AddOutSignal(outputs); + post("simpleprocessor~ constructor was called!"); + } + + private: + static void setup(t_classid c) + { + //FLEXT_CADDMETHOD(c, 0, _left_float); + + FLEXT_CADDMETHOD_(c, 0, "hello", _hello); + FLEXT_CADDMETHOD_I(c, 0, "hello", _hello_and_int); + + //FLEXT_CADDMETHOD(c, 2, _sym); // register method for all other symbols? + + FLEXT_CADDMETHOD_(c, 0, "help", _help); + + post("simpleprocessor~ was loaded for the first time!"); + } + + //void _left_float(float input) + //{ + // post("Receiving %.2f from left inlet.", input); + // //ToOutFloat(1, input); + //} + + // override signal function + virtual void CbSignal() + { + _engine.audio_callback(Blocksize(), InSig(), OutSig()); + } + + void _hello() + { + post("hello yourself!"); + } + + void _hello_and_int(int input) + { + post("hello %i!", input); + } + + //void _sym(t_symbol *s) + //{ + // post("symbol: %s", GetString(s)); + //} + + void _help() + { + post("%s - this is some useless help information.", thisName()); + } + + // FLEXT_CALLBACK_1(x, float) == FLEXT_CALLBACK_F(x) + //FLEXT_CALLBACK_F(_left_float); + + FLEXT_CALLBACK(_hello) + FLEXT_CALLBACK_I(_hello_and_int) + //FLEXT_CALLBACK_S(_sym) + + FLEXT_CALLBACK(_help) + + SimpleProcessor _engine; +}; + +// DSP external with 3 creation args: +FLEXT_NEW_DSP_3("simpleprocessor~", simpleprocessor, int, int, int) + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_change_volume.cpp b/apf/examples/jack_change_volume.cpp new file mode 100644 index 00000000..a3e72f4c --- /dev/null +++ b/apf/examples/jack_change_volume.cpp @@ -0,0 +1,247 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Example for crossfade and/or parameter interpolation (?) + +#include +#include // for assert() + +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::raised_cosine_fade, apf::Combine* +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/shareddata.h" +#include "apf/math.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + class Input; + class CombineFunction; + class Output; + + MyProcessor(); + ~MyProcessor() { this->deactivate(); } + + enum { CROSSFADE, INTERPOLATION } mode; + + apf::SharedData volume; + + private: + apf::raised_cosine_fade _fade; +}; + +class MyProcessor::Input : public MimoProcessorBase::DefaultInput +{ + public: + explicit Input(const Params& p) + : MimoProcessorBase::DefaultInput(p) + , weight(this->parent.volume) + {} + + APF_PROCESS(Input, MimoProcessorBase::DefaultInput) + { + // In real-life applications, this will be more complicated: + this->weight = this->parent.volume; + + assert(this->weight.exactly_one_assignment()); + } + + apf::BlockParameter weight; +}; + +class MyProcessor::CombineFunction +{ + public: + CombineFunction(size_t block_size) : _block_size(float(block_size)) {} + + apf::CombineChannelsResult::type select(const Input& in) + { + using namespace apf::CombineChannelsResult; + + _weight = in.weight; + _old_weight = in.weight.old(); + + if (_weight != _old_weight) + { + _interpolator.set(_old_weight, _weight, _block_size); + return change; + } + + if (_weight != 0.0f) return constant; + + return nothing; + } + + float operator()(float in) + { + return in * _weight; + } + + float operator()(float in, apf::fade_out_tag) + { + return in * _old_weight; + } + + float operator()(float in, float index) + { + return in * _interpolator(index); + } + + void update() + { + // This is called between fade-out and fade-in + } + + private: + const float _block_size; + float _weight, _old_weight; + apf::math::linear_interpolator _interpolator; +}; + +class MyProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combine_and_interpolate(this->parent.get_input_list(), *this) + , _combine_and_crossfade(this->parent.get_input_list(), *this + , this->parent._fade) + , _combine_function(this->parent.block_size()) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + switch (this->parent.mode) + { + case INTERPOLATION: + _combine_and_interpolate.process(_combine_function); + break; + + case CROSSFADE: + _combine_and_crossfade.process(_combine_function); + break; + } + } + + private: + apf::CombineChannelsInterpolation, Output> + _combine_and_interpolate; + apf::CombineChannelsCrossfade, Output + , apf::raised_cosine_fade> _combine_and_crossfade; + CombineFunction _combine_function; +}; + +MyProcessor::MyProcessor() + : MimoProcessorBase() + , mode(CROSSFADE) + , volume(_fifo, 1.0f) + , _fade(this->block_size()) +{ + // Let's create 2 inputs ... + this->add(); + this->add(); + + // ... and 1 output, OK? + this->add(); + + std::cout << "following keys are supported:\n" + " + to increment volume\n" + " - to decrement volume\n" + " 0 to mute\n" + " 1 to unmute\n" + " c to switch to crossfade mode\n" + " i to switch to interpolation mode\n" + " q to quit\n" + " press and hold the key to continuously toggle the mute state\n" + << std::endl; + std::cout << "current mode: crossfade" << std::endl; + + this->activate(); +} + +int main() +{ + MyProcessor processor; + + std::string input; + auto volume = 1.0f; + + for (;;) + { + std::cout << volume << std::endl; + processor.volume = volume; + + std::getline(std::cin, input); + if (input == "+") + { + volume = apf::math::dB2linear(apf::math::linear2dB(volume+0.01f) + 1); + } + else if (input == "-") + { + volume = apf::math::dB2linear(apf::math::linear2dB(volume) - 1); + } + else if (input == "0") + { + volume = 0.0f; + } + else if (input == "1") + { + volume = 1.0f; + } + else if (input == "") + { + if (volume > 0.0001f) + { + volume = 0.0f; + } + else + { + volume = 1.0f; + } + } + else if (input == "c") + { + processor.mode = MyProcessor::CROSSFADE; + std::cout << "current mode: crossfade" << std::endl; + } + else if (input == "i") + { + processor.mode = MyProcessor::INTERPOLATION; + std::cout << "current mode: interpolation" << std::endl; + } + else if (input == "q") + { + break; + } + else + { + std::cout << "What? Type q to quit!" << std::endl; + } + } +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_convolver.cpp b/apf/examples/jack_convolver.cpp new file mode 100644 index 00000000..ffe63d6f --- /dev/null +++ b/apf/examples/jack_convolver.cpp @@ -0,0 +1,182 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Example for the Convolver. + +#include +#include + +#include "apf/mimoprocessor.h" +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/shareddata.h" +#include "apf/convolver.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + using Input = MimoProcessorBase::DefaultInput; + + template + MyProcessor(In first, In last); + + ~MyProcessor() { this->deactivate(); } + + struct Output : MimoProcessorBase::DefaultOutput + { + Output(const Params& p) : MimoProcessorBase::DefaultOutput(p) {} + + // Deactivate process() function, fetch_buffer() is called earlier! + virtual void process() {} + }; + + APF_PROCESS(MyProcessor, MimoProcessorBase) + { + _convolver.add_block(_input->begin()); + + if (!_convolver.queues_empty()) _convolver.rotate_queues(); + + if (this->reverb != _old_reverb) + { + if (this->reverb) + { + _convolver.set_filter(_filter); + } + else + { + _convolver.set_filter(_dirac); + } + _old_reverb = this->reverb; + } + float* result = _convolver.convolve(); + + // This is necessary because _output is used before _output_list is + // processed: + _output->fetch_buffer(); + + std::copy(result, result + this->block_size(), _output->begin()); + } + + apf::SharedData reverb; + + private: + bool _old_reverb; + + Input* _input; + Output* _output; + + size_t _partitions; + + apf::conv::Filter _filter; + apf::conv::Convolver _convolver; + + apf::conv::Filter _dirac; +}; + +template +MyProcessor::MyProcessor(In first, In last) + : MimoProcessorBase() + , reverb(_fifo, true) + , _old_reverb(false) + , _filter(this->block_size(), first, last) + , _convolver(this->block_size(), _filter.partitions()) + , _dirac(this->block_size(), 1) +{ + // Load Dirac + float one = 1.0f; + _convolver.prepare_filter(&one, (&one)+1, _dirac); + + _input = this->add(); + _output = this->add(); + + std::cout << "Press to switch and q to quit" << std::endl; + this->activate(); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) + { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + SndfileHandle in(argv[1], SFM_READ); + + if (in.error()) throw std::runtime_error(in.strError()); + + if (in.channels() != 1) + { + throw std::runtime_error("Only mono files are supported!"); + } + + std::vector ir(in.frames()); + + if (in.readf(&ir[0], in.frames()) != in.frames()) + { + throw std::runtime_error("Couldn't load audio file!"); + } + + MyProcessor processor(ir.begin(), ir.end()); + + if (in.samplerate() != int(processor.sample_rate())) + { + throw std::runtime_error("Samplerate mismatch!"); + } + + std::string input; + bool reverb = true; + + for (;;) + { + std::getline(std::cin, input); + if (input == "") + { + if (reverb) + { + processor.reverb = false; + reverb = false; + std::cout << "filter off" << std::endl; + } + else + { + processor.reverb = true; + reverb = true; + std::cout << "filter on" << std::endl; + } + } + else if (input == "q") + { + break; + } + else + { + std::cout << "What? Type q to quit!" << std::endl; + } + } +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_dynamic_inputs.cpp b/apf/examples/jack_dynamic_inputs.cpp new file mode 100644 index 00000000..ba57123b --- /dev/null +++ b/apf/examples/jack_dynamic_inputs.cpp @@ -0,0 +1,117 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// A small example of the MimoProcessor with varying JACK input ports. +// This is a stand-alone program. + +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::CombineChannels +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + using Input = MimoProcessorBase::DefaultInput; + + class Output : public MimoProcessorBase::DefaultOutput + { + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combiner(this->parent.get_input_list(), *this) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + float weight = 1.0f/static_cast( + this->parent.get_input_list().size()); + _combiner.process(simple_predicate(weight)); + } + + private: + class simple_predicate + { + public: + explicit simple_predicate(float weight) : _weight(weight) {} + + // trivial, all inputs are used; no crossfade/interpolation + apf::CombineChannelsResult::type select(const Input&) + { + return apf::CombineChannelsResult::constant; + } + + float operator()(float in) { return in * _weight; } + + private: + float _weight; + }; + + apf::CombineChannels, Output> _combiner; + }; + + MyProcessor() + { + this->add(); + } +}; + +int main() +{ + int in_channels = 20; + + MyProcessor engine; + engine.activate(); + + sleep(2); + + std::vector inputs; + + for (int i = 1; i <= in_channels; ++i) + { + MyProcessor::Input::Params p; + p.set("id", i * 10); + inputs.push_back(engine.add(p)); + sleep(1); + } + + sleep(2); + + // remove the inputs one by one ... + while (inputs.begin() != inputs.end()) + { + engine.rem(inputs.front()); + engine.wait_for_rt_thread(); + inputs.erase(inputs.begin()); + sleep(1); + } + + sleep(2); + + engine.deactivate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_dynamic_outputs.cpp b/apf/examples/jack_dynamic_outputs.cpp new file mode 100644 index 00000000..da6a9a7d --- /dev/null +++ b/apf/examples/jack_dynamic_outputs.cpp @@ -0,0 +1,112 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// A small example of the MimoProcessor with varying JACK output ports. +// This is a stand-alone program. + +#include + +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::CombineChannelsCopy +#include "apf/jack_policy.h" +#include "apf/dummy_thread_policy.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + using Input = MimoProcessorBase::DefaultInput; + + class Output : public MimoProcessorBase::DefaultOutput + { + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combiner(this->parent.get_input_list(), *this) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + _combiner.process(select_all_inputs()); + } + + private: + struct select_all_inputs + { + apf::CombineChannelsResult::type select(const Input&) + { + return apf::CombineChannelsResult::constant; + } + }; + + apf::CombineChannelsCopy, Output> _combiner; + }; + + MyProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) + { + this->add(); + } +}; + +int main() +{ + int out_channels = 20; + + apf::parameter_map p; + p.set("threads", 1); + //p.set("threads", 2); // not allowed with dummy_thread_policy! + MyProcessor engine(p); + engine.activate(); + + sleep(2); + + std::vector outputs; + + for (int i = 1; i <= out_channels; ++i) + { + MyProcessor::Output::Params op; + op.set("id", i * 10); + outputs.push_back(engine.add(op)); + sleep(1); + } + + sleep(2); + + // remove the outputs one by one ... + while (outputs.begin() != outputs.end()) + { + engine.rem(outputs.front()); + engine.wait_for_rt_thread(); + outputs.erase(outputs.begin()); + sleep(1); + } + + sleep(2); + + engine.deactivate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_matrix.cpp b/apf/examples/jack_matrix.cpp new file mode 100644 index 00000000..9a6cf1c7 --- /dev/null +++ b/apf/examples/jack_matrix.cpp @@ -0,0 +1,307 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// A small (static) example of the MimoProcessor with the fixed_matrix class. +// This is a stand-alone program. + +#include +#include // for assert() + +#include "apf/mimoprocessor.h" +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/container.h" // for fixed_matrix + +class MatrixProcessor : public apf::MimoProcessor +{ + public: + using matrix_t = apf::fixed_matrix; + using channel_iterator = matrix_t::channel_iterator; + using slice_iterator = matrix_t::slice_iterator; + using Channel = matrix_t::Channel; + using Slice = matrix_t::Slice; + + using Input = MimoProcessorBase::DefaultInput; + + class m1_channel; + class m2_channel; + class m3_slice; + class Output; + + explicit MatrixProcessor(const apf::parameter_map& p); + + ~MatrixProcessor() + { + this->deactivate(); + _m3_list.clear(); + _m2_list.clear(); + _m1_list.clear(); + } + + APF_PROCESS(MatrixProcessor, MimoProcessorBase) + { + _process_list(_m1_list); + _process_list(_m2_list); + _process_list(_m3_list); + } + + private: + /// make sure blocksize is divisible by parts. + static int _get_parts(int x, int blocksize) + { + int parts = x; + while (blocksize % parts != 0) parts /= 2; + return parts; + } + + const int _channels, _blocksize, _parts, _part_length, _part_channels; + matrix_t _m1, _m2, _m3; + rtlist_t _m1_list, _m2_list, _m3_list; +}; + +class MatrixProcessor::m1_channel : public ProcessItem +{ + public: + struct Params + { + Params() : input(nullptr), part(0), part_size(0) {} + Channel channel; + const Input* input; + int part, part_size; + }; + + class Setup + { + public: + Setup(int parts, int part_length, const rtlist_proxy& input_list) + : _part(0) + , _parts(parts) + , _part_length(part_length) + , _input(input_list.begin()) + {} + + m1_channel* operator()(const Channel& channel) + { + Params p; + p.channel = channel; + p.input = &*_input; + p.part_size = _part_length; + p.part = _part; + + ++_part; + if (_part >= _parts) + { + _part = 0; + ++_input; + } + return new m1_channel(p); + } + + private: + int _part; + const int _parts, _part_length; + rtlist_proxy::iterator _input; + }; + + APF_PROCESS(m1_channel, ProcessItem) + { + assert(_input != nullptr); + auto begin = _input->begin() + _part * _part_size; + std::copy(begin, begin + _part_size, _channel.begin()); + } + + private: + m1_channel(const Params& p) + : _channel(p.channel) + , _input(p.input) + , _part(p.part) + , _part_size(p.part_size) + {} + + Channel _channel; + const Input* const _input; + const int _part, _part_size; +}; + +class MatrixProcessor::m2_channel : public ProcessItem +{ + public: + struct Params { Channel channel; Slice input; }; + + static m2_channel* create(const Channel& channel, const Slice& input) + { + Params temp; + temp.channel = channel; + temp.input = input; + return new m2_channel(temp); + } + + APF_PROCESS(m2_channel, ProcessItem) + { + std::copy(_input.begin(), _input.end(), _channel.begin()); + } + + private: + m2_channel(const Params& p) : _channel(p.channel) , _input(p.input) {} + + Channel _channel; + Slice _input; +}; + +class MatrixProcessor::m3_slice : public ProcessItem +{ + public: + struct Params { Slice slice; Channel input; }; + + static m3_slice* create(const Slice& slice, const Channel& input) + { + Params temp; + temp.slice = slice; + temp.input = input; + return new m3_slice(temp); + } + + APF_PROCESS(m3_slice, ProcessItem) + { + std::copy(_input.begin(), _input.end(), _slice.begin()); + } + + private: + m3_slice(const Params& p) : _slice(p.slice), _input(p.input) {} + + Slice _slice; + Channel _input; +}; + +class MatrixProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + struct Params : MimoProcessorBase::DefaultOutput::Params + { + std::list channel_list; + }; + + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _channel_list(p.channel_list) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + auto out = this->begin(); + + for (const auto& ch: _channel_list) + { + out = std::copy(ch.begin(), ch.end(), out); + } + assert(out = this->end()); + } + + private: + std::list _channel_list; +}; + +MatrixProcessor::MatrixProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) + , _channels(p.get("channels")) // if no channels -> exception! + , _blocksize(this->block_size()) + , _parts(_get_parts(16, _blocksize)) + , _part_length(_blocksize / _parts) + , _part_channels(_channels * _parts) + , _m1(_part_channels, _part_length) + , _m2(_part_length, _part_channels) + , _m3(_part_channels, _part_length) + , _m1_list(_fifo) + , _m2_list(_fifo) + , _m3_list(_fifo) +{ + std::cout << "channels: " << _channels << ", parts: " << _parts + << ", blocksize: " << _blocksize << std::endl; + + std::cout << "Creating Matrix with " + << _part_channels << " channels and " + << _part_length << " slices." << std::endl; + + // first, set parameters for all inputs ... + Input::Params ip; + for (int i = 1; i <= _channels; ++i) + { + ip.set("id", i); + this->add(ip); + } + + // m1: input channels are split up in more (and smaller) channels + + m1_channel::Setup m1_setup(_parts, _part_length, this->get_input_list()); + for (const auto& ch: _m1.channels) + { + _m1_list.add(m1_setup(ch)); + } + + // m2: reading slices from first matrix and writing to channels of second + // matrix (= transpose matrix) + + std::list m2_temp; + std::transform(_m2.channels.begin(), _m2.channels.end(), _m1.slices.begin() + , back_inserter(m2_temp), m2_channel::create); + _m2_list.add(m2_temp.begin(), m2_temp.end()); + + // m3: reading channels, writing slices + + std::list m3_temp; + std::transform(_m3.slices.begin(), _m3.slices.end(), _m2.channels.begin() + , back_inserter(m3_temp), m3_slice::create); + _m3_list.add(m3_temp.begin(), m3_temp.end()); + + // set parameters for all outputs ... + Output::Params op; + op.parent = this; + auto next_channel = _m3.channels.begin(); + for (int i = 1; i <= _channels; ++i) + { + op.set("id", i); + op.channel_list.clear(); + for (int j = 0; j < _parts; ++j) + { + op.channel_list.push_back(*next_channel++); + } + this->add(op); + } + + this->activate(); +} + +int main() +{ + apf::parameter_map p; + p.set("channels", 2); + //p.set("channels", 120); + p.set("threads", 2); + MatrixProcessor engine(p); + sleep(60); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_minimal.cpp b/apf/examples/jack_minimal.cpp new file mode 100644 index 00000000..0810b821 --- /dev/null +++ b/apf/examples/jack_minimal.cpp @@ -0,0 +1,83 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Minimalistic example for the MimoProcessor with JACK. + +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::CombineChannelsCopy +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + using Input = MimoProcessorBase::DefaultInput; + class Output; + + MyProcessor(); +}; + +class MyProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combiner(this->parent.get_input_list(), *this) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + _combiner.process(my_predicate()); + } + + private: + struct my_predicate + { + // trivial, all inputs are used + apf::CombineChannelsResult::type select(const Input&) + { + return apf::CombineChannelsResult::constant; + } + }; + + apf::CombineChannelsCopy, DefaultOutput> _combiner; +}; + +MyProcessor::MyProcessor() + : MimoProcessorBase() +{ + this->add(); + this->add(); +} + +int main() +{ + MyProcessor processor; + processor.activate(); + sleep(30); + processor.deactivate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_query_thread.cpp b/apf/examples/jack_query_thread.cpp new file mode 100644 index 00000000..661162fc --- /dev/null +++ b/apf/examples/jack_query_thread.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Example that shows how to get information out of the realtime thread(s). + +#include + +#include "apf/mimoprocessor.h" +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/shareddata.h" + +class MyProcessor : public apf::MimoProcessor +{ + public: + MyProcessor() + : MimoProcessorBase() + , ch(_fifo, '_') + , _query(*this) + , _query_thread(QueryThread(_query_fifo), 1000*1000 / this->block_size()) + {} + + // MyProcessor doesn't process anything, no Process struct needed + + void start_querying() + { + this->new_query(_query); + } + + apf::SharedData ch; + + private: + class my_query + { + public: + my_query(MyProcessor& parent) + : _parent(parent) + {} + + void query() + { + _ch = _parent.ch; + } + + void update() + { + std::cout << _ch << std::flush; + } + + private: + MyProcessor& _parent; + char _ch; + } _query; + + ScopedThread _query_thread; +}; + +int main() +{ + MyProcessor processor; + processor.activate(); + processor.start_querying(); + sleep(3); + processor.ch = '*'; + sleep(1); + processor.ch = '+'; + sleep(1); + processor.ch = '#'; + sleep(1); + processor.ch = '.'; + sleep(1); + processor.deactivate(); + std::cout << std::endl; +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/jack_simpleprocessor.cpp b/apf/examples/jack_simpleprocessor.cpp new file mode 100644 index 00000000..51912784 --- /dev/null +++ b/apf/examples/jack_simpleprocessor.cpp @@ -0,0 +1,71 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Usage example for the MimoProcessor with JACK. + +#include + +#include "apf/stringtools.h" + +// First the policies ... +#include "apf/jack_policy.h" +#include "apf/posix_thread_policy.h" +// ... then the SimpleProcessor. +#include "simpleprocessor.h" + +using apf::str::S2A; +using apf::str::A2S; + +int main(int argc, char *argv[]) +{ + if (argc < 4) + { + std::cerr << "Error: too few arguments!" << std::endl; + std::cout << "Usage: " << argv[0] + << " inchannels inportprefix outchannels [outportprefix]" << std::endl; + return 42; + } + + apf::parameter_map e; + e.set("name", "my_engine"); + e.set("threads", 2); + + e.set("in_channels", argv[1]); + e.set("in_port_prefix", argv[2]); + e.set("out_channels", argv[3]); + if (argc > 4) e.set("out_port_prefix", argv[4]); + else e.set("out_port_prefix", "system:playback_"); + + SimpleProcessor engine(e); + + sleep(2); + + SimpleProcessor::Input::Params p3; + p3.set("port_name", "another_port_just_for_fun"); + engine.add(p3); + + sleep(60); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/mex_simpleprocessor.cpp b/apf/examples/mex_simpleprocessor.cpp new file mode 100644 index 00000000..f2816057 --- /dev/null +++ b/apf/examples/mex_simpleprocessor.cpp @@ -0,0 +1,223 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Example for the MimoProcessor running as a Matlab (or GNU octave) MEX file. +// +// Compile for GNU Octave: mkoctfile --mex mex_simpleengine.cpp +// Compile for Matlab: not tested yet! +// +// Usage example: +// +// mex_simpleprocessor('init', 2, 3, 2, 4, 44100) +// x = mex_simpleprocessor('process', single(ones(4,2))) +// mex_simpleprocessor clear + +#include +#include +#include +#include // for std::unique_ptr + +#ifdef MEX_USE_DOUBLE +#define APF_MIMOPROCESSOR_SAMPLE_TYPE double +#else +#define APF_MIMOPROCESSOR_SAMPLE_TYPE float +#endif + +#include "apf/pointer_policy.h" +#include "apf/posix_thread_policy.h" + +#include "simpleprocessor.h" + +// The single entry-point for Matlab is the function mexFunction(), see below! + +using sample_type = SimpleProcessor::sample_type; + +// global variables holding the state +std::unique_ptr engine; +mwSize in_channels, out_channels, threads=1, block_size=64, sample_rate=44100; +std::vector inputs, outputs; + +void engine_init(int nrhs, const mxArray* prhs[]) +{ + if (nrhs < 2) + { + mexErrMsgTxt("At least 2 further parameters are needed for \"init\"!"); + } + if (nrhs > 0) + { + in_channels = static_cast(*mxGetPr(prhs[0])); + --nrhs; ++prhs; + } + if (nrhs > 0) + { + out_channels = static_cast(*mxGetPr(prhs[0])); + --nrhs; ++prhs; + } + if (nrhs > 0) + { + threads = static_cast(*mxGetPr(prhs[0])); + --nrhs; ++prhs; + } + if (nrhs > 0) + { + block_size = static_cast(*mxGetPr(prhs[0])); + --nrhs; ++prhs; + } + if (nrhs > 0) + { + sample_rate = static_cast(*mxGetPr(prhs[0])); + --nrhs; ++prhs; + } + if (nrhs > 0) + { + mexErrMsgTxt("Too many input arguments!"); + } + + mexPrintf("Starting SimpleProcessor with following settings:\n" + " * in channels: %d\n" + " * out channels: %d\n" + " * threads: %d\n" + " * block size: %d\n" + " * sample rate: %d\n" +#ifdef MEX_USE_DOUBLE + " * data type: double precision\n" +#else + " * data type: single precision\n" +#endif + , in_channels, out_channels, threads, block_size, sample_rate); + + auto temp = apf::parameter_map(); + temp.set("in_channels", in_channels); + temp.set("out_channels", out_channels); + temp.set("threads", threads); + temp.set("block_size", block_size); + temp.set("sample_rate", sample_rate); + engine.reset(new SimpleProcessor(temp)); + + inputs.resize(in_channels); + outputs.resize(out_channels); +} + +void engine_process(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) +{ + if (!engine) + { + mexErrMsgTxt("SimpleProcessor isn't initialized, use 'init' first!"); + } + if (nlhs != 1 || nrhs != 1) + { + mexErrMsgTxt("Exactly one input and one output is needed!"); + } + if (static_cast(mxGetM(prhs[0])) != block_size) + { + mexErrMsgTxt("Number of rows must be the same as block size!"); + } + if (static_cast(mxGetN(prhs[0])) != in_channels) + { + mexErrMsgTxt("Number of columns must be the same as number of inputs!"); + } + if (mxIsComplex(prhs[0])) + { + mexErrMsgTxt("Complex values are not allowed!"); + } + if (!mxIsNumeric(prhs[0])) + { + mexErrMsgTxt("Input must be a numeric matrix!"); + } + +#ifdef MEX_USE_DOUBLE + if (!mxIsDouble(prhs[0])) + { + mexErrMsgTxt("This function only works with double precision data!"); + } + plhs[0] = mxCreateDoubleMatrix(block_size, out_channels, mxREAL); + sample_type* output = mxGetPr(plhs[0]); + sample_type* input = mxGetPr(prhs[0]); +#else + if (mxGetClassID(prhs[0]) != mxSINGLE_CLASS) + { + mexErrMsgTxt("This function only works with single precision data!"); + } + plhs[0] = mxCreateNumericMatrix(block_size, out_channels + , mxSINGLE_CLASS, mxREAL); + sample_type* output = static_cast(mxGetData(plhs[0])); + sample_type* input = static_cast(mxGetData(prhs[0])); +#endif + + for (int i = 0; i <= in_channels; ++i) + { + inputs[i] = input; + input += block_size; + } + + for (int i = 0; i <= out_channels; ++i) + { + outputs[i] = output; + output += block_size; + } + + engine->audio_callback(block_size, inputs.data(), outputs.data()); +} + +void mexFunction(int nlhs, mxArray* plhs[], int nrhs, const mxArray* prhs[]) +{ + auto command = std::string(); + + if (nrhs >= 1 && mxIsChar(prhs[0])) + { + auto temp = mxArrayToString(prhs[0]); + command = temp; + mxFree(temp); + --nrhs; + ++prhs; + } + else + { + mexErrMsgTxt("First argument must be a string!"); + } + + if (command == "help") + { + mexPrintf("This is a useless help text.\n"); + } + else if (command == "init") + { + engine_init(nrhs, prhs); + } + else if (command == "process") + { + engine_process(nlhs, plhs, nrhs, prhs); + } + else if (command == "free" || command == "delete" || command == "clear") + { + engine.reset(); + } + else + { + mexPrintf("Command: \"%s\"\n", command.c_str()); + mexErrMsgTxt("Unknown command!"); + } +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/package.txt b/apf/examples/package.txt new file mode 100644 index 00000000..e1e4d5b3 --- /dev/null +++ b/apf/examples/package.txt @@ -0,0 +1,9 @@ +NAME = simpleprocessor~ + +SRCS = flext_simpleprocessor.cpp + +INCPATH = -I.. + +CXXFLAGS = -std=c++11 + +# vim:filetype=make diff --git a/apf/examples/portaudio_simpleprocessor.cpp b/apf/examples/portaudio_simpleprocessor.cpp new file mode 100644 index 00000000..2a2e4f65 --- /dev/null +++ b/apf/examples/portaudio_simpleprocessor.cpp @@ -0,0 +1,67 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Usage example for the MimoProcessor with PortAudio. + +#include + +#include "apf/stringtools.h" + +// First the policies ... +#include "apf/portaudio_policy.h" +#include "apf/posix_thread_policy.h" +// ... then the SimpleProcessor. +#include "simpleprocessor.h" + +using apf::str::S2A; +using apf::str::A2S; + +int main(int argc, char *argv[]) +{ + if (argc < 5) + { + std::cerr << "Error: too few arguments!" << std::endl; + std::cout << "Usage: " << argv[0] + << " inchannels outchannels samplerate blocksize [device-id]" << std::endl; + + std::cout << "\nList of devices:" << std::endl; + std::cout << SimpleProcessor::device_info() << std::endl; + return 42; + } + + apf::parameter_map e; + e.set("threads", 2); + + e.set("in_channels", argv[1]); + e.set("out_channels", argv[2]); + e.set("sample_rate", argv[3]); + e.set("block_size", argv[4]); + e.set("device_id", argv[5]); + + SimpleProcessor engine(e); + + sleep(60); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/simpleprocessor.h b/apf/examples/simpleprocessor.h new file mode 100644 index 00000000..97b9b908 --- /dev/null +++ b/apf/examples/simpleprocessor.h @@ -0,0 +1,159 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// A simple example for the usage of the MimoProcessor. +// The used policies can be specified with the preprocessor macros +// APF_MIMOPROCESSOR_*_POLICY. + +#include + +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for CombineChannels +#include "apf/stringtools.h" +#include "apf/misc.h" + +// Make sure that APF_MIMOPROCESSOR_INTERFACE_POLICY and +// APF_MIMOPROCESSOR_THREAD_POLICY are +// #define'd before #include'ing this header file! + +class SimpleProcessor : public apf::MimoProcessor +{ + public: + class Input : public MimoProcessorBase::Input + , public apf::has_begin_and_end + { + private: + using _begin_end_base + = apf::has_begin_and_end; + + public: + using iterator = _begin_end_base::iterator; + + explicit Input(const Params& p) + : MimoProcessorBase::Input(p) + , _buffer(this->parent.block_size()) + { + // initialize protected members from has_begin_and_end + _begin = this->buffer.begin(); + _end = this->buffer.end(); + } + + APF_PROCESS(Input, MimoProcessorBase::Input) + { + // Copying the input buffers is only needed for the Pd external + // because input buffers are re-used as output buffers! In + // non-trivial applications there will be some intermediate buffer + // anyway and copying the input buffers will not be necessary. + + std::copy(this->buffer.begin(), this->buffer.end(), _buffer.begin()); + } + + private: + std::vector _buffer; + }; + + class Output; + + explicit SimpleProcessor(const apf::parameter_map& p=apf::parameter_map()); + + ~SimpleProcessor() { this->deactivate(); } +}; + +class SimpleProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + using Params = MimoProcessorBase::Output::Params; + + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combiner(this->parent.get_input_list(), *this) + {} + + APF_PROCESS(Output, MimoProcessorBase::Output) + { + float weight = 1.0f / float(this->parent.get_input_list().size()); + _combiner.process(simple_predicate(weight)); + } + + private: + class simple_predicate + { + public: + explicit simple_predicate(float weight) + : _weight(weight) + {} + + apf::CombineChannelsResult::type select(const Input&) + { + // trivial, all inputs are used; no crossfade/interpolation + return apf::CombineChannelsResult::constant; + } + + float operator()(float in) + { + return in * _weight; + } + + private: + float _weight; + }; + + apf::CombineChannels, Output> _combiner; +}; + +SimpleProcessor::SimpleProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) +{ + Input::Params ip; + std::string in_port_prefix = p.get("in_port_prefix", ""); + int in_ch = p.get("in_channels"); + for (int i = 1; i <= in_ch; ++i) + { + ip.set("id", i); + if (in_port_prefix != "") + { + ip.set("connect_to", in_port_prefix + apf::str::A2S(i)); + } + this->add(ip); // ignore return value + } + + Output::Params op; + std::string out_port_prefix = p.get("out_port_prefix", ""); + auto out_ch = p.get("out_channels"); + for (int i = 1; i <= out_ch; ++i) + { + op.set("id", i); + if (out_port_prefix != "") + { + op.set("connect_to", out_port_prefix + apf::str::A2S(i)); + } + this->add(op); // ignore return value + } + + this->activate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/examples/startpd.sh b/apf/examples/startpd.sh new file mode 100755 index 00000000..07a88931 --- /dev/null +++ b/apf/examples/startpd.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +PD_PATCH=test_simpleprocessor.pd + +pd -path pd-linux/release-single "$PD_PATCH" & diff --git a/apf/examples/test_simpleprocessor.pd b/apf/examples/test_simpleprocessor.pd new file mode 100644 index 00000000..a0a9086e --- /dev/null +++ b/apf/examples/test_simpleprocessor.pd @@ -0,0 +1,16 @@ +#N canvas 870 549 450 300 10; +#X obj 60 223 dac~; +#X obj 64 43 osc~ 440; +#X obj 64 78 *~ 0.2; +#X msg 200 39 help; +#X msg 198 64 hello; +#X msg 199 89 hello 2; +#X obj 60 152 simpleprocessor~ 2 2 1; +#X connect 1 0 2 0; +#X connect 2 0 6 1; +#X connect 2 0 6 0; +#X connect 3 0 6 0; +#X connect 4 0 6 0; +#X connect 5 0 6 0; +#X connect 6 0 0 0; +#X connect 6 1 0 1; diff --git a/apf/gitmakedoc b/apf/gitmakedoc new file mode 100755 index 00000000..902e610a --- /dev/null +++ b/apf/gitmakedoc @@ -0,0 +1,49 @@ +#!/bin/sh + +# Create Doxygen documentation and (if on "master" branch) push to git server. + +# Prepare your working copy for pushing the documentation to Github: +# +# cd doc +# git clone https://user@github.com/AudioProcessingFramework/apf-doc.git html + +set -e + +VERSION=$(git describe) +BRANCH=$(git symbolic-ref --short HEAD) + +cd doc + +if [ -d html ] +then + cd html + if [ -d .git -a "$BRANCH" = master ] + then + git rm -rf . + fi + cd .. +fi + +( cat Doxyfile \ +; echo PROJECT_NUMBER = \"version $VERSION\"\ +; echo OUTPUT_DIRECTORY = . \ +; echo QUIET = YES \ +; echo WARNINGS = NO \ +; echo GENERATE_LATEX = NO \ +; echo GENERATE_RTF = NO \ +; echo GENERATE_MAN = NO \ +; echo GENERATE_XML = NO \ +) | doxygen - > /dev/null + +cd html + +if [ -d .git -a "$BRANCH" = master ] +then + echo -n "Push to Github? [yN] " + read ANSWER + if [ "$ANSWER" = Y -o "$ANSWER" = y ] + then + git add . && git commit -m "Doxygen documentation for $VERSION" \ + && git push --force + fi +fi diff --git a/apf/misc/Makefile.dependencies b/apf/misc/Makefile.dependencies new file mode 100644 index 00000000..6c5e105b --- /dev/null +++ b/apf/misc/Makefile.dependencies @@ -0,0 +1,64 @@ +# This Makefile automagically creates dependency files for .cpp source files. +# Rules for other file types can be added easily. +# +# In your primary Makefile, set a DEPENDENCIES variable containing your +# executable files (if they have according .cpp files) and additional object +# files and include Makefile.dependencies at the very end: +# +# DEPENDENCIES = mysource1 mysource2 mysource3 myobject1.o +# include Makefile.dependencies +# +# For further customization you can also set the variables DEPENDENCY_EXT, +# DEPENDENCY_DIR and NO_INCLUDE_DEPENDENCIES. +# +# To remove all temporary dependency files, run 'make clean'. +# You can still define your own rule for the target 'clean', if needed. + +# Author: Matthias Geier, 2011 + +DEPENDENCY_EXT ?= dep +DEPENDENCY_DIR ?= .dep + +# don't include dependencies for those targets: +NO_INCLUDE_DEPENDENCIES += clean + +DECORATED_DEPENDENCIES = $(DEPENDENCIES:%=$(DEPENDENCY_DIR)/%.$(DEPENDENCY_EXT)) + +DEPENDENCY_MESSAGE = @echo Dependency file \"$@\" updated + +$(DEPENDENCY_DIR): + mkdir -p $(DEPENDENCY_DIR) + +$(DEPENDENCY_DIR)/%.o.$(DEPENDENCY_EXT): %.cpp | $(DEPENDENCY_DIR) + @$(RM) $@; $(CXX) -MM -MF $@ -MT "$*.o $@" \ + $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) $< > /dev/null + $(DEPENDENCY_MESSAGE) + +$(DEPENDENCY_DIR)/%.$(DEPENDENCY_EXT): %.cpp | $(DEPENDENCY_DIR) + @$(RM) $@; $(CXX) -MM -MF $@ -MT "$* $@" \ + $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) $< > /dev/null + $(DEPENDENCY_MESSAGE) + +# other file types (e.g. *.c) could be added like this: + +#$(DEPENDENCY_DIR)/%.o.$(DEPENDENCY_EXT): %.c | $(DEPENDENCY_DIR) +# somehow generate dependency files ... + +# filter additional dependency files from default rule: +%: %.cpp + $(LINK.cpp) $< $(filter %.o, $^) $(LOADLIBES) $(LDLIBS) -o $@ + +# include dependency files generated in the previous step +ifeq (,$(findstring $(MAKECMDGOALS), $(NO_INCLUDE_DEPENDENCIES))) +-include $(DECORATED_DEPENDENCIES) +endif + +# If a rule for 'clean' already exists, it is not overwritten here! +# This merely adds a dependency on the target 'clean.dependencies': +clean: clean.dependencies + +clean.dependencies: + $(RM) $(DECORATED_DEPENDENCIES) + rmdir $(DEPENDENCY_DIR) 2> /dev/null || true + +.PHONY: clean clean.dependencies diff --git a/apf/performance_tests/Makefile b/apf/performance_tests/Makefile new file mode 100644 index 00000000..3631471d --- /dev/null +++ b/apf/performance_tests/Makefile @@ -0,0 +1,64 @@ +EXECUTABLES += crossfade +EXECUTABLES += interpolation +EXECUTABLES += biquad_denormals +EXECUTABLES += biquad_count_denormals + +OPT ?= -O3 + +# TODO: automatic tests of different combinations for biquad_denormals: +OPT += -march=native +#OPT += -mfpmath=sse +#OPT += -msse +#OPT += -msse3 + +CXXFLAGS += $(OPT) +CXXFLAGS += -g + +CXXFLAGS += -std=c++11 + +LDLIBS += -lpthread + +# show all warnings +CXXFLAGS += -Wall -Wextra +CXXFLAGS += -pedantic +# warnings are errors +CXXFLAGS += -pedantic-errors +CXXFLAGS += -Werror +# even more warnings: +CXXFLAGS += -Wpointer-arith +CXXFLAGS += -Wcast-align +CXXFLAGS += -Wwrite-strings +CXXFLAGS += -Wredundant-decls +CXXFLAGS += -Wconversion +CXXFLAGS += -Wshadow +CXXFLAGS += -Wold-style-cast +CXXFLAGS += -Wlong-long +CXXFLAGS += -Wconversion +#CXXFLAGS += -Winline +#CXXFLAGS += -Weffc++ + +CPPFLAGS += -I.. +CPPFLAGS += -D_REENTRANT +CPPFLAGS += -DNDEBUG + +#### no more setting below here #### + +# without this, intermediate .o files are generated: +.SUFFIXES: +.SUFFIXES: .cpp .o + +all: $(EXECUTABLES) + +.PHONY: all + +clean: + $(RM) $(EXECUTABLES) $(OBJECTS) + +.PHONY: clean + +# rebuild everything when Makefile changes +$(OBJECTS) $(EXECUTABLES): Makefile + +DEPENDENCIES = $(EXECUTABLES) $(OBJECTS) + +include ../misc/Makefile.dependencies diff --git a/apf/performance_tests/biquad_count_denormals.cpp b/apf/performance_tests/biquad_count_denormals.cpp new file mode 100644 index 00000000..14e40ad4 --- /dev/null +++ b/apf/performance_tests/biquad_count_denormals.cpp @@ -0,0 +1,164 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Count denormals to check if denormal prevention works + +// TODO: include into unit tests? + +#include +#include +#include + +#include "apf/biquad.h" + +#define MAKE_DENORMAL_COUNTER(Name, Prevention) \ +template \ +struct Name : Prevention { \ + void prevent_denormals(T& val) { \ + this->Prevention::prevent_denormals(val); \ + if (std::abs(val) < std::numeric_limits::min() && (val != 0)) \ + denormal_counter[#Name].first++; \ + denormal_counter[#Name].second++; } }; + +#define COUNT_DENORMALS_IN_CASCADE(coeffs, cascade, name, prevention) \ + std::vector \ + temp##coeffs(cascade.number_of_sections(), coeffs); \ + cascade.set(temp##coeffs.begin(), temp##coeffs.end()); \ + denormal_counter[#name] = std::make_pair(0, 0); \ + input[0] = 1; \ + for (int n = 0; n < number_of_blocks_count; ++n) { \ + cascade.execute(input.begin(), input.end() , output.begin()); \ + input[0] = 0; } \ + std::cout << std::string(#cascade) << " (" << #prevention << ") created " \ + << denormal_counter[#name].first << " denormal numbers (" \ + << static_cast(denormal_counter[#name].first) / static_cast(denormal_counter[#name].second) * 100.0f \ + << "%)." << std::endl; + +#define COUNT_DENORMALS(coeffs_flt, coeffs_dbl, name, prevention) { \ + apf::Cascade> \ + cascade_flt(number_of_sections_count); \ + apf::Cascade> \ + cascade_dbl(number_of_sections_count); \ + COUNT_DENORMALS_IN_CASCADE(coeffs_flt, cascade_flt, name, prevention) \ + COUNT_DENORMALS_IN_CASCADE(coeffs_dbl, cascade_dbl, name, prevention) \ + std::cout << std::endl; } + +int block_size = 1024; +int number_of_blocks_count = 200; +int number_of_sections_count = 10; + +// denormal counter map +std::map> denormal_counter; + +// create denormal counter classes +MAKE_DENORMAL_COUNTER(count_dc, apf::dp::dc) +MAKE_DENORMAL_COUNTER(count_ac, apf::dp::ac) +MAKE_DENORMAL_COUNTER(count_quant, apf::dp::quantization) +MAKE_DENORMAL_COUNTER(count_set_zero_1, apf::dp::set_zero_2) // TODO: remove apf::dp::set_zero_1 +MAKE_DENORMAL_COUNTER(count_set_zero_2, apf::dp::set_zero_3) +MAKE_DENORMAL_COUNTER(count_ftz_on, apf::dp::none) +MAKE_DENORMAL_COUNTER(count_daz_on, apf::dp::none) +MAKE_DENORMAL_COUNTER(count_ftz_on_and_daz_on, apf::dp::none) +MAKE_DENORMAL_COUNTER(count_none, apf::dp::none) + +int main() +{ + // We're only interested in single precision audio data + std::vector input(block_size); + std::vector output(block_size); + + apf::SosCoefficients benign_flt, malignant_flt; + apf::SosCoefficients benign_dbl, malignant_dbl; + + // simple LPF + benign_flt.b0 = 0.2f; benign_dbl.b0 = 0.2; + benign_flt.b1 = 0.5f; benign_dbl.b1 = 0.5; + benign_flt.b2 = 0.2f; benign_dbl.b2 = 0.2; + benign_flt.a1 = 0.5f; benign_dbl.a1 = 0.5; + benign_flt.a2 = 0.2f; benign_dbl.a2 = 0.2; + + // HPF similar to the one used in HOA algorithm + malignant_flt.b0 = 0.98f; malignant_dbl.b0 = 0.98; + malignant_flt.b1 = -1.9f; malignant_dbl.b1 = -1.9; + malignant_flt.b2 = 0.93f; malignant_dbl.b2 = 0.93; + malignant_flt.a1 = -1.85f; malignant_dbl.a1 = -1.85; + malignant_flt.a2 = 0.9f; malignant_dbl.a2 = 0.9; + + std::cout << "\n==> First the benign coefficients:\n" << std::endl; + + COUNT_DENORMALS(benign_flt, benign_dbl, count_ac, apf::dp::ac) + COUNT_DENORMALS(benign_flt, benign_dbl, count_dc, apf::dp::dc) + COUNT_DENORMALS(benign_flt, benign_dbl, count_quant, apf::dp::quantization) + COUNT_DENORMALS(benign_flt, benign_dbl, count_set_zero_1, apf::dp::set_zero_2) + COUNT_DENORMALS(benign_flt, benign_dbl, count_set_zero_2, apf::dp::set_zero_3) + +#ifdef __SSE__ + std::cout << "FTZ on" << std::endl; + apf::dp::ftz_on(); + COUNT_DENORMALS(benign_flt, benign_dbl, count_ftz_on, apf::dp::none) +#ifdef __SSE3__ + std::cout << "DAZ on" << std::endl; + apf::dp::daz_on(); + COUNT_DENORMALS(benign_flt, benign_dbl, count_daz_on, apf::dp::none) +#endif + std::cout << "FTZ off" << std::endl; + apf::dp::ftz_off(); +#ifdef __SSE3__ + COUNT_DENORMALS(benign_flt, benign_dbl, count_ftz_on_and_daz_on, apf::dp::none) + std::cout << "DAZ off" << std::endl; + apf::dp::daz_off(); +#endif +#endif + COUNT_DENORMALS(benign_flt, benign_dbl, count_none, apf::dp::none) + + std::cout << "\n==> Now the malignant coefficients:" << std::endl; + std::cout << std::endl; + + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_ac, apf::dp::ac) + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_dc, apf::dp::dc) + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_quant, apf::dp::quantization) + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_set_zero_1, apf::dp::set_zero_2) + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_set_zero_2, apf::dp::set_zero_3) + +#ifdef __SSE__ + std::cout << "FTZ on" << std::endl; + apf::dp::ftz_on(); + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_ftz_on, apf::dp::none) +#ifdef __SSE3__ + std::cout << "DAZ on" << std::endl; + apf::dp::daz_on(); + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_daz_on, apf::dp::none) +#endif + std::cout << "FTZ off" << std::endl; + apf::dp::ftz_off(); +#ifdef __SSE3__ + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_ftz_on_and_daz_on, apf::dp::none) + std::cout << "DAZ off" << std::endl; + apf::dp::daz_off(); +#endif +#endif + + std::cout << "The following can take quite a while ... abort with Ctrl+C\n\n"; + + COUNT_DENORMALS(malignant_flt, malignant_dbl, count_none, apf::dp::none) +} diff --git a/apf/performance_tests/biquad_denormals.cpp b/apf/performance_tests/biquad_denormals.cpp new file mode 100644 index 00000000..3c93c328 --- /dev/null +++ b/apf/performance_tests/biquad_denormals.cpp @@ -0,0 +1,173 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Performance tests for BiQuad and denormal prevention. + +// TODO: make proper statistics for easily comparable test runs +// TODO: run with different compiler flags (see Makefile) + +#include +#include + +#include "apf/biquad.h" +#include "apf/stopwatch.h" + +const int block_size = 1024; +const int number_of_blocks = 50000; +const int number_of_sections_test1 = 1; +const int number_of_sections_test2 = 10; +const int number_of_sections_test3 = 14; + +#define FILL_CASCADE(cascade, coeffs) \ +std::vector temp ## cascade(cascade.number_of_sections(), coeffs); \ +cascade.set(temp ## cascade.begin(), temp ## cascade.end()); + +#define TEST_BIQUAD_INTERNAL(coeffs, cascade_test1, cascade_test2, cascade_test3, prevention) \ + FILL_CASCADE(cascade_test1, coeffs); \ + FILL_CASCADE(cascade_test2, coeffs); \ + FILL_CASCADE(cascade_test3, coeffs); \ + { \ + apf::StopWatch watch(std::string(#cascade_test1) + " (" + #prevention + ")"); \ + input[0] = 1; \ + for (int n = 0; n < number_of_blocks; ++n) { \ + cascade_test1.execute(input.begin(), input.end() , output.begin()); \ + input[0] = 0; } \ + } \ + { \ + apf::StopWatch watch(std::string(#cascade_test2) + " (" + #prevention + ")"); \ + input[0] = 1; \ + for (int n = 0; n < number_of_blocks; ++n) { \ + cascade_test2.execute(input.begin(), input.end() , output.begin()); \ + input[0] = 0; } \ + } \ + { \ + apf::StopWatch watch(std::string(#cascade_test3) + " (" + #prevention + ")"); \ + input[0] = 1; \ + for (int n = 0; n < number_of_blocks; ++n) { \ + cascade_test3.execute(input.begin(), input.end() , output.begin()); \ + input[0] = 0; } \ + } + +#define TEST_BIQUAD(coeffs_flt, coeffs_dbl, prevention) { \ + apf::Cascade> \ + cascade_test1_flt(number_of_sections_test1); \ + apf::Cascade> \ + cascade_test1_dbl(number_of_sections_test1); \ + apf::Cascade> \ + cascade_test2_flt(number_of_sections_test2); \ + apf::Cascade> \ + cascade_test2_dbl(number_of_sections_test2); \ + apf::Cascade> \ + cascade_test3_flt(number_of_sections_test3); \ + apf::Cascade> \ + cascade_test3_dbl(number_of_sections_test3); \ + TEST_BIQUAD_INTERNAL(coeffs_flt, cascade_test1_flt, cascade_test2_flt, cascade_test3_flt, prevention) \ + TEST_BIQUAD_INTERNAL(coeffs_dbl, cascade_test1_dbl, cascade_test2_dbl, cascade_test3_dbl, prevention) \ + std::cout << std::endl; } + +int main() +{ + // We're only interested in single precision audio data + std::vector input(block_size); + std::vector output(block_size); + + apf::SosCoefficients benign_flt, malignant_flt; + apf::SosCoefficients benign_dbl, malignant_dbl; + + // simple LPF + benign_flt.b0 = 0.2f; benign_dbl.b0 = 0.2; + benign_flt.b1 = 0.5f; benign_dbl.b1 = 0.5; + benign_flt.b2 = 0.2f; benign_dbl.b2 = 0.2; + benign_flt.a1 = 0.5f; benign_dbl.a1 = 0.5; + benign_flt.a2 = 0.2f; benign_dbl.a2 = 0.2; + + // HPF similar to the one used in NFC-HOA algorithm + malignant_flt.b0 = 0.98f; malignant_dbl.b0 = 0.98; + malignant_flt.b1 = -1.9f; malignant_dbl.b1 = -1.9; + malignant_flt.b2 = 0.93f; malignant_dbl.b2 = 0.93; + malignant_flt.a1 = -1.85f; malignant_dbl.a1 = -1.85; + malignant_flt.a2 = 0.9f; malignant_dbl.a2 = 0.9; + + std::cout << "\n==> First the benign coefficients:\n" << std::endl; + + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::ac) + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::dc) + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::quantization) + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::set_zero_1) + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::set_zero_2) + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::set_zero_3) + +#ifdef __SSE__ + std::cout << "FTZ on" << std::endl; + apf::dp::ftz_on(); + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::none) +#ifdef __SSE3__ + std::cout << "DAZ on" << std::endl; + apf::dp::daz_on(); + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::none) +#endif + std::cout << "FTZ off" << std::endl; + apf::dp::ftz_off(); +#ifdef __SSE3__ + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::none) + std::cout << "DAZ off" << std::endl; + apf::dp::daz_off(); +#endif +#endif + TEST_BIQUAD(benign_flt, benign_dbl, apf::dp::none) + std::cout << std::endl; + + std::cout << "\n==> And now the malignant coefficients:" << std::endl; + + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::ac) + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::dc) + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::quantization) + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::set_zero_1) + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::set_zero_2) + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::set_zero_3) + +#ifdef __SSE__ + std::cout << "FTZ on" << std::endl; + apf::dp::ftz_on(); + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::none) +#ifdef __SSE3__ + std::cout << "DAZ on" << std::endl; + apf::dp::daz_on(); + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::none) +#endif + std::cout << "FTZ off" << std::endl; + apf::dp::ftz_off(); +#ifdef __SSE3__ + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::none) + std::cout << "DAZ off" << std::endl; + apf::dp::daz_off(); +#endif +#endif + + std::cout << "The following can take quite a while ... abort with Ctrl+C\n\n"; + + TEST_BIQUAD(malignant_flt, malignant_dbl, apf::dp::none) +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/performance_tests/crossfade.cpp b/apf/performance_tests/crossfade.cpp new file mode 100644 index 00000000..5063ad90 --- /dev/null +++ b/apf/performance_tests/crossfade.cpp @@ -0,0 +1,145 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Performance tests for the crossfade. + +#include // for random() + +#include "apf/pointer_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::raised_cosine_fade, Combine... +#include "apf/container.h" // for apf::fixed_matrix +#include "apf/stopwatch.h" + +class MyProcessor : public apf::MimoProcessor + , apf::posix_thread_policy> +{ + public: + using Input = DefaultInput; + class Output; + class CombineFunction; + + MyProcessor(const apf::parameter_map& p); + + private: + apf::raised_cosine_fade _fade; +}; + +class MyProcessor::CombineFunction +{ + public: + apf::CombineChannelsResult::type select(const Input&) + { + return apf::CombineChannelsResult::change; // Always force crossfade + } + + float operator()(float in, apf::fade_out_tag) + { + return in * 0.5f; + } + + float operator()(float in) + { + return in * 3.14f; + } + + void update() {} // Unused. Call will be optimized away. +}; + +class MyProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combine_and_crossfade(this->parent.get_input_list(), *this + , this->parent._fade) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + _combine_and_crossfade.process(CombineFunction()); + } + + private: + apf::CombineChannelsCrossfade, Output + , apf::raised_cosine_fade> _combine_and_crossfade; +}; + +MyProcessor::MyProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) + , _fade(this->block_size()) +{ + for (int i = 0; i < p.get("in_channels"); ++i) + { + this->add(); + } + + for (int i = 0; i < p.get("out_channels"); ++i) + { + this->add(); + } +} + +int main() +{ + // TODO: check for input arguments + + int in_channels = 10; + int out_channels = 70; + int block_size = 512; + int repetitions = 1000; + int threads = 1; + + apf::fixed_matrix m_in(in_channels, block_size); + apf::fixed_matrix m_out(out_channels, block_size); + + // WARNING: this is not really a meaningful audio signal: + std::generate(m_in.begin(), m_in.end(), random); + + apf::parameter_map p; + p.set("in_channels", in_channels); + p.set("out_channels", out_channels); + p.set("block_size", block_size); + p.set("sample_rate", 44100); // Not really relevant in this case + p.set("threads", threads); + + MyProcessor processor(p); + + processor.activate(); + + { + apf::StopWatch watch("processing"); + for (int i = 0; i < repetitions; ++i) + { + processor.audio_callback(block_size + , m_in.get_channel_ptrs(), m_out.get_channel_ptrs()); + } + } + + processor.deactivate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/performance_tests/interpolation.cpp b/apf/performance_tests/interpolation.cpp new file mode 100644 index 00000000..a11586e3 --- /dev/null +++ b/apf/performance_tests/interpolation.cpp @@ -0,0 +1,150 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Performance tests for the interpolation. + +#include // for random() + +#include "apf/pointer_policy.h" +#include "apf/posix_thread_policy.h" +#include "apf/mimoprocessor.h" +#include "apf/combine_channels.h" // for apf::CombineChannelsInterpolation +#include "apf/container.h" // for fixed_matrix +#include "apf/stopwatch.h" + +class MyProcessor : public apf::MimoProcessor + , apf::posix_thread_policy> +{ + public: + using Input = DefaultInput; + class Output; + class CombineFunction; + + MyProcessor(const apf::parameter_map& p); +}; + +class MyProcessor::CombineFunction +{ + public: + CombineFunction(size_t block_size) + : _block_size(float(block_size)) + {} + + apf::CombineChannelsResult::type select(const Input&) + { + _interpolator.set(3.14f, 666.666f, _block_size); + return apf::CombineChannelsResult::change; // Always force interpolation + } + + float operator()(float) + { + throw std::logic_error("This is never used!"); + return 0.0f; + } + + float operator()(float in, float index) + { + return in * _interpolator(index); + } + + private: + // float is much faster here than int because less casts are necessary + float _block_size; + apf::math::linear_interpolator _interpolator; +}; + +class MyProcessor::Output : public MimoProcessorBase::DefaultOutput +{ + public: + explicit Output(const Params& p) + : MimoProcessorBase::DefaultOutput(p) + , _combine_and_interpolate(this->parent.get_input_list(), *this) + {} + + APF_PROCESS(Output, MimoProcessorBase::DefaultOutput) + { + _combine_and_interpolate.process( + CombineFunction(this->parent.block_size())); + } + + private: + apf::CombineChannelsInterpolation, Output> + _combine_and_interpolate; +}; + +MyProcessor::MyProcessor(const apf::parameter_map& p) + : MimoProcessorBase(p) +{ + for (int i = 0; i < p.get("in_channels"); ++i) + { + this->add(); + } + + for (int i = 0; i < p.get("out_channels"); ++i) + { + this->add(); + } +} + +int main() +{ + // TODO: check for input arguments + + int in_channels = 10; + int out_channels = 70; + int block_size = 512; + int repetitions = 1000; + int threads = 1; + + apf::fixed_matrix m_in(in_channels, block_size); + apf::fixed_matrix m_out(out_channels, block_size); + + // WARNING: this is not really a meaningful audio signal: + std::generate(m_in.begin(), m_in.end(), random); + + apf::parameter_map p; + p.set("in_channels", in_channels); + p.set("out_channels", out_channels); + p.set("block_size", block_size); + p.set("sample_rate", 44100); // Not really relevant in this case + p.set("threads", threads); + + MyProcessor processor(p); + + processor.activate(); + + { + apf::StopWatch watch("processing"); + for (int i = 0; i < repetitions; ++i) + { + processor.audio_callback(block_size + , m_in.get_channel_ptrs(), m_out.get_channel_ptrs()); + } + } + + processor.deactivate(); +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/Makefile b/apf/unit_tests/Makefile new file mode 100644 index 00000000..53b6267f --- /dev/null +++ b/apf/unit_tests/Makefile @@ -0,0 +1,84 @@ +# Makefile for unit tests + +TESTS += test_math +TESTS += test_stringtools +TESTS += test_trivial_iterator +TESTS += test_iterator +TESTS += test_accumulating_iterator +TESTS += test_cast_iterator +TESTS += test_circular_iterator +TESTS += test_transform_iterator +TESTS += test_index_iterator +TESTS += test_stride_iterator +TESTS += test_dual_iterator +TESTS += test_discard_iterator +TESTS += test_iterator_combinations +TESTS += test_biquad +TESTS += test_blockdelayline +TESTS += test_container +TESTS += test_mimoprocessor +TESTS += test_combine_channels +TESTS += test_misc + +ifneq (,$(findstring $(MAKECMDGOALS), fftw clean)) +TESTS += test_fftwtools +TESTS += test_convolver +endif + +OBJECTS = $(TESTS:=.o) + +CXXFLAGS += -std=c++11 + +CXXFLAGS += -g + +# show all warnings +CXXFLAGS += -Wall -Wextra +CXXFLAGS += -pedantic +# warnings are errors +CXXFLAGS += -pedantic-errors +#CXXFLAGS += -Werror +# even more warnings: +CXXFLAGS += -Wpointer-arith +CXXFLAGS += -Wcast-align +CXXFLAGS += -Wwrite-strings +CXXFLAGS += -Wredundant-decls +CXXFLAGS += -Wconversion +CXXFLAGS += -Wshadow +CXXFLAGS += -Wold-style-cast +CXXFLAGS += -Wlong-long +CXXFLAGS += -Winline + +#CXXFLAGS += -Wno-sign-conversion + +CPPFLAGS += -I.. + +# this adds (very slow) runtime checks for many STL functions: +CPPFLAGS += -D_GLIBCXX_DEBUG + +all: + $(MAKE) fftw + +run_tests: build_tests + ./main + +build_tests: main + +fftw: LDLIBS += -lfftw3f -lfftw3 -lfftw3l -lm +fftw: run_tests + +main: $(OBJECTS) + +# TODO: check why this gives false(?) positives in test_blockdelayline.h +test_blockdelayline.o: CPPFLAGS := $(filter-out -D_GLIBCXX_DEBUG,$(CPPFLAGS)) + +DEPENDENCIES = main $(OBJECTS) + +clean: + $(RM) $(DEPENDENCIES) + +.PHONY: all build_tests run_tests clean fftw + +# rebuild everything when Makefile changes +$(DEPENDENCIES): Makefile + +include ../misc/Makefile.dependencies diff --git a/apf/unit_tests/catch/README b/apf/unit_tests/catch/README new file mode 100644 index 00000000..4c656545 --- /dev/null +++ b/apf/unit_tests/catch/README @@ -0,0 +1 @@ +C++ unit testing framework from https://github.com/philsquared/Catch diff --git a/apf/unit_tests/catch/catch.hpp b/apf/unit_tests/catch/catch.hpp new file mode 100644 index 00000000..4bca13ce --- /dev/null +++ b/apf/unit_tests/catch/catch.hpp @@ -0,0 +1,6000 @@ +/* + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +// #included from: internal/catch_context.h + +// #included from: catch_interfaces_reporter.h + +// #included from: catch_common.h + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#ifdef __GNUC__ +#define ATTRIBUTE_NORETURN __attribute__ ((noreturn)) +#else +#define ATTRIBUTE_NORETURN +#endif + +#include +#include +#include + +namespace Catch +{ + class NonCopyable { + NonCopyable( const NonCopyable& ); + void operator = ( const NonCopyable& ); + protected: + NonCopyable() {} + virtual ~NonCopyable() {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + { + delete *it; + } + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + { + delete it->second; + } + } + + template + inline void forEach( ContainerT& container, Function function ) { + std::for_each( container.begin(), container.end(), function ); + } + + template + inline void forEach( const ContainerT& container, Function function ) { + std::for_each( container.begin(), container.end(), function ); + } + + struct SourceLineInfo + { + SourceLineInfo() : line( 0 ){} + SourceLineInfo( const std::string& _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo( const SourceLineInfo& other ) + : file( other.file ), + line( other.line ) + {} + void swap( SourceLineInfo& other ){ + file.swap( other.file ); + std::swap( line, other.line ); + } + + std::string file; + std::size_t line; + }; + + inline std::ostream& operator << ( std::ostream& os, const SourceLineInfo& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << "): "; +#else + os << info.file << ":" << info.line << ": "; +#endif + return os; + } + + ATTRIBUTE_NORETURN + inline void throwLogicError( const std::string& message, const std::string& file, std::size_t line ) { + std::ostringstream oss; + oss << "Internal Catch error: '" << message << "' at: " << SourceLineInfo( file, line ); + throw std::logic_error( oss.str() ); + } +} + +#define CATCH_INTERNAL_ERROR( msg ) throwLogicError( msg, __FILE__, __LINE__ ); +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, __LINE__ ) + +// #included from: catch_totals.hpp + +namespace Catch +{ + struct Counts + { + Counts() : passed( 0 ), failed( 0 ) {} + + Counts operator - ( const Counts& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + return diff; + } + + std::size_t total() const { + return passed + failed; + } + + std::size_t passed; + std::size_t failed; + }; + + struct Totals + { + Totals operator - ( const Totals& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_ptr.hpp + +namespace Catch +{ + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr + { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + m_p->addRef(); + } + Ptr( const Ptr& other ) : m_p( other.m_p ){ + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ){ + std::swap( m_p, other.m_p ); + } + + T* get(){ + return m_p; + } + const T* get() const{ + return m_p; + } + + T& operator*(){ + return *m_p; + } + const T& operator*() const{ + return *m_p; + } + + T* operator->(){ + return m_p; + } + const T* operator->() const{ + return m_p; + } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(){} + virtual void addRef() = 0; + virtual void release() = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef(){ + ++m_rc; + } + virtual void release(){ + if( --m_rc == 0 ) + delete this; + } + + int m_rc; + }; + +} // end namespace Catch + +#include +#include +#include + +namespace Catch +{ + struct IReporterConfig { + virtual ~IReporterConfig() {} + virtual std::ostream& stream () const = 0; + virtual bool includeSuccessfulResults () const = 0; + virtual std::string getName () const = 0; + }; + + class TestCaseInfo; + class ResultInfo; + + struct IReporter : IShared { + virtual ~IReporter() {} + virtual bool shouldRedirectStdout() const = 0; + virtual void StartTesting() = 0; + virtual void EndTesting( const Totals& totals ) = 0; + virtual void StartGroup( const std::string& groupName ) = 0; + virtual void EndGroup( const std::string& groupName, const Totals& totals ) = 0; + virtual void StartSection( const std::string& sectionName, const std::string description ) = 0; + virtual void EndSection( const std::string& sectionName, const Counts& assertions ) = 0; + virtual void StartTestCase( const TestCaseInfo& testInfo ) = 0; + virtual void EndTestCase( const TestCaseInfo& testInfo, const Totals& totals, const std::string& stdOut, const std::string& stdErr ) = 0; + virtual void Result( const ResultInfo& result ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory() {} + virtual IReporter* create( const IReporterConfig& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry() {} + virtual IReporter* create( const std::string& name, const IReporterConfig& config ) const = 0; + virtual void registerReporter( const std::string& name, IReporterFactory* factory ) = 0; + virtual const FactoryMap& getFactories() const = 0; + }; + + inline std::string trim( const std::string& str ) + { + std::string::size_type start = str.find_first_not_of( "\n\r\t " ); + std::string::size_type end = str.find_last_not_of( "\n\r\t " ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } +} + +#include +#include +#include + +namespace Catch +{ + class TestCaseInfo; + struct IResultCapture; + struct ITestCaseRegistry; + struct IRunner; + struct IExceptionTranslatorRegistry; + class GeneratorsForTest; + + class StreamBufBase : public std::streambuf{}; + + class Context + { + Context(); + Context( const Context& ); + void operator=( const Context& ); + static Context& me(); + + public: + static void setRunner( IRunner* runner ); + static void setResultCapture( IResultCapture* resultCapture ); + static IResultCapture& getResultCapture(); + static IReporterRegistry& getReporterRegistry(); + static ITestCaseRegistry& getTestCaseRegistry(); + static IExceptionTranslatorRegistry& getExceptionTranslatorRegistry(); + static std::streambuf* createStreamBuf( const std::string& streamName ); + static IRunner& getRunner(); + static size_t getGeneratorIndex( const std::string& fileInfo, size_t totalSize ); + static bool advanceGeneratorsForCurrentTest(); + static void cleanUp(); + + private: + static Context*& singleInstance(); + GeneratorsForTest* findGeneratorsForCurrentTest(); + GeneratorsForTest& getGeneratorsForCurrentTest(); + + private: + std::auto_ptr m_reporterRegistry; + std::auto_ptr m_testCaseRegistry; + std::auto_ptr m_exceptionTranslatorRegistry; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; +} + +// #included from: internal/catch_test_registry.hpp + +// #included from: catch_interfaces_testcase.h + +#include + +namespace Catch +{ + struct ITestCase + { + virtual ~ITestCase + () + {} + + virtual void invoke + () const = 0; + + virtual ITestCase* clone + () const = 0; + + virtual bool operator == + ( const ITestCase& other + ) const = 0; + + virtual bool operator < + ( const ITestCase& other + ) const = 0; + }; + + class TestCaseInfo; + + struct ITestCaseRegistry + { + virtual ~ITestCaseRegistry + () + {} + + virtual void registerTest + ( const TestCaseInfo& testInfo + ) = 0; + + virtual const std::vector& getAllTests + () const = 0; + + virtual std::vector getMatchingTestCases + ( const std::string& rawTestSpec + ) = 0; + }; +} + +namespace Catch +{ + +template +class MethodTestCase : public ITestCase +{ +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + + virtual ITestCase* clone() const { + return new MethodTestCase( m_method ); + } + + virtual bool operator == ( const ITestCase& other ) const { + const MethodTestCase* mtOther = dynamic_cast( &other ); + return mtOther && m_method == mtOther->m_method; + } + + virtual bool operator < ( const ITestCase& other ) const { + const MethodTestCase* mtOther = dynamic_cast( &other ); + return mtOther && &m_method < &mtOther->m_method; + } + +private: + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct AutoReg +{ + AutoReg( TestFunction function, + const char* name, + const char* description, + const SourceLineInfo& lineInfo ); + + template + AutoReg( void (C::*method)(), + const char* name, + const char* description, + const SourceLineInfo& lineInfo ) { + registerTestCase( new MethodTestCase( method ), name, description, lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + const char* name, + const char* description, + const SourceLineInfo& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( const AutoReg& ); + void operator= ( const AutoReg& ); +}; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ ), Name, Desc, CATCH_INTERNAL_LINEINFO ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ )() + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TESTCASE_NORETURN( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ )() ATTRIBUTE_NORETURN; \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ ), Name, Desc, CATCH_INTERNAL_LINEINFO ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( TestCaseFunction_catch_internal_ )() + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, Name, Desc, CATCH_INTERNAL_LINEINFO ); } + +/////////////////////////////////////////////////////////////////////////////// +#define TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( TestCaseMethod_catch_internal_ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( TestCaseMethod_catch_internal_ )::test, TestName, Desc, CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( TestCaseMethod_catch_internal_ )::test() + +// #included from: internal/catch_capture.hpp + +// #included from: catch_expression.hpp + +// #included from: catch_resultinfo_builder.hpp + +// #included from: catch_tostring.hpp + +#include + +namespace Catch +{ +namespace Detail +{ + struct NonStreamable { + template NonStreamable( const T& ){} + }; + + // If the type does not have its own << overload for ostream then + // this one will be used instead + inline std::ostream& operator << ( std::ostream& ss, NonStreamable ){ + return ss << "{?}"; + } + + template + inline std::string makeString( const T& value ) { + std::ostringstream oss; + oss << value; + return oss.str(); + } + + template + inline std::string makeString( T* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + std::ostringstream oss; + oss << p; + return oss.str(); + } + + template + inline std::string makeString( const T* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + std::ostringstream oss; + oss << p; + return oss.str(); + } + +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( const T& value ) { + return Detail::makeString( value ); +} + +// Built in overloads + +inline std::string toString( const std::string& value ) { + return "\"" + value + "\""; +} + +inline std::string toString( const std::wstring& value ) { + std::ostringstream oss; + oss << "\""; + for(size_t i = 0; i < value.size(); ++i ) + oss << static_cast( value[i] <= 0xff ? value[i] : '?'); + oss << "\""; + return oss.str(); +} + +inline std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +inline std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +inline std::string toString( int value ) { + std::ostringstream oss; + oss << value; + return oss.str(); +} + +inline std::string toString( unsigned long value ) { + std::ostringstream oss; + if( value > 8192 ) + oss << "0x" << std::hex << value; + else + oss << value; + return oss.str(); +} + +inline std::string toString( unsigned int value ) { + return toString( static_cast( value ) ); +} + +inline std::string toString( const double value ) { + std::ostringstream oss; + oss << value; + return oss.str(); +} + +inline std::string toString( bool value ) { + return value ? "true" : "false"; +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +inline std::string toString( std::nullptr_t null ) { + return "nullptr"; +} +#endif + +} // end namespace Catch + +// #included from: catch_resultinfo.hpp + +#include +// #included from: catch_result_type.h + +namespace Catch +{ + +struct ResultWas{ enum OfType + { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2 + + }; }; + + struct ResultAction + { + enum Value + { + None, + Failed = 1, // Failure - but no debug break if Debug bit not set + DebugFailed = 3 // Indicates that the debugger should break, if possible + }; + }; + +} + + +namespace Catch +{ + class ResultInfo + { + public: + + /////////////////////////////////////////////////////////////////////////// + ResultInfo + () + : m_macroName(), + m_expr(), + m_lhs(), + m_rhs(), + m_op(), + m_message(), + m_result( ResultWas::Unknown ), + m_isNot( false ) + {} + + /////////////////////////////////////////////////////////////////////////// + ResultInfo + ( + const char* expr, + ResultWas::OfType result, + bool isNot, + const SourceLineInfo& lineInfo, + const char* macroName, + const char* message + ) + : m_macroName( macroName ), + m_lineInfo( lineInfo ), + m_expr( expr ), + m_lhs(), + m_rhs(), + m_op( isNotExpression( expr ) ? "!" : "" ), + m_message( message ), + m_result( result ), + m_isNot( isNot ) + { + if( isNot ) + m_expr = "!" + m_expr; + } + + /////////////////////////////////////////////////////////////////////////// + virtual ~ResultInfo + () + { + } + + /////////////////////////////////////////////////////////////////////////// + bool ok + () + const + { + return ( m_result & ResultWas::FailureBit ) != ResultWas::FailureBit; + } + + /////////////////////////////////////////////////////////////////////////// + ResultWas::OfType getResultType + () + const + { + return m_result; + } + + /////////////////////////////////////////////////////////////////////////// + bool hasExpression + () + const + { + return !m_expr.empty(); + } + + /////////////////////////////////////////////////////////////////////////// + bool hasMessage + () + const + { + return !m_message.empty(); + } + + /////////////////////////////////////////////////////////////////////////// + std::string getExpression + () + const + { + return m_expr; + } + + /////////////////////////////////////////////////////////////////////////// + bool hasExpandedExpression + () + const + { + return hasExpression() && getExpandedExpressionInternal() != m_expr; + } + + /////////////////////////////////////////////////////////////////////////// + std::string getExpandedExpression + () + const + { + return hasExpression() ? getExpandedExpressionInternal() : ""; + } + + /////////////////////////////////////////////////////////////////////////// + std::string getMessage + () + const + { + return m_message; + } + + /////////////////////////////////////////////////////////////////////////// + std::string getFilename + () + const + { + return m_lineInfo.file; + } + + /////////////////////////////////////////////////////////////////////////// + std::size_t getLine + () + const + { + return m_lineInfo.line; + } + + /////////////////////////////////////////////////////////////////////////// + std::string getTestMacroName + () + const + { + return m_macroName; + } + + protected: + + /////////////////////////////////////////////////////////////////////////// + std::string getExpandedExpressionInternal + () + const + { + if( m_op == "" || m_isNot ) + return m_lhs.empty() ? m_expr : m_op + m_lhs; + else if( m_op == "matches" ) + return m_lhs + " " + m_rhs; + else if( m_op != "!" ) + { + if( m_lhs.size() + m_rhs.size() < 30 ) + return m_lhs + " " + m_op + " " + m_rhs; + else if( m_lhs.size() < 70 && m_rhs.size() < 70 ) + return "\n\t" + m_lhs + "\n\t" + m_op + "\n\t" + m_rhs; + else + return "\n" + m_lhs + "\n" + m_op + "\n" + m_rhs + "\n\n"; + } + else + return "{can't expand - use " + m_macroName + "_FALSE( " + m_expr.substr(1) + " ) instead of " + m_macroName + "( " + m_expr + " ) for better diagnostics}"; + } + + /////////////////////////////////////////////////////////////////////////// + bool isNotExpression + ( + const char* expr + ) + { + return expr && expr[0] == '!'; + } + + protected: + std::string m_macroName; + SourceLineInfo m_lineInfo; + std::string m_expr, m_lhs, m_rhs, m_op; + std::string m_message; + ResultWas::OfType m_result; + bool m_isNot; + }; + +} // end namespace Catch + +// #included from: catch_evaluate.hpp + +namespace Catch +{ +namespace Internal +{ + enum Operator + { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template + struct OperatorTraits{ static const char* getName(){ return "*error - unknown operator*"; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return "=="; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return "!="; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return "<"; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return ">"; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return "<="; } }; + + template<> + struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs) + { + return const_cast( lhs ) == const_cast( rhs ); + } + }; + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs ) + { + return const_cast( lhs ) != const_cast( rhs ); + } + }; + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs ) + { + return const_cast( lhs ) < const_cast( rhs ); + } + }; + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs ) + { + return const_cast( lhs ) > const_cast( rhs ); + } + }; + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs ) + { + return const_cast( lhs ) >= const_cast( rhs ); + } + }; + template + struct Evaluator + { + static bool evaluate( const T1& lhs, const T2& rhs ) + { + return const_cast( lhs ) <= const_cast( rhs ); + } + }; + + template + bool applyEvaluator( const T1& lhs, const T2& rhs ) + { + return Evaluator::evaluate( lhs, rhs ); + } + + // "base" overload + template + bool compare( const T1& lhs, const T2& rhs ) + { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) + { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) + { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + template + bool compare( long lhs, const T* rhs ) + { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + + template + bool compare( long lhs, T* rhs ) + { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + + template + bool compare( const T* lhs, long rhs ) + { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + template + bool compare( T* lhs, long rhs ) + { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +} // end of namespace Internal +} // end of namespace Catch + +namespace Catch +{ + +struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + +class ResultInfoBuilder : public ResultInfo +{ +public: + + ResultInfoBuilder() {} + + ResultInfoBuilder( const char* expr, + bool isNot, + const SourceLineInfo& lineInfo, + const char* macroName, + const char* message = "" ) + : ResultInfo( expr, ResultWas::Unknown, isNot, lineInfo, macroName, message ) + {} + + void setResultType( ResultWas::OfType result ) { + // Flip bool results if isNot is set + if( m_isNot && result == ResultWas::Ok ) + m_result = ResultWas::ExpressionFailed; + else if( m_isNot && result == ResultWas::ExpressionFailed ) + m_result = ResultWas::Ok; + else + m_result = result; + } + + void setMessage( const std::string& message ) { + m_message = message; + } + + void setLineInfo( const SourceLineInfo& lineInfo ) { + m_lineInfo = lineInfo; + } + + void setLhs( const std::string& lhs ) { + m_lhs = lhs; + } + + void setRhs( const std::string& rhs ) { + m_rhs = rhs; + } + + void setOp( const std::string& op ) { + m_op = op; + } + + template + STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || + ( + const RhsT& + ); + + template + STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && + ( + const RhsT& + ); + +private: + friend class ResultBuilder; + template friend class Expression; + + template friend class PtrExpression; + + ResultInfoBuilder& captureBoolExpression( bool result ) { + m_lhs = Catch::toString( result ); + m_op = m_isNot ? "!" : ""; + setResultType( result ? ResultWas::Ok : ResultWas::ExpressionFailed ); + return *this; + } + + template + ResultInfoBuilder& captureExpression( const T1& lhs, const T2& rhs ) { + setResultType( Internal::compare( lhs, rhs ) ? ResultWas::Ok : ResultWas::ExpressionFailed ); + m_lhs = Catch::toString( lhs ); + m_rhs = Catch::toString( rhs ); + m_op = Internal::OperatorTraits::getName(); + return *this; + } + + template + ResultInfoBuilder& captureExpression( const T* lhs, int rhs ) { + return captureExpression( lhs, reinterpret_cast( rhs ) ); + } +}; + +} // end namespace Catch + +namespace Catch +{ + +template +class Expression +{ + void operator = ( const Expression& ); + +public: + Expression( ResultInfoBuilder& result, T lhs ) + : m_result( result ), + m_lhs( lhs ) + {} + + template + ResultInfoBuilder& operator == ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator != ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator < ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator > ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator <= ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator >= ( const RhsT& rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + ResultInfoBuilder& operator == ( bool rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + ResultInfoBuilder& operator != ( bool rhs ) { + return m_result.captureExpression( m_lhs, rhs ); + } + + operator ResultInfoBuilder& () { + return m_result.captureBoolExpression( m_lhs ); + } + + template + STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( const RhsT& ); + + template + STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( const RhsT& ); + +private: + ResultInfoBuilder& m_result; + T m_lhs; +}; + +template +class PtrExpression +{ +public: + + PtrExpression ( ResultInfoBuilder& result, const LhsT* lhs ) + : m_result( &result ), + m_lhs( lhs ) + {} + + template + ResultInfoBuilder& operator == ( const RhsT* rhs ) { + return m_result->captureExpression( m_lhs, rhs ); + } + + // This catches NULL + ResultInfoBuilder& operator == ( LhsT* rhs ) { + return m_result->captureExpression( m_lhs, rhs ); + } + + template + ResultInfoBuilder& operator != ( const RhsT* rhs ) { + return m_result->captureExpression( m_lhs, rhs ); + } + + // This catches NULL + ResultInfoBuilder& operator != ( LhsT* rhs ) { + return m_result->captureExpression( m_lhs, rhs ); + } + + operator ResultInfoBuilder& () { + return m_result->captureBoolExpression( m_lhs ); + } + +private: + ResultInfoBuilder* m_result; + const LhsT* m_lhs; +}; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h + +#include + +namespace Catch +{ + class TestCaseInfo; + class ScopedInfo; + class ResultInfoBuilder; + class ResultInfo; + + struct IResultCapture + { + virtual ~IResultCapture + () + {} + + virtual void testEnded + ( const ResultInfo& result + ) = 0; + virtual bool sectionStarted + ( const std::string& name, + const std::string& description, + const SourceLineInfo& lineInfo, + Counts& assertions + ) = 0; + virtual void sectionEnded + ( const std::string& name, + const Counts& assertions + ) = 0; + virtual void pushScopedInfo + ( ScopedInfo* scopedInfo + ) = 0; + virtual void popScopedInfo + ( ScopedInfo* scopedInfo + ) = 0; + virtual bool shouldDebugBreak + () const = 0; + + virtual ResultAction::Value acceptResult + ( bool result + ) = 0; + virtual ResultAction::Value acceptResult + ( ResultWas::OfType result + ) = 0; + virtual ResultAction::Value acceptExpression + ( const ResultInfoBuilder& resultInfo + ) = 0; + virtual void acceptMessage + ( const std::string& msg + ) = 0; + + virtual std::string getCurrentTestName + () const = 0; + virtual const ResultInfo* getLastResult + () const = 0; + + }; +} + +// #included from: catch_debugger.hpp + +#include + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch + { + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + inline bool isDebuggerActive() + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + { + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + assert(junk == 0); + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } + + // The following code snippet taken from: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define BreakIntoDebugger() \ + if( Catch::isDebuggerActive() ) \ + { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define BreakIntoDebugger() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #else + inline void BreakIntoDebugger(){} + #endif + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + #define BreakIntoDebugger() if (IsDebuggerPresent() ) { __debugbreak(); } + inline bool isDebuggerActive() + { + return IsDebuggerPresent() != 0; + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define BreakIntoDebugger() if (IsDebuggerPresent() ) { DebugBreak(); } + inline bool isDebuggerActive() + { + return IsDebuggerPresent() != 0; + } +#else + inline void BreakIntoDebugger(){} + inline bool isDebuggerActive() { return false; } +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); +inline void writeToDebugConsole( const std::string& text ) +{ + ::OutputDebugStringA( text.c_str() ); +} +#else +inline void writeToDebugConsole( const std::string& text ) +{ + // !TBD: Need a version for Mac/ XCode and other IDEs + std::cout << text; +} +#endif // CATCH_PLATFORM_WINDOWS + +#include + +namespace Catch +{ + +struct TestFailureException{}; +struct DummyExceptionType_DontUse{}; + +class ResultBuilder +{ +public: + + /////////////////////////////////////////////////////////////////////////// + ResultBuilder + ( + const SourceLineInfo& lineInfo, + const char* macroName, + const char* expr = "", + bool isNot = false + ) + : m_result( expr, isNot, lineInfo, macroName ), + m_messageStream() + {} + + /////////////////////////////////////////////////////////////////////////// + template + Expression operator->* + ( + const T & operand + ) + { + Expression expr( m_result, operand ); + + return expr; + } + + /////////////////////////////////////////////////////////////////////////// + Expression operator->* + ( + const char* const& operand + ) + { + Expression expr( m_result, operand ); + + return expr; + } + + /////////////////////////////////////////////////////////////////////////// + template + PtrExpression operator->* + ( + const T* operand + ) + { + PtrExpression expr( m_result, operand ); + + return expr; + } + + /////////////////////////////////////////////////////////////////////////// + template + PtrExpression operator->* + ( + T* operand + ) + { + PtrExpression expr( m_result, operand ); + + return expr; + } + + /////////////////////////////////////////////////////////////////////////// + Expression operator->* + ( + bool value + ) + { + Expression expr( m_result, value ); + return expr; + } + + /////////////////////////////////////////////////////////////////////////// + template + ResultBuilder& operator << + ( + const T & value + ) + { + m_messageStream << Catch::toString( value ); + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + template + ResultBuilder& acceptMatcher + ( + const MatcherT& matcher, + const ArgT& arg, + const std::string& matcherCallAsString + ) + { + std::string matcherAsString = Catch::toString( matcher ); + if( matcherAsString == "{?}" ) + matcherAsString = matcherCallAsString; + m_result.setLhs( Catch::toString( arg ) ); + m_result.setRhs( matcherAsString ); + m_result.setOp( "matches" ); + m_result.setResultType( matcher( arg ) ? ResultWas::Ok : ResultWas::ExpressionFailed ); + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + template + ResultBuilder& acceptMatcher + ( + const MatcherT& matcher, + ArgT* arg, + const std::string& matcherCallAsString + ) + { + std::string matcherAsString = Catch::toString( matcher ); + if( matcherAsString == "{?}" ) + matcherAsString = matcherCallAsString; + m_result.setLhs( Catch::toString( arg ) ); + m_result.setRhs( matcherAsString ); + m_result.setOp( "matches" ); + m_result.setResultType( matcher( arg ) ? ResultWas::Ok : ResultWas::ExpressionFailed ); + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + ResultBuilder& setResultType + ( + ResultWas::OfType resultType + ) + { + m_result.setResultType( resultType ); + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + operator ResultInfoBuilder& + () + { + m_result.setMessage( m_messageStream.str() ); + return m_result; + } + +private: + ResultInfoBuilder m_result; + std::ostringstream m_messageStream; + +}; + +class ScopedInfo +{ +public: + ScopedInfo() : m_oss() { + Context::getResultCapture().pushScopedInfo( this ); + } + + ~ScopedInfo() { + Context::getResultCapture().popScopedInfo( this ); + } + + template + ScopedInfo& operator << ( const T& value ) { + m_oss << value; + return *this; + } + + std::string getInfo () const { + return m_oss.str(); + } + +private: + std::ostringstream m_oss; +}; + +// This is just here to avoid compiler warnings with macro constants +inline bool isTrue( bool value ){ return value; } + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ACCEPT_EXPR( expr, stopOnFailure, originalExpr ) \ + if( Catch::ResultAction::Value internal_catch_action = Catch::Context::getResultCapture().acceptExpression( expr ) ) \ + { \ + if( internal_catch_action == Catch::ResultAction::DebugFailed ) BreakIntoDebugger(); \ + if( Catch::isTrue( stopOnFailure ) ) throw Catch::TestFailureException(); \ + if( Catch::isTrue( false ) ){ bool this_is_here_to_invoke_warnings = ( originalExpr ); Catch::isTrue( this_is_here_to_invoke_warnings ); } \ + } + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, isNot, stopOnFailure, macroName ) \ + do{ try{ \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr, isNot )->*expr ), stopOnFailure, expr ); \ + }catch( Catch::TestFailureException& ){ \ + throw; \ + } catch( ... ){ \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ) << Catch::Context::getExceptionTranslatorRegistry().translateActiveException() ).setResultType( Catch::ResultWas::ThrewException ), false, expr ); \ + throw; \ + }}while( Catch::isTrue( false ) ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, isNot, stopOnFailure, macroName ) \ + INTERNAL_CATCH_TEST( expr, isNot, stopOnFailure, macroName ); \ + if( Catch::Context::getResultCapture().getLastResult()->ok() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, isNot, stopOnFailure, macroName ) \ + INTERNAL_CATCH_TEST( expr, isNot, stopOnFailure, macroName ); \ + if( !Catch::Context::getResultCapture().getLastResult()->ok() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, stopOnFailure, macroName ) \ + try \ + { \ + expr; \ + INTERNAL_CATCH_ACCEPT_EXPR( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ).setResultType( Catch::ResultWas::Ok ), stopOnFailure, false ); \ + } \ + catch( ... ) \ + { \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ) << Catch::Context::getExceptionTranslatorRegistry().translateActiveException() ).setResultType( Catch::ResultWas::ThrewException ), stopOnFailure, false ); \ + } + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, exceptionType, stopOnFailure, macroName ) \ + try \ + { \ + expr; \ + INTERNAL_CATCH_ACCEPT_EXPR( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ).setResultType( Catch::ResultWas::DidntThrowException ), stopOnFailure, false ); \ + } \ + catch( Catch::TestFailureException& ) \ + { \ + throw; \ + } \ + catch( exceptionType ) \ + { \ + INTERNAL_CATCH_ACCEPT_EXPR( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ).setResultType( Catch::ResultWas::Ok ), stopOnFailure, false ); \ + } + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, stopOnFailure, macroName ) \ + INTERNAL_CATCH_THROWS( expr, exceptionType, stopOnFailure, macroName ) \ + catch( ... ) \ + { \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #expr ) << Catch::Context::getExceptionTranslatorRegistry().translateActiveException() ).setResultType( Catch::ResultWas::ThrewException ), stopOnFailure, false ); \ + } + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( reason, resultType, stopOnFailure, macroName ) \ + Catch::Context::getResultCapture().acceptExpression( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName ) << reason ).setResultType( resultType ) ); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_SCOPED_INFO( log ) \ + Catch::ScopedInfo INTERNAL_CATCH_UNIQUE_NAME( info ); \ + INTERNAL_CATCH_UNIQUE_NAME( info ) << log + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, stopOnFailure, macroName ) \ + do{ try{ \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #arg " " #matcher, false ).acceptMatcher( matcher, arg, #matcher ) ), stopOnFailure, false ); \ + }catch( Catch::TestFailureException& ){ \ + throw; \ + } catch( ... ){ \ + INTERNAL_CATCH_ACCEPT_EXPR( ( Catch::ResultBuilder( CATCH_INTERNAL_LINEINFO, macroName, #arg " " #matcher ) << Catch::Context::getExceptionTranslatorRegistry().translateActiveException() ).setResultType( Catch::ResultWas::ThrewException ), false, false ); \ + throw; \ + }}while( Catch::isTrue( false ) ) + +// #included from: internal/catch_section.hpp + +#include + +namespace Catch +{ + class Section + { + public: + /////////////////////////////////////////////////////////////////////// + Section + ( + const std::string& name, + const std::string& description, + const SourceLineInfo& lineInfo + ) + : m_name( name ), + m_sectionIncluded( Context::getResultCapture().sectionStarted( name, description, lineInfo, m_assertions ) ) + { + } + + /////////////////////////////////////////////////////////////////////// + ~Section + () + { + if( m_sectionIncluded ) + Context::getResultCapture().sectionEnded( m_name, m_assertions ); + } + + /////////////////////////////////////////////////////////////////////// + // This indicates whether the section should be executed or not + operator bool + () + { + return m_sectionIncluded; + } + + private: + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::Section( name, desc, CATCH_INTERNAL_LINEINFO ) ) + +// #included from: internal/catch_generators.hpp + +#include +#include +#include +#include + +namespace Catch +{ + +template +struct IGenerator +{ + virtual ~IGenerator + () + {} + + virtual T getValue + ( std::size_t index + ) const = 0; + + virtual std::size_t size + () const = 0; +}; + +template +class BetweenGenerator : public IGenerator +{ +public: + /////////////////////////////////////////////////////////////////////////// + BetweenGenerator + ( + T from, + T to + ) + : m_from( from ), + m_to( to ) + { + } + + /////////////////////////////////////////////////////////////////////////// + virtual T getValue + ( + std::size_t index + ) + const + { + return m_from+static_cast( index ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual std::size_t size + () + const + { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator +{ +public: + /////////////////////////////////////////////////////////////////////////// + ValuesGenerator + () + { + } + + /////////////////////////////////////////////////////////////////////////// + void add + ( + T value + ) + { + m_values.push_back( value ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual T getValue + ( + std::size_t index + ) + const + { + return m_values[index]; + } + + /////////////////////////////////////////////////////////////////////////// + virtual std::size_t size + () + const + { + return m_values.size(); + } + +private: + + std::vector m_values; +}; + +template +class CompositeGenerator +{ +public: + /////////////////////////////////////////////////////////////////////////// + CompositeGenerator() + : m_totalSize( 0 ) + { + } + + /////////////////////////////////////////////////////////////////////////// + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + /////////////////////////////////////////////////////////////////////////// + CompositeGenerator& setFileInfo + ( + const char* fileInfo + ) + { + m_fileInfo = fileInfo; + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + ~CompositeGenerator + () + { + deleteAll( m_composed ); + } + + /////////////////////////////////////////////////////////////////////////// + operator T + () + const + { + size_t overallIndex = Context::getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + /////////////////////////////////////////////////////////////////////////// + void add + ( + const IGenerator* generator + ) + { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + /////////////////////////////////////////////////////////////////////////// + CompositeGenerator& then + ( + CompositeGenerator& other + ) + { + move( other ); + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + CompositeGenerator& then + ( + T value + ) + { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + /////////////////////////////////////////////////////////////////////////// + void move + ( + CompositeGenerator& other + ) + { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + /////////////////////////////////////////////////////////////////////////// + template + CompositeGenerator between + ( + T from, + T to + ) + { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + /////////////////////////////////////////////////////////////////////////// + template + CompositeGenerator values + ( + T val1, + T val2 + ) + { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + /////////////////////////////////////////////////////////////////////////// + template + CompositeGenerator values + ( + T val1, + T val2, + T val3 + ) + { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + /////////////////////////////////////////////////////////////////////////// + template + CompositeGenerator values + ( + T val1, + T val2, + T val3, + T val4 + ) + { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h + +#include + +namespace Catch +{ + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator + { + virtual ~IExceptionTranslator(){} + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry + { + virtual ~IExceptionTranslatorRegistry + () + {} + + virtual void registerTranslator + ( IExceptionTranslator* translator + ) = 0; + virtual std::string translateActiveException + () const = 0; + + }; + + class ExceptionTranslatorRegistrar + { + template + class ExceptionTranslator : public IExceptionTranslator + { + public: + + ExceptionTranslator + ( + std::string(*translateFunction)( T& ) + ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate + () + const + { + try + { + throw; + } + catch( T& ex ) + { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar + ( + std::string(*translateFunction)( T& ) + ) + { + Catch::Context::getExceptionTranslatorRegistry().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp + +#include +#include + +namespace Catch +{ + namespace Detail + { + class Approx + { + public: + /////////////////////////////////////////////////////////////////////////// + explicit Approx + ( + double value + ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + { + } + + /////////////////////////////////////////////////////////////////////////// + Approx + ( + const Approx& other + ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + { + } + + /////////////////////////////////////////////////////////////////////////// + static Approx custom + () + { + return Approx( 0 ); + } + + /////////////////////////////////////////////////////////////////////////// + Approx operator() + ( + double value + ) + { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + /////////////////////////////////////////////////////////////////////////// + friend bool operator == + ( + double lhs, + const Approx& rhs + ) + { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + /////////////////////////////////////////////////////////////////////////// + friend bool operator == + ( + const Approx& lhs, + double rhs + ) + { + return operator==( rhs, lhs ); + } + + /////////////////////////////////////////////////////////////////////////// + friend bool operator != + ( + double lhs, + const Approx& rhs + ) + { + return !operator==( lhs, rhs ); + } + + /////////////////////////////////////////////////////////////////////////// + friend bool operator != + ( + const Approx& lhs, + double rhs + ) + { + return !operator==( rhs, lhs ); + } + + /////////////////////////////////////////////////////////////////////////// + Approx& epsilon + ( + double newEpsilon + ) + { + m_epsilon = newEpsilon; + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + Approx& scale + ( + double newScale + ) + { + m_scale = newScale; + return *this; + } + + /////////////////////////////////////////////////////////////////////////// + std::string toString() const + { + std::ostringstream oss; + oss << "Approx( " << m_value << ")"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; + } + + /////////////////////////////////////////////////////////////////////////////// + template<> + inline std::string toString + ( + const Detail::Approx& value + ) + { + return value.toString(); + } + +} // end namespace Catch + +// #included from: internal/catch_test_case_info.hpp + +#include +#include + +namespace Catch +{ + class TestCaseInfo + { + public: + /////////////////////////////////////////////////////////////////////// + TestCaseInfo + ( + ITestCase* testCase, + const char* name, + const char* description, + const SourceLineInfo& lineInfo + ) + : m_test( testCase ), + m_name( name ), + m_description( description ), + m_lineInfo( lineInfo ) + { + } + + /////////////////////////////////////////////////////////////////////// + TestCaseInfo + () + : m_test( NULL ), + m_name(), + m_description() + { + } + + /////////////////////////////////////////////////////////////////////// + TestCaseInfo + ( + const TestCaseInfo& other + ) + : m_test( other.m_test->clone() ), + m_name( other.m_name ), + m_description( other.m_description ), + m_lineInfo( other.m_lineInfo ) + { + } + + /////////////////////////////////////////////////////////////////////// + TestCaseInfo + ( + const TestCaseInfo& other, + const std::string& name + ) + : m_test( other.m_test->clone() ), + m_name( name ), + m_description( other.m_description ), + m_lineInfo( other.m_lineInfo ) + { + } + + /////////////////////////////////////////////////////////////////////// + TestCaseInfo& operator = + ( + const TestCaseInfo& other + ) + { + TestCaseInfo temp( other ); + swap( temp ); + return *this; + } + + /////////////////////////////////////////////////////////////////////// + ~TestCaseInfo + () + { + delete m_test; + } + + /////////////////////////////////////////////////////////////////////// + void invoke + () + const + { + m_test->invoke(); + } + + /////////////////////////////////////////////////////////////////////// + const std::string& getName + () + const + { + return m_name; + } + + /////////////////////////////////////////////////////////////////////// + const std::string& getDescription + () + const + { + return m_description; + } + + /////////////////////////////////////////////////////////////////////// + const SourceLineInfo& getLineInfo + () + const + { + return m_lineInfo; + } + + /////////////////////////////////////////////////////////////////////// + bool isHidden + () + const + { + return m_name.size() >= 2 && m_name[0] == '.' && m_name[1] == '/'; + } + + /////////////////////////////////////////////////////////////////////// + void swap + ( + TestCaseInfo& other + ) + { + std::swap( m_test, other.m_test ); + m_name.swap( other.m_name ); + m_description.swap( other.m_description ); + m_lineInfo.swap( other.m_lineInfo ); + } + + /////////////////////////////////////////////////////////////////////// + bool operator == + ( + const TestCaseInfo& other + ) + const + { + return *m_test == *other.m_test && m_name == other.m_name; + } + + /////////////////////////////////////////////////////////////////////// + bool operator < + ( + const TestCaseInfo& other + ) + const + { + return m_name < other.m_name; + } + + private: + ITestCase* m_test; + std::string m_name; + std::string m_description; + SourceLineInfo m_lineInfo; + }; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + class TestSpec + { + public: + /////////////////////////////////////////////////////////////////////// + TestSpec + ( + const std::string& rawSpec + ) + : m_rawSpec( rawSpec ), + m_isWildcarded( false ) + { + if( m_rawSpec[m_rawSpec.size()-1] == '*' ) + { + m_rawSpec = m_rawSpec.substr( 0, m_rawSpec.size()-1 ); + m_isWildcarded = true; + } + } + + /////////////////////////////////////////////////////////////////////// + bool matches + ( + const std::string& testName + ) + const + { + if( !m_isWildcarded ) + return m_rawSpec == testName; + else + return testName.size() >= m_rawSpec.size() && testName.substr( 0, m_rawSpec.size() ) == m_rawSpec; + } + + private: + std::string m_rawSpec; + bool m_isWildcarded; + }; +} + +// #included from: internal/catch_matchers.hpp + +namespace Catch +{ +namespace Matchers +{ + namespace Impl + { + namespace StdString + { + struct Contains + { + Contains( const std::string& substr ) : m_substr( substr ){} + + bool operator()( const std::string& str ) const + { + return str.find( m_substr ) != std::string::npos; + } + + friend std::ostream& operator<<( std::ostream& os, const Contains& matcher ) + { + os << "contains: \"" << matcher.m_substr << "\""; + return os; + } + std::string m_substr; + }; + + struct StartsWith + { + StartsWith( const std::string& substr ) : m_substr( substr ){} + + bool operator()( const std::string& str ) const + { + return str.find( m_substr ) == 0; + } + + friend std::ostream& operator<<( std::ostream& os, const StartsWith& matcher ) + { + os << "starts with: \"" << matcher.m_substr << "\""; + return os; + } + std::string m_substr; + }; + + struct EndsWith + { + EndsWith( const std::string& substr ) : m_substr( substr ){} + + bool operator()( const std::string& str ) const + { + return str.find( m_substr ) == str.size() - m_substr.size(); + } + + friend std::ostream& operator<<( std::ostream& os, const EndsWith& matcher ) + { + os << "ends with: \"" << matcher.m_substr << "\""; + return os; + } + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + inline Impl::StdString::Contains Contains( const std::string& substr ){ return Impl::StdString::Contains( substr ); } + inline Impl::StdString::StartsWith StartsWith( const std::string& substr ){ return Impl::StdString::StartsWith( substr ); } + inline Impl::StdString::EndsWith EndsWith( const std::string& substr ){ return Impl::StdString::EndsWith( substr ); } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp + +#import +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED + inline void arcSafeRelease( NSObject* obj ) + { + [obj release]; + } + inline id performOptionalSelector( id obj, SEL sel ) + { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; + } + #define CATCH_UNSAFE_UNRETAINED +#else + inline void arcSafeRelease( NSObject* ){} + inline id performOptionalSelector( id obj, SEL sel ) + { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Warc-performSelector-leaks" + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + #pragma clang diagnostic pop + return nil; + } + #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#endif + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch +{ + class OcMethod : public ITestCase + { + public: + /////////////////////////////////////////////////////////////////////// + OcMethod + ( + Class cls, + SEL sel + ) + : m_cls( cls ), + m_sel( sel ) + { + } + + /////////////////////////////////////////////////////////////////////// + virtual void invoke + () + const + { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + + /////////////////////////////////////////////////////////////////////// + virtual ITestCase* clone + () + const + { + return new OcMethod( m_cls, m_sel ); + } + + /////////////////////////////////////////////////////////////////////// + virtual bool operator == + ( + const ITestCase& other + ) + const + { + const OcMethod* ocmOther = dynamic_cast ( &other ); + return ocmOther && ocmOther->m_sel == m_sel; + } + + /////////////////////////////////////////////////////////////////////// + virtual bool operator < + ( + const ITestCase& other + ) + const + { + const OcMethod* ocmOther = dynamic_cast ( &other ); + return ocmOther && ocmOther->m_sel < m_sel; + } + + private: + Class m_cls; + SEL m_sel; + }; + + namespace Detail + { + + /////////////////////////////////////////////////////////////////////// + inline bool startsWith + ( + const std::string& str, + const std::string& sub + ) + { + return str.length() > sub.length() && str.substr( 0, sub.length() ) == sub; + } + + /////////////////////////////////////////////////////////////////////// + inline std::string getAnnotation + ( + Class cls, + const std::string& annotationName, + const std::string& testCaseName + ) + { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + /////////////////////////////////////////////////////////////////////////// + inline size_t registerTestMethods + () + { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) + { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( int m = 0; m < count ; m++ ) + { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( Detail::startsWith( methodName, "Catch_TestCase_" ) ) + { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + + Context::getTestCaseRegistry().registerTest( TestCaseInfo( new OcMethod( cls, selector ), name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + + } + } + free(methods); + } + } + return noTestMethods; + } + + inline std::string toString( NSString* const& nsstring ) + { + return std::string( "@\"" ) + [nsstring UTF8String] + "\""; + } + + namespace Matchers + { + namespace Impl + { + namespace NSStringMatchers + { + struct StringHolder + { + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder() + { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder + { + Equals( NSString* substr ) : StringHolder( substr ){} + + bool operator()( NSString* str ) const + { + return [str isEqualToString:m_substr]; + } + + friend std::ostream& operator<<( std::ostream& os, const Equals& matcher ) + { + os << "equals string: " << Catch::toString( matcher.m_substr ); + return os; + } + }; + + struct Contains : StringHolder + { + Contains( NSString* substr ) : StringHolder( substr ){} + + bool operator()( NSString* str ) const + { + return [str rangeOfString:m_substr].location != NSNotFound; + } + + friend std::ostream& operator<<( std::ostream& os, const Contains& matcher ) + { + os << "contains: " << Catch::toString( matcher.m_substr ); + return os; + } + }; + + struct StartsWith : StringHolder + { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + bool operator()( NSString* str ) const + { + return [str rangeOfString:m_substr].location == 0; + } + + friend std::ostream& operator<<( std::ostream& os, const StartsWith& matcher ) + { + os << "starts with: " << Catch::toString( matcher.m_substr ); + return os; + } + }; + struct EndsWith : StringHolder + { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + bool operator()( NSString* str ) const + { + return [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + friend std::ostream& operator<<( std::ostream& os, const EndsWith& matcher ) + { + os << "ends with: " << Catch::toString( matcher.m_substr ); + return os; + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#if defined( CATCH_CONFIG_MAIN ) || defined( CATCH_CONFIG_RUNNER ) +// #included from: catch_runner.hpp + +// #included from: internal/catch_context_impl.hpp +// #included from: catch_test_case_registry_impl.hpp + +#include +#include +#include +#include + +namespace Catch +{ + class TestRegistry : public ITestCaseRegistry { + public: + TestRegistry() : m_unnamedCount( 0 ) {} + + virtual void registerTest( const TestCaseInfo& testInfo ) { + if( testInfo.getName() == "" ) { + std::ostringstream oss; + oss << testInfo.getName() << "unnamed/" << ++m_unnamedCount; + return registerTest( TestCaseInfo( testInfo, oss.str() ) ); + } + + if( m_functions.find( testInfo ) == m_functions.end() ) { + m_functions.insert( testInfo ); + m_functionsInOrder.push_back( testInfo ); + } + else { + const TestCaseInfo& prev = *m_functions.find( testInfo ); + std::cerr << "error: TEST_CASE( \"" << testInfo.getName() << "\" ) already defined.\n" + << "\tFirst seen at " << SourceLineInfo( prev.getLineInfo() ) << "\n" + << "\tRedefined at " << SourceLineInfo( testInfo.getLineInfo() ) << std::endl; + exit(1); + } + } + + virtual const std::vector& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector getMatchingTestCases( const std::string& rawTestSpec ) { + TestSpec testSpec( rawTestSpec ); + + std::vector testList; + std::vector::const_iterator it = m_functionsInOrder.begin(); + std::vector::const_iterator itEnd = m_functionsInOrder.end(); + for(; it != itEnd; ++it ) { + if( testSpec.matches( it->getName() ) ) { + testList.push_back( *it ); + } + } + return testList; + } + + private: + + std::set m_functions; + std::vector m_functionsInOrder; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public ITestCase { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + virtual ITestCase* clone() const { + return new FreeFunctionTestCase( m_fun ); + } + + virtual bool operator == ( const ITestCase& other ) const { + const FreeFunctionTestCase* ffOther = dynamic_cast ( &other ); + return ffOther && m_fun == ffOther->m_fun; + } + + virtual bool operator < ( const ITestCase& other ) const { + const FreeFunctionTestCase* ffOther = dynamic_cast ( &other ); + return ffOther && m_fun < ffOther->m_fun; + } + + private: + TestFunction m_fun; + }; + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + const char* name, + const char* description, + const SourceLineInfo& lineInfo ) { + registerTestCase( new FreeFunctionTestCase( function ), name, description, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + const char* name, + const char* description, + const SourceLineInfo& lineInfo ) { + Context::getTestCaseRegistry().registerTest( TestCaseInfo( testCase, name, description, lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_runner_impl.hpp + +// #included from: catch_interfaces_runner.h + +#include + +namespace Catch +{ + class TestCaseInfo; + + struct IRunner + { + virtual ~IRunner + () + {} + + virtual void runAll + ( bool runHiddenTests = false + ) = 0; + + virtual std::size_t runMatching + ( const std::string& rawTestSpec + ) = 0; + + virtual Totals getTotals + () const = 0; + + }; +} + +// #included from: catch_config.hpp + +#include +#include +#include +#include + +namespace Catch +{ + + class Config : public IReporterConfig + { + private: + Config( const Config& other ); + Config& operator = ( const Config& other ); + public: + + struct Include { enum What + { + FailedOnly, + SuccessfulResults + }; }; + + struct List{ enum What + { + None = 0, + + Reports = 1, + Tests = 2, + All = 3, + + WhatMask = 0xf, + + AsText = 0x10, + AsXml = 0x11, + + AsMask = 0xf0 + }; }; + + /////////////////////////////////////////////////////////////////////////// + Config() + : m_listSpec( List::None ), + m_shouldDebugBreak( false ), + m_showHelp( false ), + m_streambuf( NULL ), + m_os( std::cout.rdbuf() ), + m_includeWhat( Include::FailedOnly ) + {} + + /////////////////////////////////////////////////////////////////////////// + ~Config() + { + m_os.rdbuf( std::cout.rdbuf() ); + delete m_streambuf; + } + + /////////////////////////////////////////////////////////////////////////// + void setReporter( const std::string& reporterName ) + { + if( m_reporter.get() ) + return setError( "Only one reporter may be specified" ); + setReporter( Context::getReporterRegistry().create( reporterName, *this ) ); + } + + /////////////////////////////////////////////////////////////////////////// + void addTestSpec( const std::string& testSpec ) + { + m_testSpecs.push_back( testSpec ); + } + + /////////////////////////////////////////////////////////////////////////// + bool testsSpecified() const + { + return !m_testSpecs.empty(); + } + + /////////////////////////////////////////////////////////////////////////// + const std::vector& getTestSpecs() const + { + return m_testSpecs; + } + + /////////////////////////////////////////////////////////////////////////// + List::What getListSpec( void ) const + { + return m_listSpec; + } + + /////////////////////////////////////////////////////////////////////////// + void setListSpec( List::What listSpec ) + { + m_listSpec = listSpec; + } + + /////////////////////////////////////////////////////////////////////////// + void setFilename( const std::string& filename ) + { + m_filename = filename; + } + + /////////////////////////////////////////////////////////////////////////// + const std::string& getFilename() const + { + return m_filename; + } + + /////////////////////////////////////////////////////////////////////////// + const std::string& getMessage() const + { + return m_message; + } + + /////////////////////////////////////////////////////////////////////////// + void setError( const std::string& errorMessage ) + { + m_message = errorMessage + "\n\n" + "Usage: ..."; + } + + /////////////////////////////////////////////////////////////////////////// + void setReporter( IReporter* reporter ) + { + m_reporter = reporter; + } + + /////////////////////////////////////////////////////////////////////////// + Ptr getReporter() + { + if( !m_reporter.get() ) + const_cast( this )->setReporter( Context::getReporterRegistry().create( "basic", *this ) ); + return m_reporter; + } + + /////////////////////////////////////////////////////////////////////////// + List::What listWhat() const + { + return static_cast( m_listSpec & List::WhatMask ); + } + + /////////////////////////////////////////////////////////////////////////// + List::What listAs() const + { + return static_cast( m_listSpec & List::AsMask ); + } + + /////////////////////////////////////////////////////////////////////////// + void setIncludeWhat( Include::What includeWhat ) + { + m_includeWhat = includeWhat; + } + + /////////////////////////////////////////////////////////////////////////// + void setShouldDebugBreak( bool shouldDebugBreakFlag ) + { + m_shouldDebugBreak = shouldDebugBreakFlag; + } + + /////////////////////////////////////////////////////////////////////////// + void setName( const std::string& name ) + { + m_name = name; + } + + /////////////////////////////////////////////////////////////////////////// + std::string getName() const + { + return m_name; + } + + /////////////////////////////////////////////////////////////////////////// + bool shouldDebugBreak() const + { + return m_shouldDebugBreak; + } + + /////////////////////////////////////////////////////////////////////////// + void setShowHelp( bool showHelpFlag ) + { + m_showHelp = showHelpFlag; + } + + /////////////////////////////////////////////////////////////////////////// + bool showHelp() const + { + return m_showHelp; + } + + /////////////////////////////////////////////////////////////////////////// + virtual std::ostream& stream() const + { + return m_os; + } + + /////////////////////////////////////////////////////////////////////////// + void setStreamBuf( std::streambuf* buf ) + { + m_os.rdbuf( buf ? buf : std::cout.rdbuf() ); + } + + /////////////////////////////////////////////////////////////////////////// + void useStream( const std::string& streamName ) + { + std::streambuf* newBuf = Context::createStreamBuf( streamName ); + setStreamBuf( newBuf ); + delete m_streambuf; + m_streambuf = newBuf; + } + + /////////////////////////////////////////////////////////////////////////// + virtual bool includeSuccessfulResults() const + { + return m_includeWhat == Include::SuccessfulResults; + } + + private: + Ptr m_reporter; + std::string m_filename; + std::string m_message; + List::What m_listSpec; + std::vector m_testSpecs; + bool m_shouldDebugBreak; + bool m_showHelp; + std::streambuf* m_streambuf; + mutable std::ostream m_os; + Include::What m_includeWhat; + std::string m_name; + + }; + +} // end namespace Catch + +// #included from: catch_running_test.hpp + +// #included from: catch_section_info.hpp + +#include +#include + +namespace Catch +{ + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + class SectionInfo + { + public: + enum Status + { + Root, + Unknown, + Branch, + TestedBranch, + TestedLeaf + }; + + /////////////////////////////////////////////////////////////////////// + SectionInfo + ( + SectionInfo* parent + ) + : m_status( Unknown ), + m_parent( parent ) + { + } + + /////////////////////////////////////////////////////////////////////// + SectionInfo + () + : m_status( Root ), + m_parent( NULL ) + { + } + + /////////////////////////////////////////////////////////////////////// + ~SectionInfo + () + { + deleteAllValues( m_subSections ); + } + + /////////////////////////////////////////////////////////////////////// + bool shouldRun + () + const + { + return m_status < TestedBranch; + } + + /////////////////////////////////////////////////////////////////////// + bool ran + () + { + if( m_status < Branch ) + { + m_status = TestedLeaf; + return true; + } + return false; + } + /////////////////////////////////////////////////////////////////////// + void ranToCompletion + () + { + if( m_status == Branch && !hasUntestedSections() ) + { + m_status = TestedBranch; + } + } + + /////////////////////////////////////////////////////////////////////// + SectionInfo* findSubSection + ( + const std::string& name + ) + { + std::map::const_iterator it = m_subSections.find( name ); + return it != m_subSections.end() + ? it->second + : NULL; + } + + /////////////////////////////////////////////////////////////////////// + SectionInfo* addSubSection + ( + const std::string& name + ) + { + SectionInfo* subSection = new SectionInfo( this ); + m_subSections.insert( std::make_pair( name, subSection ) ); + m_status = Branch; + return subSection; + } + + /////////////////////////////////////////////////////////////////////// + SectionInfo* getParent + () + { + return m_parent; + } + + /////////////////////////////////////////////////////////////////////// + bool hasUntestedSections + () + const + { + if( m_status == Unknown ) + return true; + + std::map::const_iterator it = m_subSections.begin(); + std::map::const_iterator itEnd = m_subSections.end(); + for(; it != itEnd; ++it ) + { + if( it->second->hasUntestedSections() ) + return true; + } + return false; + } + + private: + Status m_status; + std::map m_subSections; + SectionInfo* m_parent; + }; +} + +namespace Catch +{ + class RunningTest + { + enum RunStatus + { + NothingRun, + EncounteredASection, + RanAtLeastOneSection, + RanToCompletionWithSections, + RanToCompletionWithNoSections + }; + + public: + /////////////////////////////////////////////////////////////////////// + explicit RunningTest + ( + const TestCaseInfo* info = NULL + ) + : m_info( info ), + m_runStatus( RanAtLeastOneSection ), + m_currentSection( &m_rootSection ), + m_changed( false ) + { + } + + /////////////////////////////////////////////////////////////////////// + bool wasSectionSeen + () + const + { + return m_runStatus == RanAtLeastOneSection || + m_runStatus == RanToCompletionWithSections; + } + + /////////////////////////////////////////////////////////////////////// + void reset + () + { + m_runStatus = NothingRun; + m_changed = false; + m_lastSectionToRun = NULL; + } + + /////////////////////////////////////////////////////////////////////// + void ranToCompletion + () + { + if( m_runStatus == RanAtLeastOneSection || + m_runStatus == EncounteredASection ) + { + m_runStatus = RanToCompletionWithSections; + if( m_lastSectionToRun ) + { + m_lastSectionToRun->ranToCompletion(); + m_changed = true; + } + } + else + { + m_runStatus = RanToCompletionWithNoSections; + } + } + + /////////////////////////////////////////////////////////////////////// + bool addSection + ( + const std::string& name + ) + { + if( m_runStatus == NothingRun ) + m_runStatus = EncounteredASection; + + SectionInfo* thisSection = m_currentSection->findSubSection( name ); + if( !thisSection ) + { + thisSection = m_currentSection->addSubSection( name ); + m_changed = true; + } + + if( !wasSectionSeen() && thisSection->shouldRun() ) + { + m_currentSection = thisSection; + m_lastSectionToRun = NULL; + return true; + } + return false; + } + + /////////////////////////////////////////////////////////////////////// + void endSection + ( + const std::string& + ) + { + if( m_currentSection->ran() ) + { + m_runStatus = RanAtLeastOneSection; + m_changed = true; + } + else if( m_runStatus == EncounteredASection ) + { + m_runStatus = RanAtLeastOneSection; + m_lastSectionToRun = m_currentSection; + } + m_currentSection = m_currentSection->getParent(); + } + + /////////////////////////////////////////////////////////////////////// + const TestCaseInfo& getTestCaseInfo + () + const + { + return *m_info; + } + + /////////////////////////////////////////////////////////////////////// + bool hasUntestedSections + () + const + { + return m_runStatus == RanAtLeastOneSection || + ( m_rootSection.hasUntestedSections() && m_changed ); + } + + private: + const TestCaseInfo* m_info; + RunStatus m_runStatus; + SectionInfo m_rootSection; + SectionInfo* m_currentSection; + SectionInfo* m_lastSectionToRun; + bool m_changed; + }; +} + +#include +#include + +namespace Catch +{ + class StreamRedirect + { + public: + /////////////////////////////////////////////////////////////////////// + StreamRedirect + ( + std::ostream& stream, + std::string& targetString + ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + /////////////////////////////////////////////////////////////////////// + ~StreamRedirect + () + { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + class Runner : public IResultCapture, public IRunner + { + Runner( const Runner& ); + void operator =( const Runner& ); + + public: + + /////////////////////////////////////////////////////////////////////////// + explicit Runner + ( + Config& config + ) + : m_runningTest( NULL ), + m_config( config ), + m_reporter( config.getReporter() ), + m_prevRunner( &Context::getRunner() ), + m_prevResultCapture( &Context::getResultCapture() ) + { + Context::setRunner( this ); + Context::setResultCapture( this ); + m_reporter->StartTesting(); + } + + /////////////////////////////////////////////////////////////////////////// + ~Runner + () + { + m_reporter->EndTesting( m_totals ); + Context::setRunner( m_prevRunner ); + Context::setResultCapture( m_prevResultCapture ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void runAll + ( + bool runHiddenTests = false + ) + { + std::vector allTests = Context::getTestCaseRegistry().getAllTests(); + for( std::size_t i=0; i < allTests.size(); ++i ) + { + if( runHiddenTests || !allTests[i].isHidden() ) + runTest( allTests[i] ); + } + } + + /////////////////////////////////////////////////////////////////////////// + virtual std::size_t runMatching + ( + const std::string& rawTestSpec + ) + { + TestSpec testSpec( rawTestSpec ); + + std::vector allTests = Context::getTestCaseRegistry().getAllTests(); + std::size_t testsRun = 0; + for( std::size_t i=0; i < allTests.size(); ++i ) + { + if( testSpec.matches( allTests[i].getName() ) ) + { + runTest( allTests[i] ); + testsRun++; + } + } + return testsRun; + } + + /////////////////////////////////////////////////////////////////////////// + void runTest + ( + const TestCaseInfo& testInfo + ) + { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + m_reporter->StartTestCase( testInfo ); + + m_runningTest = new RunningTest( &testInfo ); + + do + { + do + { + m_reporter->StartGroup( "test case run" ); + m_currentResult.setLineInfo( m_runningTest->getTestCaseInfo().getLineInfo() ); + runCurrentTest( redirectedCout, redirectedCerr ); + m_reporter->EndGroup( "test case run", m_totals - prevTotals ); + } + while( m_runningTest->hasUntestedSections() ); + } + while( Context::advanceGeneratorsForCurrentTest() ); + + delete m_runningTest; + m_runningTest = NULL; + + if( m_totals.assertions.failed > prevTotals.assertions.failed ) + ++m_totals.testCases.failed; + else + ++m_totals.testCases.passed; + + m_reporter->EndTestCase( testInfo, m_totals - prevTotals, redirectedCout, redirectedCerr ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual Totals getTotals + () + const + { + return m_totals; + } + + private: // IResultCapture + + /////////////////////////////////////////////////////////////////////////// + virtual ResultAction::Value acceptResult + ( + bool result + ) + { + return acceptResult( result ? ResultWas::Ok : ResultWas::ExpressionFailed ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual ResultAction::Value acceptResult + ( + ResultWas::OfType result + ) + { + m_currentResult.setResultType( result ); + return actOnCurrentResult(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual ResultAction::Value acceptExpression + ( + const ResultInfoBuilder& resultInfo + ) + { + m_currentResult = resultInfo; + return actOnCurrentResult(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void acceptMessage + ( + const std::string& msg + ) + { + m_currentResult.setMessage( msg ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void testEnded + ( + const ResultInfo& result + ) + { + if( result.getResultType() == ResultWas::Ok ) + { + m_totals.assertions.passed++; + } + else if( !result.ok() ) + { + m_totals.assertions.failed++; + + std::vector::const_iterator it = m_info.begin(); + std::vector::const_iterator itEnd = m_info.end(); + for(; it != itEnd; ++it ) + m_reporter->Result( *it ); + m_info.clear(); + } + + if( result.getResultType() == ResultWas::Info ) + m_info.push_back( result ); + else + m_reporter->Result( result ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual bool sectionStarted + ( + const std::string& name, + const std::string& description, + const SourceLineInfo& lineInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << name << "@" << lineInfo; + + if( !m_runningTest->addSection( oss.str() ) ) + return false; + + m_currentResult.setLineInfo( lineInfo ); + m_reporter->StartSection( name, description ); + assertions = m_totals.assertions; + + return true; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void sectionEnded + ( + const std::string& name, + const Counts& prevAssertions + ) + { + m_runningTest->endSection( name ); + m_reporter->EndSection( name, m_totals.assertions - prevAssertions ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void pushScopedInfo + ( + ScopedInfo* scopedInfo + ) + { + m_scopedInfos.push_back( scopedInfo ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void popScopedInfo + ( + ScopedInfo* scopedInfo + ) + { + if( m_scopedInfos.back() == scopedInfo ) + m_scopedInfos.pop_back(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual bool shouldDebugBreak + () + const + { + return m_config.shouldDebugBreak(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual std::string getCurrentTestName + () + const + { + return m_runningTest + ? m_runningTest->getTestCaseInfo().getName() + : ""; + } + + /////////////////////////////////////////////////////////////////////////// + virtual const ResultInfo* getLastResult + () + const + { + return &m_lastResult; + } + + private: + + /////////////////////////////////////////////////////////////////////////// + ResultAction::Value actOnCurrentResult + () + { + testEnded( m_currentResult ); + m_lastResult = m_currentResult; + + m_currentResult = ResultInfoBuilder(); + if( m_lastResult.ok() ) + return ResultAction::None; + else if( shouldDebugBreak() ) + return ResultAction::DebugFailed; + else + return ResultAction::Failed; + } + + /////////////////////////////////////////////////////////////////////////// + void runCurrentTest + ( + std::string& redirectedCout, + std::string& redirectedCerr + ) + { + try + { + m_runningTest->reset(); + if( m_reporter->shouldRedirectStdout() ) + { + StreamRedirect coutRedir( std::cout, redirectedCout ); + StreamRedirect cerrRedir( std::cerr, redirectedCerr ); + m_runningTest->getTestCaseInfo().invoke(); + } + else + { + m_runningTest->getTestCaseInfo().invoke(); + } + m_runningTest->ranToCompletion(); + } + catch( TestFailureException& ) + { + // This just means the test was aborted due to failure + } + catch(...) + { + acceptMessage( Catch::Context::getExceptionTranslatorRegistry().translateActiveException() ); + acceptResult( ResultWas::ThrewException ); + } + m_info.clear(); + } + + private: + RunningTest* m_runningTest; + ResultInfoBuilder m_currentResult; + ResultInfo m_lastResult; + + const Config& m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_scopedInfos; + std::vector m_info; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + }; +} + +// #included from: catch_generators_impl.hpp + +#include +#include +#include + +namespace Catch +{ + struct GeneratorInfo + { + /////////////////////////////////////////////////////////////////////// + GeneratorInfo + ( + std::size_t size + ) + : m_size( size ), + m_currentIndex( 0 ) + { + } + + /////////////////////////////////////////////////////////////////////// + bool moveNext + () + { + if( ++m_currentIndex == m_size ) + { + m_currentIndex = 0; + return false; + } + return true; + } + + /////////////////////////////////////////////////////////////////////// + std::size_t getCurrentIndex + () + const + { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest + { + + public: + /////////////////////////////////////////////////////////////////////// + ~GeneratorsForTest + () + { + deleteAll( m_generatorsInOrder ); + } + + /////////////////////////////////////////////////////////////////////// + GeneratorInfo& getGeneratorInfo + ( + const std::string& fileInfo, + std::size_t size + ) + { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) + { + GeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + /////////////////////////////////////////////////////////////////////// + bool moveNext + () + { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) + { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: catch_console_colour_impl.hpp + +// #included from: catch_console_colour.hpp + +namespace Catch +{ + struct ConsoleColourImpl; + + class TextColour : NonCopyable + { + public: + + enum Colours + { + None, + + FileName, + ResultError, + ResultSuccess, + + Error, + Success, + + OriginalExpression, + ReconstructedExpression + }; + + TextColour( Colours colour = None ); + void set( Colours colour ); + ~TextColour(); + + private: + ConsoleColourImpl* m_impl; + }; + +} // end namespace Catch + +#ifdef CATCH_PLATFORM_WINDOWS + +#include + +namespace Catch +{ + namespace + { + WORD mapConsoleColour( TextColour::Colours colour ) + { + switch( colour ) + { + case TextColour::FileName: + return FOREGROUND_INTENSITY; // greyed out + case TextColour::ResultError: + return FOREGROUND_RED | FOREGROUND_INTENSITY; // bright red + case TextColour::ResultSuccess: + return FOREGROUND_GREEN | FOREGROUND_INTENSITY; // bright green + case TextColour::Error: + return FOREGROUND_RED; // dark red + case TextColour::Success: + return FOREGROUND_GREEN; // dark green + case TextColour::OriginalExpression: + return FOREGROUND_BLUE | FOREGROUND_GREEN; // turquoise + case TextColour::ReconstructedExpression: + return FOREGROUND_RED | FOREGROUND_GREEN; // greeny-yellow + default: return 0; + } + } + } + + struct ConsoleColourImpl + { + ConsoleColourImpl() + : hStdout( GetStdHandle(STD_OUTPUT_HANDLE) ), + wOldColorAttrs( 0 ) + { + GetConsoleScreenBufferInfo( hStdout, &csbiInfo ); + wOldColorAttrs = csbiInfo.wAttributes; + } + ~ConsoleColourImpl() + { + SetConsoleTextAttribute( hStdout, wOldColorAttrs ); + } + void set( TextColour::Colours colour ) + { + WORD consoleColour = mapConsoleColour( colour ); + if( consoleColour > 0 ) + SetConsoleTextAttribute( hStdout, consoleColour ); + } + + HANDLE hStdout; + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + WORD wOldColorAttrs; + }; + + TextColour::TextColour( Colours colour ) + : m_impl( new ConsoleColourImpl() ) + { + if( colour ) + m_impl->set( colour ); + } + TextColour::~TextColour() + { + delete m_impl; + } + void TextColour::set( Colours colour ) + { + m_impl->set( colour ); + } + +} // end namespace Catch + +#else + +namespace Catch +{ + TextColour::TextColour( Colours ){} + TextColour::~TextColour(){} + void TextColour::set( Colours ){} + +} // end namespace Catch + +#endif + + +// #included from: catch_exception_translator_registry.hpp + +namespace Catch +{ + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry + { + /////////////////////////////////////////////////////////////////////// + ~ExceptionTranslatorRegistry + () + { + deleteAll( m_translators ); + } + + /////////////////////////////////////////////////////////////////////// + virtual void registerTranslator + ( + IExceptionTranslator* translator + ) + { + m_translators.push_back( translator ); + } + + /////////////////////////////////////////////////////////////////////// + virtual std::string translateActiveException + () + const + { + try + { + throw; + } + catch( std::exception& ex ) + { + return ex.what(); + } + catch( std::string& msg ) + { + return msg; + } + catch( const char* msg ) + { + return msg; + } + catch(...) + { + return tryTranslators( m_translators.begin() ); + } + } + + /////////////////////////////////////////////////////////////////////// + std::string tryTranslators + ( + std::vector::const_iterator it + ) + const + { + if( it == m_translators.end() ) + return "Unknown exception"; + + try + { + return (*it)->translate(); + } + catch(...) + { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +// #included from: catch_reporter_registry.hpp + +#include + +namespace Catch +{ + class ReporterRegistry : public IReporterRegistry + { + public: + + /////////////////////////////////////////////////////////////////////// + ~ReporterRegistry + () + { + deleteAllValues( m_factories ); + } + + /////////////////////////////////////////////////////////////////////// + virtual IReporter* create + ( + const std::string& name, + const IReporterConfig& config + ) + const + { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( config ); + } + + /////////////////////////////////////////////////////////////////////// + void registerReporter + ( + const std::string& name, + IReporterFactory* factory + ) + { + m_factories.insert( std::make_pair( name, factory ) ); + } + + /////////////////////////////////////////////////////////////////////// + const FactoryMap& getFactories + () + const + { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_stream.hpp + +#include +#include + +namespace Catch +{ + template + class StreamBufImpl : public StreamBufBase + { + char data[bufferSize]; + WriterF m_writer; + + public: + /////////////////////////////////////////////////////////////////////// + StreamBufImpl + () + { + setp( data, data + sizeof(data) ); + } + + /////////////////////////////////////////////////////////////////////// + ~StreamBufImpl + () + { + sync(); + } + + private: + /////////////////////////////////////////////////////////////////////// + int overflow + ( + int c + ) + { + sync(); + + if( c != EOF ) + { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + /////////////////////////////////////////////////////////////////////// + int sync + () + { + if( pbase() != pptr() ) + { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter + { + /////////////////////////////////////////////////////////////////////// + void operator() + ( + const std::string &str + ) + { + writeToDebugConsole( str ); + } + }; +} + +namespace Catch +{ + Context::Context() + : m_reporterRegistry( new ReporterRegistry ), + m_testCaseRegistry( new TestRegistry ), + m_exceptionTranslatorRegistry( new ExceptionTranslatorRegistry ) + {} + + Context& Context::me() { + Context*& hub = singleInstance(); + if( !hub ) + hub = new Context(); + return *hub; + } + + void Context::cleanUp() { + Context*& hub = singleInstance(); + delete hub; + hub = NULL; + } + + Context*& Context::singleInstance() { + static Context* hub = NULL; + return hub; + } + + void Context::setRunner( IRunner* runner ) { + me().m_runner = runner; + } + + void Context::setResultCapture( IResultCapture* resultCapture ) { + me().m_resultCapture = resultCapture; + } + + IResultCapture& Context::getResultCapture() { + return *me().m_resultCapture; + } + + IRunner& Context::getRunner() { + return *me().m_runner; + } + + IReporterRegistry& Context::getReporterRegistry() { + return *me().m_reporterRegistry.get(); + } + + ITestCaseRegistry& Context::getTestCaseRegistry() { + return *me().m_testCaseRegistry.get(); + } + + IExceptionTranslatorRegistry& Context::getExceptionTranslatorRegistry() { + return *me().m_exceptionTranslatorRegistry.get(); + } + + std::streambuf* Context::createStreamBuf( const std::string& streamName ) { + if( streamName == "stdout" ) return std::cout.rdbuf(); + if( streamName == "stderr" ) return std::cerr.rdbuf(); + if( streamName == "debug" ) return new StreamBufImpl; + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + GeneratorsForTest* Context::findGeneratorsForCurrentTest() { + std::string testName = getResultCapture().getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + GeneratorsForTest& Context::getGeneratorsForCurrentTest() { + GeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) + { + std::string testName = getResultCapture().getCurrentTestName(); + generators = new GeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + size_t Context::getGeneratorIndex( const std::string& fileInfo, size_t totalSize ) + { + return me().getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + + bool Context::advanceGeneratorsForCurrentTest() { + GeneratorsForTest* generators = me().findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } +} +// #included from: internal/catch_commandline.hpp + +namespace Catch +{ + // !TBD: This could be refactored to be more "declarative" + // have a table up front that relates the mode, option strings, # arguments, names of arguments + // - may not be worth it at this scale + + // -l, --list tests [xml] lists available tests (optionally in xml) + // -l, --list reporters [xml] lists available reports (optionally in xml) + // -l, --list all [xml] lists available tests and reports (optionally in xml) + // -t, --test "testspec" ["testspec", ...] + // -r, --reporter + // -o, --out filename to write to + // -s, --success report successful cases too + // -b, --break breaks into debugger on test failure + // -n, --name specifies an optional name for the test run + class ArgParser : NonCopyable + { + enum Mode + { + modeNone, + modeList, + modeTest, + modeReport, + modeOutput, + modeSuccess, + modeBreak, + modeName, + modeHelp, + + modeError + }; + + public: + /////////////////////////////////////////////////////////////////////// + ArgParser + ( + int argc, + char * const argv[], + Config& config + ) + : m_mode( modeNone ), + m_config( config ) + { + for( int i=1; i < argc; ++i ) + { + if( argv[i][0] == '-' ) + { + std::string cmd = ( argv[i] ); + if( cmd == "-l" || cmd == "--list" ) + changeMode( cmd, modeList ); + else if( cmd == "-t" || cmd == "--test" ) + changeMode( cmd, modeTest ); + else if( cmd == "-r" || cmd == "--reporter" ) + changeMode( cmd, modeReport ); + else if( cmd == "-o" || cmd == "--out" ) + changeMode( cmd, modeOutput ); + else if( cmd == "-s" || cmd == "--success" ) + changeMode( cmd, modeSuccess ); + else if( cmd == "-b" || cmd == "--break" ) + changeMode( cmd, modeBreak ); + else if( cmd == "-n" || cmd == "--name" ) + changeMode( cmd, modeName ); + else if( cmd == "-h" || cmd == "-?" || cmd == "--help" ) + changeMode( cmd, modeHelp ); + } + else + { + m_args.push_back( argv[i] ); + } + if( m_mode == modeError ) + return; + } + changeMode( "", modeNone ); + } + + private: + /////////////////////////////////////////////////////////////////////// + std::string argsAsString + () + { + std::ostringstream oss; + std::vector::const_iterator it = m_args.begin(); + std::vector::const_iterator itEnd = m_args.end(); + for( bool first = true; it != itEnd; ++it, first = false ) + { + if( !first ) + oss << " "; + oss << *it; + } + return oss.str(); + } + + /////////////////////////////////////////////////////////////////////// + void changeMode + ( + const std::string& cmd, + Mode mode + ) + { + m_command = cmd; + switch( m_mode ) + { + case modeNone: + if( m_args.size() > 0 ) + return setErrorMode( "Unexpected arguments before " + m_command + ": " + argsAsString() ); + break; + case modeList: + if( m_args.size() > 2 ) + { + return setErrorMode( m_command + " expected upto 2 arguments but recieved: " + argsAsString() ); + } + else + { + Config::List::What listSpec = Config::List::All; + if( m_args.size() >= 1 ) + { + if( m_args[0] == "tests" ) + listSpec = Config::List::Tests; + else if( m_args[0] == "reporters" ) + listSpec = Config::List::Reports; + else + return setErrorMode( m_command + " expected [tests] or [reporters] but recieved: [" + m_args[0] + "]" ); + } + if( m_args.size() >= 2 ) + { + if( m_args[1] == "xml" ) + listSpec = static_cast( listSpec | Config::List::AsXml ); + else if( m_args[1] == "text" ) + listSpec = static_cast( listSpec | Config::List::AsText ); + else + return setErrorMode( m_command + " expected [xml] or [text] but recieved: [" + m_args[1] + "]" ); + } + m_config.setListSpec( static_cast( m_config.getListSpec() | listSpec ) ); + } + break; + case modeTest: + if( m_args.size() == 0 ) + return setErrorMode( m_command + " expected at least 1 argument but recieved none" ); + { + std::vector::const_iterator it = m_args.begin(); + std::vector::const_iterator itEnd = m_args.end(); + for(; it != itEnd; ++it ) + m_config.addTestSpec( *it ); + } + break; + case modeReport: + if( m_args.size() != 1 ) + return setErrorMode( m_command + " expected one argument, recieved: " + argsAsString() ); + m_config.setReporter( m_args[0] ); + break; + case modeOutput: + if( m_args.size() == 0 ) + return setErrorMode( m_command + " expected filename" ); + if( m_args[0][0] == '%' ) + m_config.useStream( m_args[0].substr( 1 ) ); + else + m_config.setFilename( m_args[0] ); + break; + case modeSuccess: + if( m_args.size() != 0 ) + return setErrorMode( m_command + " does not accept arguments" ); + m_config.setIncludeWhat( Config::Include::SuccessfulResults ); + break; + case modeBreak: + if( m_args.size() != 0 ) + return setErrorMode( m_command + " does not accept arguments" ); + m_config.setShouldDebugBreak( true ); + break; + case modeName: + if( m_args.size() != 1 ) + return setErrorMode( m_command + " requires exactly one argument (a name)" ); + m_config.setName( m_args[0] ); + break; + case modeHelp: + if( m_args.size() != 0 ) + return setErrorMode( m_command + " does not accept arguments" ); + m_config.setShowHelp( true ); + break; + case modeError: + default: + break; + } + m_args.clear(); + m_mode = mode; + } + + /////////////////////////////////////////////////////////////////////// + void setErrorMode + ( + const std::string& errorMessage + ) + { + m_mode = modeError; + m_command = ""; + m_config.setError( errorMessage ); + } + + private: + + Mode m_mode; + std::string m_command; + std::vector m_args; + Config& m_config; + }; + +} // end namespace Catch + +// #included from: internal/catch_list.hpp + +#include + +namespace Catch +{ + /////////////////////////////////////////////////////////////////////////// + inline int List + ( + Config& config + ) + { + if( config.listWhat() & Config::List::Reports ) + { + std::cout << "Available reports:\n"; + IReporterRegistry::FactoryMap::const_iterator it = Context::getReporterRegistry().getFactories().begin(); + IReporterRegistry::FactoryMap::const_iterator itEnd = Context::getReporterRegistry().getFactories().end(); + for(; it != itEnd; ++it ) + { + // !TBD: consider listAs() + std::cout << "\t" << it->first << "\n\t\t'" << it->second->getDescription() << "'\n"; + } + std::cout << std::endl; + } + if( config.listWhat() & Config::List::Tests ) + { + std::cout << "Available tests:\n"; + std::vector::const_iterator it = Context::getTestCaseRegistry().getAllTests().begin(); + std::vector::const_iterator itEnd = Context::getTestCaseRegistry().getAllTests().end(); + for(; it != itEnd; ++it ) + { + // !TBD: consider listAs() + std::cout << "\t" << it->getName() << "\n\t\t '" << it->getDescription() << "'\n"; + } + std::cout << std::endl; + } + if( ( config.listWhat() & Config::List::All ) == 0 ) + { + std::cerr << "Unknown list type" << std::endl; + return (std::numeric_limits::max)(); + } + + if( config.getReporter().get() ) + { + std::cerr << "Reporters ignored when listing" << std::endl; + } + if( !config.testsSpecified() ) + { + std::cerr << "Test specs ignored when listing" << std::endl; + } + return 0; + + } + +} // end namespace Catch + +// #included from: reporters/catch_reporter_basic.hpp + +// #included from: ../internal/catch_reporter_registrars.hpp + +namespace Catch +{ + template + class ReporterRegistrar + { + class ReporterFactory : public IReporterFactory + { + /////////////////////////////////////////////////////////////////// + virtual IReporter* create + ( + const IReporterConfig& config + ) + const + { + return new T( config ); + } + /////////////////////////////////////////////////////////////////// + virtual std::string getDescription + () + const + { + return T::getDescription(); + } + }; + + public: + + /////////////////////////////////////////////////////////////////////// + ReporterRegistrar + ( + const std::string& name + ) + { + Context::getReporterRegistry().registerReporter( name, new ReporterFactory() ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); + +namespace Catch +{ + struct pluralise + { + pluralise( std::size_t count, const std::string& label ) + : m_count( count ), + m_label( label ) + {} + + friend std::ostream& operator << ( std::ostream& os, const pluralise& pluraliser ) + { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + std::size_t m_count; + std::string m_label; + }; + + class BasicReporter : public SharedImpl + { + struct SpanInfo + { + SpanInfo() + : emitted( false ) + {} + + SpanInfo( const std::string& spanName ) + : name( spanName ), + emitted( false ) + {} + + SpanInfo( const SpanInfo& other ) + : name( other.name ), + emitted( other.emitted ) + {} + + std::string name; + bool emitted; + }; + + public: + /////////////////////////////////////////////////////////////////////////// + BasicReporter + ( + const IReporterConfig& config + ) + : m_config( config ), + m_firstSectionInTestCase( true ) + { + } + + /////////////////////////////////////////////////////////////////////////// + static std::string getDescription + () + { + return "Reports test results as lines of text"; + } + + private: + + /////////////////////////////////////////////////////////////////////////// + void ReportCounts + ( + const std::string& label, + const Counts& counts + ) + { + if( counts.passed ) + m_config.stream() << counts.failed << " of " << counts.total() << " " << label << "s failed"; + else + m_config.stream() << ( counts.failed > 1 ? "All " : "" ) << pluralise( counts.failed, label ) << " failed"; + } + + /////////////////////////////////////////////////////////////////////////// + void ReportCounts + ( + const Totals& totals + ) + { + if( totals.assertions.total() == 0 ) + { + m_config.stream() << "No tests ran"; + } + else if( totals.assertions.failed ) + { + TextColour colour( TextColour::ResultError ); + ReportCounts( "test case", totals.testCases ); + if( totals.testCases.failed > 0 ) + { + m_config.stream() << " ("; + ReportCounts( "assertion", totals.assertions ); + m_config.stream() << ")"; + } + } + else + { + TextColour colour( TextColour::ResultSuccess ); + m_config.stream() << "All tests passed (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")"; + } + } + + private: // IReporter + + /////////////////////////////////////////////////////////////////////////// + virtual bool shouldRedirectStdout + () + const + { + return false; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTesting + () + { + m_testingSpan = SpanInfo(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTesting + ( + const Totals& totals + ) + { + // Output the overall test results even if "Started Testing" was not emitted + m_config.stream() << "\n[Testing completed. "; + ReportCounts( totals); + m_config.stream() << "]\n" << std::endl; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartGroup + ( + const std::string& groupName + ) + { + m_groupSpan = groupName; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndGroup + ( + const std::string& groupName, + const Totals& totals + ) + { + if( m_groupSpan.emitted && !groupName.empty() ) + { + m_config.stream() << "[End of group: '" << groupName << "'. "; + ReportCounts( totals ); + m_config.stream() << "]\n" << std::endl; + m_groupSpan = SpanInfo(); + } + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTestCase + ( + const TestCaseInfo& testInfo + ) + { + m_testSpan = testInfo.getName(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartSection + ( + const std::string& sectionName, + const std::string /*description*/ + ) + { + m_sectionSpans.push_back( SpanInfo( sectionName ) ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndSection + ( + const std::string& sectionName, + const Counts& assertions + ) + { + SpanInfo& sectionSpan = m_sectionSpans.back(); + if( sectionSpan.emitted && !sectionSpan.name.empty() ) + { + m_config.stream() << "[End of section: '" << sectionName << "' "; + + if( assertions.failed ) + { + TextColour colour( TextColour::ResultError ); + ReportCounts( "assertion", assertions); + } + else + { + TextColour colour( TextColour::ResultSuccess ); + m_config.stream() << ( assertions.passed > 1 ? "All " : "" ) + << pluralise( assertions.passed, "assertion" ) << "passed" ; + } + m_config.stream() << "]\n" << std::endl; + } + m_sectionSpans.pop_back(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void Result + ( + const ResultInfo& resultInfo + ) + { + if( !m_config.includeSuccessfulResults() && resultInfo.getResultType() == ResultWas::Ok ) + return; + + StartSpansLazily(); + + if( !resultInfo.getFilename().empty() ) + { + TextColour colour( TextColour::FileName ); + m_config.stream() << SourceLineInfo( resultInfo.getFilename(), resultInfo.getLine() ); + } + + if( resultInfo.hasExpression() ) + { + TextColour colour( TextColour::OriginalExpression ); + m_config.stream() << resultInfo.getExpression(); + if( resultInfo.ok() ) + { + TextColour successColour( TextColour::Success ); + m_config.stream() << " succeeded"; + } + else + { + TextColour errorColour( TextColour::Error ); + m_config.stream() << " failed"; + } + } + switch( resultInfo.getResultType() ) + { + case ResultWas::ThrewException: + { + TextColour colour( TextColour::Error ); + if( resultInfo.hasExpression() ) + m_config.stream() << " with unexpected"; + else + m_config.stream() << "Unexpected"; + m_config.stream() << " exception with message: '" << resultInfo.getMessage() << "'"; + } + break; + case ResultWas::DidntThrowException: + { + TextColour colour( TextColour::Error ); + if( resultInfo.hasExpression() ) + m_config.stream() << " because no exception was thrown where one was expected"; + else + m_config.stream() << "No exception thrown where one was expected"; + } + break; + case ResultWas::Info: + streamVariableLengthText( "info", resultInfo.getMessage() ); + break; + case ResultWas::Warning: + m_config.stream() << "warning:\n'" << resultInfo.getMessage() << "'"; + break; + case ResultWas::ExplicitFailure: + { + TextColour colour( TextColour::Error ); + m_config.stream() << "failed with message: '" << resultInfo.getMessage() << "'"; + } + break; + case ResultWas::Unknown: // These cases are here to prevent compiler warnings + case ResultWas::Ok: + case ResultWas::FailureBit: + case ResultWas::ExpressionFailed: + case ResultWas::Exception: + default: + if( !resultInfo.hasExpression() ) + { + if( resultInfo.ok() ) + { + TextColour colour( TextColour::Success ); + m_config.stream() << " succeeded"; + } + else + { + TextColour colour( TextColour::Error ); + m_config.stream() << " failed"; + } + } + break; + } + + if( resultInfo.hasExpandedExpression() ) + { + m_config.stream() << " for: "; + TextColour colour( TextColour::ReconstructedExpression ); + m_config.stream() << resultInfo.getExpandedExpression(); + } + m_config.stream() << std::endl; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTestCase + ( + const TestCaseInfo& testInfo, + const Totals& totals, + const std::string& stdOut, + const std::string& stdErr + ) + { + if( !stdOut.empty() ) + { + StartSpansLazily(); + streamVariableLengthText( "stdout", stdOut ); + } + + if( !stdErr.empty() ) + { + StartSpansLazily(); + streamVariableLengthText( "stderr", stdErr ); + } + + if( m_testSpan.emitted ) + { + m_config.stream() << "[Finished: '" << testInfo.getName() << "' "; + ReportCounts( totals ); + m_config.stream() << "]" << std::endl; + } + } + + private: // helpers + + /////////////////////////////////////////////////////////////////////////// + void StartSpansLazily() + { + if( !m_testingSpan.emitted ) + { + if( m_config.getName().empty() ) + m_config.stream() << "[Started testing]" << std::endl; + else + m_config.stream() << "[Started testing: " << m_config.getName() << "]" << std::endl; + m_testingSpan.emitted = true; + } + + if( !m_groupSpan.emitted && !m_groupSpan.name.empty() ) + { + m_config.stream() << "[Started group: '" << m_groupSpan.name << "']" << std::endl; + m_groupSpan.emitted = true; + } + + if( !m_testSpan.emitted ) + { + m_config.stream() << std::endl << "[Running: " << m_testSpan.name << "]" << std::endl; + m_testSpan.emitted = true; + } + + if( !m_sectionSpans.empty() ) + { + SpanInfo& sectionSpan = m_sectionSpans.back(); + if( !sectionSpan.emitted && !sectionSpan.name.empty() ) + { + if( m_firstSectionInTestCase ) + { + m_config.stream() << "\n"; + m_firstSectionInTestCase = false; + } + std::vector::iterator it = m_sectionSpans.begin(); + std::vector::iterator itEnd = m_sectionSpans.end(); + for(; it != itEnd; ++it ) + { + SpanInfo& prevSpan = *it; + if( !prevSpan.emitted && !prevSpan.name.empty() ) + { + m_config.stream() << "[Started section: '" << prevSpan.name << "']" << std::endl; + prevSpan.emitted = true; + } + } + } + } + } + + /////////////////////////////////////////////////////////////////////////// + void streamVariableLengthText + ( + const std::string& prefix, + const std::string& text + ) + { + std::string trimmed = trim( text ); + if( trimmed.find_first_of( "\r\n" ) == std::string::npos ) + { + m_config.stream() << "[" << prefix << ": " << trimmed << "]\n"; + } + else + { + m_config.stream() << "\n[" << prefix << "] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" << trimmed + << "\n[end of " << prefix << "] <<<<<<<<<<<<<<<<<<<<<<<<\n"; + } + } + + private: + const IReporterConfig& m_config; + bool m_firstSectionInTestCase; + + SpanInfo m_testingSpan; + SpanInfo m_groupSpan; + SpanInfo m_testSpan; + std::vector m_sectionSpans; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "basic", BasicReporter ) + +} // end namespace Catch + +// #included from: reporters/catch_reporter_xml.hpp + +// #included from: ../internal/catch_xmlwriter.hpp + +#include +#include +#include + +namespace Catch +{ + class XmlWriter + { + public: + + class ScopedElement + { + public: + /////////////////////////////////////////////////////////////////// + ScopedElement + ( + XmlWriter* writer + ) + : m_writer( writer ) + { + } + + /////////////////////////////////////////////////////////////////// + ScopedElement + ( + const ScopedElement& other + ) + : m_writer( other.m_writer ) + { + other.m_writer = NULL; + } + + /////////////////////////////////////////////////////////////////// + ~ScopedElement + () + { + if( m_writer ) + m_writer->endElement(); + } + + /////////////////////////////////////////////////////////////////// + ScopedElement& writeText + ( + const std::string& text + ) + { + m_writer->writeText( text ); + return *this; + } + + /////////////////////////////////////////////////////////////////// + template + ScopedElement& writeAttribute + ( + const std::string& name, + const T& attribute + ) + { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + /////////////////////////////////////////////////////////////////////// + XmlWriter + () + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &std::cout ) + { + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter + ( + std::ostream& os + ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + { + } + + /////////////////////////////////////////////////////////////////////// + ~XmlWriter + () + { + while( !m_tags.empty() ) + { + endElement(); + } + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& operator = + ( + const XmlWriter& other + ) + { + XmlWriter temp( other ); + swap( temp ); + return *this; + } + + /////////////////////////////////////////////////////////////////////// + void swap + ( + XmlWriter& other + ) + { + std::swap( m_tagIsOpen, other.m_tagIsOpen ); + std::swap( m_needsNewline, other.m_needsNewline ); + std::swap( m_tags, other.m_tags ); + std::swap( m_indent, other.m_indent ); + std::swap( m_os, other.m_os ); + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& startElement + ( + const std::string& name + ) + { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + /////////////////////////////////////////////////////////////////////// + ScopedElement scopedElement + ( + const std::string& name + ) + { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& endElement + () + { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) + { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else + { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& writeAttribute + ( + const std::string& name, + const std::string& attribute + ) + { + if( !name.empty() && !attribute.empty() ) + { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& writeAttribute + ( + const std::string& name, + bool attribute + ) + { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + /////////////////////////////////////////////////////////////////////// + template + XmlWriter& writeAttribute + ( + const std::string& name, + const T& attribute + ) + { + if( !name.empty() ) + { + stream() << " " << name << "=\"" << attribute << "\""; + } + return *this; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& writeText + ( + const std::string& text + ) + { + if( !text.empty() ) + { + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& writeComment + ( + const std::string& text + ) + { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + /////////////////////////////////////////////////////////////////////// + XmlWriter& writeBlankLine + () + { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + private: + + /////////////////////////////////////////////////////////////////////// + std::ostream& stream + () + { + return *m_os; + } + + /////////////////////////////////////////////////////////////////////// + void ensureTagClosed + () + { + if( m_tagIsOpen ) + { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + /////////////////////////////////////////////////////////////////////// + void newlineIfNecessary + () + { + if( m_needsNewline ) + { + stream() << "\n"; + m_needsNewline = false; + } + } + + /////////////////////////////////////////////////////////////////////// + void writeEncodedText + ( + const std::string& text + ) + { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) + { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) + { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch +{ + class XmlReporter : public SharedImpl + { + public: + /////////////////////////////////////////////////////////////////////////// + XmlReporter + ( + const IReporterConfig& config + ) + : m_config( config ) + { + } + + /////////////////////////////////////////////////////////////////////////// + static std::string getDescription + () + { + return "Reports test results as an XML document"; + } + + private: // IReporter + + /////////////////////////////////////////////////////////////////////////// + virtual bool shouldRedirectStdout + () + const + { + return true; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTesting + () + { + m_xml = XmlWriter( m_config.stream() ); + m_xml.startElement( "Catch" ); + if( !m_config.getName().empty() ) + m_xml.writeAttribute( "name", m_config.getName() ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTesting + ( + const Totals& totals + ) + { + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", totals.assertions.passed ) + .writeAttribute( "failures", totals.assertions.failed ); + m_xml.endElement(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartGroup + ( + const std::string& groupName + ) + { + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupName ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndGroup + ( + const std::string& /*groupName*/, + const Totals& totals + ) + { + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", totals.assertions.passed ) + .writeAttribute( "failures", totals.assertions.failed ); + m_xml.endElement(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartSection( const std::string& sectionName, const std::string description ) + { + m_xml.startElement( "Section" ) + .writeAttribute( "name", sectionName ) + .writeAttribute( "description", description ); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndSection( const std::string& /*sectionName*/, const Counts& assertions ) + { + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", assertions.passed ) + .writeAttribute( "failures", assertions.failed ); + m_xml.endElement(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) + { + m_xml.startElement( "TestCase" ).writeAttribute( "name", testInfo.getName() ); + m_currentTestSuccess = true; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void Result( const Catch::ResultInfo& resultInfo ) + { + if( !m_config.includeSuccessfulResults() && resultInfo.getResultType() == ResultWas::Ok ) + return; + + if( resultInfo.hasExpression() ) + { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", resultInfo.ok() ) + .writeAttribute( "filename", resultInfo.getFilename() ) + .writeAttribute( "line", resultInfo.getLine() ); + + m_xml.scopedElement( "Original" ) + .writeText( resultInfo.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( resultInfo.getExpandedExpression() ); + m_currentTestSuccess &= resultInfo.ok(); + } + + switch( resultInfo.getResultType() ) + { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", resultInfo.getFilename() ) + .writeAttribute( "line", resultInfo.getLine() ) + .writeText( resultInfo.getMessage() ); + m_currentTestSuccess = false; + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( resultInfo.getMessage() ); + break; + case ResultWas::Warning: + m_xml.scopedElement( "Warning" ) + .writeText( resultInfo.getMessage() ); + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( resultInfo.getMessage() ); + m_currentTestSuccess = false; + break; + case ResultWas::Unknown: + case ResultWas::Ok: + case ResultWas::FailureBit: + case ResultWas::ExpressionFailed: + case ResultWas::Exception: + case ResultWas::DidntThrowException: + default: + break; + } + if( resultInfo.hasExpression() ) + m_xml.endElement(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals& /* totals */, const std::string& /*stdOut*/, const std::string& /*stdErr*/ ) + { + m_xml.scopedElement( "OverallResult" ).writeAttribute( "success", m_currentTestSuccess ); + m_xml.endElement(); + } + + private: + const IReporterConfig& m_config; + bool m_currentTestSuccess; + XmlWriter m_xml; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: reporters/catch_reporter_junit.hpp + +namespace Catch +{ + class JunitReporter : public SharedImpl + { + struct TestStats + { + std::string m_element; + std::string m_resultType; + std::string m_message; + std::string m_content; + }; + + struct TestCaseStats + { + TestCaseStats( const std::string& name = std::string() ) + : m_name( name ) + { + } + + double m_timeInSeconds; + std::string m_status; + std::string m_className; + std::string m_name; + std::vector m_testStats; + }; + + struct Stats + { + Stats( const std::string& name = std::string() ) + : m_testsCount( 0 ), + m_failuresCount( 0 ), + m_disabledCount( 0 ), + m_errorsCount( 0 ), + m_timeInSeconds( 0 ), + m_name( name ) + { + } + + std::size_t m_testsCount; + std::size_t m_failuresCount; + std::size_t m_disabledCount; + std::size_t m_errorsCount; + double m_timeInSeconds; + std::string m_name; + + std::vector m_testCaseStats; + }; + + public: + /////////////////////////////////////////////////////////////////////////// + JunitReporter( const IReporterConfig& config ) + : m_config( config ), + m_testSuiteStats( "AllTests" ), + m_currentStats( &m_testSuiteStats ) + { + } + + /////////////////////////////////////////////////////////////////////////// + static std::string getDescription() + { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + private: // IReporter + + /////////////////////////////////////////////////////////////////////////// + virtual bool shouldRedirectStdout + () + const + { + return true; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTesting() + { + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartGroup( const std::string& groupName ) + { + + m_statsForSuites.push_back( Stats( groupName ) ); + m_currentStats = &m_statsForSuites.back(); + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndGroup( const std::string&, const Totals& totals ) + { + m_currentStats->m_testsCount = totals.assertions.total(); + m_currentStats = &m_testSuiteStats; + } + + virtual void StartSection( const std::string& /*sectionName*/, const std::string /*description*/ ) + { + } + + virtual void EndSection( const std::string& /*sectionName*/, const Counts& /* assertions */ ) + { + } + + /////////////////////////////////////////////////////////////////////////// + virtual void StartTestCase( const Catch::TestCaseInfo& testInfo ) + { + m_currentStats->m_testCaseStats.push_back( TestCaseStats( testInfo.getName() ) ); + + } + + /////////////////////////////////////////////////////////////////////////// + virtual void Result( const Catch::ResultInfo& resultInfo ) + { + if( resultInfo.getResultType() != ResultWas::Ok || m_config.includeSuccessfulResults() ) + { + TestCaseStats& testCaseStats = m_currentStats->m_testCaseStats.back(); + TestStats stats; + std::ostringstream oss; + if( !resultInfo.getMessage().empty() ) + { + oss << resultInfo.getMessage() << " at "; + } + oss << SourceLineInfo( resultInfo.getFilename(), resultInfo.getLine() ); + stats.m_content = oss.str(); + stats.m_message = resultInfo.getExpandedExpression(); + stats.m_resultType = resultInfo.getTestMacroName(); + switch( resultInfo.getResultType() ) + { + case ResultWas::ThrewException: + stats.m_element = "error"; + m_currentStats->m_errorsCount++; + break; + case ResultWas::Info: + stats.m_element = "info"; // !TBD ? + break; + case ResultWas::Warning: + stats.m_element = "warning"; // !TBD ? + break; + case ResultWas::ExplicitFailure: + stats.m_element = "failure"; + m_currentStats->m_failuresCount++; + break; + case ResultWas::ExpressionFailed: + stats.m_element = "failure"; + m_currentStats->m_failuresCount++; + break; + case ResultWas::Ok: + stats.m_element = "success"; + break; + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + case ResultWas::DidntThrowException: + default: + stats.m_element = "unknown"; + break; + } + testCaseStats.m_testStats.push_back( stats ); + + } + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTestCase( const Catch::TestCaseInfo&, const Totals& /* totals */, const std::string& stdOut, const std::string& stdErr ) + { + if( !stdOut.empty() ) + m_stdOut << stdOut << "\n"; + if( !stdErr.empty() ) + m_stdErr << stdErr << "\n"; + } + + /////////////////////////////////////////////////////////////////////////// + virtual void EndTesting( const Totals& /* totals */ ) + { + std::ostream& str = m_config.stream(); + { + XmlWriter xml( str ); + + if( m_statsForSuites.size() > 0 ) + xml.startElement( "testsuites" ); + + std::vector::const_iterator it = m_statsForSuites.begin(); + std::vector::const_iterator itEnd = m_statsForSuites.end(); + + for(; it != itEnd; ++it ) + { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + xml.writeAttribute( "name", it->m_name ); + xml.writeAttribute( "errors", it->m_errorsCount ); + xml.writeAttribute( "failures", it->m_failuresCount ); + xml.writeAttribute( "tests", it->m_testsCount ); + xml.writeAttribute( "hostname", "tbd" ); + xml.writeAttribute( "time", "tbd" ); + xml.writeAttribute( "timestamp", "tbd" ); + + OutputTestCases( xml, *it ); + } + + xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ) ); + xml.scopedElement( "system-err" ).writeText( trim( m_stdOut.str() ) ); + } + } + + /////////////////////////////////////////////////////////////////////////// + void OutputTestCases( XmlWriter& xml, const Stats& stats ) + { + std::vector::const_iterator it = stats.m_testCaseStats.begin(); + std::vector::const_iterator itEnd = stats.m_testCaseStats.end(); + for(; it != itEnd; ++it ) + { + xml.writeBlankLine(); + xml.writeComment( "Test case" ); + + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + xml.writeAttribute( "classname", it->m_className ); + xml.writeAttribute( "name", it->m_name ); + xml.writeAttribute( "time", "tbd" ); + + OutputTestResult( xml, *it ); + } + } + + /////////////////////////////////////////////////////////////////////////// + void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats ) + { + std::vector::const_iterator it = stats.m_testStats.begin(); + std::vector::const_iterator itEnd = stats.m_testStats.end(); + for(; it != itEnd; ++it ) + { + if( it->m_element != "success" ) + { + XmlWriter::ScopedElement e = xml.scopedElement( it->m_element ); + + xml.writeAttribute( "message", it->m_message ); + xml.writeAttribute( "type", it->m_resultType ); + if( !it->m_content.empty() ) + xml.writeText( it->m_content ); + } + } + } + + private: + const IReporterConfig& m_config; + bool m_currentTestSuccess; + + Stats m_testSuiteStats; + Stats* m_currentStats; + std::vector m_statsForSuites; + std::ostringstream m_stdOut; + std::ostringstream m_stdErr; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +#include +#include +#include + +namespace Catch +{ + ////////////////////////////////////////////////////////////////////////// + inline int Main + ( + Config& config + ) + { + // Handle list request + if( config.listWhat() != Config::List::None ) + return List( config ); + + // Open output file, if specified + std::ofstream ofs; + if( !config.getFilename().empty() ) + { + ofs.open( config.getFilename().c_str() ); + if( ofs.fail() ) + { + std::cerr << "Unable to open file: '" << config.getFilename() << "'" << std::endl; + return (std::numeric_limits::max)(); + } + config.setStreamBuf( ofs.rdbuf() ); + } + + Runner runner( config ); + + // Run test specs specified on the command line - or default to all + if( !config.testsSpecified() ) + { + config.getReporter()->StartGroup( "" ); + runner.runAll(); + config.getReporter()->EndGroup( "", runner.getTotals() ); + } + else + { + // !TBD We should get all the testcases upfront, report any missing, + // then just run them + std::vector::const_iterator it = config.getTestSpecs().begin(); + std::vector::const_iterator itEnd = config.getTestSpecs().end(); + for(; it != itEnd; ++it ) + { + Totals prevTotals = runner.getTotals(); + config.getReporter()->StartGroup( *it ); + if( runner.runMatching( *it ) == 0 ) + { + // Use reporter? +// std::cerr << "\n[Unable to match any test cases with: " << *it << "]" << std::endl; + } + config.getReporter()->EndGroup( *it, runner.getTotals() - prevTotals ); + } + } + + return static_cast( runner.getTotals().assertions.failed ); + } + + ////////////////////////////////////////////////////////////////////////// + inline void showHelp + ( + std::string exeName + ) + { + std::string::size_type pos = exeName.find_last_of( "/\\" ); + if( pos != std::string::npos ) + { + exeName = exeName.substr( pos+1 ); + } + + std::cout << exeName << " is a CATCH host application. Options are as follows:\n\n" + << "\t-l, --list [xml]\n" + << "\t-t, --test [...]\n" + << "\t-r, --reporter \n" + << "\t-o, --out |<%stream name>\n" + << "\t-s, --success\n" + << "\t-b, --break\n" + << "\t-n, --name \n\n" + << "For more detail usage please see: https://github.com/philsquared/Catch/wiki/Command-line" << std::endl; + } + + ////////////////////////////////////////////////////////////////////////// + inline int Main + ( + int argc, + char* const argv[], + Config& config + ) + { + ArgParser( argc, argv, config ); + + if( !config.getMessage().empty() ) + { + std::cerr << config.getMessage() << std::endl; + return (std::numeric_limits::max)(); + } + + // Handle help + if( config.showHelp() ) + { + showHelp( argv[0] ); + return 0; + } + + return Main( config ); + } + + ////////////////////////////////////////////////////////////////////////// + inline int Main + ( + int argc, + char* const argv[] + ) + { + Config config; +// if( isDebuggerActive() ) +// config.useStream( "debug" ); + int result = Main( argc, argv, config ); + Catch::Context::cleanUp(); + return result; + } + +} // end namespace Catch + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Main( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Main( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +////// + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, false, true, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, true, true, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, ..., true, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, true, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, true, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, false, false, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, true, false, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, false, false, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, false, false, "CHECKED_ELSE" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, ..., false, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, false, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, false, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, false, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, true, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Info, false, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::Warning, false, "WARN" ) +#define FAIL( msg ) INTERNAL_CATCH_MSG( msg, Catch::ResultWas::ExplicitFailure, true, "FAIL" ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_SCOPED_INFO( msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_MSG( #msg " := " << msg, Catch::ResultWas::Info, false, "CAPTURE" ) + +#define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + +#define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) +#define TEST_CASE_NORETURN( name, description ) INTERNAL_CATCH_TESTCASE_NORETURN( name, description ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "Anonymous test case" ) +#define METHOD_AS_TEST_CASE( method, name, description ) CATCH_METHOD_AS_TEST_CASE( method, name, description ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +/////////////// +// Still to be implemented +#define CHECK_NOFAIL( expr ) // !TBD - reports violation, but doesn't fail Test + +using Catch::Detail::Approx; + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/apf/unit_tests/iterator_test_macros.h b/apf/unit_tests/iterator_test_macros.h new file mode 100644 index 00000000..f5ca160a --- /dev/null +++ b/apf/unit_tests/iterator_test_macros.h @@ -0,0 +1,154 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Some macros which can be used for all iterator tests. + +// The iterator requirements are based on +// http://www.cplusplus.com/reference/std/iterator/RandomAccessIterator/ + +// NOTE: base() should be "const" +// TODO: how to forbid non-const version? +#define ITERATOR_TEST_SECTION_BASE(iterator_type, base_type) \ +SECTION("base", "Test base() member function") { \ + base_type ptr; \ + const iterator_type iter(&ptr); \ + CHECK(iter.base() == &ptr); } + +// no CHECKs here, but at least it has to compile: +#define ITERATOR_TEST_SECTION_DEFAULT_CTOR(iterator_type) \ +SECTION("default ctor", "X b; X();") { \ + iterator_type iter1; \ + iter1 = iterator_type(); } + +#define ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(iterator_type, base_type) \ +SECTION("copy ctor, assignment", "X b(a); b=a;") { \ + base_type ptr; \ + iterator_type iter1(&ptr); \ + iterator_type iter2(iter1); \ + iterator_type iter3 = iter1; \ + iterator_type iter4; \ + iter4 = iter1; \ + CHECK(iter1.base() == &ptr); \ + CHECK(iter2.base() == &ptr); \ + CHECK(iter3.base() == &ptr); \ + CHECK(iter4.base() == &ptr); } + +#define ITERATOR_TEST_SECTION_DEREFERENCE(iterator_type, base_type, value) \ +SECTION("dereference", "*a; a->m;") { \ + base_type n = value; \ + iterator_type iter1(&n); \ + CHECK(*iter1 == value); \ + CHECK(iter1.operator->() == &n); \ + base_type o = base_type(); \ + iterator_type iter2(&o); \ + *iter2 = value; \ + CHECK(o == value); } + +#define ITERATOR_TEST_SECTION_OFFSET_DEREFERENCE(iterator_type, base_type, \ + value0, value1) \ +SECTION("offset dereference", "a[n]") { \ + base_type n[] = { value0, value1 }; \ + iterator_type iter1(n); \ + CHECK(iter1.base() == &n[0]); \ + CHECK(iter1[0] == value0); \ + CHECK(iter1[1] == value1); \ + iter1[1] = value0; \ + CHECK(n[1] == value0); } + +#define ITERATOR_TEST_SECTION_EQUALITY(iterator_type, base_type) \ +SECTION("equality/inequality", "a == b; a != b") { \ + base_type ptr1; \ + iterator_type iter1(&ptr1); \ + iterator_type iter2(&ptr1); \ + CHECK(iter1 == iter2); \ + CHECK_FALSE(iter1 != iter2); \ + base_type ptr2; \ + iterator_type iter3(&ptr2); \ + CHECK(iter1 != iter3); \ + CHECK_FALSE(iter1 == iter3); } + +#define ITERATOR_TEST_SECTION_INCREMENT(iterator_type, base_type) \ +SECTION("increment", "++a; a++") { \ + base_type n[3]; \ + iterator_type iter1(n); \ + iterator_type iter2; \ + CHECK(iter1.base() == &n[0]); \ + iter2 = ++iter1; \ + CHECK(iter1.base() == &n[1]); \ + CHECK(iter2.base() == &n[1]); \ + iter2 = iter1++; \ + CHECK(iter1.base() == &n[2]); \ + CHECK(iter2.base() == &n[1]); } + +#define ITERATOR_TEST_SECTION_DECREMENT(iterator_type, base_type) \ +SECTION("decrement", "--a; a--") { \ + base_type n[3]; \ + iterator_type iter1(&n[2]); \ + iterator_type iter2; \ + CHECK(iter1.base() == &n[2]); \ + iter2 = --iter1; \ + CHECK(iter1.base() == &n[1]); \ + CHECK(iter2.base() == &n[1]); \ + iter2 = iter1--; \ + CHECK(iter1.base() == &n[0]); \ + CHECK(iter2.base() == &n[1]); } + +#define ITERATOR_TEST_SECTION_PLUS_MINUS(iterator_type, base_type) \ +SECTION("plus/minus", "a + n; n + a; a - n; a - b; a += n; a -= n") { \ + base_type n[3]; \ + iterator_type iter1(n); \ + iterator_type iter2; \ + CHECK(iter1.base() == &n[0]); \ + iter2 = iter1 + 2; \ + CHECK(iter2.base() == &n[2]); \ + iter2 = 2 + iter1; \ + CHECK(iter2.base() == &n[2]); \ + iter1 += 2; \ + CHECK(iter1.base() == &n[2]); \ + iter2 = iter1 - 2; \ + CHECK(iter2.base() == &n[0]); \ + CHECK((iter1 - iter2) == 2); \ + iter1 -= 2; \ + CHECK(iter1.base() == &n[0]); } + +#define ITERATOR_TEST_SECTION_LESS(iterator_type, base_type) \ +SECTION("less et al.", "a < b; a > b; a <= b; a >= b") { \ + base_type n[2]; \ + iterator_type iter1(&n[0]); \ + iterator_type iter2(&n[1]); \ + iterator_type iter3(&n[1]); \ + CHECK(iter1 < iter2); \ + CHECK_FALSE(iter2 < iter3); \ + CHECK_FALSE(iter2 < iter1); \ + CHECK(iter2 > iter1); \ + CHECK_FALSE(iter2 > iter3); \ + CHECK_FALSE(iter1 > iter2); \ + CHECK(iter1 <= iter2); \ + CHECK(iter2 <= iter3); \ + CHECK_FALSE(iter2 <= iter1); \ + CHECK(iter2 >= iter1); \ + CHECK(iter2 >= iter3); \ + CHECK_FALSE(iter1 >= iter2); } + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/main.cpp b/apf/unit_tests/main.cpp new file mode 100644 index 00000000..e9ef7fee --- /dev/null +++ b/apf/unit_tests/main.cpp @@ -0,0 +1,9 @@ +// This is the main file for all unit tests using CATCH. +// +// The tests are compiled and executed with "make check", further tests can be +// added in src/Makefile.am. +// +// See also: https://github.com/philsquared/Catch/wiki/Tutorial + +#define CATCH_CONFIG_MAIN +#include "catch/catch.hpp" diff --git a/apf/unit_tests/test_accumulating_iterator.cpp b/apf/unit_tests/test_accumulating_iterator.cpp new file mode 100644 index 00000000..134ceeaf --- /dev/null +++ b/apf/unit_tests/test_accumulating_iterator.cpp @@ -0,0 +1,69 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for accumulating_iterator. + +#include "apf/iterator.h" // for accumulating_iterator +#include "iterator_test_macros.h" + +#include "catch/catch.hpp" + +using ai = apf::accumulating_iterator; + +TEST_CASE("iterators/accumulating_iterator" + , "Test all functions of accumulating_iterator") +{ + +ITERATOR_TEST_SECTION_DEFAULT_CTOR(ai) +ITERATOR_TEST_SECTION_BASE(ai, int) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(ai, int) +ITERATOR_TEST_SECTION_INCREMENT(ai, int) + +SECTION("dereference/increment", "*a, *a++") +{ + int n = 5; + auto iter = ai(&n); + + *iter = 4; + + CHECK(n == 9); + + *iter++ = 2; + + CHECK(n == 11); + CHECK(iter.base() == &n+1); + + // NOTE: operator->() is purposely not implemented! +} + +SECTION("test make_accumulating_iterator", "namespace-level helper function") +{ + int n = 5; + auto iter = apf::make_accumulating_iterator(&n); + CHECK(iter.base() == &n); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_biquad.cpp b/apf/unit_tests/test_biquad.cpp new file mode 100644 index 00000000..753c3a1f --- /dev/null +++ b/apf/unit_tests/test_biquad.cpp @@ -0,0 +1,55 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for BiQuad and Cascade. + +// see also ../performance_tests/biquad_*.cpp + +#include "apf/biquad.h" + +#include "catch/catch.hpp" + +TEST_CASE("BiQuad", "Test BiQuad") +{ + +// TODO: more tests! + +SECTION("basic", "only instantiations and very basic stuff") +{ + auto a = apf::BiQuad(); + auto b = apf::BiQuad(); + (void)b; + + auto c = apf::SosCoefficients(0.1, 0.1, 0.1, 0.1, 0.1); + a = c; + + CHECK(a.b0 == 0.1); + + auto d = apf::Cascade>(25); + auto e = apf::Cascade>(25); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_blockdelayline.cpp b/apf/unit_tests/test_blockdelayline.cpp new file mode 100644 index 00000000..eceb2faa --- /dev/null +++ b/apf/unit_tests/test_blockdelayline.cpp @@ -0,0 +1,116 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for delay line. + +#include "apf/blockdelayline.h" + +#include "catch/catch.hpp" + +#define CHECK_RANGE(left, right, range) \ + for (int i = 0; i < range; ++i) { \ + INFO("i = " << i); \ + CHECK((left)[i] == (right)[i]); } + +TEST_CASE("block delay line", "Test the delay line") +{ + +int src[] = { 1, 2, 3, 4, 5, 6 }; +int target[6] = { 0 }; + +SECTION("blocksize=1, max_delay=0", "") +{ + apf::BlockDelayLine d(1, 0); + d.write_block(src); + CHECK(d.read_block(target, 0)); + CHECK(*target == 1); + CHECK_FALSE(d.read_block(target, 1)); + d.write_block(src+1); + CHECK(d.read_block(target, 0)); + CHECK(*target == 2); +} + +SECTION("blocksize=1, max_delay=1", "") +{ + apf::BlockDelayLine d(1, 1); + d.write_block(src); + d.write_block(src+1); + CHECK(d.read_block(target, 0)); + CHECK(*target == 2); + CHECK(d.read_block(target, 1)); + CHECK(*target == 1); + d.write_block(src+2); + CHECK(d.read_block(target, 0)); + CHECK(*target == 3); +} + +SECTION("blocksize=3, max_delay=5", "") +{ + int empty[3] = { 0 }; + apf::BlockDelayLine d(3, 5); + d.write_block(src); + d.write_block(src+3); + d.write_block(empty); + d.write_block(empty); + CHECK(d.read_block(target, 4)); + int expected[3] = { 6, 0, 0 }; + CHECK_RANGE(target, expected, 3); + + CHECK(d.read_block(target, 4, 2)); + + expected[0] = 12; + CHECK_RANGE(target, expected, 3); +} + +SECTION("non-causal delay line", "") +{ + int empty[3] = { 0 }; + apf::NonCausalBlockDelayLine d(3, 5, 1); + d.write_block(src); + d.write_block(src+3); + d.write_block(empty); + d.write_block(empty); + + CHECK(d.delay_is_valid(-1)); + CHECK_FALSE(d.delay_is_valid(-2)); + CHECK(d.delay_is_valid(5)); + CHECK_FALSE(d.delay_is_valid(6)); + + CHECK(d.read_block(target, 3)); + int expected[3] = { 6, 0, 0 }; + CHECK_RANGE(target, expected, 3); + + CHECK(d.read_block(target, 3, 2)); + + expected[0] = 12; + CHECK_RANGE(target, expected, 3); + + d.write_block(src); + CHECK(d.read_block(target, -1)); + CHECK_RANGE(target, src, 3); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_cast_iterator.cpp b/apf/unit_tests/test_cast_iterator.cpp new file mode 100644 index 00000000..02a6f984 --- /dev/null +++ b/apf/unit_tests/test_cast_iterator.cpp @@ -0,0 +1,157 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for cast_iterator. + +#include "apf/iterator.h" // for cast_iterator +#include "iterator_test_macros.h" + +#include "catch/catch.hpp" + +struct dummy_type {}; +using ci = apf::cast_iterator; + +struct Base {}; + +struct Derived : Base +{ + Derived(int n) : _n(n) {} + + int _n; +}; + +// The iterator requirements are based on +// http://www.cplusplus.com/reference/std/iterator/RandomAccessIterator + +TEST_CASE("iterators/cast_iterator", "Test all functions of cast_iterator") +{ + +// First, the straightforward functions: + +ITERATOR_TEST_SECTION_DEFAULT_CTOR(ci) +ITERATOR_TEST_SECTION_BASE(ci, int) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(ci, int) +ITERATOR_TEST_SECTION_EQUALITY(ci, int) +ITERATOR_TEST_SECTION_INCREMENT(ci, int) +ITERATOR_TEST_SECTION_DECREMENT(ci, int) +ITERATOR_TEST_SECTION_PLUS_MINUS(ci, int) +ITERATOR_TEST_SECTION_LESS(ci, int) + +// now the "special" operators * and -> and []: + +SECTION("dereference", "*a; a->m; a[n]") +{ + auto d1 = Derived(42); + Base* pb = &d1; + + auto iter = apf::cast_iterator(&pb); + + Derived d2 = *iter; + Derived d3 = iter[0]; + + CHECK(d2._n == 42); + CHECK(iter->_n == 42); + CHECK(d3._n == 42); + + iter->_n = 23; + CHECK(d1._n == 23); + + (*iter)._n = 42; + CHECK(d1._n == 42); + + iter[0]._n = 666; + CHECK(d1._n == 666); +} + +SECTION("dereference and increment", "*a++") +{ + auto d = Derived(42); + Base* pb = &d; + + auto iter = apf::cast_iterator(&pb); + + Derived d2 = *iter++; + + CHECK(d2._n == 42); + + CHECK(iter.base() == &pb+1); +} + +SECTION("offset dereference", "a[n]") +{ + auto d = Derived(42); + + Base* b[] = { nullptr, nullptr, &d }; + + auto iter = apf::cast_iterator(b); + + Derived d2 = iter[2]; + CHECK(d2._n == 42); +} + +SECTION("test make_cast_iterator", "namespace-level helper function") +{ + int n = 5; + auto iter = apf::make_cast_iterator(&n); + CHECK(iter.base() == &n); +} + +using vb = std::vector; +vb b; +b.push_back(new Derived(1)); +b.push_back(new Derived(2)); +b.push_back(new Derived(3)); + +SECTION("test cast_proxy", "") +{ + auto p = apf::cast_proxy(b); + + CHECK(p.size() == 3); + CHECK(p.begin()->_n == 1); + CHECK((*p.begin())._n == 1); + CHECK((p.begin() + 3) == p.end()); + + p.begin()->_n = 42; + CHECK(p.begin()->_n == 42); + + CHECK(apf::make_cast_proxy(b).size() == 3); +} + +SECTION("test cast_proxy_const", "") +{ + auto p = apf::cast_proxy_const(b); + + CHECK(p.size() == 3); + CHECK(p.begin()->_n == 1); + CHECK((*p.begin())._n == 1); + CHECK((p.begin() + 3) == p.end()); + + //p.begin()->_n = 42; // compile-time error (as expected) + + CHECK(apf::make_cast_proxy_const(b).size() == 3); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_circular_iterator.cpp b/apf/unit_tests/test_circular_iterator.cpp new file mode 100644 index 00000000..12ac9014 --- /dev/null +++ b/apf/unit_tests/test_circular_iterator.cpp @@ -0,0 +1,281 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for circular_iterator. + +#include "apf/iterator.h" // for circular_iterator +#include "iterator_test_macros.h" +#include "catch/catch.hpp" + +using ci = apf::circular_iterator; + +TEST_CASE("iterators/circular_iterator/1" + , "Test all straightforward functions of circular_iterator") +{ + +ITERATOR_TEST_SECTION_BASE(ci, int) +ITERATOR_TEST_SECTION_DEFAULT_CTOR(ci) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(ci, int) +ITERATOR_TEST_SECTION_DEREFERENCE(ci, int, 5) +ITERATOR_TEST_SECTION_EQUALITY(ci, int) + +// NOTE: comparison operators (except == and !=) don't make sense! + +} // TEST_CASE + +TEST_CASE("iterators/circular_iterator/2" + , "Test all non-trivial functions of circular_iterator") +{ + +int a[] = { 0, 1, 2 }; +ci iter1(&a[0], &a[3]); +ci iter2(&a[0], &a[3], &a[1]); +ci iter3(&a[0]); // "useless" constructor +ci iter4(&a[0], &a[3], &a[3]); // wrapping, current == end -> current = begin + +SECTION("special constructors", "") +{ + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[1]); + CHECK(iter3.base() == &a[0]); + CHECK(iter4.base() == &a[0]); + + // expected error: + //ci iter5(&a[0], &a[0]); // assertion, begin == end +} + +SECTION("increment", "++a; a++") +{ + CHECK(iter1.base() == &a[0]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[1]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[2]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[0]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[1]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[2]); + iter2 = ++iter1; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[0]); + + iter2 = iter1++; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[0]); + iter2 = iter1++; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[1]); + iter2 = iter1++; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[2]); + iter2 = iter1++; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[0]); + iter2 = iter1++; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[1]); + iter2 = iter1++; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[2]); +} + +SECTION("decrement", "--a; a--") +{ + CHECK(iter1.base() == &a[0]); + iter2 = --iter1; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[2]); + iter2 = --iter1; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[1]); + iter2 = --iter1; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[0]); + iter2 = --iter1; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[2]); + iter2 = --iter1; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[1]); + iter2 = --iter1; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[0]); + + iter2 = iter1--; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[0]); + iter2 = iter1--; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[2]); + iter2 = iter1--; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[1]); + iter2 = iter1--; + CHECK(iter1.base() == &a[2]); + CHECK(iter2.base() == &a[0]); + iter2 = iter1--; + CHECK(iter1.base() == &a[1]); + CHECK(iter2.base() == &a[2]); + iter2 = iter1--; + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[1]); +} + +SECTION("plus/minus", "a + n; n + a; a - n; a - b; a += n; a -= n") +{ + CHECK((iter1 + -9).base() == &a[0]); + CHECK((iter1 + -8).base() == &a[1]); + CHECK((iter1 + -7).base() == &a[2]); + CHECK((iter1 + -6).base() == &a[0]); + CHECK((iter1 + -5).base() == &a[1]); + CHECK((iter1 + -4).base() == &a[2]); + CHECK((iter1 + -3).base() == &a[0]); + CHECK((iter1 + -2).base() == &a[1]); + CHECK((iter1 + -1).base() == &a[2]); + CHECK((iter1 + 0).base() == &a[0]); + CHECK((iter1 + 1).base() == &a[1]); + CHECK((iter1 + 2).base() == &a[2]); + CHECK((iter1 + 3).base() == &a[0]); + CHECK((iter1 + 4).base() == &a[1]); + CHECK((iter1 + 5).base() == &a[2]); + CHECK((iter1 + 6).base() == &a[0]); + CHECK((iter1 + 7).base() == &a[1]); + CHECK((iter1 + 8).base() == &a[2]); + CHECK((iter1 + 9).base() == &a[0]); + + CHECK((iter1 - 9).base() == &a[0]); + CHECK((iter1 - 8).base() == &a[1]); + CHECK((iter1 - 7).base() == &a[2]); + CHECK((iter1 - 6).base() == &a[0]); + CHECK((iter1 - 5).base() == &a[1]); + CHECK((iter1 - 4).base() == &a[2]); + CHECK((iter1 - 3).base() == &a[0]); + CHECK((iter1 - 2).base() == &a[1]); + CHECK((iter1 - 1).base() == &a[2]); + CHECK((iter1 - 0).base() == &a[0]); + CHECK((iter1 - -1).base() == &a[1]); + CHECK((iter1 - -2).base() == &a[2]); + CHECK((iter1 - -3).base() == &a[0]); + CHECK((iter1 - -4).base() == &a[1]); + CHECK((iter1 - -5).base() == &a[2]); + CHECK((iter1 - -6).base() == &a[0]); + CHECK((iter1 - -7).base() == &a[1]); + CHECK((iter1 - -8).base() == &a[2]); + CHECK((iter1 - -9).base() == &a[0]); + + CHECK((0 + iter1).base() == &a[0]); + CHECK((1 + iter1).base() == &a[1]); + CHECK((2 + iter1).base() == &a[2]); + CHECK((3 + iter1).base() == &a[0]); + CHECK((4 + iter1).base() == &a[1]); + CHECK((5 + iter1).base() == &a[2]); + CHECK((6 + iter1).base() == &a[0]); + CHECK((7 + iter1).base() == &a[1]); + CHECK((8 + iter1).base() == &a[2]); + CHECK((9 + iter1).base() == &a[0]); + + CHECK((ci(&a[0], &a[3], &a[0]) - ci(&a[0], &a[3])) == 0); + CHECK((ci(&a[0], &a[3], &a[1]) - ci(&a[0], &a[3])) == 1); + CHECK((ci(&a[0], &a[3], &a[2]) - ci(&a[0], &a[3])) == 2); + + // all differences are positive! + CHECK((ci(&a[0], &a[3]) - ci(&a[0], &a[3], &a[0])) == 0); + CHECK((ci(&a[0], &a[3]) - ci(&a[0], &a[3], &a[1])) == 2); + CHECK((ci(&a[0], &a[3]) - ci(&a[0], &a[3], &a[2])) == 1); + + iter2 = (iter1 += 0); + CHECK(iter1.base() == &a[0]); + CHECK(iter2.base() == &a[0]); + iter1 += 2; + CHECK(iter1.base() == &a[2]); + iter1 += 2; + CHECK(iter1.base() == &a[1]); + iter1 -= 2; + CHECK(iter1.base() == &a[2]); + iter1 -= 2; + CHECK(iter1.base() == &a[0]); + + CHECK((iter3 + 666).base() == &a[0]); +} + +SECTION("offset dereference", "a[n]") +{ + CHECK(iter1[-5] == 1); + CHECK(iter1[-4] == 2); + CHECK(iter1[-3] == 0); + CHECK(iter1[-2] == 1); + CHECK(iter1[-1] == 2); + CHECK(iter1[ 0] == 0); + CHECK(iter1[ 1] == 1); + CHECK(iter1[ 2] == 2); + CHECK(iter1[ 3] == 0); + CHECK(iter1[ 4] == 1); + CHECK(iter1[ 5] == 2); + + // can we also assign? + iter1[-3] = 42; + CHECK(a[0] == 42); +} + +SECTION("make_circular_iterator", "") +{ + iter1 = apf::make_circular_iterator(&a[0], &a[3]); + CHECK(iter1.base() == &a[0]); + iter1 = apf::make_circular_iterator(&a[0], &a[3], &a[2]); + CHECK(iter1.base() == &a[2]); + iter1 = apf::make_circular_iterator(&a[0], &a[3], &a[3]); + CHECK(iter1.base() == &a[0]); +} + +} // TEST_CASE + +#include + +TEST_CASE("iterators/circular_iterator/3" + , "Test if it also works with a bidirectional iterator") +{ + + std::list l = {0, 1, 2}; + apf::circular_iterator::iterator> it(l.begin(), l.end()); + + CHECK(*it == 0); + --it; + CHECK(*it == 2); + it--; + CHECK(*it == 1); + ++it; + CHECK(*it == 2); + it++; + CHECK(*it == 0); + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_combine_channels.cpp b/apf/unit_tests/test_combine_channels.cpp new file mode 100644 index 00000000..d86419f3 --- /dev/null +++ b/apf/unit_tests/test_combine_channels.cpp @@ -0,0 +1,129 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for Combine*. + +#include "apf/combine_channels.h" + +#include "catch/catch.hpp" + +#include + +using Item = std::vector; + +using L = std::vector; + +struct SelectChange +{ + apf::CombineChannelsResult::type select(const Item&) + { + return apf::CombineChannelsResult::change; + } + + void update() { /* ... */ } +}; + +class Crossfade +{ + public: + using vi = std::vector; + + Crossfade(size_t block_size) + : _size(block_size) + , _fade_out(_size, 2) + , _fade_in(_size, 3) + {} + + vi::const_iterator fade_out_begin() const { return _fade_out.begin(); } + vi::const_iterator fade_in_begin() const { return _fade_in.begin(); } + size_t size() const { return _size; } + + private: + const size_t _size; + + vi _fade_out; + vi _fade_in; +}; + +TEST_CASE("CombineChannels*", "") +{ + +int a[] = { 1, 2, 3, 4, 5, 6 }; + +L source; +source.push_back(Item(a, a+3)); +source.push_back(Item(a+3, a+6)); + +Crossfade crossfade(3); + +Item target(3, 0); + +SECTION("CombineChannelsCopy", "") +{ + apf::CombineChannelsCopy c(source, target); + + // TODO: checks +} + +SECTION("CombineChannels", "") +{ + apf::CombineChannels c(source, target); + + // TODO: checks +} + +SECTION("CombineChannelsInterpolation", "") +{ + apf::CombineChannelsInterpolation c(source, target); + + // TODO: checks +} + +SECTION("CombineChannelsCrossfadeCopy", "") +{ + apf::CombineChannelsCrossfadeCopy + c(source, target, crossfade); + + c.process(SelectChange()); + + // TODO: use CHECK_RANGE() from test_convolver.cpp + + CHECK(target[0] == 25); + CHECK(target[1] == 35); + CHECK(target[2] == 45); + + // TODO: more checks? +} + +SECTION("CombineChannelsCrossfade", "") +{ + apf::CombineChannelsCrossfade + c(source, target, crossfade); + + // TODO: checks +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_container.cpp b/apf/unit_tests/test_container.cpp new file mode 100644 index 00000000..1e0d5306 --- /dev/null +++ b/apf/unit_tests/test_container.cpp @@ -0,0 +1,465 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for fixed_vector, fixed_list, fixed_matrix + +#include "apf/container.h" + +#include "catch/catch.hpp" + +struct NonCopyableButMovable +{ + explicit NonCopyableButMovable(int x_ = 666) : x(x_) {} + NonCopyableButMovable(NonCopyableButMovable&&) = default; + + NonCopyableButMovable(const NonCopyableButMovable&) = delete; + NonCopyableButMovable& operator=(const NonCopyableButMovable&) = delete; + + int x; +}; + +struct mystruct +{ + mystruct(int one, int two) : first(one), second(two) {} + int first, second; +}; + +TEST_CASE("fixed_vector", "Test fixed_vector") +{ + +SECTION("default constructor", "") +{ + apf::fixed_vector fv; + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 0); +} + +SECTION("default constructor and allocator", "") +{ + auto a = std::allocator(); + apf::fixed_vector fv(a); + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 0); +} + +SECTION("constructor from size", "uses default constructor") +{ + apf::fixed_vector fv(3); + CHECK(fv[1] == 0); + + apf::fixed_vector fv2(0); + CHECK(fv2.size() == 0); +} + +SECTION("constructor from size and allocator", "") +{ + auto a = std::allocator(); + + apf::fixed_vector fv(3, a); + CHECK(fv[2] == 0); + + CHECK(apf::fixed_vector(0, a).size() == 0); + + // rvalue allocator + CHECK(apf::fixed_vector(0, std::allocator()).size() == 0); +} + +SECTION("constructor from size and default value", "") +{ + apf::fixed_vector fv(3, 99); + + CHECK(fv[2] == 99); +} + +SECTION("constructor from size and default value and allocator", "") +{ + auto a = std::allocator(); + + apf::fixed_vector fv(3, 99, a); + + CHECK(fv[2] == 99); + + // rvalue allocator + CHECK(apf::fixed_vector(3, 99, std::allocator())[2] == 99); +} + +SECTION("constructor from size and initializer arguments", "") +{ + apf::fixed_vector fv(3, 4, 5); + CHECK(fv[2].second == 5); +} + +SECTION("copy constructor", "") +{ + apf::fixed_vector fv(3, 99); + apf::fixed_vector fv2(fv); + CHECK(fv[2] == 99); + CHECK(fv2[2] == 99); +} + +SECTION("move constructor", "") +{ + apf::fixed_vector fv(apf::fixed_vector(3, 99)); + + CHECK(fv[2] == 99); +} + +SECTION("constructor from initializer list", "") +{ + apf::fixed_vector fv{42}; + CHECK(fv.size() == 1); + CHECK(fv[0] == 42); + + // Note: extra parentheses because of commas + CHECK((apf::fixed_vector{42, 43}.size()) == 2); + CHECK((apf::fixed_vector{42, 43, 44}.size()) == 3); +} + +const int size = 4; +int data[size] = { 1, 2, 3, 4 }; + +SECTION("constructor from range", "") +{ + apf::fixed_vector fv(data, data+size); + CHECK(fv[1] == 2); + fv[1] = 100; + CHECK(fv[1] == 100); + + CHECK(*fv.begin() == 1); + CHECK(*fv.rbegin() == 4); + + CHECK(fv.size() == 4); + CHECK_FALSE(fv.empty()); + + CHECK(fv.front() == 1); + CHECK(fv.back() == 4); +} + +SECTION("constructor from range (const)", "") +{ + const apf::fixed_vector fv(data, data+4); + + CHECK(*fv.begin() == 1); + CHECK(fv[2] == 3); + + CHECK(*fv.rbegin() == 4); + + CHECK(fv.size() == 4); + CHECK_FALSE(fv.empty()); + + CHECK(fv.front() == 1); + CHECK(fv.back() == 4); +} + +SECTION("reserve() and emplace_back()", "") +{ + apf::fixed_vector fv; + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 0); + + CHECK_THROWS_AS(fv.emplace_back(666), std::logic_error); + + fv.reserve(1); + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 1); + + fv.emplace_back(1); + CHECK(fv[0] == 1); + + CHECK_THROWS_AS(fv.emplace_back(666), std::logic_error); + + CHECK_THROWS_AS(fv.reserve(42), std::logic_error); +} + +SECTION("fixed_vector of non-copyable type", "") +{ + apf::fixed_vector fv(1000); + CHECK(fv[999].x == 666); + + apf::fixed_vector fv2(1000, 42); + CHECK(fv2[999].x == 42); +} + +SECTION("fixed_vector of non-copyable type, emplace_back()", "") +{ + apf::fixed_vector fv; + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 0); + + fv.reserve(1); + CHECK(fv.size() == 0); + CHECK(fv.capacity() == 1); + + fv.emplace_back(27); + CHECK(fv.front().x == 27); + + CHECK_THROWS_AS(fv.emplace_back(23), std::logic_error); +} + +} // TEST_CASE fixed_vector + +TEST_CASE("fixed_list", "Test fixed_list") +{ + +SECTION("default constructor", "") +{ + apf::fixed_list fl; + CHECK(fl.size() == 0); +} + +SECTION("constructor from size", "") +{ + apf::fixed_list fl(3); + CHECK(fl.size() == 3); + CHECK(fl.front() == 0); +} + +SECTION("constructor from size and initializer", "") +{ + apf::fixed_list fl(3, 42); + CHECK(fl.size() == 3); + CHECK(fl.front() == 42); +} + +SECTION("constructor from size and several initializers", "") +{ + apf::fixed_list fl(3, 42, 25); + CHECK(fl.size() == 3); + CHECK(fl.front().second == 25); +} + +SECTION("constructor from initializer list", "") +{ + apf::fixed_list fl{3, 42}; + CHECK(fl.size() == 2); + CHECK(fl.front() == 3); +} + +SECTION("constructor from sequence and more", "") +{ + int data[] = { 1, 2, 3, 4 }; + apf::fixed_list fl(data, data+4); + CHECK(*fl.begin() == 1); + CHECK(*(--fl.end()) == 4); + CHECK(*fl.rbegin() == 4); + CHECK(*(--fl.rend()) == 1); + + CHECK(fl.front() == 1); + CHECK(fl.back() == 4); + fl.front() = 100; + CHECK(fl.front() == 100); + fl.front() = 1; + + CHECK(fl.size() == 4); + CHECK_FALSE(fl.empty()); + + fl.move(fl.begin(), fl.end()); + CHECK(*(fl.begin()) == 2); + CHECK(*(++fl.begin()) == 3); + CHECK(*(++++fl.begin()) == 4); + CHECK(*(++++++fl.begin()) == 1); + + fl.move(++fl.begin(), fl.end()); + CHECK(*(fl.begin()) == 2); + CHECK(*(++fl.begin()) == 4); + CHECK(*(++++fl.begin()) == 1); + CHECK(*(++++++fl.begin()) == 3); + + fl.move(--fl.end(), fl.begin()); + CHECK(*(fl.begin()) == 3); + CHECK(*(++fl.begin()) == 2); + CHECK(*(++++fl.begin()) == 4); + CHECK(*(++++++fl.begin()) == 1); + + fl.move(++fl.begin(), ++++++fl.begin(), fl.begin()); + CHECK(*(fl.begin()) == 2); + CHECK(*(++fl.begin()) == 4); + CHECK(*(++++fl.begin()) == 3); + CHECK(*(++++++fl.begin()) == 1); + + const apf::fixed_list cfl(data, data+4); + + CHECK(cfl.front() == 1); + CHECK(cfl.back() == 4); + + CHECK(*cfl.begin() == 1); + CHECK(*(--cfl.end()) == 4); + CHECK(*cfl.rbegin() == 4); + CHECK(*(--cfl.rend()) == 1); + + CHECK(cfl.size() == 4); + CHECK_FALSE(cfl.empty()); +} + +SECTION("empty()", "not really useful ...") +{ + apf::fixed_list fl(0); + CHECK(fl.empty()); +} + +SECTION("fixed_list", "") +{ + apf::fixed_list fl(1000); + CHECK(fl.back().x == 666); + apf::fixed_list fl2(1000, 42); + CHECK(fl2.back().x == 42); +} + +} // TEST_CASE fixed_list + +using fm = apf::fixed_matrix; + +TEST_CASE("fixed_matrix", "Test fixed_matrix") +{ + +SECTION("default constructor", "... and initialize()") +{ + fm matrix; + CHECK(matrix.empty()); + CHECK(matrix.channels.begin() == matrix.channels.end()); // not allowed! + CHECK(matrix.slices.begin() == matrix.slices.end()); // not allowed! + + matrix.initialize(2, 3); + CHECK_FALSE(matrix.empty()); + CHECK(std::distance(matrix.channels.begin(), matrix.channels.end()) == 2); + CHECK(std::distance(matrix.slices.begin(), matrix.slices.end()) == 3); +} + +SECTION("the normal constructor and more", "") +{ + fm matrix(3, 2); + CHECK_FALSE(matrix.empty()); + CHECK(std::distance(matrix.channels.begin(), matrix.channels.end()) == 3); + CHECK(std::distance(matrix.slices.begin(), matrix.slices.end()) == 2); + + matrix.channels[2][0] = 42; + + fm matrix2(2, 3); + matrix2.set_channels(matrix.slices); + + CHECK(matrix2.channels[0][2] == 42); + CHECK(matrix2.slices[2][0] == 42); + + CHECK(matrix2.get_channel_ptrs()[0][2] == 42); +} + +// TODO: check channels_iterator and slices_iterator + +} // TEST_CASE fixed_matrix + +#include + +struct ClassWithSublist +{ + std::list sublist; +}; + +TEST_CASE("misc", "the rest") +{ + +SECTION("append_pointers()", "") +{ + apf::fixed_vector v(3); + std::list target; + apf::append_pointers(v, target); + CHECK(*target.begin() == &*v.begin()); +} + +SECTION("const append_pointers()", "") +{ + const apf::fixed_vector v(3); + std::list target; + apf::append_pointers(v, target); + CHECK(*target.begin() == &*v.begin()); +} + +SECTION("distribute_list()", "... and undistribute_list()") +{ + std::list in; + in.push_back(1); + in.push_back(2); + in.push_back(3); + apf::fixed_vector out(3); + + distribute_list(in, out, &ClassWithSublist::sublist); + + CHECK(in.empty() == true); + // lists have different size -> exception: + CHECK_THROWS_AS(distribute_list(in, out, &ClassWithSublist::sublist) + , std::logic_error); + CHECK(out[2].sublist.size() == 1); + CHECK(out[2].sublist.front() == 3); + + in.clear(); + in.push_back(4); + in.push_back(5); + in.push_back(6); + + distribute_list(in, out, &ClassWithSublist::sublist); + CHECK(out[2].sublist.size() == 2); + CHECK(out[2].sublist.front() == 3); + CHECK(out[2].sublist.back() == 6); + + CHECK(in.size() == 0); // 'in' is empty again ... + + // For undistribute_list(), the first argument can be a different type: + apf::fixed_vector in2(3); + in2[0] = 1; + in2[1] = 2; + in2[2] = 3; + + std::list garbage; + + undistribute_list(in2, out, &ClassWithSublist::sublist, garbage); + + CHECK(garbage.size() == 3); + CHECK(in2.size() == 3); + CHECK(out[2].sublist.size() == 1); + CHECK(out[2].sublist.front() == 6); + + in.push_back(666); + + // in and out have different size -> exception: + CHECK_THROWS_AS(undistribute_list(in, out, &ClassWithSublist::sublist + , garbage), std::logic_error); + + CHECK(in.size() == 1); + + in.push_back(5); + in.push_back(6); + + // list item is not found -> exception: + CHECK_THROWS_AS(undistribute_list(in, out, &ClassWithSublist::sublist + , garbage), std::logic_error); + + CHECK(in.size() == 3); + + in.front() = 4; + + CHECK_NOTHROW(undistribute_list(in, out, &ClassWithSublist::sublist,garbage)); +} + +} // TEST_CASE misc + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_convolver.cpp b/apf/unit_tests/test_convolver.cpp new file mode 100644 index 00000000..2f571daf --- /dev/null +++ b/apf/unit_tests/test_convolver.cpp @@ -0,0 +1,245 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for the Convolver. + +#include "apf/convolver.h" + +#include "catch/catch.hpp" + +#define CHECK_RANGE(left, right, range) \ + for (int i = 0; i < range; ++i) { \ + INFO("i = " << i); \ + CHECK((left)[i] == Approx((right)[i])); } + +namespace c = apf::conv; + +TEST_CASE("Convolver", "Test Convolver") +{ + +float test_signal[] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f + , 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f }; +float zeros[20] = { 0.0f }; + +float filter_data[16] = { 0.0f }; +filter_data[10] = 5.0f; +filter_data[11] = 4.0f; +filter_data[12] = 3.0f; + +auto partitions = c::min_partitions(8, 16); +auto conv_input = c::Input(8, partitions); +auto conv_output = c::Output(conv_input); +auto filter = c::Filter(8, filter_data, filter_data + 16); + +float* result; + +SECTION("silence", "") +{ + conv_input.add_block(test_signal); + result = conv_output.convolve(); + + CHECK_RANGE(result, zeros, 8); + + conv_input.add_block(test_signal); + result = conv_output.convolve(); + + CHECK_RANGE(result, zeros, 8); +} + +SECTION("impulse", "") +{ + float one = 1.0f; + auto impulse = c::Filter(8, &one, (&one)+1); + + conv_input.add_block(test_signal); + conv_output.set_filter(impulse); + result = conv_output.convolve(); + + CHECK_RANGE(result, test_signal, 8); + + conv_input.add_block(test_signal + 8); + result = conv_output.convolve(); + + CHECK_RANGE(result, test_signal + 8, 8); +} + +SECTION("... and more", "") +{ + conv_output.set_filter(filter); + + float input[8] = { 0.0f }; + + input[1] = 1.0f; + conv_input.add_block(input); + result = conv_output.convolve(); + + CHECK_RANGE(result, zeros, 8); + + input[1] = 2.0f; + conv_input.add_block(input); + CHECK_FALSE(conv_output.queues_empty()); + conv_output.rotate_queues(); + result = conv_output.convolve(); + + float expected[8] = { 0.0f }; + expected[3] = 5.0f; + expected[4] = 4.0f; + expected[5] = 3.0f; + + CHECK_RANGE(result, expected, 8); + + input[1] = 0.0f; + conv_input.add_block(input); + CHECK(conv_output.queues_empty()); + //conv_filter.rotate_queues(); // not necessary, because queues are empty + result = conv_output.convolve(); + + expected[3] = 10.0f; + expected[4] = 8.0f; + expected[5] = 6.0f; + + CHECK_RANGE(result, expected, 8); + + conv_input.add_block(input); + CHECK(conv_output.queues_empty()); + //conv_filter.rotate_queues(); // not necessary, because queues are empty + result = conv_output.convolve(); + + CHECK_RANGE(result, zeros, 8); + + CHECK(conv_output.queues_empty()); +} + +SECTION("StaticOutput impulse", "") +{ + float one = 1.0f; + + auto sconv_input = c::Input(8, 1); + auto sconv_output = c::StaticOutput(sconv_input, &one, (&one)+1); + + sconv_input.add_block(test_signal); + result = sconv_output.convolve(); + + CHECK_RANGE(result, test_signal, 8); + + sconv_input.add_block(test_signal + 8); + result = sconv_output.convolve(); + + CHECK_RANGE(result, test_signal + 8, 8); +} + +SECTION("StaticOutput frequency domain", "") +{ + float one = 1.0f; + // 3 partitions (only 1 is really needed), blocksize 8 + auto fd_filter = c::Filter(8, 3); + // 7 partitions (just for fun), blocksize 8 + auto sconv_input = c::Input(8, 7); + + conv_input.prepare_filter(&one, (&one)+1, fd_filter); + auto sconv_output = c::StaticOutput(sconv_input, fd_filter); + + CHECK(sconv_input.partitions() == 7); + CHECK(fd_filter.partitions() == 3); + + sconv_input.add_block(test_signal); + result = sconv_output.convolve(); + + CHECK_RANGE(result, test_signal, 8); +} + +SECTION("combinations", "") +{ + auto so = c::StaticOutput(conv_input, filter); + + auto conv = c::Convolver(8, partitions); + conv.set_filter(filter); + conv.rotate_queues(); + + auto sconv = c::StaticConvolver(filter, partitions); + + float input[8] = { 0.0f }; + + input[1] = 1.0f; + conv_input.add_block(input); + conv.add_block(input); + sconv.add_block(input); + + result = so.convolve(); + CHECK_RANGE(result, zeros, 8); + result = conv.convolve(); + CHECK_RANGE(result, zeros, 8); + result = sconv.convolve(); + CHECK_RANGE(result, zeros, 8); + + input[1] = 2.0f; + conv_input.add_block(input); + conv.add_block(input); + sconv.add_block(input); + + float expected[8] = { 0.0f }; + expected[3] = 5.0f; + expected[4] = 4.0f; + expected[5] = 3.0f; + + result = so.convolve(); + CHECK_RANGE(result, expected, 8); + result = conv.convolve(); + CHECK_RANGE(result, expected, 8); + result = sconv.convolve(); + CHECK_RANGE(result, expected, 8); + + input[1] = 0.0f; + conv_input.add_block(input); + conv.add_block(input); + sconv.add_block(input); + + expected[3] = 10.0f; + expected[4] = 8.0f; + expected[5] = 6.0f; + + result = so.convolve(); + CHECK_RANGE(result, expected, 8); + result = conv.convolve(); + CHECK_RANGE(result, expected, 8); + result = sconv.convolve(); + CHECK_RANGE(result, expected, 8); + + conv_input.add_block(input); + conv.add_block(input); + sconv.add_block(input); + + result = so.convolve(); + CHECK_RANGE(result, zeros, 8); + result = conv.convolve(); + CHECK_RANGE(result, zeros, 8); + result = sconv.convolve(); + CHECK_RANGE(result, zeros, 8); +} + +// TODO: test copy_nested() and transform_nested()! + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_discard_iterator.cpp b/apf/unit_tests/test_discard_iterator.cpp new file mode 100644 index 00000000..dc3b9d95 --- /dev/null +++ b/apf/unit_tests/test_discard_iterator.cpp @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for discard_iterator. + +#include "apf/iterator.h" // for discard_iterator + +#include "catch/catch.hpp" + +using di = apf::discard_iterator; + +TEST_CASE("iterators/discard_iterator" + , "Test all functions of discard_iterator") +{ + +SECTION("everything", "") +{ + auto iter = di(); + ++iter; + iter++; + *iter = 42; + *iter = 3.14; + *iter += 1; + + // No actual checks are possible ... +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_dual_iterator.cpp b/apf/unit_tests/test_dual_iterator.cpp new file mode 100644 index 00000000..3e1635c5 --- /dev/null +++ b/apf/unit_tests/test_dual_iterator.cpp @@ -0,0 +1,133 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for dual_iterator. + +#include "apf/iterator.h" // for dual_iterator + +#include "catch/catch.hpp" + +TEST_CASE("iterators/dual_iterator", "Test all functions of dual_iterator") +{ + +using di = apf::dual_iterator; + +int x[] = { 0, 0 }; +int y[] = { 0, 0 }; + +SECTION("default ctor", "") +{ + di one; + di(); +} + +SECTION("copy ctor, assignment", "X b(a); b=a;") +{ + auto iter1 = di(x, y); + auto iter2 = di(iter1); + auto iter3 = iter1; // same as above + (void)iter2; + (void)iter3; + di iter4; + iter4 = iter1; + + // TODO: actually CHECK something? +} + +SECTION("dereference/increment", "*a, *a++") +{ + auto iter = di(x, y); + + *iter++ = 1; + + CHECK(x[0] == 1); + CHECK(y[0] == 1); + + *iter = 2; + + CHECK(x[1] == 2); + CHECK(y[1] == 2); +} + +SECTION("(un)equality", "... and pre/post-increment") +{ + auto iter1 = di(x, y); + auto iter2 = di(x, y); + + ++iter1; + + CHECK_FALSE(iter1 == iter2); + CHECK(iter1 != iter2); + + iter2++; + + CHECK(iter1 == iter2); + CHECK_FALSE(iter1 != iter2); +} + +SECTION("test make_dual_iterator", "... and assignment") +{ + auto iter = apf::make_dual_iterator(x, y); + *iter = 1; + CHECK(*x == 1); + CHECK(*y == 1); +} + +SECTION("dual_iterator assign from std::pair", "") +{ + int i = 5; + double d = 6; + auto iter = apf::make_dual_iterator(&i, &d); + *iter = std::make_pair(2, 3.0); + CHECK(i == 2); + CHECK(d == 3.0); +} + +SECTION("dual_iterator assign to std::pair", "") +{ + int i = 5; + double d = 6; + auto iter = apf::make_dual_iterator(&i, &d); + // The pair types don't have to match exactly: + std::pair p = *iter; + CHECK(p.first == 5); + CHECK(p.second == 6.0); +} + +SECTION("dereference ... and do stuff", "+=") +{ + auto iter = apf::make_dual_iterator(x, y); + + *iter += 5; + CHECK(*x == 5); + CHECK(*y == 5); + + *iter += std::make_pair(4, 2); + CHECK(*x == 9); + CHECK(*y == 7); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_fftwtools.cpp b/apf/unit_tests/test_fftwtools.cpp new file mode 100644 index 00000000..c74a6d08 --- /dev/null +++ b/apf/unit_tests/test_fftwtools.cpp @@ -0,0 +1,61 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for FFTW tools. + +#include "apf/fftwtools.h" + +#include // for uintptr_t +#include "catch/catch.hpp" +#include "apf/container.h" // for fixed_vector + +bool is16bytealigned(void* ptr) +{ + return (uintptr_t(ptr) & 0xF) == 0; +} + +TEST_CASE("fftw_allocator", "Test fftw_allocator") +{ + +SECTION("stuff", "") +{ + std::vector> vf; + vf.push_back(3.1415f); + + CHECK(vf.front() == 3.1415f); + + std::vector> vd; + std::vector> vl; + + apf::fixed_vector> ff(42); + apf::fixed_vector> fd(42); + apf::fixed_vector> fl(42); + + CHECK(is16bytealigned(&vf.front())); + CHECK(is16bytealigned(&ff.front())); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_index_iterator.cpp b/apf/unit_tests/test_index_iterator.cpp new file mode 100644 index 00000000..b51beabb --- /dev/null +++ b/apf/unit_tests/test_index_iterator.cpp @@ -0,0 +1,154 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for index_iterator. + +#include "apf/iterator.h" // for index_iterator + +#include "catch/catch.hpp" + +using ii = apf::index_iterator; + +TEST_CASE("iterators/index_iterator", "Test all functions of index_iterator") +{ + +// check if default constructor compiles +ii it; +it = ii(); + +SECTION("copy ctor, assignment", "X b(a); b=a;") +{ + auto iter1 = ii(42); + auto iter2 = ii(iter1); + ii iter3; + iter3 = iter1; + + CHECK(*iter2 == 42); + CHECK(*iter3 == 42); +} + +SECTION("comparisons", "a == b; a != b, a < b, ...") +{ + auto iter1 = ii(4); + auto iter2 = ii(4); + auto iter3 = ii(5); + + CHECK(iter1 == iter2); + CHECK(iter2 != iter3); + CHECK_FALSE(iter1 != iter2); + CHECK_FALSE(iter2 == iter3); + + CHECK(iter1 < iter3); + CHECK_FALSE(iter1 > iter3); + CHECK(iter3 > iter1); + CHECK_FALSE(iter3 < iter1); + + CHECK(iter1 <= iter2); + CHECK(iter1 <= iter3); + CHECK(iter2 <= iter1); + + CHECK(iter3 >= iter1); + CHECK(iter2 >= iter1); +} + +SECTION("dereference", "*a; a[]") +{ + auto iter = ii(4); + + CHECK(*iter == 4); + + CHECK(iter[4] == 8); + + // NOTE: operator->() is purposely not implemented! +} + +SECTION("increment, decrement", "++a; a++; *a++; --a; a--; *a--") +{ + auto iter1 = ii(0); + ii iter2; + + CHECK(*iter1++ == 0); + + iter2 = iter1++; + CHECK(*iter1 == 2); + CHECK(*iter2 == 1); + + iter2 = ++iter1; + CHECK(*iter1 == 3); + CHECK(*iter2 == 3); + + CHECK(*iter1-- == 3); + + iter2 = iter1--; + CHECK(*iter1 == 1); + CHECK(*iter2 == 2); + + iter2 = --iter1; + CHECK(*iter1 == 0); + CHECK(*iter2 == 0); +} + +SECTION("plus, minus", "a + n; a += n; ...") +{ + ii iter1; + ii iter2; + + CHECK(*(iter1 + 5) == 5); + CHECK(*(iter1 - 5) == -5); + CHECK(*(5 + iter1) == 5); + + iter2 = iter1 += 3; + CHECK(*iter1 == 3); + CHECK(*iter2 == 3); + iter2 = iter1 -= 1; + CHECK(*iter1 == 2); + CHECK(*iter2 == 2); + + iter2 += 5; + CHECK((iter2 - iter1) == 5); + CHECK((iter1 - iter2) == -5); +} + +SECTION("test make_index_iterator", "namespace-level helper function") +{ + CHECK(*apf::make_index_iterator(5) == 5); +} + +SECTION("unsigned", "see what happens with unsigned types") +{ + apf::index_iterator iter1; + apf::index_iterator iter2(2); + + //CHECK((iter1 - iter2) == -2); + + //--iter1; + //CHECK(*iter1 == -1); + + // This is dangerous (because errors may remain unnoticed)! + // Stay away from unsigned types! +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_iterator.cpp b/apf/unit_tests/test_iterator.cpp new file mode 100644 index 00000000..e7550d8f --- /dev/null +++ b/apf/unit_tests/test_iterator.cpp @@ -0,0 +1,104 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests iterators. +// See also test_*_iterator.cpp + +#include "apf/iterator.h" + +#include "catch/catch.hpp" + +TEST_CASE("iterators/has_begin_and_end", "Test has_begin_and_end") +{ + +int a[] = { 1, 2, 3 }; + +SECTION("default constructor", "") +{ + apf::has_begin_and_end range; + CHECK(range.begin() == range.end()); +} + +SECTION("constructor with begin and end", "... and assignment op") +{ + auto range = apf::has_begin_and_end(a, a+3); + CHECK(range.begin() == a); + CHECK(range.end() == a + 3); + apf::has_begin_and_end range2; + range2 = range; + CHECK(range2.begin() == a); + CHECK(range2.end() == a + 3); +} + +SECTION("constructor with begin and length", "... and copy ctor") +{ + auto range = apf::has_begin_and_end(a, 3); + CHECK(range.begin() == a); + CHECK(range.end() == a + 3); + auto range2 = apf::has_begin_and_end(range); + CHECK(range2.begin() == a); + CHECK(range2.end() == a + 3); +} + +SECTION("subscript operator", "") +{ + auto range = apf::has_begin_and_end(a, 3); + CHECK(range[1] == 2); + range[1] = 42; + CHECK(range[1] == 42); +} + +SECTION("const subscript operator", "") +{ + const int* b = a; + auto range = apf::has_begin_and_end(b, 3); + CHECK(range[1] == 2); + //range[1] = 42; // doesn't work (as expected) +} + +} // TEST_CASE + +#include + +TEST_CASE("bidirectional iterator", "") +{ + +std::list l; +l.push_back(1); +l.push_back(2); +l.push_back(3); + +SECTION("bidirectional iterator", "") +{ + auto range + = apf::has_begin_and_end::iterator>(l.begin(), l.end()); + CHECK(range.begin() == l.begin()); + CHECK(range.end() == l.end()); + CHECK(*range.begin() == 1); + //CHECK(range[1] == 2); // doesn't work (as expected) +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_iterator_combinations.cpp b/apf/unit_tests/test_iterator_combinations.cpp new file mode 100644 index 00000000..09503460 --- /dev/null +++ b/apf/unit_tests/test_iterator_combinations.cpp @@ -0,0 +1,66 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for combinations of different iterators. + +#include "apf/iterator.h" // for *_iterator + +#include "catch/catch.hpp" + +struct three_halves +{ + float operator()(int in) { return static_cast(in) * 1.5f; } +}; + +using ii = apf::index_iterator; +using fii = apf::transform_iterator; +using si = apf::stride_iterator; +using fsi = apf::transform_iterator; + +TEST_CASE("iterators/combinations", "Test combinations of iterators") +{ + +SECTION("index_iterator + transform_iterator", "") +{ + auto iter = fii(apf::make_index_iterator(2), three_halves()); + CHECK(*iter == 3.0f); +} + +SECTION("index_iterator + stride_iterator + transform_iterator", "") +{ + auto iter = fsi(si(apf::make_index_iterator(2), 2), three_halves()); + CHECK(*iter == 3.0f); + ++iter; + CHECK(*iter == 6.0f); + + auto iter2 = fsi(si(apf::make_index_iterator(2), -2), three_halves()); + CHECK(*iter2 == 3.0f); + ++iter2; + CHECK(*iter2 == 0.0f); +} + + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_math.cpp b/apf/unit_tests/test_math.cpp new file mode 100644 index 00000000..2d45ab2d --- /dev/null +++ b/apf/unit_tests/test_math.cpp @@ -0,0 +1,239 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for math functions. + +#include "apf/math.h" + +#include "catch/catch.hpp" + +using namespace apf::math; + +TEST_CASE("math", "Test all functions of math namespace") +{ + +SECTION("pi", "") +{ + CHECK(pi() == 4.0f * std::atan(1.0f)); + CHECK(pi() == 4.0 * std::atan(1.0)); + CHECK(pi() == 4.0l * std::atan(1.0l)); + + // pi divided by 180 + + CHECK((pi_div_180() * 180.0f / pi()) == 1.0f); + CHECK((pi_div_180() * 180.0 / pi()) == 1.0); + CHECK((pi_div_180() * 180.0l / pi()) == 1.0l); +} + +SECTION("square", "a*a") +{ + CHECK(square(2.0f) == 4.0f); + CHECK(square(2.0) == 4.0f); + CHECK(square(2.0l) == 4.0l); +} + +SECTION("dB2linear", "") +{ + CHECK(dB2linear(6.0f) == Approx(1.99526231)); + CHECK(dB2linear(6.0) == Approx(1.99526231)); + // Approx doesn't exist for long double + CHECK(static_cast(dB2linear(6.0l)) == Approx(1.99526231)); + + // now with the "power" option + + CHECK(dB2linear(3.0f, true) == Approx(1.99526231)); + CHECK(dB2linear(3.0, true) == Approx(1.99526231)); + CHECK(static_cast(dB2linear(3.0l, true)) == Approx(1.99526231)); +} + +SECTION("linear2dB", "") +{ + CHECK(linear2dB(1.99526231f) == Approx(6.0)); + CHECK(linear2dB(1.99526231) == Approx(6.0)); + CHECK(static_cast(linear2dB(1.99526231l)) == Approx(6.0)); + + CHECK(linear2dB(1.99526231f, true) == Approx(3.0)); + CHECK(linear2dB(1.99526231, true) == Approx(3.0)); + CHECK(static_cast(linear2dB(1.99526231l, true)) == Approx(3.0)); + + CHECK(linear2dB(0.0f) == -std::numeric_limits::infinity()); + CHECK(linear2dB(0.0) == -std::numeric_limits::infinity()); + CHECK(linear2dB(0.0l) == -std::numeric_limits::infinity()); + + // TODO: how to check NaN results? + // linear2dB(-0.1f) + // linear2dB(-0.1) + // linear2dB(-0.1l) +} + +SECTION("deg2rad", "") +{ + CHECK(deg2rad(180.0f) == pi()); + CHECK(deg2rad(180.0) == pi()); + CHECK(deg2rad(180.0l) == pi()); +} + +SECTION("rad2deg", "") +{ + CHECK(rad2deg(pi()) == 180.0f); + CHECK(rad2deg(pi()) == 180.0); + CHECK(rad2deg(pi()) == 180.0l); +} + +SECTION("wrap int", "") +{ + CHECK(wrap(-1, 7) == 6); + CHECK(wrap(0, 7) == 0); + CHECK(wrap(6, 7) == 6); + CHECK(wrap(7, 7) == 0); + CHECK(wrap(8, 7) == 1); +} + +SECTION("wrap double", "") +{ + CHECK(wrap(-0.5, 360.0) == 359.5); + CHECK(wrap(0.0, 360.0) == 0.0); + CHECK(wrap(359.5, 360.0) == 359.5); + CHECK(wrap(360.0, 360.0) == 0.0); + CHECK(wrap(360.5, 360.0) == 0.5); +} + +SECTION("wrap_two_pi", "") +{ +#define WRAP_TWO_PI(type) \ + CHECK(wrap_two_pi(-pi()) == pi()); \ + CHECK(wrap_two_pi( pi()) == pi()); \ + CHECK(wrap_two_pi(2 * pi()) == 0.0); \ + CHECK(wrap_two_pi(3 * pi()) == pi()); + + // TODO: use Approx, enable float and long double + + WRAP_TWO_PI(double); + //WRAP_TWO_PI(float); + //WRAP_TWO_PI(long double); + +#undef WRAP_TWO_PI +} + +SECTION("next_power_of_2", "") +{ + CHECK(next_power_of_2(-3) == 1); + CHECK(next_power_of_2(-2) == 1); + CHECK(next_power_of_2(-1) == 1); + CHECK(next_power_of_2(0) == 1); + CHECK(next_power_of_2(1) == 1); + CHECK(next_power_of_2(2) == 2); + CHECK(next_power_of_2(3) == 4); + + CHECK(next_power_of_2(1.0f) == 1.0f); + CHECK(next_power_of_2(2.0f) == 2.0f); + CHECK(next_power_of_2(3.0f) == 4.0f); + CHECK(next_power_of_2(1.0) == 1.0); + CHECK(next_power_of_2(2.0) == 2.0); + CHECK(next_power_of_2(2.5) == 4.0); + CHECK(next_power_of_2(3.0) == 4.0); + CHECK(next_power_of_2(1.0l) == 1.0l); + CHECK(next_power_of_2(2.0l) == 2.0l); + CHECK(next_power_of_2(3.0l) == 4.0l); +} + +SECTION("max_amplitude", "") +{ + auto sig = std::vector(5); + + CHECK(max_amplitude(sig.begin(), sig.end()) == 0.0); + + sig[2] = -2.0; + CHECK(max_amplitude(sig.begin(), sig.end()) == 2.0); + + sig[3] = 4.0; + CHECK(max_amplitude(sig.begin(), sig.end()) == 4.0); +} + +SECTION("rms", "") +{ + auto sig = std::vector(5); + + CHECK(rms(sig.begin(), sig.end()) == 0.0); + + sig[0] = -1.0; + sig[1] = -1.0; + sig[2] = -1.0; + sig[3] = 1.0; + sig[4] = 1.0; + + CHECK(rms(sig.begin(), sig.end()) == 1.0); +} + +SECTION("raised_cosine", "") +{ + auto rc1 = raised_cosine(1.5f); + CHECK(rc1(0.75f) == 0.0f); + CHECK(rc1(1.5f) == 1.0f); + + auto rc2 = raised_cosine(1.5); + CHECK(rc2(0.75) == 0.0); + CHECK(rc2(1.5) == 1.0); + + auto rc3 = raised_cosine(360); + CHECK(rc3(60) == 0.75); +} + +SECTION("linear_interpolator", "") +{ + auto in = linear_interpolator(1.5, 3.0, 3.0); + CHECK(in(0.0) == 1.5); + CHECK(in(1.0) == 2.0); + CHECK(in(2.0) == 2.5); + + // using default length 1: + in = make_linear_interpolator(5.0, 6.0); + CHECK(in(0.0) == 5.0); + CHECK(in(0.5) == 5.5); + CHECK(in(1.0) == 6.0); +} + +SECTION("linear_interpolator, integer index", "") +{ + auto in = linear_interpolator(5.0, 6.0, 2); + CHECK(in(0) == 5.0); + CHECK(in(1) == 5.5); +} + +SECTION("linear_interpolator, integer index converted to double", "") +{ + auto in = linear_interpolator(1.0, 2.0, 4); + CHECK(in(0) == 1.0); + CHECK(in(1) == 1.25); +} + +SECTION("identity", "") +{ + identity id; + CHECK(id(0.5f) == 0.5f); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_mimoprocessor.cpp b/apf/unit_tests/test_mimoprocessor.cpp new file mode 100644 index 00000000..c122c63d --- /dev/null +++ b/apf/unit_tests/test_mimoprocessor.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for MimoProcessor. + +#include "apf/mimoprocessor.h" + +#include "catch/catch.hpp" + +#include "apf/pointer_policy.h" +#include "apf/dummy_thread_policy.h" + +struct DummyProcessor : public apf::MimoProcessor + , apf::dummy_thread_policy> +{ + DummyProcessor(const apf::parameter_map& p) + : apf::MimoProcessor + , apf::dummy_thread_policy>(p) + {} + + void process() {} +}; + +TEST_CASE("MimoProcessor", "Test MimoProcessor") +{ + +SECTION("compilation", "does it compile?") +{ + apf::parameter_map p; + // some strange values: + p.set("sample_rate", 1000); + p.set("block_size", 33); + DummyProcessor dummy(p); +} + +// TODO: more tests! + +} // TEST_CASE MimoProcessor + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_misc.cpp b/apf/unit_tests/test_misc.cpp new file mode 100644 index 00000000..dba530fa --- /dev/null +++ b/apf/unit_tests/test_misc.cpp @@ -0,0 +1,185 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for misc.h. + +#include "apf/misc.h" + +#include +#include "catch/catch.hpp" + +template +struct B : apf::CRTP +{ + int value() { return this->derived().i; } +}; + +struct D : B +{ + int i = 42; +}; + +TEST_CASE("CRTP", "") +{ +auto d = D(); +CHECK(d.value() == 42); +} + +TEST_CASE("BlockParameter", "") +{ + +SECTION("default ctor", "") +{ + auto bp = apf::BlockParameter(); + CHECK(0 == bp.get()); + CHECK(0 == bp.old()); +} + +SECTION("int", "") +{ + auto bp = apf::BlockParameter(111); + CHECK(111 == bp.get()); + CHECK(111 == bp.old()); + CHECK(111 == bp.both()); + CHECK(bp.both() == 111); + CHECK_FALSE(bp.changed()); + + // TODO: once Catch supports checking for asserts, enable this: + //assert(bp.exactly_one_assignment()); + + bp = 222; + CHECK(222 == bp.get()); + CHECK(111 == bp.old()); + CHECK(bp.changed()); + + CHECK_FALSE(111 == bp.both()); + CHECK_FALSE(bp.both() == 111); + CHECK(bp.both() != 0); + CHECK(0 != bp.both()); + CHECK(bp.both() > 0); + CHECK(bp.both() >= 0); + CHECK_FALSE(0 > bp.both()); + CHECK_FALSE(0 >= bp.both()); + CHECK_FALSE(bp.both() > 333); + CHECK_FALSE(bp.both() >= 333); + CHECK(333 > bp.both()); + CHECK(333 >= bp.both()); + CHECK(bp.both() < 333); + CHECK(bp.both() <= 333); + CHECK_FALSE(333 < bp.both()); + CHECK_FALSE(333 <= bp.both()); + CHECK_FALSE(bp.both() < 0); + CHECK_FALSE(bp.both() <= 0); + CHECK(0 < bp.both()); + CHECK(0 <= bp.both()); + + assert(bp.exactly_one_assignment()); + + bp = 333; + CHECK(333 == bp.get()); + CHECK(222 == bp.old()); + CHECK(bp.changed()); + + bp -= 111; + CHECK_FALSE(bp.changed()); + + ++bp; + CHECK(222 == bp.old()); + CHECK(223 == bp.get()); + + assert(bp.exactly_one_assignment()); +} + +SECTION("conversion operator", "") +{ + auto bp = apf::BlockParameter(42); + int i = 0; + CHECK(0 == i); + i = bp; + CHECK(42 == i); + CHECK((i - bp) == 0); +} + +SECTION("conversion operator from const object", "") +{ + const auto bp = apf::BlockParameter(42); + int i = 0; + CHECK(0 == i); + i = bp; + CHECK(42 == i); + CHECK((i - bp) == 0); +} + +struct NonCopyable +{ + NonCopyable(int) {}; // hypothetical constructor + NonCopyable(const NonCopyable&) = delete; + NonCopyable(NonCopyable&&) = default; + NonCopyable& operator=(NonCopyable&& other) = default; +}; + +SECTION("non-copyable T", "") +{ + // These are just compile-time checks: + + auto bp = apf::BlockParameter{42}; + bp = NonCopyable(43); +}; + +// TODO: move CountCtors in a separate file? + +struct CountCtors +{ + CountCtors() { ++ default_constructor; } + CountCtors(const CountCtors&) { ++ copy_constructor; } + CountCtors(CountCtors&&) { ++move_constructor; } + + CountCtors& operator=(const CountCtors&) { ++copy_assignment; return *this; }; + CountCtors& operator=(CountCtors&&) { ++move_assignment; return *this; }; + + int default_constructor = 0; + int copy_constructor = 0; + int move_constructor = 0; + + int copy_assignment = 0; + int move_assignment = 0; +}; + +SECTION("check if move ctor and move assignment is used", "") +{ + auto bp = apf::BlockParameter{CountCtors()}; + CHECK(bp.get().copy_constructor == 1); + CHECK(bp.old().move_constructor == 1); + + bp = CountCtors(); + + CHECK(bp.get().move_assignment == 1); + CHECK(bp.get().copy_assignment == 0); + CHECK(bp.old().move_assignment == 1); + CHECK(bp.old().copy_assignment == 0); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_stride_iterator.cpp b/apf/unit_tests/test_stride_iterator.cpp new file mode 100644 index 00000000..94d48fc8 --- /dev/null +++ b/apf/unit_tests/test_stride_iterator.cpp @@ -0,0 +1,104 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for stride_iterator. + +#define APF_STRIDE_ITERATOR_DEFAULT_STRIDE 1 +#include "apf/iterator.h" // for stride_iterator +#include "iterator_test_macros.h" + +#include "catch/catch.hpp" + +using si = apf::stride_iterator; + +TEST_CASE("iterators/stride_iterator", "Test all functions of stride_iterator") +{ + +// First, the straightforward functions (with a default stride of 1) + +ITERATOR_TEST_SECTION_BASE(si, int) +ITERATOR_TEST_SECTION_DEFAULT_CTOR(si) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(si, int) +ITERATOR_TEST_SECTION_DEREFERENCE(si, int, 5) +ITERATOR_TEST_SECTION_OFFSET_DEREFERENCE(si, int, 5, 6) +ITERATOR_TEST_SECTION_EQUALITY(si, int) +ITERATOR_TEST_SECTION_INCREMENT(si, int) +ITERATOR_TEST_SECTION_DECREMENT(si, int) +ITERATOR_TEST_SECTION_PLUS_MINUS(si, int) +ITERATOR_TEST_SECTION_LESS(si, int) + +// ... then the specific stride_iterator stuff + +SECTION("stride", "Test if stride works.") +{ + int array[9]; + + auto iter = si(array, 2); + + CHECK(iter.base() == &array[0]); + CHECK(iter.step_size() == 2); + + ++iter; + CHECK(iter.base() == &array[2]); + + iter++; + CHECK(iter.base() == &array[4]); + + CHECK((iter + 2).base() == &array[8]); + CHECK((2 + iter).base() == &array[8]); + + iter += 2; + CHECK(iter.base() == &array[8]); + + iter--; + CHECK(iter.base() == &array[6]); + + --iter; + CHECK(iter.base() == &array[4]); + + CHECK((iter - 2).base() == &array[0]); + + iter -= 2; + CHECK(iter.base() == &array[0]); +} + +SECTION("special constructor" + , "Test if constructor from another stride_iterator works.") +{ + int array[9]; + + auto iter1 = si(array, 2); + CHECK(iter1.step_size() == 2); + + auto iter2 = si(iter1, 3); + CHECK(iter2.step_size() == 6); + + ++iter2; + + CHECK(iter2.base() == &array[6]); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_stringtools.cpp b/apf/unit_tests/test_stringtools.cpp new file mode 100644 index 00000000..eb651794 --- /dev/null +++ b/apf/unit_tests/test_stringtools.cpp @@ -0,0 +1,150 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for stringtools.h + +#include "apf/stringtools.h" + +#include "catch/catch.hpp" + +using namespace apf::str; + +TEST_CASE("stringtools", "Test all functions of apf::str namespace") +{ + +SECTION("A2S", "Anything to String") +{ + CHECK(A2S(42) == "42"); + CHECK(A2S(123.4) == "123.4"); + CHECK(A2S(-123.4) == "-123.4"); + CHECK(A2S("char array") == "char array"); + CHECK(A2S(std::string("string")) == "string"); + CHECK(A2S(true) == "true"); + CHECK(A2S(false) == "false"); +} + +SECTION("S2A", "String to Anything") +{ + int res_int; + double res_dbl; + std::string res_str; + bool res_bool = false; + + CHECK(S2A(" 42 ", res_int)); + CHECK(res_int == 42); + CHECK(S2A(" 42 ", res_dbl)); + CHECK(res_dbl == 42.0); + CHECK(S2A(" 42 ", res_str)); + CHECK(res_str == "42"); + + CHECK_FALSE(S2A(" - 42 ", res_int)); + CHECK(S2A(" -42 ", res_int)); + CHECK(res_int == -42); + + CHECK_FALSE(S2A(" true ", res_int)); + CHECK_FALSE(S2A(" true ", res_dbl)); + CHECK(S2A(" true ", res_str)); + CHECK(res_str == "true"); + CHECK(S2A(" true ", res_bool)); + CHECK(res_bool == true); + CHECK(S2A(" false ", res_bool)); + CHECK(res_bool == false); + CHECK(S2A(" 1 ", res_bool)); + CHECK(res_bool == true); + CHECK(S2A(" 0 ", res_bool)); + CHECK(res_bool == false); + CHECK_FALSE(S2A("True", res_bool)); + CHECK_FALSE(S2A("False", res_bool)); + CHECK_FALSE(S2A("42", res_bool)); + + CHECK_FALSE(S2A(" 42 3 ", res_int)); + CHECK_FALSE(S2A("42!", res_int)); + CHECK_FALSE(S2A("42 .", res_dbl)); + + // too lazy to repeat all tests for std::string ... + CHECK(S2A(std::string(" 42 "), res_int)); + CHECK(res_int == 42); +} + +SECTION("S2RV", "String to Return Value") +{ + // first the version with default values + + CHECK(S2RV(" 42 ", 0) == 42); + CHECK(S2RV(" 42.42 ", 0) == 0); + CHECK(S2RV(" 42.42 ", 0.0) == 42.42); + CHECK(S2RV(" 0 ", true) == false); + CHECK(S2RV(" 42 ", true) == true); + CHECK(S2RV(" false ", true) == false); + CHECK(S2RV(" false . ", true) == true); + + // now the throwing version + + CHECK(S2RV(" 42 ") == 42); + CHECK(S2RV(" 42 ") == 42.0); + CHECK_THROWS_AS(S2RV(" 42.0 "), std::invalid_argument); +} + +SECTION("string2time", "String to Time in Seconds") +{ + double res; + int res2; + CHECK_FALSE(string2time(" 4 : 33 ", res)); + CHECK_FALSE(string2time(" 4:33.2 ", res2)); + CHECK(string2time(" 4:33 ", res)); + CHECK(res == 273); + CHECK(string2time(std::string(" 01:33.3 "), res)); + CHECK(res == 93.3); + CHECK(string2time("-2:11:33", res)); + CHECK(res == -7893); + CHECK(string2time(" 33h ", res2)); + CHECK(res2 == 118800); + CHECK(string2time(" 33min ", res)); + CHECK(res == 1980); + CHECK(string2time(" 33 min ", res)); + CHECK(res == 1980); + CHECK(string2time(" 33s ", res)); + CHECK(res == 33); + CHECK(string2time(" 33 ms ", res)); + CHECK(res == 0.033); + CHECK(string2time("- 1:59.9", res)); + CHECK(res == -119.9); + + CHECK_FALSE(string2time("1:60.0", res)); + CHECK_FALSE(string2time("0:00:0", res)); + CHECK_FALSE(string2time("0:0:0", res)); + CHECK_FALSE(string2time("71:33", res)); + CHECK_FALSE(string2time("4:33 min", res)); + CHECK_FALSE(string2time("2: 11:33.3", res)); + CHECK_FALSE(string2time("2:11:33.", res)); + CHECK_FALSE(string2time("2:11 33", res)); + CHECK_FALSE(string2time("2 11:33", res)); + CHECK_FALSE(string2time("2:60:33", res)); + CHECK_FALSE(string2time("2:11:.33", res)); + CHECK_FALSE(string2time(" - - 2:33 ", res)); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_transform_iterator.cpp b/apf/unit_tests/test_transform_iterator.cpp new file mode 100644 index 00000000..d4c0c8a0 --- /dev/null +++ b/apf/unit_tests/test_transform_iterator.cpp @@ -0,0 +1,206 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Tests for transform_iterator. + +#include "apf/iterator.h" // for transform_iterator +#include "iterator_test_macros.h" + +#include "catch/catch.hpp" + +// Requirements for input iterators: +// http://www.cplusplus.com/reference/std/iterator/InputIterator/ + +struct three_halves +{ + float operator()(int in) { return static_cast(in) * 1.5f; } +}; + +using fii = apf::transform_iterator; + +TEST_CASE("iterators/transform_iterator" + , "Test all functions of transform_iterator") +{ + +ITERATOR_TEST_SECTION_BASE(fii, int) +ITERATOR_TEST_SECTION_DEFAULT_CTOR(fii) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(fii, int) +ITERATOR_TEST_SECTION_EQUALITY(fii, int) +ITERATOR_TEST_SECTION_INCREMENT(fii, int) +ITERATOR_TEST_SECTION_DECREMENT(fii, int) +ITERATOR_TEST_SECTION_PLUS_MINUS(fii, int) +ITERATOR_TEST_SECTION_LESS(fii, int) + +int array[] = { 1, 2, 3 }; + +SECTION("dereference", "*a; *a++; a[]") +{ + auto iter = fii(array); + + CHECK(*iter == 1.5f); + CHECK(iter.base() == &array[0]); + CHECK(*iter++ == 1.5f); + CHECK(iter.base() == &array[1]); + + CHECK(*iter-- == 3.0f); + CHECK(iter.base() == &array[0]); + + CHECK(iter[2] == 4.5f); + + // NOTE: operator->() doesn't make too much sense here, it's not working. + // See below for an example where it does work. +} + +SECTION("test make_transform_iterator", "namespace-level helper function") +{ + CHECK(*apf::make_transform_iterator(array, three_halves()) == 1.5f); +} + +} // TEST_CASE + +struct mystruct +{ + struct inner + { + inner() : num(42) {} + int num; + } innerobj; +}; + +struct special_function1 +{ + int operator()(mystruct s) + { + return s.innerobj.num; + } +}; + +struct special_function2 +{ + // Note: mystruct&& doesn't work + mystruct::inner& operator()(mystruct& s) + { + return s.innerobj; + } +}; + +struct special_function3 +{ + const mystruct::inner& operator()(const mystruct& s) const + { + return s.innerobj; + } +}; + +struct special_function4 +{ + template + int operator()(T&& s) + { + return s.innerobj.num; + } +}; + +TEST_CASE("iterators/transform_iterator with strange functions", "") +{ + +mystruct x; + +SECTION("special_function1", "") +{ + apf::transform_iterator + it(&x, special_function1()); + CHECK(*it == 42); +} + +SECTION("special_function2", "") +{ + apf::transform_iterator + it(&x, special_function2()); + CHECK(&*it == &x.innerobj); + CHECK(it->num == 42); +} + +SECTION("special_function3", "") +{ + apf::transform_iterator + it(&x, special_function3()); + CHECK(&*it == &x.innerobj); + CHECK(it->num == 42); +} + +SECTION("special_function4", "") +{ + apf::transform_iterator + it(&x, special_function4()); + CHECK(*it == 42); +} + +SECTION("lambda functions", "") +{ + apf::transform_iterator + it(&x, special_function4()); + + CHECK(42 == *apf::make_transform_iterator(&x + , [] (mystruct s) { return s.innerobj.num; })); + + CHECK(42 == *apf::make_transform_iterator(&x + , [] (mystruct& s) { return s.innerobj.num; })); + + CHECK(42 == *apf::make_transform_iterator(&x + , [] (const mystruct& s) { return s.innerobj.num; })); +} + +} // TEST_CASE + +TEST_CASE("transform_proxy", "Test transform_proxy") +{ + +using vi = std::vector; +vi input; +input.push_back(1); +input.push_back(2); +input.push_back(3); + +SECTION("test transform_proxy", "") +{ + auto p = apf::transform_proxy(input); + + CHECK(p.size() == 3); + CHECK((*p.begin()) == 1.5); + CHECK((p.begin() + 3) == p.end()); +} + +SECTION("test tranform_proxy_const", "") +{ + auto p = apf::transform_proxy_const(input); + + CHECK(p.size() == 3); + CHECK((*p.begin()) == 1.5); + CHECK((p.begin() + 3) == p.end()); +} + +} // TEST_CASE + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent diff --git a/apf/unit_tests/test_trivial_iterator.cpp b/apf/unit_tests/test_trivial_iterator.cpp new file mode 100644 index 00000000..504b8be8 --- /dev/null +++ b/apf/unit_tests/test_trivial_iterator.cpp @@ -0,0 +1,90 @@ +/****************************************************************************** + * Copyright © 2012-2013 Institut für Nachrichtentechnik, Universität Rostock * + * Copyright © 2006-2012 Quality & Usability Lab, * + * Telekom Innovation Laboratories, TU Berlin * + * * + * This file is part of the Audio Processing Framework (APF). * + * * + * The APF 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. * + * * + * The APF 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://AudioProcessingFramework.github.com * + ******************************************************************************/ + +// Define and test a trivial iterator. +// This is basically to show and test all iterator macros. + +#include "apf/iterator.h" + +#include "catch/catch.hpp" + +template +class trivial_iterator +{ + private: + using self = trivial_iterator; + + public: + using value_type = typename std::iterator_traits::value_type; + using pointer = typename std::iterator_traits::pointer; + using reference = typename std::iterator_traits::reference; + using difference_type = typename std::iterator_traits::difference_type; + using iterator_category + = typename std::iterator_traits::iterator_category; + + APF_ITERATOR_CONSTRUCTORS(trivial_iterator, I, _base_iterator) + APF_ITERATOR_BASE(I, _base_iterator) + + APF_ITERATOR_RANDOMACCESS_EQUAL(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DEREFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ARROW(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREINCREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_PREDECREMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_ADDITION_ASSIGNMENT(_base_iterator) + APF_ITERATOR_RANDOMACCESS_DIFFERENCE(_base_iterator) + APF_ITERATOR_RANDOMACCESS_LESS(_base_iterator) + APF_ITERATOR_RANDOMACCESS_SUBSCRIPT + APF_ITERATOR_RANDOMACCESS_UNEQUAL + APF_ITERATOR_RANDOMACCESS_OTHER_COMPARISONS + APF_ITERATOR_RANDOMACCESS_POSTINCREMENT + APF_ITERATOR_RANDOMACCESS_POSTDECREMENT + APF_ITERATOR_RANDOMACCESS_THE_REST + + private: + I _base_iterator; +}; + +#include "iterator_test_macros.h" + +#include + +TEST_CASE("int*", "") +{ + +using iter_t = trivial_iterator; + +ITERATOR_TEST_SECTION_BASE(iter_t, int) +ITERATOR_TEST_SECTION_DEFAULT_CTOR(iter_t) +ITERATOR_TEST_SECTION_COPY_ASSIGNMENT(iter_t, int) +ITERATOR_TEST_SECTION_DEREFERENCE(iter_t, int, 42) +ITERATOR_TEST_SECTION_OFFSET_DEREFERENCE(iter_t, int, 23, 42) +ITERATOR_TEST_SECTION_EQUALITY(iter_t, int) +ITERATOR_TEST_SECTION_INCREMENT(iter_t, int) +ITERATOR_TEST_SECTION_DECREMENT(iter_t, int) +ITERATOR_TEST_SECTION_PLUS_MINUS(iter_t, int) +ITERATOR_TEST_SECTION_LESS(iter_t, int) + +} + +// Settings for Vim (http://www.vim.org/), please do not remove: +// vim:softtabstop=2:shiftwidth=2:expandtab:textwidth=80:cindent